using HMI.UI.Util; using Panuon.WPF.UI; using Plugin.DeviceConfig.Entity; using Plugin.DeviceConfig.Model; using System; 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; 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; // 填充数据 tbDeviceId.Text = device.DeviceId; tbDeviceId.IsReadOnly = !_isNew; cbKind.SelectedItem = device.Kind; tbDriverType.Text = device.DriverType; cbTransport.SelectedItem = device.Transport; tbEndpoint.Text = device.Endpoint; // 默认驱动类型建议 & 端点提示 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; 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 保存/取消 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; } } // 保存设备 _device.DeviceId = deviceId; _device.Kind = kind; _device.DriverType = tbDriverType.Text?.Trim() ?? string.Empty; _device.Transport = transport; _device.Endpoint = endpoint; _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(); } /// /// 根据传输方式更新端点输入框提示 /// 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 } }