winform尝试使用NanUI 日志(实际安装winformium)(七)CS与BS的相互调用--COM口与HTML页面互动

       通过前面6章,实现了winform中安装NanUI或者winformium、调用了HTML页面、与HTML页面通过Handler及注入进行了互动。

       在第5章的时候,提到过“项目功能预想”,有5个主要功能。这章主要尝试第一个功能实现

一、实现“通过CS读取COM口数据,放到页面上,通过页面对COM口进行互动”

         1、基础的类

    public class RuanBaseClass
    {
        /// <summary>
        /// 检测是否,带上原因公共构造
        /// </summary>
        public class TestBoolAndString
        {
            /// <summary>
            /// 检测是否成功,默认为否,True 是警报,False 不警报
            /// </summary>
            public bool IsTestOk { get; set; } = false;
            /// <summary>
            /// 检测结果内容,默认为空
            /// </summary>
            public string TestRueseltString { get; set; } = "";
            /// <summary>
            /// 检测结果的JSON格式
            /// </summary>
            public string RuseltJsonString
            {
                get { return "{IsTestOK:" + this.IsTestOk.ToString() + ",TestRueseltString:" + this.TestRueseltString + "}"; ; }
            }
        }
        /// <summary>
        /// 传递页面的类型枚举下。随时更新
        /// </summary>
        public enum WindowPageType : int
        {
            /// <summary>
            /// 主页面
            /// </summary>
            SystemIndex = 1,
            /// <summary>
            /// 主页面的JS弹窗
            /// </summary>
            SystemIndexMessPageJS = 2,
        }


        public class SeralPortsHelper
        {

            #region 下拉框相关设置

            /// <summary>
            /// CS中设置串口号的下拉框
            /// </summary>
            /// <param name="obj"></param>
            public void SetPortNameValues(ComboBox obj, string ComName)
            {
                obj.Items.Clear();
                foreach (string str in SerialPort.GetPortNames())
                {
                    obj.Items.Add(str);
                    if (str == ComName)
                    {
                        obj.SelectedItem = ComName;
                    }
                }
            }

            /// <summary>
            /// CS中设置波特率的下拉框
            /// </summary>
            public void SetBauRateValues(ComboBox obj, string BrvValue)
            {
                obj.Items.Clear();
                foreach (SerialPortBaudRates rate in Enum.GetValues(typeof(SerialPortBaudRates)))
                {
                    obj.Items.Add(((int)rate).ToString());
                    if (((int)rate).ToString() == BrvValue)
                    {
                        obj.SelectedItem = BrvValue;
                    }
                }
            }

            /// <summary>
            /// CS中设置数据位的下拉框
            /// </summary>
            public void SetDataBitsValues(ComboBox obj, string BitValue)
            {
                obj.Items.Clear();
                foreach (SerialPortDatabits databit in Enum.GetValues(typeof(SerialPortDatabits)))
                {
                    obj.Items.Add(((int)databit).ToString());
                    if (((int)databit).ToString() == BitValue)
                    {
                        obj.SelectedItem = BitValue.ToString();
                    }
                }
            }

            /// <summary>
            /// CS中设置校验位列表的下拉框
            /// </summary>
            public void SetParityValues(ComboBox obj, string parityValue)
            {
                obj.Items.Clear();
                foreach (string str in Enum.GetNames(typeof(Parity)))
                {
                    obj.Items.Add(str);
                    if (str == parityValue)
                    {
                        obj.SelectedItem = str;
                    }
                }
            }

            /// <summary>
            /// CS中设置停止位的下拉框
            /// </summary>
            public void SetStopBitValues(ComboBox obj, string stopbitvalue)
            {
                obj.Items.Clear();
                foreach (int str in Enum.GetValues(typeof(StopBits)))
                {
                    obj.Items.Add(str);
                    if (str.ToString() == stopbitvalue)
                    {
                        obj.SelectedItem = str;
                    }
                }
            }


            #endregion

            #region 格式化数据

            /// <summary>
            /// 通过字符串设置串口名称,其实是验证下是否包含这个名字的端口号。
            /// </summary>
            /// <param name="searialPortStringName"></param>
            /// <returns>TestBoolAndString格式的结果,第一个值是是否成功,第二个是具体原因或返回值</returns>
            public TestBoolAndString GetAndCheckSerialPortName(string searialPortStringName)
            {
                TestBoolAndString _testBoolAndString = new TestBoolAndString();
                _testBoolAndString.IsTestOk = false;
                _testBoolAndString.TestRueseltString = "正在对【"+ searialPortStringName + "】进行设置。";
                foreach (string str in SerialPort.GetPortNames())
                {
                    if (str == searialPortStringName)
                    {
                        _testBoolAndString.IsTestOk = true;
                        _testBoolAndString.TestRueseltString = searialPortStringName;
                    }
                }
                if (!_testBoolAndString.IsTestOk) 
                {
                    _testBoolAndString.TestRueseltString = "设置COM口不存在,请检查。";
                }
                return _testBoolAndString;
            }

            /// <summary>
            /// 通过字符串获取停止位
            /// </summary>
            /// <param name="StopBitsText"></param>
            /// <returns></returns>
            public StopBits GetStopBits(string StopBitsText)
            {
                switch (StopBitsText.Trim().ToLower())
                {
                    case "none": return System.IO.Ports.StopBits.One;
                    case "one": return System.IO.Ports.StopBits.One;
                    case "onePointFive": return System.IO.Ports.StopBits.OnePointFive;
                    case "two": return System.IO.Ports.StopBits.Two;
                }
                return StopBits.One;
            }
            /// <summary>
            /// 获取数据位,默认为8,异常为8
            /// </summary>
            /// <param name="DatabitString">字符串的数据位</param>
            /// <returns></returns>
            public int GetDatabits(string DatabitString)
            {
                int tmpdata = 8;
                if (DatabitString == "5")
                {
                    tmpdata = 5;
                }
                else if (DatabitString == "6")
                {
                    tmpdata = 6;
                }
                else if (DatabitString == "7")
                {
                    tmpdata = 7;
                }
                else if (DatabitString == "8")
                {
                    tmpdata = 8;
                }
                else
                {
                    tmpdata = 8;
                }
                return tmpdata;
            }
            /// <summary>
            /// 获取数据位,默认为8,异常为8
            /// </summary>
            /// <param name="GetDatabits">数字的数据位</param>
            /// <returns></returns>
            public int GetDatabits(int DatabitString)
            {
                int tmpdata = 8;
                if (DatabitString == 5)
                {
                    tmpdata = 5;
                }
                else if (DatabitString == 6)
                {
                    tmpdata = 6;
                }
                else if (DatabitString == 7)
                {
                    tmpdata = 7;
                }
                else if (DatabitString == 8)
                {
                    tmpdata = 8;
                }
                else
                {
                    tmpdata = 8;
                }
                return tmpdata;
            }
            /// <summary>
            /// 将字符串转化为串口奇偶校验
            /// </summary>
            /// <param name="ParityString">输入的字符串奇偶</param>
            /// <returns></returns>
            public Parity GetParity(string ParityString)
            {
                if (ParityString == "Mark")
                {
                    return System.IO.Ports.Parity.Mark;
                }
                else if (ParityString == "Odd")
                {
                    return System.IO.Ports.Parity.Odd;
                }
                else if (ParityString == "Space")
                {
                    return System.IO.Ports.Parity.Space;
                }
                else if (ParityString == "Even")
                {
                    return System.IO.Ports.Parity.Even;
                }
                else
                {
                    return System.IO.Ports.Parity.None;
                }
            }

            /// <summary>
            ///通过数字,格式化波特率,默认或出错为9600
            /// </summary>
            public int GetBaudRates(int BaudRatesString)
            {
                int tempRate = 9600;
                if ((int)BaudRatesString > 0)
                {
                    tempRate = (int)BaudRatesString;
                }
                return tempRate;
            }
            /// <summary>
            ///通过字符串格式化波特率,默认或出错为9600
            /// </summary>
            public int GetBaudRates(string BaudRatesString)
            {
                int tempRate = 9600;
                if (Convert.ToInt32(BaudRatesString) > 0)
                {
                    tempRate = Convert.ToInt32(BaudRatesString);
                }
                return tempRate;
            }
            /// <summary>
            /// 将字符串转化为HOMS握手规则
            /// </summary>
            /// <param name="HandshakeString"></param>
            /// <returns></returns>
            public System.IO.Ports.Handshake GetHandshake(string HandshakeText)
            {
                switch (HandshakeText.Trim().ToLower())
                {
                    case "none": return System.IO.Ports.Handshake.None;
                    case "xonxoff": return System.IO.Ports.Handshake.XOnXOff;
                    case "requesttosend": return System.IO.Ports.Handshake.RequestToSend;
                    case "requesttosendxonxoff": return System.IO.Ports.Handshake.RequestToSendXOnXOff;
                }
                return System.IO.Ports.Handshake.None;
            }

            #endregion

            #region 串口数据规整
            /// <summary>
            /// 串口数据位列表(5,6,7,8)
            /// </summary>
            public enum SerialPortDatabits : int
            {
                FiveBits = 5,
                SixBits = 6,
                SeventBits = 7,
                EightBits = 8
            }

            /// <summary>
            /// 串口波特率列表。
            /// 75,110,150,300,600,1200,2400,4800,9600,14400,19200,28800,38400,56000,57600,
            /// 115200,128000,230400,256000
            /// </summary>
            public enum SerialPortBaudRates : int
            {
                BaudRate_75 = 75,
                BaudRate_110 = 110,
                BaudRate_150 = 150,
                BaudRate_300 = 300,
                BaudRate_600 = 600,
                BaudRate_1200 = 1200,
                BaudRate_2400 = 2400,
                BaudRate_4800 = 4800,
                BaudRate_9600 = 9600,
                BaudRate_14400 = 14400,
                BaudRate_19200 = 19200,
                BaudRate_28800 = 28800,
                BaudRate_38400 = 38400,
                BaudRate_56000 = 56000,
                BaudRate_57600 = 57600,
                BaudRate_115200 = 115200,
                BaudRate_128000 = 128000,
                BaudRate_230400 = 230400,
                BaudRate_256000 = 256000
            }


            /// <summary>
            /// 设置握手类型
            /// </summary>
            public enum SerialPortHandshake : int
            {
                None = 0,   //没有用于握手的控件。
                XOnXOff = 1,//使用 XON/XOFF 软件控制协议。 发送 XOFF 控制以停止数据传输。 发送 XON 控制以继续传输。 使用这些软件控制,而不是使用请求发送(RTS) 和清除发送(CTS) 硬件控制。
                RequestToSend = 2,//使用请求发送(RTS) 硬件流控制。 RTS 发出信号,指出数据可用于传输。 如果输入缓冲区已满,RTS 行将被设置为 false。 当输入缓冲区中有更多可用空间时,RTS 行将被设置为 true。
                RequestToSendXOnXOff = 3,//同时使用请求发送(RTS) 硬件控制和 XON/XOFF 软件控制。

            }
            /// <summary>
            /// 设置停止位
            /// </summary>
            public enum SerialPortStopBits : int
            {
                None = 0,
                One = 1,
                Two = 2,
                OnePointFive = 3
            }
            /// <summary>
            /// 设置奇偶校验位
            /// </summary>
            public enum SerialPortParity : int
            {
                //
                // 摘要:
                //     没有奇偶校验检查时发生。
                None = 0,
                //
                // 摘要:
                //     设置奇偶校验位,以便设置了位数为奇数。
                Odd = 1,
                //
                // 摘要:
                //     设置奇偶校验位,以便设置了位的计数为偶数。
                Even = 2,
                //
                // 摘要:
                //     将奇偶校验位设置为 1。
                Mark = 3,
                //
                // 摘要:
                //     将奇偶校验位设置为 0。
                Space = 4
            }

            #endregion
        }


    }

         2、CS中关键代码( SystemWindow: Formium)的CS代码中

 在之前项目的项目中,主要增加了下面代码

       (1)在代码开始前

        /// <summary>
        /// 获取下基础,这个窗口要调取的页面
        /// </summary>
        private RuanBaseClass.WindowPageType SystemMainPage = RuanBaseClass.WindowPageType.SystemIndex;
        /// <summary>
        /// new出个串口对象的新类。
        /// </summary>
        private RuanBaseClass.SeralPortsHelper thisPageSerialPort = new RuanBaseClass.SeralPortsHelper();
        /// <summary>
        /// 定义本页面的COM口
        /// </summary>
        private SerialPort serialPort = new SerialPort();
        /// <summary>
        /// 对外的字符串
        /// </summary>
        public string serialPortGetString = "等待数据传输中。。。";

       (2)在页面加载的时候(Window_Loaded)

            //获取加载的页面对象
            var frame = e.Browser.GetMainFrame();

            //开始注入JS前,获取注入的页面
            var hbrjso = BeginRegisterJavaScriptObject(frame);

            //这里建立obj,创建注入的对象,需要了解对应页面的JS的方法名。下面进行obj的创建
            //下面引用,阮2024.8.30.自己写的主页注入的方法,与主页一致。尝试
            var obj = RegisteredWindowPageJS(SystemMainPage);
            RegisterJavaScriptObject(hbrjso, "AllJsOfIndex", obj);
            //把第三个参数(仪器com口的JS,注入到页面中并在页面中的名字是DeviceSerialPort)
            RegisterJavaScriptObject(hbrjso, "DeviceSerialPort", RegisteredDeviceSerialPortJS());
            //结束注册
            EndRegisterJavaScriptObject(hbrjso);
         (3)新增COM口的JS注册方法

        /// <summary>
        /// 阮。2024.9.4 开始编写,把仪器中COM口的事情,注入到页面的JS
        /// </summary>
        /// <param name="systemMainPage">注入的页面</param>
        /// <returns>返回一个JS</returns>
        private JavaScriptObject RegisteredDeviceSerialPortJS()
        {
            //定义返回的JsObject
            var _regObg = new JavaScriptObject();
            var _GetComRuselt = new RuanBaseClass.TestBoolAndString();

            //给页面调取串口数据定义个获取值的属性
            _regObg.DefineProperty("SerialPortValue", () => serialPortGetString);

            //获取设备COM口
            _regObg.Add("GetDeviceSerialPortNameJS", args =>
            {
              
                _GetComRuselt.IsTestOk = false;
                _GetComRuselt.TestRueseltString = "未找到COM口";
                if (SerialPort.GetPortNames().Length > 0)
                {
                    _GetComRuselt.IsTestOk = true;
                    _GetComRuselt.TestRueseltString = string.Join(",", SerialPort.GetPortNames());
                }
                return _GetComRuselt.RuseltJsonString;
            });

            //设置SerialPort参数,并尝试打开。
            _regObg.Add("SetSerialPortTryOpen", async (args, promise) =>
            {
                var retval = string.Join(",", args.Select(x => x.GetString()));
                JsonDocument doc = JsonDocument.Parse(retval);
                JsonElement root = doc.RootElement;
                string portname = root.GetProperty("PortName").GetString();
                string portBaudRate = root.GetProperty("BaudRate").GetString();
                string portDataBits = root.GetProperty("DataBits").GetString();
                string portParity = root.GetProperty("Parity").GetString();
                string portStopBits = root.GetProperty("StopBits").GetString();
                string portOpen = root.GetProperty("Open").GetString();

                //输入参数正确
                if (portname != "" && portBaudRate != "" && portDataBits != "" && portParity != "" && portStopBits != "" && portOpen != "" )
                {

                    //如果设置参数是打开
                    if (portOpen == "true" || portOpen == "True" || portOpen == "TRUE")
                    {
                        _GetComRuselt = thisPageSerialPort.GetAndCheckSerialPortName(portname);

                        //检测名字通过,检测名字并尝试打开
                        if (_GetComRuselt.IsTestOk)
                        {
                            try
                            {

                                if (serialPort.IsOpen)
                                {
                                    serialPort.Close();
                                    serialPort.PortName = portname;
                                    serialPort.BaudRate = thisPageSerialPort.GetBaudRates(portBaudRate);
                                    serialPort.DataBits = thisPageSerialPort.GetDatabits(portDataBits);
                                    serialPort.Parity = thisPageSerialPort.GetParity(portParity);
                                    serialPort.StopBits = thisPageSerialPort.GetStopBits(portStopBits);
                                    serialPort.DataReceived += AllPort_DataReceived; // 绑定数据接收事件
                                    serialPort.Open();
                                    promise.Resolve("检测端口正常,此次关闭后重新打开");
                                }
                                else
                                {
                                    serialPort.PortName = portname;
                                    serialPort.BaudRate = thisPageSerialPort.GetBaudRates(portBaudRate);
                                    serialPort.DataBits = thisPageSerialPort.GetDatabits(portDataBits);
                                    serialPort.Parity = thisPageSerialPort.GetParity(portParity);
                                    serialPort.StopBits = thisPageSerialPort.GetStopBits(portStopBits);
                                    serialPort.DataReceived += AllPort_DataReceived; // 绑定数据接收事件
                                    serialPort.Open();
                                    promise.Resolve("检测端口正常,刚刚打开");

                                }
 
                            }

                            catch (Exception e)
                            {
                                string _msg = _GetComRuselt.RuseltJsonString + "\r\n 系统警报:\r\n" + e.ToString();
                                promise.Reject(_msg);
                            }
                        }
                        else
                        {
                            promise.Reject(_GetComRuselt.RuseltJsonString);
                        }
                     }
                    //不打开,只是测试
                    else
                    {
                        try
                        {
                            _GetComRuselt = thisPageSerialPort.GetAndCheckSerialPortName(portname);
                            thisPageSerialPort.GetBaudRates(portBaudRate);
                            thisPageSerialPort.GetDatabits(portDataBits);
                            thisPageSerialPort.GetParity(portParity);
                            thisPageSerialPort.GetStopBits(portStopBits);
                            promise.Resolve("检测端口正常。");
                        }

                        catch (Exception e)
                        {
                            string _msg = _GetComRuselt.RuseltJsonString + "\r\n 系统警报:\r\n" + e.ToString();
                            promise.Reject(_msg);
                        }
                    }
                   
                }
                else
                {
                    _GetComRuselt.IsTestOk = false;
                    _GetComRuselt.TestRueseltString = "你输入的值为:"+ retval.ToString() + "\r\n SerialPoart 参数错误:PortName 、 BaudRate、   DataBits 、  Parity 、  StopBits 、 Open ";
                    promise.Reject(_GetComRuselt.RuseltJsonString);
                }
                
            });
            return _regObg;
        }

        3、在HTML页面中的关键JS( index.html)的JS脚本中

