From 9d1660270272180cd71147d6ddd53d1382643d2e Mon Sep 17 00:00:00 2001
From: wenxiangchao <wenxiangchao@ihengxin.com.cn>
Date: 星期一, 02 十一月 2026 18:00:14 +0800
Subject: [PATCH] merge: 并入远程 Initial commit 作为历史根节点

---
 Plugin.DODriverControl/SingleControl.xaml.cs |  585 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 585 insertions(+), 0 deletions(-)

diff --git a/Plugin.DODriverControl/SingleControl.xaml.cs b/Plugin.DODriverControl/SingleControl.xaml.cs
new file mode 100644
index 0000000..d63d0ce
--- /dev/null
+++ b/Plugin.DODriverControl/SingleControl.xaml.cs
@@ -0,0 +1,585 @@
+锘縰sing 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
+{
+    /// <summary>
+    /// SingleControl.xaml 鐨勪氦浜掗�昏緫
+    /// </summary>
+    [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<DIOData> datasource = new ObservableCollection<DIOData>();
+
+        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<FrameworkElement> doControls = new List<FrameworkElement>();
+
+        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<DIOData> datas = ExcelHelper.ReadFromExcel<DIOData>(fileName);
+                //foreach (var data in datas)
+                //{
+                //    Datasource.Add(data);
+                //}
+                Datasource = new ObservableCollection<DIOData>(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}");
+                }));
+            }, "璇风◢鍊�", "姝e湪閲嶈繛...");
+        }
+
+        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)
+            {
+                if (data.ControlType == ControlType.BOOL)
+                {
+                    return new LightBall
+                    {
+                        Size = 40,
+                        DataContext = data
+                    };
+                }
+                else
+                {
+                    return new TextBox
+                    {
+                        IsReadOnly = true
+                    };
+                }
+            }
+            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<DIOData>();
+                }
+                else
+                {
+                    Datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(dataFilePath);
+                }
+            }
+            catch (Exception ex)
+            {
+                log.Error("Load Data Error.", ex);
+                Datasource = new ObservableCollection<DIOData>();
+            }
+            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<ConfigData>(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<DIOData>();
+                var connectStatus = false;
+                try
+                {
+                    if (File.Exists(dataFilePath))
+                    {
+                        datasource = XmlHelper.DeserializeFromXml<ObservableCollection<DIOData>>(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:
+                            case ControlType.PULSE:
+                                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:
+                                ((TextBox)ui).Text = value;
+                                break;
+                            default:
+                                ((TextBox)ui).Text = value;
+                                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;
+            }
+            string msg;
+            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 msg);
+                    }
+                    else
+                    {
+                        res = controller.ReadDOBool(data.Address, out val, out msg);
+                    }
+                    if (res == true)
+                    {
+                        value = val.ToString();
+                        return true;
+                    }
+                    value = null;
+                    return false;
+                default:
+                    if (controller.ReadDIDouble(data.Address, out double? dVal, out msg))
+                    {
+                        value = dVal.ToString();
+                    }
+                    value = null;
+                    return true;
+            }
+        }
+    }
+}

--
Gitblit v1.9.1