NewLife/XCoder

完成串口调试页面,未测试
笑笑 authored at 2019-11-13 23:25:52
ca0d9d5
Tree
1 Parent(s) b8fc2ab
Summary: 9 changed files with 1483 additions and 10 deletions.
Modified +10 -0
Modified +3 -0
Modified +1 -0
Modified +10 -10
Modified +14 -0
Added +220 -0
Added +457 -0
Added +565 -0
Added +203 -0
Modified +10 -0
diff --git a/.gitignore b/.gitignore
index 7bf46c5..94d1d68 100644
--- a/.gitignore
+++ b/.gitignore
@@ -30,3 +30,13 @@ bld/
 /XCoder40/Entity
 /XCoder40/obj4
 /Config
+/netcoreapp3.0
+/XCoder.pdb
+/XCoder.exe.config
+/XCoder.exe
+/XCode.pdb
+/XCode.dll
+/NewLife.Net.pdb
+/NewLife.Net.dll
+/NewLife.Core.pdb
+/NewLife.Core.dll
Modified +3 -0
diff --git a/XCoder/XCom/SerialTransport.cs b/XCoder/XCom/SerialTransport.cs
index 1284846..1ebf425 100644
--- a/XCoder/XCom/SerialTransport.cs
+++ b/XCoder/XCom/SerialTransport.cs
@@ -373,6 +373,7 @@ namespace NewLife.Net
         public static Dictionary<String, String> GetNames()
         {
             var dic = new Dictionary<String, String>(StringComparer.OrdinalIgnoreCase);
+#if !NC30
             using (var key = Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\SERIALCOMM", false))
             using (var usb = Registry.LocalMachine.OpenSubKey(@"SYSTEM\CurrentControlSet\Enum\USB", false))
             {
@@ -417,6 +418,8 @@ namespace NewLife.Net
                     }
                 }
             }
+#endif
+
             return dic;
         }
 
Modified +1 -0
diff --git a/XCoderLinux/Program.cs b/XCoderLinux/Program.cs
index 2db58c5..6f56138 100644
--- a/XCoderLinux/Program.cs
+++ b/XCoderLinux/Program.cs
@@ -24,6 +24,7 @@ namespace XCoder
             Application.Init();
             var window = new SharpApp();
             window.ShowAll();
+            // Window.InteractiveDebugging = true;
             //XTrace2.UseWinForm(window);
             Application.Run();
         }
Modified +10 -10
diff --git a/XCoderLinux/SharpApp.cs b/XCoderLinux/SharpApp.cs
index 8d06a03..a13f2d5 100644
--- a/XCoderLinux/SharpApp.cs
+++ b/XCoderLinux/SharpApp.cs
@@ -7,7 +7,8 @@ using Gdk;
 using Gtk;
 using NewLife.Reflection;
 //using XApi;
-using XNet;
+//using XNet;
+using XCom;
 using Window = Gtk.Window;
 
 namespace XCoder
