You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET MAUI中SerialPort数据无法实时完整显示到XAML列表的问题

问题描述

我正在使用.NET MAUI开发Windows桌面应用,该应用通过SerialPort连接LoRa扫描器。扫描器每秒从模拟LoRa发射器获取一次数据,共约8次,数据包含网络名称、信号强度、经纬度等信息。我在UI中设置了专门的网络名称列表区域,尝试将SerialPort缓冲区中的网络名称填充到该列表中。

但遇到的问题是:运行扫描时,网络名称仅显示一次,有时甚至完全不显示,而实际上应该显示约8次(当前为同一网络名称);若有多个模拟LoRa发射器,UI中也仅显示一个或完全不显示。

目前我在UI的代码后置文件中实现连接(计划功能正常后移至独立文件),并使用MainThread.BeginInvokeOnMainThread尝试在XAML列表中显示名称,但未生效。


相关代码

myPage.xaml.cs 代码片段

namespace Scanner_MAUI.Pages;

public partial class RealTimeData : ContentPage
{
   
    private SerialPort serialPort;


    public RealTimeData()
    {
        InitializeComponent();
        BindingContext = this;
        NetworkNames = new ObservableCollection<string>();
        //NetworkListView.ItemsSource = NetworkNames;

    }
    class ScannerData
    {
        public string Name { get; set; }
        //...
    }
    private void StartMenuItem_Clicked(object sender, EventArgs e)
    {
        // TODO: Connect to the LoRa scanner and start scanning
        ConnectToScanner();
    }
public void ConnectToScanner()
    {
        // Clear the NetworkNames list before connecting to the scanner
        //NetworkNames.Clear();
        try
        {
            serialPort = new SerialPort("COMx", baudrate);

            serialPort.Open();
            Debug.WriteLine("Serial Port conn created");
            Debug.WriteLine("Serial Port Is Open: " + serialPort.IsOpen);

            var data = new byte[] { (byte)'1', 13 };
            serialPort.Write(data, 0, data.Length);

            // TODO: Handle the scanner's output and display the information in your app
            serialPort.DataReceived += SerialPort_DataReceived;


            //Debug.WriteLine("test");
        }
        catch (Exception ex)
        {
            // Handle the exception
            Debug.WriteLine($"Failed to connect to the scanner: {ex.Message}");
        }
    }
public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        //NetworkNames = new ObservableCollection<string>();
        //NetworkNames.Clear();
        
        try
        {
            while (serialPort.BytesToRead > 0)
            {
            string DataIn = serialPort.ReadLine();
                //Debug.WriteLine("Data received from the scanner: " + DataIn);

                // Process the received data and extract the fields
                //ScannerData scannerData = ProcessReceivedData(DataIn);

                //Task.Delay(1000).Wait();
                foreach (string name in NetworkNames)
                {
                    Debug.WriteLine($"Name: {name}");
                }
                //ProcessReceivedData(DataIn);
                MainThread.BeginInvokeOnMainThread(() =>
                {
                    ProcessReceivedData(DataIn);
                    NetworkListView.ItemsSource = NetworkNames;
                });
            }
        }
        catch (OperationCanceledException)
        {
            // Handle the cancellation if needed
            Debug.WriteLine("Reading data from the scanner was canceled.");
        }
        catch (Exception ex)
        {
            // Handle other exceptions
            Debug.WriteLine($"Failed to read data from the scanner: {ex.Message}");
        }
        
    }
    
    public ObservableCollection<string> NetworkNames { get; set; } = new ObservableCollection<string>();

    private ScannerData ProcessReceivedData(string data)
    {
        ScannerData scannerData = new ScannerData();

        // Parse and extract relevant information from the data string
        string[] fields = data.Split(new string[] { ", " }, StringSplitOptions.None);

        Debug.WriteLine($"Received data fields count: {fields.Length}");
        //Debug.WriteLine($"Received data fields: {string.Join(", ", fields)}");

        if (fields.Length >= 8)
        {
            scannerData.Message = fields[0].Split(':')[1].Trim();
            string input = fields[1];
            string startMarker = "b'";
            string endMarker = "'";

            int startIndex = input.IndexOf(startMarker) + startMarker.Length;
            int endIndex = input.IndexOf(endMarker, startIndex);

            if (startIndex >= 0 && endIndex >= 0)
            {
                string extractedString = input.Substring(startIndex, endIndex - startIndex);
                scannerData.Name = extractedString;

                // Add non-null and non-empty names to the collection
                //if (!string.IsNullOrEmpty(scannerData.Name))
                //{
                    NetworkNames.Add(extractedString);
                //}
                //NetworkNames.Add(scannerData.Name); // Add network name to the list
            }
            // ....More code 
        }
        else
        {
            Debug.WriteLine("Insufficient fields in the data string.");
        }

        return scannerData;
    }

    private double? ParseNullableDouble(string value)
    {
        if (double.TryParse(value, out double result))
            return result;

        return null;
    }

