using CommunityToolkit.Mvvm.ComponentModel; using HMI.Base; using HMI.Core.Util; using HMI.UI; using HMI.UI.Util; using HMI.Util; using log4net; using Microsoft.Win32; using Plugin.DODriverControl.Api; using Plugin.DODriverControl.Entity; using System.Collections.ObjectModel; using System.IO; using System.Timers; using System.Windows; using System.Windows.Controls; using System.Windows.Media; namespace Plugin.DODriverControl { /// /// SingleControl.xaml 的交互逻辑 /// [ObservableObject] public partial class SingleControl : SimpleMaskControl { public SingleControl(string name, string baseFolder) { this.name = name; this.baseFolder = Path.Combine(baseFolder, name); if (!Directory.Exists(this.baseFolder)) Directory.CreateDirectory(this.baseFolder); this.dataFilePath = Path.Combine(this.baseFolder, "data.xml"); this.configFilePath = Path.Combine(this.baseFolder, "config.xml"); this.DataContext = this; refreshTimer = new System.Timers.Timer(); refreshTimer.Interval = 200; refreshTimer.AutoReset = false; refreshTimer.Elapsed += RefreshTimer_Elapsed; InitializeComponent(); commonColor = Application.Current.FindResource("VerBar") as Brush; successColor = Application.Current.FindResource("GreenVerBar") as Brush; errorColor = Application.Current.FindResource("DangerVerBar") as Brush; } [ObservableProperty] private bool connectStatus = false; [ObservableProperty] private ObservableCollection datasource = new ObservableCollection(); public ILog log { get; set; } public string name { get; private set; } public string baseFolder { get; private set; } private IDIOController controller; private string dataFilePath; private string configFilePath; private System.Timers.Timer refreshTimer; private ConfigData config; private List doControls = new List(); private Brush successColor; private Brush errorColor; private Brush commonColor; private CancellationTokenSource cts = new CancellationTokenSource(); public void Active() { if (refreshTimer != null) refreshTimer.Enabled = true; } public void DeActive() { if (refreshTimer != null) refreshTimer.Enabled = false; } public void Close() { cts.Cancel(); ConnectStatus = false; if (refreshTimer != null) { refreshTimer.Enabled = false; refreshTimer.Elapsed -= RefreshTimer_Elapsed; refreshTimer = null; } controller?.Dispose(); controller = null; } private void Button_Click(object sender, RoutedEventArgs e) { var btn = sender as Button; var data = btn?.Tag as DIOData; switch (btn.Name) { case "addBtn": DoAddData(); break; case "editBtn": DoEditData(data); break; case "exportBtn": DoExportExcel(); break; case "importBtn": DoImportExcel(); break; case "reConnectBtn": DoReconnect(); break; case "configBtn": DoOpenConfig(); break; case "rfhBtn": Refresh(); break; case "delBtn": DoDeleteData(data); break; default: break; } } private void DoAddData() { var win = new DataModal { Owner = Window.GetWindow(this), Title = "新增" }; if (win.Open(new DIOData(), Datasource, dataFilePath) == true) { ToastHelper.Success("添加成功"); Refresh(); } } private void DoEditData(DIOData data) { var win = new DataModal { Owner = Window.GetWindow(this), Title = "编辑" }; if (win.Open(data, Datasource, dataFilePath) == true) { ToastHelper.Success("编辑成功"); Refresh(); } } private void DoExportExcel() { try { SaveFileDialog sfd = new SaveFileDialog(); sfd.Filter = "Excel|*.xls"; if (sfd.ShowDialog() != true) { return; } string fileName = sfd.FileName; ExcelHelper.ExportExcel(fileName, Datasource); ToastHelper.Success("导出成功"); } catch (Exception ex) { log.Error("Export Error.", ex); ToastHelper.Error($"导出失败: {ex.Message}"); } } private void DoImportExcel() { try { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "Excel|*.xls"; if (ofd.ShowDialog() != true) { return; } string fileName = ofd.FileName; List datas = ExcelHelper.ReadFromExcel(fileName); //foreach (var data in datas) //{ // Datasource.Add(data); //} Datasource = new ObservableCollection(datas); SaveDatasourceToXml(); ToastHelper.Success("导入成功"); RefreshDOControl(); } catch (Exception ex) { log.Error("Export Error.", ex); ToastHelper.Error($"导入失败: {ex.Message}"); } } private void DoReconnect() { ConnectStatus = false; ShowMask(() => { refreshTimer.Enabled = false; controller?.Close(); controller = CreateController(); var res = controller.Start(out string msg); if (!res) { throw new Exception($"{msg}"); } refreshTimer.Enabled = true; Dispatcher.BeginInvoke(new Action(() => { ConnectStatus = true; })); }, (ex) => { controller = null; log.Error("Reconnect Error.", ex); Dispatcher.BeginInvoke(new Action(() => { MsgBoxHelper.Error(this, $"连接失败:{ex.Message}"); })); }, "请稍候", "正在重连..."); } private void DoOpenConfig() { ConfigModal modal = new ConfigModal { Title = "设置", Owner = Window.GetWindow(this) }; if (modal.Open(config) == true) { config = modal.config; SaveConfigToXml(); DoReconnect(); } } private void DoDeleteData(DIOData data) { if (MsgBoxHelper.WarnMessage(Window.GetWindow(this), "确定要删除选中的数据吗?") == MessageBoxResult.OK) { int index = Datasource.IndexOf(data); if (index < 0) return; Datasource.RemoveAt(index); SaveDatasourceToXml(); ToastHelper.Success("删除成功"); RefreshDOControl(); } } private void SaveConfigToXml() { XmlHelper.SerializeToXml(configFilePath, config); } private void SaveDatasourceToXml() { XmlHelper.SerializeToXml(dataFilePath, Datasource); } private void RefreshDOControl() { try { refreshTimer.Enabled = false; diWrap.Children.Clear(); doWrap.Children.Clear(); doControls.Clear(); foreach (var data in Datasource) { var fg = new GroupBox { //Header = $"{data.Name} 地址:{data.Address}", Header = string.IsNullOrEmpty(data.Name)?"预留":data.Name,// $"{data.Name}", Content = CreateButton(data), Height = 80, Width = 180 }; doControls.Add((FrameworkElement)fg.Content); if (data.Dio == DIOType.DI) { diWrap.Children.Add(fg); } else { doWrap.Children.Add(fg); } } } finally { refreshTimer.Enabled = true; } } private FrameworkElement CreateButton(DIOData data) { if (data.Dio == DIOType.DI) { return new LightBall { Size = 40, DataContext = data }; } Style btnStyle = this.FindResource("PrimaryButton") as Style; var btn = new Button { DataContext = data, Style = btnStyle, MinWidth = 120, MinHeight = 40 }; btn.Click += ControlButton_Click; return btn; } private void ControlButton_Click(object sender, RoutedEventArgs args) { if (!ConnectStatus) { ToastHelper.Error("请先连接控制器"); return; } Button btn = sender as Button; DIOData data = btn.DataContext as DIOData; try { refreshTimer.Enabled = false; var value = btn.Content?.ToString(); string val = value == data.PressDownText ? data.PressUpValue : data.PressDownValue; var res = controller.WriteBool(data.Address, bool.Parse(val), out string msg); if (!res) throw new Exception(msg); } catch (Exception ex) { log.Error("ControlButton_Click", ex); ToastHelper.Error($"{data.Name}: {ex.Message}"); } finally { refreshTimer.Enabled = true; } } private void Refresh() { try { if (!File.Exists(dataFilePath)) { Datasource = new ObservableCollection(); } else { Datasource = XmlHelper.DeserializeFromXml>(dataFilePath); } } catch (Exception ex) { log.Error("Load Data Error.", ex); Datasource = new ObservableCollection(); } RefreshDOControl(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { ShowMask(() => { try { if (!File.Exists(configFilePath)) { config = new ConfigData { Type = ConfigType.ModBusTcp, IP = "127.0.0.1", Port = "502" }; } else { config = XmlHelper.DeserializeFromXml(configFilePath); } } catch (Exception ex) { log.Error("Load Config Error.", ex); config = new ConfigData { Type = ConfigType.ModBusTcp, IP = "127.0.0.1", Port = "502" }; } var datasource = new ObservableCollection(); var connectStatus = false; try { if (File.Exists(dataFilePath)) { datasource = XmlHelper.DeserializeFromXml>(dataFilePath); } } catch (Exception ex) { log.Error("Load Data Error.", ex); } try { controller = CreateController(); var res = controller.Start(out string msg); if (!res) { throw new Exception($"控制器连接失败: {msg}"); } connectStatus = true; } catch (Exception ex) { controller = null; log.Error("Modbus Connect Error.", ex); connectStatus = false; } Dispatcher.BeginInvoke(new Action(() => { ConnectStatus = connectStatus; Datasource = datasource; RefreshDOControl(); refreshTimer.Enabled = true; this.Loaded -= UserControl_Loaded; })); }, null, "请稍后", "加载中..."); } public IDIOController CreateController() { switch (config.Type) { case ConfigType.ModBusTcp: return new ModBusTcpController { Ip = config.IP, Port = int.Parse(config.Port), }; case ConfigType.PCI_7432: return new PCIController { Name = "PCI_7432", }; default: return null; } } private void RefreshTimer_Elapsed(object sender, ElapsedEventArgs e) { bool isConnected = ConnectStatus; if (!isConnected) return; try { refreshTimer.Enabled = false; if (cts.IsCancellationRequested) { isConnected = false; return; } for (int i = doControls.Count - 1; i >= 0; i--) { var ui = doControls[i]; ui.Dispatcher.Invoke(() => { DIOData data = ui.DataContext as DIOData; if (data == null) return; isConnected = GetValueByModbusTcp(data, out string value); if (!isConnected) return; switch (data.ControlType) { case ControlType.BOOL: if (data.Dio == DIOType.DI) { ((LightBall)ui).Value = value == data.PressDownValue; ((LightBall)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText; } else { var color = value == data.PressDownValue ? successColor : errorColor; ((Button)ui).Background = color; ((Button)ui).Content = value == data.PressDownValue ? data.PressDownText : data.PressUpText; } break; case ControlType.INT: case ControlType.WORD: case ControlType.REAL: case ControlType.LREAL: case ControlType.STRING: case ControlType.WSTRING: ((Button)ui).Content = value; break; default: break; } }); } } catch (TaskCanceledException ex) { isConnected = false; } finally { if (isConnected && ConnectStatus) refreshTimer.Enabled = true; else ConnectStatus = false; } } private bool GetValueByModbusTcp(DIOData data, out string value) { if (controller == null) { value = null; return false; } switch (data.ControlType) { case ControlType.BOOL: bool val = false; bool res = false; if (data.Dio == DIOType.DI) { res = controller.ReadDIBool(data.Address, out val, out string msg); } else { res = controller.ReadDOBool(data.Address, out val, out string msg); } if (res == true) { value = val.ToString(); return true; } value = null; return false; default: value = null; return true; } } } }