异步处理定时任务,避免耗时过长
大石头 authored at 2019-12-22 20:29:25
18.45 KiB
XCoder
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Ports;
using System.Linq;
using System.Text;
using Gtk;
using NewLife.Log;
using NewLife.Net;
using NewLife.Threading;
using XCoder.Util;

namespace NewLife.Windows
{
    /// <summary>串口列表控件</summary>
    [DefaultEvent("ReceivedString")]
    public partial class SerialPortList : HBox
    {
        #region 属性
        /// <summary>端口</summary>
        public SerialTransport Port { get; set; }

        /// <summary>选择的端口</summary>
        public String SelectedPort { get { return cbName.GetActiveObject() + ""; } set { } }

        /// <summary>收到的字节数</summary>
        public Int32 BytesOfReceived { get; set; }

        /// <summary>发送的字节数</summary>
        public Int32 BytesOfSent { get; set; }
        #endregion

        #region 构造
        /// <summary></summary>
        public SerialPortList()
        {
            InitializeComponent();
        }

        TimerX _timer;
        private void SerialPortList_Load(Object sender, EventArgs e)
        {
            LoadInfo();

            // 挂载定时器
            _timer = new TimerX(OnCheck, null, 300, 300) { Async = true };

            //if (Parent is Form frm)
            //{
            //    frm.FormClosing += frm_FormClosing;
            //}
        }

        //void frm_FormClosing(Object sender, FormClosingEventArgs e)
        //{
        //    SaveInfo();

        //    _timer.Dispose();
        //    _timer = null;

        //    if (Port != null) Port.Close();
        //}
        #endregion

        #region 加载保存信息
        /// <summary>加载配置信息</summary>
        public void LoadInfo()
        {
            ShowPorts();

            //BindMenu(mi数据位, On数据位Click, new Int32[] { 5, 6, 7, 8 });
            //BindMenu(mi停止位, On停止位Click, Enum.GetValues(typeof(StopBits)));
            //BindMenu(mi校验, On校验Click, Enum.GetValues(typeof(Parity)));

            //var arr = new Int32[] { 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000 };
            var bs = new List<Int32>(new Int32[] { 1200, 2400, 4800, 9600, 14400, 19200, 38400, 56000, 57600, 115200, 128000, 194000, 256000, 512000, 1024000, 2048000 });

            var cfg = SerialPortConfig.Current;
            if (cfg.BaudRate > 0 && !bs.Contains(cfg.BaudRate)) bs.Add(cfg.BaudRate);
            cbBaudrate.AppendValues(bs);

            cbName.SetActive(cfg.PortName);
            cbBaudrate.SetActive(cfg.BaudRate);
            //SetMenuItem(mi数据位, cfg.DataBits);
            //SetMenuItem(mi停止位, cfg.StopBits);
            //SetMenuItem(mi校验, cfg.Parity);

            //cbEncoding.DataSource = new String[] { Encoding.UTF8.WebName, Encoding.ASCII.WebName, Encoding.UTF8.WebName };
            // 添加编码子菜单
            var encs = new Encoding[] { Encoding.UTF8, Encoding.ASCII, Encoding.UTF7, Encoding.Unicode, Encoding.BigEndianUnicode/*, Encoding.UTF32,Encoding.GetEncoding("GB2312") */};
            var list = new List<Encoding>(encs);
            // 暂时不用这么多编码
            //list.AddRange(Encoding.GetEncodings().Select(e => e.GetEncoding()).Where(e => !encs.Contains(e)));
            var k = 0;
            //foreach (var item in list)
            //{
            //    if (k++ == encs.Length)
            //    {
            //        var sep = new ToolStripSeparator();
            //        mi字符串编码.DropDownItems.Add(sep);
            //    }
            //    var ti = mi字符串编码.DropDownItems.Add(item.EncodingName) as ToolStripMenuItem;
            //    ti.Name = item.WebName;
            //    ti.Tag = item;
            //    ti.Checked = item.WebName.EqualIgnoreCase(cfg.WebEncoding);
            //    ti.Click += On编码Click;
            //}

            //miHEX编码接收.Checked = cfg.HexShow;
            //mi字符串编码.Checked = !cfg.HexShow;
            //miHex不换行.Checked = !cfg.HexNewLine;
            //miHex自动换行.Checked = cfg.HexNewLine;
            //miHEX编码发送.Checked = cfg.HexSend;
            //miDTR.Checked = cfg.DtrEnable;
            //miRTS.Checked = cfg.RtsEnable;
            //miBreak.Checked = cfg.BreakState;
        }

