C# 学习笔记17:上位机助手_页面生成多控件滚动结果_保存与加载控件文本到 ...

打印 上一主题 下一主题

主题 556|帖子 556|积分 1668

今日继续完善更新我的上位机助手,这次完善多字符串发送的部门:
   目前上位机助手支持以下功能:
  1、  普通的16进制\ASCLL表现收发
  2、  全页更新HEX表现(会自动断串口)
  3、  日记辅助表现报错
  4、  必要的清除日记区、吸取区的逻辑
  5、  背景线程刷新查找串口,自动选择CH340串口
  6、  串口数据绘图,单元1ms,精度0.0001ms
  7、  多字符串发送(单击编辑,双击发送),发送计数成功次数
  8、  多字符串文本可以通过写入\读取文件进行加载
  文章提供完备代码解释、设计点解释、测试结果图、测试工程下载
   目次
  开发学习目标:
  动态添加TextBox到Panel:
  打开Panel控件的AutoScroll属性:
  编写代码:
  结果如下:
  TextBox的双击事件生成:
  结果如下:
  保存全部TextBox到文件:
  加载全部TextBox到历程:
  完备测试结果图如下:
  1、最初打开状态:
  2、初始化加载多字符串:
  3、加载之前保存的条目状态:
  4、修改后保存目前条目状态:
   5、先全删,再加载上次条目状态:
  测试工程下载:
   
  遇到的问题记录:
  解决后的源码如下:
  
  
开发学习目标:

   完善之前写的上位机助手,可以在页面中生成浩繁TextBox控件,双击即可发送数据
  

  
动态添加TextBox到Panel:

打开Panel控件的AutoScroll属性:

   AutoScroll属性被设置为true,允许Panel在内容超出其可视范围时表现滚动条。
  

   这里我设置了一个加载按键,在它按下后开始加载界面的Textbox组件:
  

  

  
编写代码:

   在点击事件中添加如下代码:
  1.         //开始加载多字符串发送助手
  2.         private void Load_multi_textbox_Click(object sender, EventArgs e)
  3.         {
  4.             int yPosition = 0; // 初始Y位置
  5.             int textBoxHeight = 25; // 假设每个TextBox的高度为20
  6.             for (int i = 0; i < 36; i++) // 假设我们要添加10个TextBox
  7.             {
  8.                 System.Windows.Forms.TextBox textBox = new System.Windows.Forms.TextBox();
  9.                 textBox.Text = $"TextBox {i + 1}"; // 为TextBox设置文本  
  10.                 textBox.Location = new Point(10, yPosition); // 设置TextBox的位置  
  11.                 textBox.Width = 1144; // 设置TextBox的宽度  
  12.                 textBox.Height = textBoxHeight; // 设置TextBox的高度  
  13.                 panel1.Controls.Add(textBox); // 将TextBox添加到Panel中
  14.                 yPosition += textBoxHeight + 5; // 更新Y位置,为下一个TextBox留出空间  
  15.             }
  16.             // 如需要可调整Panel的大小确保所有TextBox都可见  
  17.             // 由于设置了AutoScroll,这一步通常是可选的  
  18.             // 例如:panel1.Height = yPosition;  
  19.         }
复制代码

  
结果如下:

   

  点击按钮后:
  

  
TextBox的双击事件生成:

   只需添加在循环中为每个 TextBox 控件添加一个事件处理程序。由于事件处理程序需要引用到具体的 TextBox 控件,可以利用匿名方法或 lambda 表达式来捕捉当前的 TextBox 实例
  
  这个代码我并未将双击事件的调用写成串口发送,而是打印哪个编号的TextBox被双击来测试代码是否正常运行:
  1.         //开始加载多字符串发送助手
  2.         private void Load_multi_textbox_Click(object sender, EventArgs e)
  3.         {
  4.             int yPosition = 0; // 初始Y位置
  5.             int textBoxHeight = 25; // 假设每个TextBox的高度为20
  6.             for (int i = 0; i < 36; i++) // 假设我们要添加10个TextBox
  7.             {
  8.                 System.Windows.Forms.TextBox textBox = new System.Windows.Forms.TextBox();
  9.                 textBox.Text = $"TextBox {i + 1}"; // 为TextBox设置文本  
  10.                 textBox.Location = new Point(10, yPosition); // 设置TextBox的位置  
  11.                 textBox.Width = 1144; // 设置TextBox的宽度  
  12.                 textBox.Height = textBoxHeight; // 设置TextBox的高度  
  13.                 // 添加双击事件处理程序  
  14.                 textBox.DoubleClick += (senderTextBox, eArgs) =>
  15.                 {
  16.                     // 但由于lambda表达式的作用域,它实际上捕获了当前迭代的textBox实例  
  17.                     MessageBox.Show($"Double-clicked: {textBox.Text}"); // 显示被双击的TextBox的文本  
  18.                 };
  19.                 panel1.Controls.Add(textBox); // 将TextBox添加到Panel中
  20.                 yPosition += textBoxHeight + 5; // 更新Y位置,为下一个TextBox留出空间  
  21.             }
  22.             // 如需要可调整Panel的大小确保所有TextBox都可见
  23.             // 由于设置了AutoScroll,这一步通常是可选的  
  24.             // 例如:panel1.Height = yPosition;
  25.         }
