使用WPF编写一个读取串口的程序

WPF串口通信教程

本教程将指导您使用WPF(Windows Presentation Foundation)创建一个完整的串口通信程序。我们将逐步完成以下步骤:创建界面、建立串口通信、实时更新接收数据、停止通信。示例代码基于.NET框架的System.IO.Ports命名空间,使用C#语言编写。请确保在Visual Studio中创建WPF应用程序项目(选择.NET Framework 4.5或更高版本)。

1. 创建界面

首先,设计WPF界面,包括串口选择、数据显示和操作按钮。打开MainWindow.xaml文件,添加以下XAML代码:

复制代码
<Window x:Class="SerialPortApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="串口通信程序" Height="350" Width="500">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        
        <!-- 串口选择区域 -->
        <StackPanel Grid.Row="0" Orientation="Horizontal" Margin="10">
            <TextBlock Text="选择串口:" VerticalAlignment="Center" Margin="0,0,10,0"/>
            <ComboBox x:Name="comboBoxPorts" Width="100" VerticalAlignment="Center"/>
            <TextBlock Text="波特率:" VerticalAlignment="Center" Margin="10,0,10,0"/>
            <ComboBox x:Name="comboBoxBaudRate" Width="80" VerticalAlignment="Center">
                <ComboBoxItem Content="9600" IsSelected="True"/>
                <ComboBoxItem Content="115200"/>
            </ComboBox>
        </StackPanel>
        
        <!-- 数据显示区域 -->
        <TextBox x:Name="textBoxData" Grid.Row="1" Margin="10" Height="200" 
                 VerticalScrollBarVisibility="Auto" IsReadOnly="True" TextWrapping="Wrap"/>
        
        <!-- 操作按钮 -->
        <StackPanel Grid.Row="3" Orientation="Horizontal" HorizontalAlignment="Center" Margin="10">
            <Button x:Name="buttonOpen" Content="打开串口" Width="80" Click="ButtonOpen_Click"/>
            <Button x:Name="buttonClose" Content="关闭串口" Width="80" Margin="10,0,0,0" Click="ButtonClose_Click" IsEnabled="False"/>
        </StackPanel>
    </Grid>
</Window>

此界面包括:

  • 一个ComboBox用于选择串口号(如COM1)。
  • 一个ComboBox用于选择波特率(如9600)。
  • 一个TextBox用于显示接收到的数据。
  • 两个Button:一个用于打开串口,另一个用于关闭串口。
2. 建立通信

接下来,在后台代码(MainWindow.xaml.cs)中实现串口通信逻辑。首先,添加必要的命名空间和变量声明:

复制代码
using System;
using System.IO.Ports;
using System.Windows;
using System.Windows.Threading;

public partial class MainWindow : Window
{
    private SerialPort serialPort; // 串口对象
    private bool isPortOpen = false; // 串口状态标志

    public MainWindow()
    {
        InitializeComponent();
        LoadPorts(); // 初始化时加载可用串口
    }

    // 加载可用串口
    private void LoadPorts()
    {
        comboBoxPorts.Items.Clear();
        string[] ports = SerialPort.GetPortNames();
        foreach (string port in ports)
        {
            comboBoxPorts.Items.Add(port);
        }
        if (ports.Length > 0)
        {
            comboBoxPorts.SelectedIndex = 0;
        }
    }
}
3. 打开串口和更新数据

实现打开串口按钮的点击事件。打开串口后,订阅DataReceived事件以处理接收数据。注意:串口事件在非UI线程触发,需要使用Dispatcher更新UI。

复制代码
// 打开串口按钮点击事件
private void ButtonOpen_Click(object sender, RoutedEventArgs e)
{
    if (comboBoxPorts.SelectedItem == null)
    {
        MessageBox.Show("请选择串口!");
        return;
    }

    string portName = comboBoxPorts.SelectedItem.ToString();
    int baudRate = int.Parse((comboBoxBaudRate.SelectedItem as ComboBoxItem)?.Content.ToString() ?? "9600");

    try
    {
        serialPort = new SerialPort(portName, baudRate);
        serialPort.DataReceived += SerialPort_DataReceived; // 订阅数据接收事件
        serialPort.Open();
        isPortOpen = true;
        buttonOpen.IsEnabled = false;
        buttonClose.IsEnabled = true;
        textBoxData.AppendText($"串口 {portName} 已打开,波特率 {baudRate}。\n");
    }
    catch (Exception ex)
    {
        MessageBox.Show($"打开串口失败: {ex.Message}");
    }
}

// 数据接收事件处理
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
    // 读取数据
    string data = serialPort.ReadLine(); // 假设数据以行结束(如换行符)
    
    // 使用Dispatcher更新UI,确保线程安全
    Dispatcher.Invoke(() =>
    {
        textBoxData.AppendText($"{DateTime.Now:HH:mm:ss} 接收: {data}\n");
        textBoxData.ScrollToEnd(); // 自动滚动到最新内容
    });
}
4. 停止通信

实现关闭串口按钮的点击事件。关闭串口时,取消订阅事件并释放资源。