        /// <summary>保存配置信息</summary>
        public void SaveInfo()
        {
            try
            {
                var cfg = SerialPortConfig.Current;
                cfg.PortName = cbName.GetActiveObject() + "";
                if (cbBaudrate.GetActiveObject() != null)
                    cfg.BaudRate = cbBaudrate.GetActiveObject().ToInt();
                //else
                //    cfg.BaudRate = cbBaudrate.ToInt();
                //cfg.DataBits = (Int32)cbDataBit.GetActiveObject();
                //cfg.StopBits = (StopBits)cbStopBit.GetActiveObject();
                //cfg.Parity = (Parity)cbParity.GetActiveObject();
                //cfg.Encoding = (Encoding)cbEncoding.GetActiveObject();
                //cfg.WebEncoding = cbEncoding.GetActiveObject() + "";

                //cfg.HexSend = chkHEXSend.Checked;
                //cfg.HexShow = chkHEXShow.Checked;

                cfg.Save();
            }
            catch (Exception ex)
            {
                XTrace.WriteException(ex);
            }
        }

        String _ports = null;
        DateTime _nextport = DateTime.MinValue;
        /// <summary>下拉框显示串口</summary>
        public void ShowPorts()
        {
            if (_nextport > DateTime.Now) return;
            _nextport = DateTime.Now.AddSeconds(1);

            var ps = SerialTransport.GetPortNames();
            var str = String.Join(",", ps);
            // 如果端口有所改变,则重新绑定
            if (_ports != str)
            {
                if (_ports != null) "串口有改变".SpeechTip();

                _ports = str;

                Application.Invoke((s, e) =>
                {
                    var old = cbName.GetActiveObject() + "";
                    cbName.AppendValues(ps.ToList());
                    if (!String.IsNullOrEmpty(old) && Array.IndexOf(ps, old) >= 0) cbName.SetActive(old);
                });
            }
        }
        #endregion

        #region 菜单设置
        ///// <summary>右键菜单</summary>
        //public ContextMenuStrip Menu { get { return contextMenuStrip1; } }

        //void On数据位Click(Object sender, EventArgs e)
        //{
        //    SelectMenu(sender);

        //    var mi = sender as ToolStripMenuItem;
        //    var cfg = SerialPortConfig.Current;
        //    cfg.DataBits = (Int32)mi.Tag;
        //}

        //void On停止位Click(Object sender, EventArgs e)
        //{
        //    SelectMenu(sender);

        //    var mi = sender as ToolStripMenuItem;
        //    var cfg = SerialPortConfig.Current;
        //    cfg.StopBits = (StopBits)mi.Tag;
        //}

        //void On校验Click(Object sender, EventArgs e)
        //{
        //    SelectMenu(sender);

        //    var mi = sender as ToolStripMenuItem;
        //    var cfg = SerialPortConfig.Current;
        //    cfg.Parity = (Parity)mi.Tag;
        //}

        //void On编码Click(Object sender, EventArgs e)
        //{
        //    // 不要选其它
        //    var mi = sender as ToolStripMenuItem;
        //    if (mi == null) return;
        //    //foreach (ToolStripMenuItem item in (mi.OwnerItem as ToolStripMenuItem).DropDownItems)
        //    //{
        //    //    item.Checked = item == mi;
        //    //}
        //    SelectMenu(sender);

        //    // 保存编码
        //    var cfg = SerialPortConfig.Current;
        //    cfg.WebEncoding = mi.Name;
        //}

        //private void mi字符串编码_Click(Object sender, EventArgs e)
        //{
        //    var cfg = SerialPortConfig.Current;
        //    //cfg.HexShow = miHEX编码接收.Checked = !mi字符串编码.Checked;
        //    cfg.HexShow = SelectMenu(sender, mi字符串编码, miHEX编码接收) == 1;

        //    // 收起菜单
        //    contextMenuStrip1.Hide();
        //}

        //private void miHex自动换行_Click(Object sender, EventArgs e)
        //{
        //    var cfg = SerialPortConfig.Current;
        //    //cfg.HexNewLine = ti.Tag.ToBoolean();
        //    cfg.HexNewLine = SelectMenu(sender) == 1;
        //}

        //private void miHEX编码发送_Click(Object sender, EventArgs e)
        //{
        //    var cfg = SerialPortConfig.Current;
        //    miHEX编码发送.Checked = !miHEX编码发送.Checked;
        //    cfg.HexSend = miHEX编码发送.Checked;
        //}