主要增加了按钮,并点击按钮执行GetComPageTest(),页面增加DIV,通过innerHTML展示出来,并不断刷新。

    <h1>-----COM口测试-阮------</h1>
    <button name="testlogin" onClick="GetComPageTest()">获取COM口测试</button>

     <div id="myDiv">这里展示数据</div>

  <h1>----COM口测试结束--------</h1>

 //关于获取CS的COM口的测试
 function GetComPageTest() {

     var ComString = window.external.DeviceSerialPort.GetDeviceSerialPortNameJS();
     alert("获取COM口名称"+ComString);

     const PortObj = {
         PortName:"COM4",
         BaudRate: "9600",
         DataBits: "8",
         Parity: "None", 
         StopBits: "1", 
         Open: "true"
     }

    //将COM口配置组成JSON
     var PortObjS = JSON.stringify(PortObj);
    //获取CS执行COM口操作的promise
    var promise = window.external.DeviceSerialPort.SetSerialPortTryOpen(PortObjS);
    //将结果展示出来
     promise.then(res => { alert(res) }).catch(err => { alert(err) });
 }
 //设置获取数据的时间间隔,毫秒
 var t = setInterval('GetComdata()',100)
 function GetComdata() {
     document.getElementById('myDiv').innerHTML = window.external.DeviceSerialPort.SerialPortValue;
  }