复制代码
// 关闭串口按钮点击事件
private void ButtonClose_Click(object sender, RoutedEventArgs e)
{
    if (isPortOpen && serialPort != null)
    {
        try
        {
            serialPort.DataReceived -= SerialPort_DataReceived; // 取消订阅事件
            serialPort.Close();
            isPortOpen = false;
            buttonOpen.IsEnabled = true;
            buttonClose.IsEnabled = false;
            textBoxData.AppendText("串口已关闭。\n");
        }
        catch (Exception ex)
        {
            MessageBox.Show($"关闭串口失败: {ex.Message}");
        }
    }
}
5. 完整代码示例

以下是MainWindow.xaml.cs的完整代码:

复制代码
using System;
using System.IO.Ports;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Threading;

namespace SerialPortApp
{
    public partial class MainWindow : Window
    {
        private SerialPort serialPort;
        private bool isPortOpen = false;

        public MainWindow()
        {
            InitializeComponent();
            LoadPorts();
        }

        private void LoadPorts()
        {
            comboBoxPorts.Items.Clear();
            string[] ports = SerialPort.GetPortNames();
            foreach (string port in ports)
            {
                comboBoxPorts.Items.Add(port);
            }
            if (ports.Length > 0)
            {
                comboBoxPorts.SelectedIndex = 0;
            }
        }

        private void ButtonOpen_Click(object sender, RoutedEventArgs e)
        {
            if (comboBoxPorts.SelectedItem == null)
            {
                MessageBox.Show("请选择串口!");
                return;
            }

            string portName = comboBoxPorts.SelectedItem.ToString();
            int baudRate = int.Parse((comboBoxBaudRate.SelectedItem as ComboBoxItem)?.Content.ToString() ?? "9600");

            try
            {
                serialPort = new SerialPort(portName, baudRate);
                serialPort.DataReceived += SerialPort_DataReceived;
                serialPort.Open();
                isPortOpen = true;
                buttonOpen.IsEnabled = false;
                buttonClose.IsEnabled = true;
                textBoxData.AppendText($"串口 {portName} 已打开,波特率 {baudRate}。\n");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"打开串口失败: {ex.Message}");
            }
        }

        private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
        {
            string data = serialPort.ReadLine();
            Dispatcher.Invoke(() =>
            {
                textBoxData.AppendText($"{DateTime.Now:HH:mm:ss} 接收: {data}\n");
                textBoxData.ScrollToEnd();
            });
        }

        private void ButtonClose_Click(object sender, RoutedEventArgs e)
        {
            if (isPortOpen && serialPort != null)
            {
                try
                {
                    serialPort.DataReceived -= SerialPort_DataReceived;
                    serialPort.Close();
                    isPortOpen = false;
                    buttonOpen.IsEnabled = true;
                    buttonClose.IsEnabled = false;
                    textBoxData.AppendText("串口已关闭。\n");
                }
                catch (Exception ex)
                {
                    MessageBox.Show($"关闭串口失败: {ex.Message}");
                }
            }
        }
    }
}
6. 运行和测试
  1. 编译运行:在Visual Studio中按F5运行程序。
  2. 连接设备:将串口设备(如Arduino)连接到电脑,确保设备发送数据以换行符结束。
  3. 测试功能
    • 选择串口号和波特率,点击"打开串口"。
    • 接收到的数据将显示在TextBox中。
    • 点击"关闭串口"停止通信。
注意事项
  • 线程安全 :串口DataReceived事件在后台线程触发,必须使用Dispatcher.Invoke更新UI,避免跨线程异常。

  • 错误处理:添加更多try-catch块以处理潜在异常(如串口被占用)。

  • 数据格式 :本示例使用ReadLine()方法,假设数据以换行符结束。如果数据格式不同,可改用ReadExisting()或自定义解析。

  • 资源释放 :在窗口关闭时,确保关闭串口。可重写OnClosing方法:

    复制代码
    protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
    {
        if (isPortOpen && serialPort != null)
        {
            serialPort.Close();
        }
        base.OnClosing(e);
    }

通过本教程,您已实现了一个完整的WPF串口通信程序。根据实际需求,您可以扩展功能,如添加数据发送或更多配置选项。

相关推荐
Macbethad1 天前
如何用WPF做工控设置界面
java·开发语言·wpf
csdn_aspnet1 天前
WPF 做一个简单的电子签名板(一)
c#·wpf
玖笙&1 天前
✨WPF编程进阶【7.2】:动画类型(附源码)
c++·c#·wpf·visual studio
·心猿意码·2 天前
WPF中TemplatePart机制详解
wpf
FuckPatience3 天前
WPF 使用UserControl / ContentControl显示子界面
wpf
wangnaisheng3 天前
【WPF】WrapPanel的用法
wpf
源之缘-OFD先行者4 天前
10 万雷达点迹零卡顿回放:WPF + Vortice.Direct2D 多线程渲染实战
wpf
猫林老师4 天前
Flutter for HarmonyOS开发指南(九):测试、调试与质量保障体系
flutter·wpf·harmonyos
LateFrames4 天前
做【秒开】的程序:WPF / WinForm / WinUI3 / Electron
electron·c#·wpf·winform·winui3·claude code