复制代码

  
结果如下:

   双击不同的Textbox,打印它们各自的编号:
  

  

  

保存全部TextBox到文件:

   由于我是在循环中动态创建 TextBox 控件,并且每个 TextBox 都没有被单独存储为一个变量(除了循环中的临时变量 textBox),因此直接通过变量名来访问每个 TextBox 的内容是不可行的
  我选择遍历panel控件中的全部textbox来进行保存:
  这里如果发现没有这个文本文件,那就会自己创建一个进行保存
  1.         //保存条目输入状态
  2.         private void Save_multi_textbox_Click(object sender, EventArgs e)
  3.         {
  4.             // 定义文件路径  
  5.             string filePath = "Multi_String.txt";
  6.             // 可选:显式地清空文件内容(实际上不需要,因为接下来会用StreamWriter覆盖)  
  7.             // File.WriteAllText(filePath, string.Empty);  
  8.             // 使用StringWriter来构建要写入文件的字符串(可选,但更灵活)  
  9.             StringWriter stringWriter = new StringWriter();
  10.             foreach (Control control in panel1.Controls)
  11.             {
  12.                 // 检查控件是否是TextBox  
  13.                 if (control is System.Windows.Forms.TextBox textBox)
  14.                 {
  15.                     // 将TextBox的内容写入StringWriter  
  16.                     // stringWriter.WriteLine(textBox.Text);
  17.                     // 或者,如果您想要一些前缀或格式  
  18.                     stringWriter.WriteLine($"TextBox Content:{textBox.Text}");
  19.                 }
  20.             }
  21.             // 现在,将StringWriter的内容一次性写入文件  
  22.             // 注意:这实际上会覆盖文件,所以不需要先清空文件  
  23.             File.WriteAllText(filePath, stringWriter.ToString());
  24.             // 可选:显示消息框通知用户  
  25.             MessageBox.Show("所有TextBox内容已成功保存到Multi_String.txt文件中!");
  26.             // 释放StringWriter资源(虽然在这个简单的例子中,它会在GC时被自动处理)
  27.             stringWriter.Dispose();
  28.         }
复制代码

  
加载全部TextBox到历程:

   依旧是遍历全部Panel进行遍历并用文本中内容进行覆盖,这里我之前的保存文件代码中添加了标识头,需要去除:
  1.                 // 去除前缀(如果存在)  
  2.                 if (line.StartsWith("TextBox Content:"))  
  3.                 {  
  4.                     line = line.Substring("TextBox Content:".Length).Trim();  
  5.                 }  
复制代码
完备代码如下:
  1.         //加载条目状态:
  2.         private void Load_Muiti_string_Click(object sender, EventArgs e)
  3.         {
  4.             // 定义文件路径  
  5.             string filePath = "Multi_String.txt";
  6.             // 尝试读取文件  
  7.             if (File.Exists(filePath))
  8.             {
  9.                 int textBoxIndex = 0; // 用于跟踪当前应该填充哪个TextBox的索引  
  10.                 using (StreamReader reader = new StreamReader(filePath))
  11.                {
  12.                     string line;
  13.                     while ((line = reader.ReadLine()) != null)
  14.                     {
  15.                         // 去除前缀(如果存在)  
  16.                         if (line.StartsWith("TextBox Content:"))
  17.                         {
  18.                             line = line.Substring("TextBox Content:".Length).Trim();
  19.                         }
  20.                         // 尝试将读取的文本设置回TextBox(如果索引在范围内)  
  21.                         if (textBoxIndex < panel1.Controls.Count && panel1.Controls[textBoxIndex] is System.Windows.Forms.TextBox textBox)
  22.                         {
  23.                             textBox.Text = line;
  24.                         }
  25.                         // 移动到下一个TextBox  
  26.                         textBoxIndex++;
  27.                         // 如果TextBox的数量少于文件中的行数,则停止读取(可选)  
  28.                        // if (textBoxIndex >= panel1.Controls.OfType<TextBox>().Count()) break;  
  29.                     }
  30.                 }
  31.             }
  32.             else
  33.             {
  34.                 MessageBox.Show("文件Multi_String.txt不存在!");
  35.             }
  36.         }
复制代码

  
完备测试结果图如下:

   1、最初打开状态:

  

    2、初始化加载多字符串:

  

    3、加载之前保存的条目状态:

  

    4、修改后保存目前条目状态:

  

     5、先全删,再加载上次条目状态:

  

  

  
测试工程下载:

   https://download.csdn.net/download/qq_64257614/89642319
   