@@ -40,13 +41,12 @@ namespace XCoder
         private void SharpApp_Shown(System.Object sender, System.EventArgs e)
         {
             var set = XConfig.Current;
-            //if (set.Width > 0 || set.Height > 0)
-            //{
-            //    WidthRequest = set.Width;
-            //    //Top = set.Top;
-            //    //Left = set.Left;
-            //    HeightRequest = set.Height;
-            //}
+            if (set.Width > 0 || set.Height > 0)
+            {
+                DefaultWidth = set.Width;
+                DefaultHeight = set.Height;
+                //SetDefaultSize(set.Width, set.Height);
+            }
 
             SetPosition(WindowPosition.Center);
 
@@ -54,9 +54,9 @@ namespace XCoder
 
             AddMenu();
 
-            //var xapi = new FrmMain();
+            var frm = new FrmMain();
 
-            //_windowBox.PackStart(xapi, true, true, 0);
+            _windowBox.PackStart(frm, true, true, 0);
 
             Add(_windowBox);
 
Modified +14 -0
diff --git a/XCoderLinux/XCoderLinux.csproj b/XCoderLinux/XCoderLinux.csproj
index be84ff4..1158eff 100644
--- a/XCoderLinux/XCoderLinux.csproj
+++ b/XCoderLinux/XCoderLinux.csproj
@@ -31,6 +31,8 @@
     <Compile Include="..\XCoder\IXForm.cs" Link="IXForm.cs" />
     <Compile Include="..\XCoder\XApi\ApiConfig.cs" Link="XApi\ApiConfig.cs" />
     <Compile Include="..\XCoder\XApi\MyApiController.cs" Link="XApi\MyApiController.cs" />
+    <Compile Include="..\XCoder\XCom\SerialPortConfig.cs" Link="XCom\SerialPortConfig.cs" />
+    <Compile Include="..\XCoder\XCom\SerialTransport.cs" Link="XCom\SerialTransport.cs" />
     <Compile Include="..\XCoder\XNet\BenchHelper.cs" Link="XNet\BenchHelper.cs" />
     <Compile Include="..\XCoder\XNet\IPPacket.cs" Link="XNet\IPPacket.cs" />
     <Compile Include="..\XCoder\XNet\MySqlParser.cs" Link="XNet\MySqlParser.cs" />
@@ -44,6 +46,7 @@
     <PackageReference Include="GtkSharp" Version="3.22.25.24" />
     <PackageReference Include="NewLife.Core" Version="8.4.2019.1109" />
     <PackageReference Include="NewLife.XCode" Version="9.14.2019.1109" />
+    <PackageReference Include="System.IO.Ports" Version="4.6.0" />
   </ItemGroup>
 
   <ItemGroup>
@@ -51,6 +54,17 @@
     <Folder Include="Engine\" />
   </ItemGroup>
 
+  <ItemGroup>
+    <Compile Update="XCom\FrmMain.cs" />
+    <Compile Update="XCom\FrmMain.designer.cs">
+      <DependentUpon>FrmMain.cs</DependentUpon>
+    </Compile>
+    <Compile Update="XCom\SerialPortList.cs" />
+    <Compile Update="XCom\SerialPortList.designer.cs">
+      <DependentUpon>SerialPortList.cs</DependentUpon>
+    </Compile>
+  </ItemGroup>
+
   <!--<ItemGroup>
     <Reference Include="NewLife.Core">
       <HintPath>..\..\Bin\netstandard2.1\NewLife.Core.dll</HintPath>
Added +220 -0
diff --git a/XCoderLinux/XCom/FrmMain.cs b/XCoderLinux/XCom/FrmMain.cs
new file mode 100644
index 0000000..f0f211b
--- /dev/null
+++ b/XCoderLinux/XCom/FrmMain.cs
@@ -0,0 +1,220 @@
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using Gtk;
+using NewLife.Log;
+using NewLife.Threading;
+using NewLife.Windows;
+using XCoder;
+using XCoder.Util;
+
+namespace XCom
+{
+    [DisplayName("串口调试工具")]
+    public partial class FrmMain : VBox, IXForm
+    {
+        /// <summary>业务日志输出</summary>
+        ILog BizLog;
+
+        #region 窗体
+        public FrmMain()
+        {
+            InitializeComponent();
+
+            // 动态调节宽度高度,兼容高DPI
+            //this.FixDpi();
+
+            //Icon = IcoHelper.GetIcon("串口");
+        }
+
+        private void FrmMain_Load(Object sender, EventArgs e)
+        {
+            var log = TextFileLog.Create(null, "Serial_{0:yyyy_MM_dd}.log");
+            BizLog = txtReceive.Combine(log);
+            txtReceive.UseWinFormControl();
+
+            //txtReceive.SetDefaultStyle(12);
+            //txtSend.SetDefaultStyle(12);
+            //numMutilSend.SetDefaultStyle(12);
+
+            //gbReceive.Tag = gbReceive.Label;
+            //gbSend.Tag = gbSend.Label;
+
+            //var menu = spList.Menu;
+            //txtReceive.ContextMenuStrip = menu;
+
+            //// 添加清空
+            //menu.Items.Insert(0, new ToolStripSeparator());
+            //var ti = new ToolStripMenuItem("清空");
+            //menu.Items.Insert(0, ti);
+            //ti.Click += mi清空_Click;
+
+            // 加载保存的颜色
+            //UIConfig.Apply(txtReceive);
+
+            //ti = new ToolStripMenuItem("日志着色");
+            //ti.Name = "日志着色";
+            //menu.Items.Insert(2, ti);
+            //ti.Click += miCheck_Click;
+            //ti.Checked = XConfig.Current.ColorLog;
+        }
+        #endregion
+
+        #region 收发数据
+        void Connect()
+        {
+            spList.Connect();
+            var st = spList.Port;
+            //st.FrameSize = 8;
+            if (st == null) return;
+
+            // 需要考虑UI线程
+            st.Disconnected += Disconnect;
+
+            // 发现USB2401端口,自动发送设置命令
+            if (st.Description.Contains("USB2401") || st.Description.Contains("USBSER"))
+            {
+                var cmd = "AT+SET=00070000000000";
+                st.Send(cmd.GetBytes());
+                BizLog.Info(cmd);
+                //TextControlLog.WriteLog(txtReceive, cmd);
+            }
+
+            "连接串口{0}".F(st.PortName).SpeechTip();
+
+            btnConnect.Label = "关闭";
+
+            //var menu = txtReceive.ContextMenuStrip;
+            //var mi = menu.Items.Find("日志着色", false).FirstOrDefault() as ToolStripMenuItem;
+
+            var cfg = XConfig.Current;
+            //cfg.ColorLog = mi.Checked;
+            cfg.Save();
+        }
+
+        void Disconnect(Object sender, EventArgs e) =>
+            Application.Invoke((s, e) =>
+            {
+                //if (spList.Enabled) return;
+                if (btnConnect.Label == "打开") return;
+
+                var st = spList.Port;
+                if (st != null) st.Disconnected -= Disconnect;
+                spList.Disconnect();
+
+                "串口已断开".SpeechTip();
+
+                btnConnect.Label = "打开";
+            });
+
+        private void btnConnect_Click(Object sender, EventArgs e)
+        {
+            var btn = sender as Button;
+            if (btn.Label == "打开")
+                Connect();
+            else
+                Disconnect(null, null);
+        }
+
+        void OnReceived(Object sender, StringEventArgs e)
+        {
+            var line = e.Value;
+            //TextControlLog.WriteLog(txtReceive, line);
+
+            BizLog?.Info(line);
+        }
+
+        Int32 _pColor = 0;
+        Int32 lastReceive = 0;
+        Int32 lastSend = 0;
+        //private void timer1_Tick(Object sender, EventArgs e)
+        //{
+        //    var sp = spList.Port;
+        //    if (sp != null)
+        //    {
+        //        // 检查串口是否已断开,自动关闭已断开的串口,避免内存暴涨
+        //        if (!spList.Sensitive && btnConnect.Label == "打开")
+        //        {
+        //            Disconnect(null,null);
+        //            return;
+        //        }
+
+        //        var rcount = spList.BytesOfReceived;
+        //        var tcount = spList.BytesOfSent;
+        //        if (rcount != lastReceive)
+        //        {
+        //            gbReceive.Label = (gbReceive.Tag + "").Replace("0", rcount + "");
+        //            lastReceive = rcount;
+        //        }
+        //        if (tcount != lastSend)
+        //        {
+        //            gbSend.Label = (gbSend.Tag + "").Replace("0", tcount + "");
+        //            lastSend = tcount;
+        //        }
+
+        //        var set = XConfig.Current;
+        //        if (set.ColorLog) txtReceive.ColourDefault(_pColor);
+        //        _pColor = txtReceive.TextLength;
+        //    }
+        //}
+
+        private void btnSend_Click(Object sender, EventArgs e)
+        {
+            var str = txtSend.Buffer.Text;
+            if (String.IsNullOrEmpty(str))
+            {
+                MessageBox.Show("发送内容不能为空!");
+                //txtSend.Focus();
+                return;
+            }
+
+            // 多次发送
+            var count = (Int32)numMutilSend.Value;
+            var sleep = (Int32)numSleep.Value;
+            if (count <= 0) count = 1;
+            if (sleep <= 0) sleep = 100;
+
+            // 处理换行
+            str = str.Replace("\n", "\r\n");
+
+            if (count == 1)
+            {
+                spList.Send(str);
+                return;
+            }
+
+            ThreadPoolX.QueueUserWorkItem(() =>
+            {
+                for (var i = 0; i < count; i++)
+                {
+                    spList.Send(str);
+
+                    if (count > 1) Thread.Sleep(sleep);
+                }
+            });
+        }
+        #endregion
+
+        #region 右键菜单
+        private void mi清空_Click(Object sender, EventArgs e)
+        {
+            txtReceive.Buffer.Clear();
+            spList.ClearReceive();
+        }
+
+        private void mi清空2_Click(Object sender, EventArgs e)
+        {
+            txtSend.Buffer.Clear();
+            spList.ClearSend();
+        }
+
+        //private void miCheck_Click(Object sender, EventArgs e)
+        //{
+        //    //var mi = sender as ToolStripMenuItem;
+        //    //mi.Checked = !mi.Checked;
+        //}
+        #endregion
+    }
+}
\ No newline at end of file
Added +457 -0
diff --git a/XCoderLinux/XCom/FrmMain.designer.cs b/XCoderLinux/XCom/FrmMain.designer.cs
new file mode 100644
index 0000000..2704bb1
--- /dev/null
+++ b/XCoderLinux/XCom/FrmMain.designer.cs
@@ -0,0 +1,457 @@
+using Gtk;
+using NewLife.Windows;
+
+namespace XCom
+{
+    partial class FrmMain
+    {
+        #region Windows 窗体设计器生成的代码
+
+        /// <summary>
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.gbReceive = new Frame();
+            this.txtReceiveScrolledWindow = new ScrolledWindow();
+            this.txtReceive = new TextView();
+            //this.menuReceive = new ContextMenuStrip(this.components);
+            //this.toolStripMenuItem1 = new ToolStripMenuItem();
+            //this.mi日志着色 = new ToolStripMenuItem();
+            //this.toolStripMenuItem3 = new ToolStripSeparator();
+            //this.mi显示应用日志 = new ToolStripMenuItem();
+            //this.mi显示编码日志 = new ToolStripMenuItem();
+            //this.mi显示发送数据 = new ToolStripMenuItem();
+            //this.mi显示接收数据 = new ToolStripMenuItem();
+            //this.mi显示统计信息 = new ToolStripMenuItem();
+            //this.menuSend = new ContextMenuStrip(this.components);
+            //this.mi清空2 = new ToolStripMenuItem();
+            this.btnConnect = new Button();
+            //this.timer1 = new Timer(this.components);
+            //this.fontDialog1 = new FontDialog();
+            //this.colorDialog1 = new ColorDialog();
+            this.label1 = new Label();
+            this.lbAddr = new Label();
+            this.cbMode = new ComboBox(new string[] { });
+            this.cbLocal = new ComboBox(new string[] { });
+            this.cbRemote = ComboBoxText.NewWithEntry();
+            this.pnlSetting = new HBox();
+            this.numPort = new SpinButton(1, 63353, 1);
+            this.label5 = new Label();
+            this.gbSend = new Frame();
+            this.boxSend = new VBox();
+            this.boxSendSetting = new HBox();
+            this.numThreads = new SpinButton(1, 100000, 1);
+            this.numSleep = new SpinButton(1000, 1000000, 1);
+            this.txtSendScrolledWindow = new ScrolledWindow();
+            this.txtSend = new TextView();
+            this.btnSend = new Button();
+            this.numMutilSend = new SpinButton(1, 1000000, 1);
+            this.label2 = new Label();
+            this.label7 = new Label();
+            this.label8 = new Label();
+            //this.toolTip1 = new ToolTip(this.components);
+            this.spList = new SerialPortList();
+            //
+            // gbReceive
+            //
+            this.gbReceive.Add(this.txtReceiveScrolledWindow);
+            this.gbReceive.Margin = 2;
+            this.gbReceive.Name = "gbReceive";
+            //this.gbReceive.SetSizeRequest(878, 298);
+            // gbReceive.Margin = 10;
+            this.gbReceive.Label = "接收区:已接收0字节";
+            //this.gbReceive.Halign = Align.Fill;
+            //this.gbReceive.Valign = Align.Fill;
+            this.gbReceive.ShadowType = ShadowType.Out;
+            //
+            // txtReceiveScrolledWindow
+            //
+            this.txtReceiveScrolledWindow.Add(this.txtReceive);
+            this.txtReceiveScrolledWindow.CanFocus = true;
+            this.txtReceiveScrolledWindow.ShadowType = ShadowType.In;
+            //this.txtReceiveScrolledWindow.Vexpand = true;
+            //
+            // txtReceive
+            //
+            // this.txtReceive.ContextMenuStrip = this.menuReceive;
+            // this.txtReceive.Dock = DockStyle.Fill;
+            // this.txtReceive.HideSelection = false;
+            // this.txtReceive.Location = new System.Drawing.Point(4, 25);
+            this.txtReceive.Margin = 1;
+            this.txtReceive.Name = "txtReceive";
+            // this.txtReceive.Size = new System.Drawing.Size(970, 269);
+            this.txtReceive.Editable = false;
+            // 滚动方法 https://stackoverflow.com/questions/37824865/gtk-textview-auto-scroll-when-text-is-added-to-text-buffer
+            this.txtReceive.SizeAllocated += TxtReceive_SizeAllocated;
+            ////
+            //// menuReceive
+            ////
+            //this.menuReceive.ImageScalingSize = new System.Drawing.Size(32, 32);
+            //this.menuReceive.Items.AddRange(new ToolStripItem[] {
+            //this.toolStripMenuItem1,
+            //this.mi日志着色,
+            //this.toolStripMenuItem3,
+            //this.mi显示应用日志,
+            //this.mi显示编码日志,
+            //this.mi显示发送数据,
+            //this.mi显示接收数据,
+            //this.mi显示统计信息});
+            //this.menuReceive.Name = "menuSend";
+            //this.menuReceive.Size = new System.Drawing.Size(189, 206);
+            ////
+            //// toolStripMenuItem1
+            ////
+            //this.toolStripMenuItem1.Name = "toolStripMenuItem1";
+            //this.toolStripMenuItem1.Size = new System.Drawing.Size(188, 28);
+            //this.toolStripMenuItem1.Text = "清空";
+            //this.toolStripMenuItem1.Click += new System.EventHandler(this.mi清空_Click);
+            ////
+            //// mi日志着色
+            ////
+            //this.mi日志着色.Name = "mi日志着色";
+            //this.mi日志着色.Size = new System.Drawing.Size(188, 28);
+            //this.mi日志着色.Text = "日志着色";
+            //this.mi日志着色.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// toolStripMenuItem3
+            ////
+            //this.toolStripMenuItem3.Name = "toolStripMenuItem3";
+            //this.toolStripMenuItem3.Size = new System.Drawing.Size(185, 6);
+            ////
+            //// mi显示应用日志
+            ////
+            //this.mi显示应用日志.Name = "mi显示应用日志";
+            //this.mi显示应用日志.Size = new System.Drawing.Size(188, 28);
+            //this.mi显示应用日志.Text = "显示应用日志";
+            //this.mi显示应用日志.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// mi显示编码日志
+            ////
+            //this.mi显示编码日志.Name = "mi显示编码日志";
+            //this.mi显示编码日志.Size = new System.Drawing.Size(188, 28);
+            //this.mi显示编码日志.Text = "显示编码日志";
+            //this.mi显示编码日志.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// mi显示发送数据
+            ////
+            //this.mi显示发送数据.Name = "mi显示发送数据";
+            //this.mi显示发送数据.Size = new System.Drawing.Size(188, 28);
+            //this.mi显示发送数据.Text = "显示发送数据";
+            //this.mi显示发送数据.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// mi显示接收数据
+            ////
+            //this.mi显示接收数据.Name = "mi显示接收数据";
+            //this.mi显示接收数据.Size = new System.Drawing.Size(188, 28);
+            //this.mi显示接收数据.Text = "显示接收数据";
+            //this.mi显示接收数据.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// mi显示统计信息
+            ////
+            //this.mi显示统计信息.Name = "mi显示统计信息";
+            //this.mi显示统计信息.Size = new System.Drawing.Size(188, 28);
+            //this.mi显示统计信息.Text = "显示统计信息";
+            //this.mi显示统计信息.Click += new System.EventHandler(this.miCheck_Click);
+            ////
+            //// menuSend
+            ////
+            //this.menuSend.ImageScalingSize = new System.Drawing.Size(32, 32);
+            //this.menuSend.Items.AddRange(new ToolStripItem[] {
+            //this.mi清空2});
+            //this.menuSend.Name = "menuSend";
+            //this.menuSend.Size = new System.Drawing.Size(117, 32);
+            ////
+            //// mi清空2
+            ////
+            //this.mi清空2.Name = "mi清空2";
+            //this.mi清空2.Size = new System.Drawing.Size(116, 28);
+            //this.mi清空2.Text = "清空";
+            //this.mi清空2.Click += new System.EventHandler(this.mi清空2_Click);
+            //
+            // btnConnect
+            //
+            // this.btnConnect.Location = new System.Drawing.Point(748, 12);
+            this.btnConnect.Margin = 4;
+            this.btnConnect.Name = "btnConnect";
+            // this.btnConnect.Size = new System.Drawing.Size(100, 44);
+            // this.btnConnect.TabIndex = 3;
+            this.btnConnect.Label = "打开";
+            this.btnConnect.Clicked += new System.EventHandler(this.btnConnect_Click);
+            ////
+            //// timer1
+            ////
+            //this.timer1.Enabled = true;
+            //this.timer1.Interval = 300;
+            //this.timer1.Tick += new System.EventHandler(this.timer1_Tick);
+            //
+            // label1
+            //
+            //this.label1.AutoSize = true;
+            this.label1.Xalign = 0.8F;
+            this.label1.Yalign = 0.5F;
+            this.label1.MarginTop = 4;
+            this.label1.MarginBottom = 4;
+            this.label1.Name = "label1";
+            //this.label1.WidthRequest = 62;
+            //this.label1.HeightRequest = 18;
+            this.label1.Text = "模式:";
+            //
+            // lbAddr
+            //
+            // this.lbAddr.AutoSize = true;
+            // this.lbAddr.Location = new System.Drawing.Point(374, 14);
+            this.lbAddr.Xalign = 0.8F;
+            this.lbAddr.Yalign = 0.5F;
+            this.lbAddr.MarginTop = 4;
+            this.lbAddr.MarginBottom = 4;
+            this.lbAddr.Name = "lbAddr";
+            //this.lbAddr.WidthRequest = 62;
+            //this.lbAddr.HeightRequest = 18;
+            this.lbAddr.Text = "远程:";
+            //
+            // cbMode
+            //
+            //this.cbMode.DropDownStyle = ComboBoxStyle.DropDownList;
+            //this.cbMode.FormattingEnabled = true;
+            //this.cbMode.Location = new System.Drawing.Point(72, 11);
+            //this.cbMode.Margin = 4;
+            this.cbMode.Active = 1;
+            this.cbMode.Name = "cbMode";
+            //this.cbMode.SetSizeRequest(1, 1);
+            //this.cbMode.Changed += new System.EventHandler(this.cbMode_SelectedIndexChanged);
+            //
+            // cbLocal
+            //
+            //this.cbLocal.Active = 1;
+            this.cbLocal.Name = "cbLocal";
+            //this.cbLocal.SetSizeRequest(1, 1);
+            //this.cbLocal.Changed += new System.EventHandler(this.cbLocal_SelectedIndexChanged);
+            //
+            // cbRemote
+            //
+            //this.cbRemote.Margin = 4;
+            this.cbRemote.Name = "cbRemote";
+            //this.cbRemote.SetSizeRequest(200, 18);
+            // this.cbRemote.Size = new System.Drawing.Size(247, 26);
+            this.cbRemote.Sensitive = true;
+            //this.cbRemote.
+            // 
+            // spList
+            // 
+            this.spList.BytesOfReceived = 0;
+            this.spList.BytesOfSent = 0;
+            this.spList.Name = "spList";
+            this.spList.Port = null;
+            this.spList.SelectedPort = "COM1(Serial0)";
+            this.spList.ReceivedString += new System.EventHandler<NewLife.Windows.StringEventArgs>(this.OnReceived);
+            //
+            // pnlSetting
+            //
+            this.pnlSetting.PackStart(this.spList, false, false, 1);
+            //this.pnlSetting.PackStart(this.label1, false, false, 1);
+            //this.pnlSetting.PackStart(this.cbMode, false, false, 1);
+            //this.pnlSetting.PackStart(this.label5, false, false, 1);
+            //this.pnlSetting.PackStart(this.cbLocal, false, false, 1);
+            //this.pnlSetting.PackStart(this.lbAddr, false, false, 1);
+            //this.pnlSetting.PackStart(this.cbRemote, false, false, 1);
+            //this.pnlSetting.PackStart(this.numPort, false, false, 1);
+            this.pnlSetting.PackStart(this.btnConnect, false, false, 1);
+
+            //this.pnlSetting.Add();
+            //this.pnlSetting.Position = 5;// .Location = new System.Drawing.Point(14, 12);
+            this.pnlSetting.Margin = 2;
+            this.pnlSetting.Name = "pnlSetting";
+            // this.pnlSetting.Valign = Align.Fill;
+            // this.pnlSetting.Halign = Align.Fill;
+            // this.pnlSetting.SetSizeRequest(708, 20);
+            // pnlSetting.Orientation = Orientation.Horizontal;
+            //this.pnlSetting.Size = new System.Drawing.Size(708, 46);
+            //this.pnlSetting.TabIndex = 13;
+            //
+            // numPort
+            //
+            // this.numPort.Location = new System.Drawing.Point(270, 10);
+            this.numPort.Margin = 4;
+            this.numPort.Name = "numPort";
+            this.numPort.Value = 8080;
+            //this.numPort.SetSizeRequest(94, 28);
+            //
+            // label5
+            //
+            //this.label5.AutoSize = true;
+            //this.label5.Location = new System.Drawing.Point(218, 14);
+            this.label5.Xalign = 0.8F;
+            this.label5.Yalign = 0.5F;
+            this.label5.MarginTop = 4;
+            this.label5.MarginBottom = 4;
+            this.label5.Name = "label5";
+            //this.label5.SetSizeRequest(62, 18);
+            this.label5.Text = "本地:";
+            //
+            // gbSend
+            //
+            this.gbSend.Add(this.boxSend);
+            // this.gbSend.Controls.Add(this.numThreads);
+            // this.gbSend.Controls.Add(this.numSleep);
+            // this.gbSend.Controls.Add(this.btnSend);
+            // this.gbSend.Controls.Add(this.numMutilSend);
+            // this.gbSend.Controls.Add(this.label2);
+            // this.gbSend.Controls.Add(this.label7);
+            // this.gbSend.Location = new System.Drawing.Point(14, 424);
+            this.gbSend.Margin = 1;
+            this.gbSend.Name = "gbSend";
+            //this.gbSend.SetSizeRequest(778, 126);
+            this.gbSend.Label = "发送区:已发送0字节";
+            //
+            // boxSend
+            //
+            this.boxSend.PackStart(this.txtSendScrolledWindow, false, false, 2);
+            this.boxSend.PackStart(this.boxSendSetting, false, false, 2);
+            // this.boxSend.Orientation = Orientation.Vertical;
+            this.boxSend.Margin = 1;
+            this.boxSend.Name = "boxSend";
+            //
+            // boxSendSetting
+            //
+            this.boxSendSetting.PackStart(this.label7, false, false, 2);
+            this.boxSendSetting.PackStart(this.numMutilSend, false, false, 2);
+            this.boxSendSetting.PackStart(this.label8, false, false, 2);
+            this.boxSendSetting.PackStart(this.numThreads, false, false, 2);
+            this.boxSendSetting.PackStart(this.label2, false, false, 2);
+            this.boxSendSetting.PackStart(this.numSleep, false, false, 2);
+            this.boxSendSetting.PackStart(this.btnSend, false, false, 2);
+            this.boxSendSetting.Margin = 1;
+            this.boxSendSetting.Name = "sendSetting";
+            //
+            // numThreads
+            //
+            this.numThreads.Margin = 4;
+            this.numThreads.Name = "numThreads";
+            //this.numThreads.SetSizeRequest(78, 28);
+            // this.toolTip1.SetToolTip(this.numThreads, "模拟多客户端发送,用于压力测试!");
+            //
+            // numSleep
+            //
+            this.numSleep.Margin = 1;
+            this.numSleep.Name = "numSleep";
+            //this.numSleep.SetSizeRequest(109, 28);
+            //
+            // txtSendScrolledWindow
+            //
+            this.txtSendScrolledWindow.CanFocus = true;
+            this.txtSendScrolledWindow.ShadowType = ShadowType.In;
+            this.txtSendScrolledWindow.Vexpand = true;
+            this.txtSendScrolledWindow.Add(this.txtSend);
+            //
+            // txtSend
+            //
+            // this.txtSend.ContextMenuStrip = this.menuSend;
+            // this.txtSend.Location = new System.Drawing.Point(0, 28);
+            this.txtSend.Margin = 4;
+            this.txtSend.Name = "txtSend";
+            //this.txtSend.SetSizeRequest(621, 86);
+            this.txtSend.WrapMode = WrapMode.Word;
+            //this.txtSend.Editable = false;
+            //
+            // btnSend
+            //
+            this.btnSend.Margin = 4;
+            this.btnSend.Name = "btnSend";
+            //this.btnSend.SetSizeRequest(75, 45);
+            this.btnSend.Label = "发送";
+            this.btnSend.Clicked += new System.EventHandler(this.btnSend_Click);
+            //
+            // numMutilSend
+            //
+            this.numMutilSend.Margin = 4;
+            this.numMutilSend.Name = "numMutilSend";
+            //this.numMutilSend.SetSizeRequest(109, 28);
+            //
+            // label2
+            //
+            // this.label2.Location = new System.Drawing.Point(729, 87);
+            this.label2.Margin = 4;
+            this.label2.Name = "label2";
+            //this.label2.SetSizeRequest(62, 18);
+            this.label2.Text = "间隔:";
+            //
+            // label7
+            //
+            // this.label7.Location = new System.Drawing.Point(729, 39);
+            this.label7.Margin = 4;
+            this.label7.Name = "label7";
+            //this.label7.SetSizeRequest(62, 18);
+            this.label7.Text = "次数:";
+            //
+            // label8
+            //
+            this.label8.Margin = 4;
+            this.label8.Name = "label8";
+            //this.label8.SetSizeRequest(62, 18);
+            this.label8.Text = "线程:";
+            ////
+            //// FrmMain
+            ////
+            //this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);
+            //this.AutoScaleMode = AutoScaleMode.Font;
+            //this.ClientSize = new System.Drawing.Size(1000, 568);
+            this.PackStart(this.pnlSetting, false, false, 2);
+            this.PackStart(this.gbReceive, true, true, 2);
+            this.PackStart(this.gbSend, false, false, 2);
+            //this.Margin = new Padding(4);
+            //this.Name = "FrmMain";
+            //this.StartPosition = FormStartPosition.CenterScreen;
+            //this.Text = "Api调试";
+            this.Shown += new System.EventHandler(this.FrmMain_Load);
+            //this.menuReceive.ResumeLayout(false);
+            //this.menuSend.ResumeLayout(false);
+            //this.pnlSetting.ResumeLayout(false);
+            //this.pnlSetting.PerformLayout();
+            //((System.ComponentModel.ISupportInitialize)(this.numPort)).EndInit();
+            //((System.ComponentModel.ISupportInitialize)(this.numThreads)).EndInit();
+            //((System.ComponentModel.ISupportInitialize)(this.numSleep)).EndInit();
+            //((System.ComponentModel.ISupportInitialize)(this.numMutilSend)).EndInit();
+            //this.ResumeLayout(false);
+            //this.PerformLayout();
+            //this.Fill = true;
+            //this.Halign = Align.Fill;
+            //this.Valign = Align.Fill;
+            this.Orientation = Orientation.Vertical;
+        }
+
+        private void TxtReceive_SizeAllocated(System.Object o, SizeAllocatedArgs args)
+        {
+            var s = o as TextView;
+
+            s.ScrollToIter(s.Buffer.EndIter, 0, false, 0, 0);
+        }
+        #endregion
+
+        private Frame gbReceive;
+        private Button btnConnect;
+        private ScrolledWindow txtReceiveScrolledWindow;
+        private TextView txtReceive;
+        private Label label1;
+        private Label lbAddr;
+        private ComboBox cbMode;
+        private ComboBox cbLocal;
+        private ComboBoxText cbRemote;
+        private HBox pnlSetting;
+        private Frame gbSend;
+        private VBox boxSend;
+        private HBox boxSendSetting;
+        private SpinButton numSleep;
+        private ScrolledWindow txtSendScrolledWindow;
+        private TextView txtSend;
+        private Button btnSend;
+        private SpinButton numMutilSend;
+        private Label label2;
+        private Label label7;
+        private Label label8;
+        private SpinButton numThreads;
+        private SpinButton numPort;
+        private Label label5;
+        private NewLife.Windows.SerialPortList spList;
+    }
+}
+
Added +565 -0
diff --git a/XCoderLinux/XCom/SerialPortList.cs b/XCoderLinux/XCom/SerialPortList.cs
new file mode 100644
index 0000000..2421fcb
--- /dev/null
+++ b/XCoderLinux/XCom/SerialPortList.cs
@@ -0,0 +1,565 @@
+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);
+
+            //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 = (Int32)cbBaudrate.GetActiveObject();
+                //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;
+        //}
+    }
+}
\ No newline at end of file
Added +203 -0
diff --git a/XCoderLinux/XCom/SerialPortList.designer.cs b/XCoderLinux/XCom/SerialPortList.designer.cs
new file mode 100644
index 0000000..9d5a0fe
--- /dev/null
+++ b/XCoderLinux/XCom/SerialPortList.designer.cs
@@ -0,0 +1,203 @@
+using Gtk;
+
+namespace NewLife.Windows
+{
+    partial class SerialPortList
+    {
+        #region 组件设计器生成的代码
+
+        /// <summary> 
+        /// </summary>
+        private void InitializeComponent()
+        {
+            this.cbBaudrate = new ComboBox(new string[] { });
+            this.label2 = new Label();
+            this.cbName = new ComboBox(new string[] { });
+            this.label3 = new Label();
+            //this.contextMenuStrip1 = new ContextMenuStrip(this.components);
+            //this.mi数据位 = new ToolStripMenuItem();
+            //this.mi停止位 = new ToolStripMenuItem();
+            //this.mi校验 = new ToolStripMenuItem();
+            //this.mi高级 = new ToolStripMenuItem();
+            //this.miDTR = new ToolStripMenuItem();
+            //this.miRTS = new ToolStripMenuItem();
+            //this.miBreak = new ToolStripMenuItem();
+            //this.toolStripMenuItem1 = new ToolStripSeparator();
+            //this.mi字符串编码 = new ToolStripMenuItem();
+            //this.miHEX编码接收 = new ToolStripMenuItem();
+            //this.miHex不换行 = new ToolStripMenuItem();
+            //this.miHex自动换行 = new ToolStripMenuItem();
+            //this.miHEX编码发送 = new ToolStripMenuItem();
+            //this.toolStripMenuItem2 = new ToolStripSeparator();
+            //this.contextMenuStrip1.SuspendLayout();
+            // 
+            // cbBaudrate
+            // 
+            this.cbBaudrate.Name = "cbBaudrate";
+            // 
+            // label2
+            // 
+            this.label2.Name = "label2";
+            this.label2.Text = "波特率:";
+            // 
+            // cbName
+            // 
+            this.cbName.Name = "cbName";
+            // 
+            // label3
+            // 
+            this.label3.Name = "label3";
+            this.label3.Text = "端口:";
+            //// 
+            //// contextMenuStrip1
+            //// 
+            ////this.contextMenuStrip1.Items.AddRange(new ToolStripItem[] {
+            ////this.mi数据位,
+            ////this.mi停止位,
+            ////this.mi校验,
+            ////this.mi高级,
+            ////this.toolStripMenuItem1,
+            ////this.mi字符串编码,
+            ////this.miHEX编码接收,
+            ////this.miHEX编码发送,
+            ////this.toolStripMenuItem2});
+            ////this.contextMenuStrip1.Name = "contextMenuStrip1";
+            ////this.contextMenuStrip1.Size = new System.Drawing.Size(149, 170);
+            //// 
+            //// mi数据位
+            //// 
+            //this.mi数据位.Name = "mi数据位";
+            //this.mi数据位.Size = new System.Drawing.Size(148, 22);
+            //this.mi数据位.Text = "数据位";
+            //// 
+            //// mi停止位
+            //// 
+            //this.mi停止位.Name = "mi停止位";
+            //this.mi停止位.Size = new System.Drawing.Size(148, 22);
+            //this.mi停止位.Text = "停止位";
+            //// 
+            //// mi校验
+            //// 
+            //this.mi校验.Name = "mi校验";
+            //this.mi校验.Size = new System.Drawing.Size(148, 22);
+            //this.mi校验.Text = "校验";
+            //// 
+            //// mi高级
+            //// 
+            //this.mi高级.DropDownItems.AddRange(new ToolStripItem[] {
+            //this.miDTR,
+            //this.miRTS,
+            //this.miBreak});
+            //this.mi高级.Name = "mi高级";
+            //this.mi高级.Size = new System.Drawing.Size(148, 22);
+            //this.mi高级.Text = "高级";
+            //// 
+            //// miDTR
+            //// 
+            //this.miDTR.Name = "miDTR";
+            //this.miDTR.Size = new System.Drawing.Size(110, 22);
+            //this.miDTR.Text = "DTR";
+            //this.miDTR.Click += new System.EventHandler(this.miDTR_Click);
+            //// 
+            //// miRTS
+            //// 
+            //this.miRTS.Name = "miRTS";
+            //this.miRTS.Size = new System.Drawing.Size(110, 22);
+            //this.miRTS.Text = "RTS";
+            //this.miRTS.Click += new System.EventHandler(this.miDTR_Click);
+            //// 
+            //// miBreak
+            //// 
+            //this.miBreak.Name = "miBreak";
+            //this.miBreak.Size = new System.Drawing.Size(110, 22);
+            //this.miBreak.Text = "Break";
+            //this.miBreak.Click += new System.EventHandler(this.miDTR_Click);
+            //// 
+            //// toolStripMenuItem1
+            //// 
+            //this.toolStripMenuItem1.Name = "toolStripMenuItem1";
+            //this.toolStripMenuItem1.Size = new System.Drawing.Size(145, 6);
+            //// 
+            //// mi字符串编码
+            //// 
+            //this.mi字符串编码.Name = "mi字符串编码";
+            //this.mi字符串编码.Size = new System.Drawing.Size(148, 22);
+            //this.mi字符串编码.Text = "字符串编码";
+            //this.mi字符串编码.Click += new System.EventHandler(this.mi字符串编码_Click);
+            //// 
+            //// miHEX编码接收
+            //// 
+            //this.miHEX编码接收.DropDownItems.AddRange(new ToolStripItem[] {
+            //this.miHex不换行,
+            //this.miHex自动换行});
+            //this.miHEX编码接收.Name = "miHEX编码接收";
+            //this.miHEX编码接收.Size = new System.Drawing.Size(148, 22);
+            //this.miHEX编码接收.Text = "HEX编码接收";
+            //this.miHEX编码接收.Click += new System.EventHandler(this.mi字符串编码_Click);
+            //// 
+            //// miHex不换行
+            //// 
+            //this.miHex不换行.Name = "miHex不换行";
+            //this.miHex不换行.Size = new System.Drawing.Size(124, 22);
+            //this.miHex不换行.Tag = "false";
+            //this.miHex不换行.Text = "不换行";
+            //this.miHex不换行.Click += new System.EventHandler(this.miHex自动换行_Click);
+            //// 
+            //// miHex自动换行
+            //// 
+            //this.miHex自动换行.Name = "miHex自动换行";
+            //this.miHex自动换行.Size = new System.Drawing.Size(124, 22);
+            //this.miHex自动换行.Tag = "true";
+            //this.miHex自动换行.Text = "自动换行";
+            //this.miHex自动换行.Click += new System.EventHandler(this.miHex自动换行_Click);
+            //// 
+            //// miHEX编码发送
+            //// 
+            //this.miHEX编码发送.Name = "miHEX编码发送";
+            //this.miHEX编码发送.Size = new System.Drawing.Size(148, 22);
+            //this.miHEX编码发送.Text = "HEX编码发送";
+            //this.miHEX编码发送.Click += new System.EventHandler(this.miHEX编码发送_Click);
+            //// 
+            //// toolStripMenuItem2
+            //// 
+            //this.toolStripMenuItem2.Name = "toolStripMenuItem2";
+            //this.toolStripMenuItem2.Size = new System.Drawing.Size(145, 6);
+            // 
+            // SerialPortList
+            // 
+            //this.ContextMenuStrip = this.contextMenuStrip1;
+            this.PackStart(this.label3, false, false, 1);
+            this.PackStart(this.cbName, false, false, 1);
+            this.PackStart(this.label2, false, false, 1);
+            this.PackStart(this.cbBaudrate, false, false, 1);
+            this.Name = "SerialPortList";
+            this.Shown += new System.EventHandler(this.SerialPortList_Load);
+            //this.contextMenuStrip1.ResumeLayout(false);
+            //this.ResumeLayout(false);
+            //this.PerformLayout();
+        }
+
+        #endregion
+
+        private ComboBox cbBaudrate;
+        private Label label2;
+        private ComboBox cbName;
+        private Label label3;
+        //private ContextMenuStrip contextMenuStrip1;
+        //private ToolStripMenuItem mi数据位;
+        //private ToolStripMenuItem mi停止位;
+        //private ToolStripMenuItem mi校验;
+        //private ToolStripMenuItem mi字符串编码;
+        //private ToolStripMenuItem miHEX编码接收;
+        //private ToolStripMenuItem miHex不换行;
+        //private ToolStripMenuItem miHex自动换行;
+        //private ToolStripMenuItem mi高级;
+        //private ToolStripMenuItem miDTR;
+        //private ToolStripMenuItem miRTS;
+        //private ToolStripMenuItem miBreak;
+        //private ToolStripSeparator toolStripMenuItem1;
+        //private ToolStripSeparator toolStripMenuItem2;
+        //private ToolStripMenuItem miHEX编码发送;
+
+    }
+}