        //private void miDTR_Click(Object sender, EventArgs e)
        //{
        //    var mi = sender as ToolStripMenuItem;
        //    mi.Checked = !mi.Checked;
        //}
        #endregion

        #region 窗体
        /// <summary>连接串口</summary>
        public void Connect()
        {
            var name = cbName.GetActiveObject() + "";
            if (String.IsNullOrEmpty(name))
            {
                "请选择串口".SpeechTip();
                MessageBox.Show("请选择串口!");
                //cbName.Focus();
                return;
            }
            var p = name.IndexOf("(");
            if (p > 0) name = name.Substring(0, p);

            SaveInfo();
            var cfg = SerialPortConfig.Current;

            var pt = Port;
            if (pt != null)
            {
                // 如果上次没有关闭,则关闭
                try
                {
                    pt.Close();
                }
                catch
                {
                    pt = null;
                }
            }
            if (pt == null) pt = new SerialTransport();
            pt.PortName = name;
            pt.BaudRate = cfg.BaudRate;
            pt.Parity = cfg.Parity;
            pt.DataBits = cfg.DataBits;
            pt.StopBits = cfg.StopBits;

            //pt.Open();

            pt.Disconnected += Port_Disconnected;
            pt.Received += OnReceived;
            pt.Open();

            //pt.EnsureCreate();

            Port = pt;

            var sp = pt.Serial;
            // 这几个需要打开端口以后才能设置
            //try
            //{
            //    if (sp.DtrEnable != miDTR.Checked) sp.DtrEnable = miDTR.Checked;
            //    if (sp.RtsEnable != miRTS.Checked) sp.RtsEnable = miRTS.Checked;
            //    if (sp.BreakState != miBreak.Checked) sp.BreakState = miBreak.Checked;
            //}
            //catch { }

            Sensitive = false;
        }

        void Port_Disconnected(Object sender, EventArgs e)
        {
            Disconnect();
        }

        /// <summary>断开串口连接</summary>
        public void Disconnect()
        {
            var pt = Port;
            if (pt != null)
            {
                Port = null;
                pt.Disconnected -= Port_Disconnected;
                pt.Received -= OnReceived;
                pt.Close();
            }

            ShowPorts();

            Sensitive = true;
        }

        private void OnCheck(Object state)
        {
            if (Sensitive) ShowPorts();
        }
        #endregion

        #region 菜单辅助
        //ToolStripItem FindMenu(String name)
        //{
        //    return contextMenuStrip1.Items.Find(name, false)[0];
        //}

        //void BindMenu(ToolStripMenuItem ti, EventHandler handler, IEnumerable em)
        //{
        //    ti.DropDownItems.Clear();
        //    foreach (var item in em)
        //    {
        //        var tsi = ti.DropDownItems.Add(item + "");
        //        tsi.Tag = item;
        //        tsi.Click += handler;
        //    }
        //}

        //void SetMenuItem(ToolStripMenuItem ti, Object value)
        //{
        //    foreach (ToolStripMenuItem item in ti.DropDownItems)
        //    {
        //        item.Checked = item + "" == value + "";
        //    }
        //}

        ///// <summary>在一个菜单列表中点击某一个菜单,改变菜单选择并返回选中的菜单索引</summary>
        ///// <param name="sender"></param>
        ///// <param name="ms"></param>
        //Int32 SelectMenu(Object sender, params ToolStripMenuItem[] ms)
        //{
        //    var mi = sender as ToolStripMenuItem;
        //    if (mi == null) return -1;

        //    var idx = -1;
        //    for (var i = 0; i < ms.Length; i++)
        //    {
        //        if (ms[i] == mi)
        //        {
        //            ms[i].Checked = true;
        //            idx = i;
        //        }
        //        else
        //            ms[i].Checked = false;
        //    }
        //    return idx;
        //}

        ///// <summary>在同级菜单中选择</summary>
        ///// <param name="sender"></param>
        ///// <param name="tsi"></param>
        ///// <returns></returns>
        //Int32 SelectMenu(Object sender, ToolStripItem tsi = null)
        //{
        //    if (tsi == null) tsi = (sender as ToolStripMenuItem).OwnerItem;

        //    var ms = (tsi as ToolStripMenuItem).DropDownItems;
        //    if (ms == null) return -1;

        //    var arr = new ToolStripMenuItem[ms.Count];
        //    ms.CopyTo(arr, 0);

        //    return SelectMenu(sender, arr);
        //}
        #endregion