XAML列表代码

<ListView x:Name="NetworkListView" ItemsSource="{Binding NetworkNames}">
  <ListView.ItemTemplate>
     <DataTemplate>
        <TextCell Text="{Binding}" />
     </DataTemplate>
  </ListView.ItemTemplate>
</ListView>

控制台中能正常显示所有网络名称,但UI左侧列有时仅显示一个名称。如何实现扫描过程中实时显示网络名称?哪里出错了?


解决方案

问题根源分析

  1. 重复设置ItemsSource干扰自动更新:ObservableCollection本身会在集合变化时通知UI更新,你每次收到数据都重新赋值NetworkListView.ItemsSource = NetworkNames,会破坏绑定的通知链路。
  2. 串口数据读取逻辑有缺陷:ReadLine()依赖严格的换行符分隔,若扫描器发送的数据包换行符不统一,可能导致数据读取不完整或批量读取,后续处理逻辑出错。
  3. 集合修改的线程安全细节:虽然用了MainThread.BeginInvokeOnMainThread,但要确保所有集合操作都完全在UI线程执行,避免潜在的线程冲突。

具体修复步骤

  1. 简化绑定逻辑,移除重复赋值
    在构造函数中一次性完成集合初始化与绑定,后续不再修改ItemsSource:

    public RealTimeData()
    {
        InitializeComponent();
        NetworkNames = new ObservableCollection<string>();
        BindingContext = this;
        // 手动绑定集合,和XAML的Binding二选一即可,这里选手动绑定更直观
        NetworkListView.ItemsSource = NetworkNames;
    }
    

    然后删除SerialPort_DataReceived中UI线程调用里的NetworkListView.ItemsSource = NetworkNames;这一行。

  2. 优化串口数据读取逻辑
    改用ReadExisting()读取所有可用数据,再按换行符拆分处理,避免数据截断或漏读:

    public void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        try
        {
            SerialPort sp = (SerialPort)sender;
            // 读取当前缓冲区所有数据
            string allReceivedData = sp.ReadExisting();
            // 按换行符拆分,过滤空行
            string[] dataLines = allReceivedData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
    
            foreach (string line in dataLines)
            {
                string cleanLine = line.Trim();
                if (string.IsNullOrEmpty(cleanLine)) continue;
                
                // 每条数据的处理都切换到UI线程
                MainThread.BeginInvokeOnMainThread(() =>
                {
                    ProcessReceivedData(cleanLine);
                });
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine($"Failed to read data: {ex.Message}");
        }
    }
    
  3. 恢复空值校验,避免无效数据
    在ProcessReceivedData中添加空值过滤,防止空字符串或无效名称加入集合:

    if (startIndex >= 0 && endIndex >= 0)
    {
        string extractedString = input.Substring(startIndex, endIndex - startIndex).Trim();
        scannerData.Name = extractedString;
    
        // 只添加非空非空白的名称
        if (!string.IsNullOrWhiteSpace(extractedString))
        {
            NetworkNames.Add(extractedString);
        }
    }
    
  4. 扫描前清空集合(可选)
    如果每次扫描需要重置列表,在ConnectToScanner开头添加UI线程内的清空操作:

    public void ConnectToScanner()
    {
        // 在UI线程清空集合
        MainThread.BeginInvokeOnMainThread(() =>
        {
            NetworkNames.Clear();
        });
        // 原有连接代码...
    }
    

关键原理说明

  • ObservableCollection<T>的核心价值是集合变化自动通知UI更新,前提是所有增删改操作都在UI线程执行,且不要重复设置ItemsSource。
  • 串口DataReceived事件运行在后台线程,任何涉及UI元素或绑定集合的操作,必须通过MainThread.BeginInvokeOnMainThread切换到UI线程执行,否则会出现线程安全问题和UI不更新的情况。

内容的提问来源于stack exchange,提问作者BaselE

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 20:53:12