看下实现功能的视频

CSND-COM与html-6

二、此次探坑总结

    主要遇到的坑

    1、serialPort.IsOpen (不同实例中的拒绝访问报错)

     new了serialPort 后,本想的是点击开始获取数据,再次点击停止获取数据。

    把new  serialPort  放到点击按钮后执行的注入JS 代码中,意味着每次点击开始获取数据,则新new中,运行过程中不报错,多次点击按钮后,报个“COM口拒绝访问”的错。后查相关资料,由于注入JS,打开COM口放在了子线程中,每次点击都new出一个。

    也就是说第一次点击按钮正常运行。第二次再点击按钮,则对已经开始采集的COM,再次open。

     实际代码用 isopen=true,表面看是正常,实际第二次点击按钮后new出来的COM口,并未打开,所以在打断点的时候,会让人误以为第一COM口没有打开。

      解决办法就是点击开始采集按钮的时候,不要new出新的COM口,而是对已有的COM进行操作,也就是把New COM口放外面去。

   2、页面与CS的通讯(约定JSON)

       在项目实现过程中,JS给CS传递了个object对象,但如何获取其内容,尝试了很多方法都不满意。考虑到JS与CS对于数组的读写编辑删除方面,虽然能将对象进行传递,但不能相互混用。结合将来也必定有其他对象的传递,比如XML等。为此约定了相互传递,还是以JSON为主。

    本案例,JS定义:

const PortObj = {
    PortName:"COM4",
    BaudRate: "9600",
    DataBits: "8",
    Parity: "None", 
    StopBits: "1", 
    Open: "true"
}

     在CS中,要读取和写入COM口名称等参数,也按JSON进行约定,因此有了       

          var _GetComRuselt = new RuanBaseClass.TestBoolAndString();

        JsonDocument doc = JsonDocument.Parse(retval);
        JsonElement root = doc.RootElement;

的代码,同样JS中也有JSON的操作,需要注意的是JSON的解析,需要WIN7\IE8\ .net6.0\等要求,在开发中要特别注意。

 3、JS注入对象,Promise 相关。

       CS代码中,异步调用返回,作者采用promis,Jsobj.Add(..., async (args, promise) ..

      在对promis不了解情况下,坑中挖掘2天。

      各位看官移步

前端 JS 经典:Promise 详解_js promise-CSDN博客,这里讲的比较明确。

      winformium作者,在注入JS代码中采用了该前台很经典的异步方法。

      大概意思:CS异步调用,有完成与否、成功、失败、失败后如何操作等。JS调用CS的异步操作,CS中随时调整promise的状态,则JS可以同步获取异步调用情况,而不需要始终等待CS执行完毕后的返回值。

       如果不采用这个方法,而逐一进行代码,JS和CS的异步调用,将变得超级复杂。为此,作者案例采用该方法,极大简化了异步调用的代码复杂度。赞一个。

PS,在作者的反馈群中,有大神提出了CS中用task。的确功能类似,但JS和CS如此方便的知晓相互调用状态,还是推荐作者promise的方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值