        #region 收发数据
        /// <summary>发送字节数组</summary>
        /// <param name="data"></param>
        public void Send(Byte[] data)
        {
            if (data == null || data.Length <= 0) return;

            BytesOfSent += data.Length;

            Port.Send(data);
        }

        /// <summary>发送字符串。根据配置进行十六进制编码</summary>
        /// <param name="str"></param>
        /// <returns>发送字节数</returns>
        public Int32 Send(String str)
        {
            var cfg = SerialPortConfig.Current;
            // 16进制发送
            Byte[] data = null;
            if (cfg.HexSend)
                data = str.ToHex();
            else
                data = cfg.Encoding.GetBytes(str);

            Send(data);

            return data.Length;
        }

        //public event SerialReceived Received;
        /// <summary>收到数据时触发。第一个参数是数据,第二个参数返回是否继续往下传递数据</summary>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public event EventHandler<BufferEventArgs> Received;

        /// <summary>收到数据时转为字符串后触发该事件。注意字符串编码和十六进制编码。</summary>
        /// <remarks>如果需要收到的数据,可直接在<seealso cref="Port"/>上挂载事件</remarks>
        [Browsable(true)]
        [EditorBrowsable(EditorBrowsableState.Always)]
        public event EventHandler<StringEventArgs> ReceivedString;

        MemoryStream _stream;
        StreamReader _reader;
        void OnReceived(Object sender, ReceivedEventArgs e)
        {
            var data = e.Packet.ReadBytes();
            if (data == null || data.Length < 1) return;

            BytesOfReceived += data.Length;

            // 处理数据委托
            if (Received != null)
            {
                var e2 = new BufferEventArgs { Value = data };
                Received(this, e2);
                if (!e2.Cancel) return;
                // 外部可能修改了数据
                data = e2.Value;
                //if (!BufferEventArgs.Invoke(Received, data)) return null;
            }

            // 处理字符串委托
            if (ReceivedString == null) return;

            var cfg = SerialPortConfig.Current;

            var line = "";
            if (cfg.HexShow)
            {
                if (data.Length > 32)
                    line = "[{0}]=\r\n{1}".F(data.Length, data.ToHex("-", 32));
                else
                    line = "[{0}]={1}".F(data.Length, data.ToHex("-", 32));
                if (cfg.HexNewLine) line += Environment.NewLine;
            }
            else
            {
                line = cfg.Encoding.GetString(data);
                if (_stream == null)
                    _stream = new MemoryStream();
                else if (_stream.Length > 10 * 1024 && _stream.Position == _stream.Length) // 达到最大大小时,从头开始使用
                    _stream = new MemoryStream();
                _stream.Write(data);
                _stream.Seek(-1 * data.Length, SeekOrigin.Current);

                if (_reader == null ||
                    _reader.BaseStream != _stream ||
                    _reader.CurrentEncoding != cfg.Encoding)
                    _reader = new StreamReader(_stream, cfg.Encoding);
                line = _reader.ReadToEnd();
                // 替换掉无效数据
                line = line.Replace("\0", null);
            }

            ReceivedString?.Invoke(this, new StringEventArgs { Value = line });
        }
        #endregion

        #region 收发统计
        /// <summary>清空发送</summary>
        public void ClearSend()
        {
            BytesOfSent = 0;

            var sp = Port;
            if (sp != null)
            {
                try
                {
                    if (sp.Serial != null) sp.Serial.DiscardOutBuffer();
                }
                catch { }
            }
        }

        /// <summary>清空接收</summary>
        public void ClearReceive()
        {
            BytesOfReceived = 0;
            if (_stream != null) _stream.SetLength(0);

            var sp = Port;
            if (sp != null)
            {
                try
                {
                    if (sp.Serial != null) sp.Serial.DiscardInBuffer();
                }
                catch { }
            }
        }
        #endregion
    }

    /// <summary>字符串事件参数</summary>
    public class StringEventArgs : EventArgs
    {
        private String _Value;
        /// <summary>字符串值</summary>
        public String Value { get { return _Value; } set { _Value = value; } }
    }

    /// <summary>缓冲区事件参数</summary>
    public class BufferEventArgs : CancelEventArgs
    {
        private Byte[] _Value;
        /// <summary>缓冲区数据</summary>
        public Byte[] Value { get { return _Value; } set { _Value = value; } }

        //public static Boolean Invoke(EventHandler<BufferEventArgs> handler, Object sender, Byte[] buf)
        //{
        //    var e = new BufferEventArgs { Value = buf, Cancel = false };
        //    handler(sender, e);
        //    return e.Cancel;
        //}
    }
}