遇到的问题记录:

   这是我想在串口吸取停止进行一系列数据处理分流遇到的问题,
  我想在串口吸取停止中进行以下操纵:
  1、在串口缓冲区按格式处理数据,处理好的存在一个字典列表中,供表格曲线控件打印
  2、原始数据根据勾选框来转化为ASCLL或HEX形式打印在吸取区文本框中
  
    起初,我直接想在串口吸取停止这个线程直接更新文本框UI的内容,但不行,因为串口吸取停止线程与文本框UI线程不属于一个线程,不能直接相互通报函数变量等:
  因此需要利用一些方法来确保我对UI的更新,是在UI线程上进行:
  在 serialPort1_DataReceived 事件处理程序中,利用 Invoke 或 BeginInvoke来确保 UI 更新在精确的线程(UI 线程)上执行
  起初我利用了Invoke方法,因为比较简单,是同步更新的逻辑,不需要额外写函数委托,可以正常更新UI的文本内容
  

  
  但是后来,在我频繁开关串口时发生了以下报错:
  这导致了程序的卡死!
  1. System.ObjectDisposedException:“无法访问已释放的对象。
  2. ObjectDisposed_ObjectName_Name”
  3.                 Invoke((MethodInvoker)delegate {Serial_Receiving_area.AppendText(displayText); });发生在我频繁开关串口时
复制代码
在串口吸取逻辑中,当串口被关闭时,如果仍旧有数据在serialPort1_DataReceived事件处理程序中处理,或者程序试图从已经关闭的串口读取数据,这可能会导致程序挂起或卡死。
  
  System.ObjectDisposedException 异常通常发生在尝试访问一个已经被释放(或销毁)的对象时。在串口通讯的上下文中,这通常意味着在 serialPort1_DataReceived 事件处理程序中,SerialPort 对象 sp 或其他相关对象(如 UI 控件)在事件处理过程中被关闭了
  
  这里的底层逻辑是:
  利用 Control.Invoke 或 Control.BeginInvoke 方法来在 UI 线程上执行对控件的更新。这两个方法都用于在控件的拥有线程(即 UI 线程)上执行代码委托。
  Invoke 是同步的,它会等待委托执行完毕才继续执行背面的代码;
  而 BeginInvoke 是异步的,它会立即返回,让委托在背景执行。
  
  因此在这个吸取停止里更新UI文本表现的逻辑里,显着是BeginInvoke更适合这个委托调用
    解决后的源码如下:

  1.        //串口接收逻辑
  2.        private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e)
  3.        {
  4.            SerialPort sp = (SerialPort)sender;
  5.            string indata = sp.ReadExisting(); // 读取所有可用的数据  
  6.            string[] lines = indata.Split('\n');
  7.            bool autoLinefeed = Auto_linefeed.Checked; // 获取Auto_linefeed的选中状态  
  8.                                                       // 这是你想要显示的文本  
  9.           // string displayText = "Hello, World!";
  10.            if (!isSerialPortOpen) { return; }// 如果串口已关闭,则直接返回
  11.            //Receiving_Area_Write();
  12.            foreach (string line in lines)
  13.            {
  14.                userCurve.Curve_Main(line);
  15.            }
  16.            // 直接在UI线程上更新文本区(假设这个方法在UI线程上被调用)  
  17.             或者使用Invoke/BeginInvoke来确保在UI线程上执行  
  18.            // 遍历每一行数据  
  19.            foreach (string line in lines)
  20.            {
  21.                userCurve.Curve_Main(line); // 处理每行数据(假设这个方法不需要UI线程)
  22.                if (HEX_radio.Checked)
  23.                {
  24.                    // 将字符串转换为字节数组,然后转换为十六进制字符串  
  25.                    // 这里对每行数据单独进行转换  
  26.                    byte[] bytes = Encoding.Default.GetBytes(line);
  27.                    string hexLine = UserInterface.byteToHexStr(bytes);
  28.                    if (Showtime_check.Checked)
  29.                    { displayText = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "]收→" + hexLine; }
  30.                    else
  31.                    {displayText = hexLine; }
  32.                }
  33.                else if (ASCII_radio.Checked)
  34.                {
  35.                    if (Showtime_check.Checked)
  36.                    {displayText = "[" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "]收→" + line;}
  37.                    else
  38.                    { displayText = line; }
  39.                }
  40.                // 如果启用了自动换行,则在文本末尾添加换行符  
  41.                if (autoLinefeed){ displayText += "\r\n"; }
  42.                //使用Invoke来确保在UI线程上更新文本区
  43.                //Invoke((MethodInvoker)delegate { Serial_Receiving_area.AppendText(displayText); });
  44.                UpdateTextBox(displayText);
  45.            }
  46.        }
  47.        // Serial_Receiving_area 是一个 TextBox 控件  
  48.        // 定义一个方法,用于在 UI 线程上安全地更新文本框  
  49.        private void UpdateTextBox(string text)
  50.        {
  51.            if (Serial_Receiving_area.InvokeRequired)
  52.            {
  53.                // 如果需要跨线程操作,则使用 Invoke 来在 UI 线程上执行  
  54.                Serial_Receiving_area.BeginInvoke(new Action<string>(UpdateTextBox), text);
  55.            }
  56.            else
  57.            {
  58.                // 直接在 UI 线程上更新文本框  
  59.                Serial_Receiving_area.AppendText(text);
  60.            }
  61.        }
复制代码




免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

我可以不吃啊

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表