这次是对之前学习的内容的一次实战内容,背景如下,我写了一个串口控件(用户控件)
界面上需要支持多个串口,每个串口的都有配置项,配置项需要保存到本地。
首先准备一个ItemsControl,每个子项装一个串口控件。然后,构建数据结构,每个串口对应一个SeriaInfo,然后再构建一个SeriaInfo的数组,作为数据源。由于我不想横向摆放控件,所以需要修改ItemsControl的ItemsPanel。
前台代码如下:
- <ItemsControl Grid.Row="1" ItemsSource="{Binding config_infos.list_seriaInfo}" >
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <UniformGrid/>
- ItemsPanelTemplate>
- ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate>
- <ctl:SerialControl/>
- DataTemplate>
- ItemsControl.ItemTemplate>
- ItemsControl>
数据源结构如下:
- public class SeriaInfo
- {
- // Com 端口
- public int port { get; set; } = 0;
-
- //波特率
- public int baudRate { get; set; } = 0;
-
- //奇偶校验位
- public int parity { get; set; } = 0;
-
- //数据位
- public int dataBits { get; set; } = 0;
-
- //停止位
- public int stopBits { get; set; } = 1;
-
- //字节编码
- public int encoding { get; set; } = 0;
-
-
- public bool Enable { get; set; } = true;
-
- }
-
- public class ConfigInfos
- {
- ///
- /// 注意,一定要写成属性的形式,不然无法绑定
- ///
- public List
list_seriaInfo { get; set; } = new List (); -
- }
虽然后面会用到绑定,但是这里并属性没有用到属性通知,列表也没有用到ObservableCollection而是List。是因为保存配置文件这个过程都是目标到数据源的过程,不存在程序在运行的过程中数据源变换通知到界面情况,所以普通的属性就能满足要求。(Bingding中目标到数据源的过程是默认的,界面的改变就会改变数据源,反之就需要加属性通知了)。
由于,我们已经把用户控件放入了ItemsControl的数据模板,所以暂且不管数据源具体是什么,现在只要list_seriaInfo 有几条数据,界面就会显示几个控件。一开始是不存在配置文件的,所以我们这里可以自己先构建一个(最后实现序列化和反序列化)。
- public ConfigInfos? config_infos { get; set; } = new ConfigInfos()
- {
- list_seriaInfo = new List
() - {
- new SeriaInfo(),
- new SeriaInfo(),
- new SeriaInfo()
- }
- };
这里,我们很容易犯的一个错误就是把 { get; set; } 忘记了,这样是绑定不成功的,因为绑定的话必须是属性,不能是字段!
有了这句之后,界面上就会显示三个串口。并且是横排列的。
接下来就是数据绑定,这里需要理解的重点是,每个控件已经是ItemsControl的一个子项了,这里ItemsControl的ItemsSource绑定的是config_infos.list_seriaInfo,那每个控件对应的就是list_seriaInfo的一个元素,即SeriaInfo这个数据结构。
ItemsSource="{Binding config_infos.list_seriaInfo}"
理解这一点非常重要,于是我们就可以去串口用户控件做绑定了,直接指定的就是SeriaInfo中的属性。
- <UserControl x:Class="WpfTest.Control.SerialControl"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:local="clr-namespace:WpfTest.Control"
- mc:Ignorable="d" Background="AliceBlue"
- d:DesignHeight="420" d:DesignWidth="250" >
- <Grid>
- <GroupBox Name="serialPortConfigPanel" Header="串口配置面板" Margin="5,5,0,5" BorderThickness="1" BorderBrush="#FF7199E0" Style="{x:Null}">
- <DockPanel LastChildFill="False">
-
- <Grid Margin="0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="可用端口" VerticalAlignment="Center" Grid.Column="0" Margin="0,0,25,0">TextBlock>
- <ComboBox Name="portsComboBox" Width="120" Grid.Column="1" Padding="5" IsEditable="True"
- SelectedIndex="{Binding port}"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- >ComboBox>
- Grid>
-
- <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="通讯波特率" VerticalAlignment="Center" Grid.Column="0">TextBlock>
- <ComboBox Name="baudRateComboBox" Width="120" Grid.Column="1" IsEditable="True" Padding="5"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- SelectedIndex="{Binding baudRate}"
- >
- <ComboBoxItem>1200ComboBoxItem>
- <ComboBoxItem>2400ComboBoxItem>
- <ComboBoxItem>4800ComboBoxItem>
- <ComboBoxItem>9600ComboBoxItem>
- <ComboBoxItem>19200ComboBoxItem>
- <ComboBoxItem>38400ComboBoxItem>
- <ComboBoxItem>115200ComboBoxItem>
- ComboBox>
- Grid>
-
- <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="奇偶校验位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
- <ComboBox Name="parityComboBox" Width="120" Grid.Column="1" Text="无(None)" Padding="5"
- SelectedIndex="{Binding parity}"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}">
- <ComboBoxItem Tag="None" >无(None)ComboBoxItem>
- <ComboBoxItem Tag="Even">偶校验(Even)ComboBoxItem>
- <ComboBoxItem Tag="Odd">奇校验(Odd)ComboBoxItem>
- <ComboBoxItem Tag="Space">保留为0(Space)ComboBoxItem>
- <ComboBoxItem Tag="Mark">保留为1(Mark)ComboBoxItem>
- ComboBox>
- Grid>
-
- <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="数据位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
- <ComboBox Name="dataBitsComboBox" Width="120" Grid.Column="1" Padding="5"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- SelectedIndex="{Binding dataBits}"
- >
- <ComboBoxItem>8ComboBoxItem>
- <ComboBoxItem>7ComboBoxItem>
- <ComboBoxItem>6ComboBoxItem>
- <ComboBoxItem>5ComboBoxItem>
- ComboBox>
- Grid>
-
- <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="停止位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
- <ComboBox Name="stopBitsComboBox" Width="120" Grid.Column="1" Padding="5"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- SelectedIndex="{Binding stopBits}"
- >
- <ComboBoxItem>1ComboBoxItem>
- <ComboBoxItem>1.5ComboBoxItem>
- <ComboBoxItem>2ComboBoxItem>
- ComboBox>
- Grid>
-
- <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <TextBlock Text="字节编码" VerticalAlignment="Center" Grid.Column="0">TextBlock>
- <ComboBox Name="encodingComboBox" Width="120" Grid.Column="1" Padding="5"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- SelectedIndex="{Binding encoding}"
- >
- <ComboBoxItem>DefaultComboBoxItem>
- <ComboBoxItem>ASCIIComboBoxItem>
- <ComboBoxItem>UnicodeComboBoxItem>
- <ComboBoxItem>UTF-8ComboBoxItem>
- ComboBox>
- Grid>
- <GroupBox Header="数据接收" DockPanel.Dock="Top" Style="{x:Null}">
- <TextBox x:Name="txt_rev" Height="40" IsReadOnly="True"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- />
- GroupBox>
- <GroupBox Header="使能开关" DockPanel.Dock="Top" Style="{x:Null}">
- <CheckBox x:Name="cb_enable" Margin="5" HorizontalAlignment="Right" Click="cb_enable_Click"
- IsChecked="{Binding Enable}"
- />
- GroupBox>
- <Grid DockPanel.Dock="Bottom">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- <ColumnDefinition Width="10*">ColumnDefinition>
- Grid.ColumnDefinitions>
- <Button Name="openClosePortButton" Click="OpenClosePortButton_Click" Grid.Column="1"
- IsEnabled="{Binding ElementName=portsComboBox, Path=IsEnabled}">打开Button>
- <Button Name="findPortButton" Click="FindPortButton_Click" Grid.Column="2"
- IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
- >查找Button>
- Grid>
- DockPanel>
-
- GroupBox>
- Grid>
- UserControl>
这里我们主要关注ComboBox的SelectedIndex的绑定,这个和我们的保存像相关。由于串口的配置项是固定的,我直接写到的前台(就没有写到后台然后做绑定),这样的话我只用保存ComboBox的SelectedIndex即可。
有了这个绑定,序列化和反序列换做起来是真的省心,省去了很多的不必要的代码。
比如:
再比如:
还有这种事件:
这些统统不需要,因为界面的变化的同时,数据源已经随之更新了,我们省去赋值,直接序列化和反序列化就行啦!
- public ConfigInfoViewMode()
- {
- //序列化调用
- config_infos = Read
(); - // 当文件不存在时,直接构建数据,以确保界面生成
- if (config_infos == null)
- {
- config_infos = new ConfigInfos()
- {
- list_seriaInfo = new List
() - {
- new SeriaInfo(),
- new SeriaInfo(),
- new SeriaInfo()
- }
- };
- }
- }
- // 反序列化调用
- Save
(config_infos);
- ///
- /// 序列化操作
- ///
- ///
- ///
- static public void Save<T>(T? obj)
- {
- FileInfo fi = new FileInfo(_filePath);
- if (!Directory.Exists(fi.DirectoryName))
- {
- Directory.CreateDirectory(fi.DirectoryName);
- }
-
- StreamWriter yamlWriter = File.CreateText(_filePath);
- Serializer yamlSerializer = new Serializer();
- yamlSerializer.Serialize(yamlWriter, obj);
- yamlWriter.Close();
- }
- ///
- /// 泛型反序列化操作
- ///
- ///
- ///
- ///
- static public T? Read
() - {
- if (!File.Exists(_filePath))
- {
- return default;
- }
- StreamReader yamlReader = File.OpenText(_filePath);
- Deserializer yamlDeserializer = new Deserializer();
-
- //读取持久化对象
- try
- {
- T info = yamlDeserializer.Deserialize
(yamlReader); - yamlReader.Close();
- return info;
- }
- catch (Exception)
- {
- return default;
- }
- }
关于yaml的序列化内容,可以参考我的这篇文章:
C# 配置文件的最终解决方案, yaml的序列化,反序列化_code bean的博客-CSDN博客_yaml序列化与反序列化
通过 ItemsControl + 控件模板的方式,还有一个好处就是,界面的控件可以动态的递增!