.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左侧列有时仅显示一个名称。如何实现扫描过程中实时显示网络名称?哪里出错了?
问题根源分析
- 重复设置
ItemsSource干扰自动更新:ObservableCollection本身会在集合变化时通知UI更新,你每次收到数据都重新赋值NetworkListView.ItemsSource = NetworkNames,会破坏绑定的通知链路。 - 串口数据读取逻辑有缺陷:
ReadLine()依赖严格的换行符分隔,若扫描器发送的数据包换行符不统一,可能导致数据读取不完整或批量读取,后续处理逻辑出错。 - 集合修改的线程安全细节:虽然用了
MainThread.BeginInvokeOnMainThread,但要确保所有集合操作都完全在UI线程执行,避免潜在的线程冲突。
具体修复步骤
简化绑定逻辑,移除重复赋值
在构造函数中一次性完成集合初始化与绑定,后续不再修改ItemsSource:public RealTimeData() { InitializeComponent(); NetworkNames = new ObservableCollection<string>(); BindingContext = this; // 手动绑定集合,和XAML的Binding二选一即可,这里选手动绑定更直观 NetworkListView.ItemsSource = NetworkNames; }然后删除
SerialPort_DataReceived中UI线程调用里的NetworkListView.ItemsSource = NetworkNames;这一行。优化串口数据读取逻辑
改用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}"); } }恢复空值校验,避免无效数据
在ProcessReceivedData中添加空值过滤,防止空字符串或无效名称加入集合:if (startIndex >= 0 && endIndex >= 0) { string extractedString = input.Substring(startIndex, endIndex - startIndex).Trim(); scannerData.Name = extractedString; // 只添加非空非空白的名称 if (!string.IsNullOrWhiteSpace(extractedString)) { NetworkNames.Add(extractedString); } }扫描前清空集合(可选)
如果每次扫描需要重置列表,在ConnectToScanner开头添加UI线程内的清空操作:public void ConnectToScanner() { // 在UI线程清空集合 MainThread.BeginInvokeOnMainThread(() => { NetworkNames.Clear(); }); // 原有连接代码... }
关键原理说明
ObservableCollection<T>的核心价值是集合变化自动通知UI更新,前提是所有增删改操作都在UI线程执行,且不要重复设置ItemsSource。- 串口
DataReceived事件运行在后台线程,任何涉及UI元素或绑定集合的操作,必须通过MainThread.BeginInvokeOnMainThread切换到UI线程执行,否则会出现线程安全问题和UI不更新的情况。
内容的提问来源于stack exchange,提问作者BaselE

