v9.7.2018.0421   支持运行时修改DAL连接字符串
大石头 编写于 2018-04-21 14:00:47
X
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Ports;
using System.Text;
using System.Windows.Forms;
using NewLife.Log;
using NewLife.Net;
using NewLife.Threading;

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

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

        private Int32 _BytesOfReceived;
        /// <summary>收到的字节数</summary>
        public Int32 BytesOfReceived { get { return _BytesOfReceived; } set { _BytesOfReceived = value; } }

        private Int32 _BytesOfSent;
        /// <summary>发送的字节数</summary>
        public Int32 BytesOfSent { get { return _BytesOfSent; } set { _BytesOfSent = value; } }
        #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);

            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);
            cbBaundrate.DataSource = bs;

            cbName.SelectedItem = cfg.PortName;
            cbBaundrate.SelectedItem = 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.UTF8, Encoding.Unicode, Encoding.BigEndianUnicode, Encoding.UTF32 };
            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;
        }

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

                //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;

                this.Invoke(() =>
                {
                    var old = cbName.SelectedItem + "";
                    cbName.DataSource = ps;
                    if (!String.IsNullOrEmpty(old) && Array.IndexOf(ps, old) >= 0) cbName.SelectedItem = 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.SelectedItem + "";
            if (String.IsNullOrEmpty(name))
            {
                "请选择串口".SpeechTip();
                MessageBox.Show("请选择串口!", Text);
                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 { }

            Enabled = 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();

            Enabled = true;
        }

        private void OnCheck(Object state)
        {
            if (Enabled) 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.Data;
            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;
        //}
    }
}