using HMI.UI.Util;
using Panuon.WPF.UI;
using Plugin.DeviceConfig.Entity;
using Plugin.DeviceConfig.Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace Plugin.DeviceConfig
{
///
/// DeviceEditModal.xaml 的交互逻辑
///
public partial class DeviceEditModal : WindowX
{
private DeviceConfigModel _model;
private DeviceDefinition _device;
private ObservableCollection _channels = new();
private bool _isNew;
private bool _execModeSyncing;
public DeviceEditModal()
{
DataContext = this;
InitializeComponent();
}
///
/// 打开弹窗
///
public bool? Open(DeviceDefinition device, DeviceConfigModel model)
{
_model = model;
_device = device;
_isNew = string.IsNullOrEmpty(device.DeviceId);
// 初始化枚举下拉
cbKind.ItemsSource = Enum.GetValues(typeof(DeviceKind)).Cast().ToList();
cbTransport.ItemsSource = Enum.GetValues(typeof(TransportType)).Cast().ToList();
cbTransport.SelectionChanged += CbTransport_SelectionChanged;
// 执行位置:仲裁域/BrokerId 候选由配置枚举
cbAuthority.ItemsSource = V3AuthorityCatalog.AuthorityIds;
cbBrokerId.ItemsSource = V3AuthorityCatalog.BrokerIds;
tbOwnerProcessId.Text = System.Diagnostics.Process.GetCurrentProcess().Id.ToString();
// 填充数据
tbDeviceId.Text = device.DeviceId;
tbDeviceId.IsReadOnly = !_isNew;
cbKind.SelectedItem = device.Kind;
tbDriverType.Text = device.DriverType;
cbTransport.SelectedItem = device.Transport;
tbEndpoint.Text = device.Endpoint;
// 执行位置回显:旧数据 AuthorityId 为空时默认本机 Local 域
_execModeSyncing = true;
rbLocal.IsChecked = device.ExecutionMode == ExecutionModeKind.Local;
rbBroker.IsChecked = device.ExecutionMode == ExecutionModeKind.Broker;
cbAuthority.SelectedItem = string.IsNullOrWhiteSpace(device.AuthorityId)
? V3AuthorityCatalog.LocalAuthorityId : device.AuthorityId;
if (cbAuthority.SelectedItem == null)
{
// 存量值不在当前候选枚举中:保留展示并追加
var list = V3AuthorityCatalog.AuthorityIds.ToList();
list.Add(device.AuthorityId);
cbAuthority.ItemsSource = list;
cbAuthority.SelectedItem = device.AuthorityId;
}
cbBrokerId.SelectedItem = device.BrokerId;
if (!string.IsNullOrWhiteSpace(device.OwnerProcessId))
{
tbOwnerProcessId.Text = device.OwnerProcessId;
}
_execModeSyncing = false;
RefreshExecModeUi();
RefreshSessionKeyCandidates();
// 默认驱动类型建议 & 端点提示
UpdateDriverHint();
UpdateEndpointHint();
// 加载通道
if (!_isNew && !string.IsNullOrEmpty(device.DeviceId))
{
var chList = _model.GetChannelsByDevice(device.DeviceId);
_channels = new ObservableCollection(chList);
}
else
{
_channels = new ObservableCollection();
}
channelListView.ItemsSource = _channels;
// 通道 SessionKey 已加载,重新生成会话键候选(Broker 模式)
RefreshSessionKeyCandidates();
return ShowDialog();
}
#region 通道操作
private void AddChannel_Click(object sender, RoutedEventArgs e)
{
try
{
if (!int.TryParse(tbChId.Text, out int chId) || chId < 0)
{
ToastHelper.Warning("请输入有效的通道编号(非负整数)");
return;
}
if (_channels.Any(c => c.ChannelId == chId))
{
ToastHelper.Warning($"通道 {chId} 已存在");
return;
}
double.TryParse(tbChVolt.Text, out double volt);
double.TryParse(tbChCurr.Text, out double curr);
var ch = new DeviceChannel
{
DeviceId = _device.DeviceId ?? tbDeviceId.Text,
ChannelId = chId,
Capability = tbChCap.Text ?? string.Empty,
MaxVoltage = volt,
MaxCurrent = curr,
};
_channels.Add(ch);
ClearChannelInputs();
}
catch (Exception ex)
{
ToastHelper.Error($"新增通道失败: {ex.Message}");
}
}
private void EditChannel_Click(object sender, RoutedEventArgs e)
{
try
{
if (sender is Button btn && btn.Tag is DeviceChannel ch)
{
tbChId.Text = ch.ChannelId.ToString();
tbChCap.Text = ch.Capability;
tbChVolt.Text = ch.MaxVoltage.ToString("F2");
tbChCurr.Text = ch.MaxCurrent.ToString("F2");
}
}
catch (Exception ex)
{
ToastHelper.Error($"加载通道数据失败: {ex.Message}");
}
}
private void DeleteChannel_Click(object sender, RoutedEventArgs e)
{
try
{
if (sender is Button btn && btn.Tag is DeviceChannel ch)
{
if (MsgBoxHelper.WarnMessage(this, $"确定要删除通道 {ch.ChannelId} 吗?") == MessageBoxResult.OK)
{
_channels.Remove(ch);
}
}
}
catch (Exception ex)
{
ToastHelper.Error($"删除通道失败: {ex.Message}");
}
}
private void SaveChannel_Click(object sender, RoutedEventArgs e)
{
try
{
if (!int.TryParse(tbChId.Text, out int chId) || chId < 0)
{
ToastHelper.Warning("请输入有效的通道编号");
return;
}
double.TryParse(tbChVolt.Text, out double volt);
double.TryParse(tbChCurr.Text, out double curr);
var existing = _channels.FirstOrDefault(c => c.ChannelId == chId);
if (existing != null)
{
existing.Capability = tbChCap.Text ?? string.Empty;
existing.MaxVoltage = volt;
existing.MaxCurrent = curr;
}
else
{
_channels.Add(new DeviceChannel
{
DeviceId = _device.DeviceId ?? tbDeviceId.Text,
ChannelId = chId,
Capability = tbChCap.Text ?? string.Empty,
MaxVoltage = volt,
MaxCurrent = curr,
});
}
ClearChannelInputs();
ToastHelper.Success("通道已保存");
}
catch (Exception ex)
{
ToastHelper.Error($"保存通道失败: {ex.Message}");
}
}
private void DelChannel_Click(object sender, RoutedEventArgs e)
{
try
{
var selected = channelListView.SelectedItem as DeviceChannel;
if (selected == null)
{
ToastHelper.Warning("请先选中要删除的通道");
return;
}
if (MsgBoxHelper.WarnMessage(this, $"确定要删除通道 {selected.ChannelId} 吗?") == MessageBoxResult.OK)
{
_channels.Remove(selected);
}
}
catch (Exception ex)
{
ToastHelper.Error($"删除通道失败: {ex.Message}");
}
}
private void ClearChannelInputs()
{
tbChId.Text = string.Empty;
tbChCap.Text = string.Empty;
tbChVolt.Text = string.Empty;
tbChCurr.Text = string.Empty;
}
#endregion
#region 执行位置(V3.0 §05)
/// 当前是否 Broker 模式
private bool IsBrokerMode => rbBroker.IsChecked == true;
private void ExecMode_Changed(object sender, RoutedEventArgs e)
{
// XAML 初始化阶段 rbLocal.IsChecked=True 会提前触发,此时其余控件字段尚未创建
if (_execModeSyncing || rbBroker == null || cbAuthority == null) return;
_execModeSyncing = true;
// Broker 设备的 AuthorityId 即其 Broker 域;切回 Local 时使用本机默认域
if (IsBrokerMode)
{
// 当前仍为 local 域(或未选)→ 切到首个 Broker 域
if (V3AuthorityCatalog.IsLocalAuthority(cbAuthority.SelectedItem as string) || cbAuthority.SelectedItem == null)
{
cbAuthority.SelectedItem = V3AuthorityCatalog.BrokerIds.FirstOrDefault()
?? V3AuthorityCatalog.AuthorityIds.LastOrDefault(a => !V3AuthorityCatalog.IsLocalAuthority(a));
}
}
else
{
cbAuthority.SelectedItem = V3AuthorityCatalog.LocalAuthorityId;
}
_execModeSyncing = false;
RefreshExecModeUi();
}
private void CbAuthority_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (_execModeSyncing || rbLocal == null || rbBroker == null || cbBrokerId == null) return;
var auth = cbAuthority.SelectedItem as string;
if (string.IsNullOrEmpty(auth)) return;
_execModeSyncing = true;
// 选中 local- 域→Local;选中 Broker 域→Broker 并同步 BrokerId
if (V3AuthorityCatalog.IsLocalAuthority(auth))
{
rbLocal.IsChecked = true;
}
else
{
rbBroker.IsChecked = true;
cbBrokerId.SelectedItem = V3AuthorityCatalog.BrokerIds.Contains(auth) ? auth : cbBrokerId.SelectedItem;
}
_execModeSyncing = false;
RefreshExecModeUi();
}
/// 根据模式切换 Broker 字段可见性与只读派生的访问策略
private void RefreshExecModeUi()
{
spBroker.Visibility = IsBrokerMode ? Visibility.Visible : Visibility.Collapsed;
tbAccessPolicy.Text = (IsBrokerMode ? ExecutionModeKind.Broker : ExecutionModeKind.Local)
switch
{
ExecutionModeKind.Broker => "共享仲裁 · Broker SessionGate 代理",
_ => "独占直连 · 本进程持有物理会话"
};
if (IsBrokerMode)
{
RefreshSessionKeyCandidates();
}
}
///
/// PhysicalSessionKey 候选:由端点规范化的写死字符串(V3.0 §04:地址别名必须归一为同一物理身份)
///
private void RefreshSessionKeyCandidates()
{
var deviceId = tbDeviceId.Text?.Trim() ?? string.Empty;
var transport = cbTransport.SelectedItem is TransportType tt ? tt : TransportType.Tcp;
var endpoint = tbEndpoint.Text?.Trim() ?? string.Empty;
var candidates = new List();
if (transport == TransportType.Can)
{
var bus = endpoint.Split(':')[0].Trim();
if (!string.IsNullOrEmpty(bus)) candidates.Add($"can/{bus}/main");
}
else if (transport == TransportType.Tcp && endpoint.Contains(":"))
{
candidates.Add($"tcp/{endpoint}/main");
}
else if (transport == TransportType.Serial)
{
var com = endpoint.Split(':')[0].Trim();
if (!string.IsNullOrEmpty(com)) candidates.Add($"serial/{com}/main");
}
else if (transport == TransportType.Visa)
{
candidates.Add($"visa/{deviceId}/main");
}
else if (transport == TransportType.SDK)
{
candidates.Add($"sdk/{deviceId}/main");
}
if (!string.IsNullOrEmpty(deviceId)) candidates.Add($"{deviceId}/main");
// 已存通道 SessionKey 也列入候选(去重)
foreach (var key in _channels.Select(c => c.SessionKey)
.Where(k => !string.IsNullOrWhiteSpace(k)).Distinct())
{
candidates.Add(key);
}
candidates = candidates.Distinct(StringComparer.OrdinalIgnoreCase).ToList();
string? current = _device?.PhysicalSessionKey;
cbSessionKey.ItemsSource = candidates;
cbSessionKey.Text = !string.IsNullOrWhiteSpace(current) ? current
: (candidates.Count > 0 ? candidates[0] : string.Empty);
}
#endregion
#region 保存/取消
private void SaveBtn_Click(object sender, RoutedEventArgs e)
{
try
{
// 验证
var deviceId = tbDeviceId.Text?.Trim();
if (string.IsNullOrEmpty(deviceId))
{
ToastHelper.Warning("设备ID不能为空");
return;
}
if (cbKind.SelectedItem is not DeviceKind kind)
{
ToastHelper.Warning("请选择设备类型");
return;
}
if (cbTransport.SelectedItem is not TransportType transport)
{
ToastHelper.Warning("请选择传输方式");
return;
}
var endpoint = tbEndpoint.Text?.Trim();
if (string.IsNullOrEmpty(endpoint))
{
ToastHelper.Warning("连接端点不能为空");
return;
}
// 验证 Endpoint 格式
if (transport == TransportType.Tcp)
{
if (!endpoint.Contains(":") && !endpoint.Contains("."))
{
ToastHelper.Warning("TCP端点格式不正确,应为 IP:Port 格式");
return;
}
}
else if (transport == TransportType.Can)
{
// CAN 端点格式:总线名称:节点编号 如 "CAN_BUS_1:0"
var parts = endpoint.Split(':');
if (parts.Length != 2 || string.IsNullOrWhiteSpace(parts[0]) || !int.TryParse(parts[1], out int nodeIdx) || nodeIdx < 0)
{
ToastHelper.Warning("CAN端点格式不正确,应为 总线名称:节点编号(如 CAN_BUS_1:0)");
return;
}
}
// 检查 DeviceId 是否重复(新增时)
if (_isNew)
{
var existing = _model.GetDeviceById(deviceId);
if (existing != null)
{
ToastHelper.Warning($"设备ID [{deviceId}] 已存在");
return;
}
}
// 执行位置(V3.0 §05)校验
var authorityId = cbAuthority.SelectedItem as string ?? string.Empty;
if (string.IsNullOrWhiteSpace(authorityId))
{
ToastHelper.Warning("仲裁域不能为空,请从候选中选择");
return;
}
string brokerId = string.Empty, sessionKey = string.Empty, ownerPid = string.Empty;
if (IsBrokerMode)
{
brokerId = cbBrokerId.SelectedItem as string ?? string.Empty;
if (string.IsNullOrWhiteSpace(brokerId))
{
ToastHelper.Warning("共享仪器(Broker)必须指定 BrokerId");
return;
}
sessionKey = cbSessionKey.Text?.Trim() ?? string.Empty;
if (string.IsNullOrWhiteSpace(sessionKey))
{
ToastHelper.Warning("共享仪器(Broker)必须指定物理会话键");
return;
}
ownerPid = tbOwnerProcessId.Text?.Trim() ?? string.Empty;
}
// 保存设备
_device.DeviceId = deviceId;
_device.Kind = kind;
_device.DriverType = tbDriverType.Text?.Trim() ?? string.Empty;
_device.Transport = transport;
_device.Endpoint = endpoint;
_device.ExecutionMode = IsBrokerMode ? ExecutionModeKind.Broker : ExecutionModeKind.Local;
_device.AuthorityId = authorityId;
_device.BrokerId = brokerId;
_device.PhysicalSessionKey = sessionKey;
_device.OwnerProcessId = ownerPid;
_model.SaveDevice(_device);
// 保存通道
foreach (var ch in _channels)
{
ch.DeviceId = deviceId;
_model.SaveChannel(ch);
}
DialogResult = true;
Close();
}
catch (Exception ex)
{
ToastHelper.Error($"保存失败: {ex.Message}");
}
}
private void CancelBtn_Click(object sender, RoutedEventArgs e)
{
DialogResult = false;
Close();
}
private void CbTransport_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
UpdateEndpointHint();
if (IsBrokerMode)
{
RefreshSessionKeyCandidates();
}
}
///
/// 根据传输方式更新端点输入框提示
///
private void UpdateEndpointHint()
{
if (cbTransport.SelectedItem is TransportType tt)
{
switch (tt)
{
case TransportType.Can:
Panuon.WPF.UI.TextBoxHelper.SetWatermark(tbEndpoint, "CAN总线端点,格式: 总线名称:节点编号(如 CAN_BUS_1:0)");
break;
case TransportType.Serial:
Panuon.WPF.UI.TextBoxHelper.SetWatermark(tbEndpoint, "串口号,如 COM3:115200:8:N:1");
break;
case TransportType.Visa:
Panuon.WPF.UI.TextBoxHelper.SetWatermark(tbEndpoint, "VISG 地址,如 TCPIP0::192.168.1.100::inst0::INSTR");
break;
case TransportType.SDK:
Panuon.WPF.UI.TextBoxHelper.SetWatermark(tbEndpoint, "SDK 设备标识,如 SDK://DeviceName/Instance");
break;
default:
Panuon.WPF.UI.TextBoxHelper.SetWatermark(tbEndpoint, "例: 192.168.10.124:30000 或 COM3");
break;
}
}
}
///
/// 根据设备类型建议默认驱动类型
///
private void UpdateDriverHint()
{
// 保留现有驱动类型,不自动修改。仅当为空时提示默认值
if (string.IsNullOrWhiteSpace(tbDriverType.Text) && cbKind.SelectedItem is DeviceKind kind)
{
string hint = kind switch
{
DeviceKind.CanDevice => "CAN",
DeviceKind.PowerSupply => "SCPI",
DeviceKind.PlcDevice => "S7",
DeviceKind.Chamber => "Tcp",
DeviceKind.DAQ => "Tcp",
_ => ""
};
if (!string.IsNullOrEmpty(hint))
{
tbDriverType.Text = hint;
}
}
}
#endregion
}
}