【wpf】实战 ItemsControl + 用户控件 + 绑定
前言
这次是对之前学习的内容的一次实战内容,背景如下,我写了一个串口控件(用户控件)
界面上需要支持多个串口,每个串口的都有配置项,配置项需要保存到本地。
思路
首先准备一个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
{
/// <summary>
/// 注意,一定要写成属性的形式,不然无法绑定
/// </summary>
public List<SeriaInfo> list_seriaInfo { get; set; } = new List<SeriaInfo> ();
}
虽然后面会用到绑定,但是这里并属性没有用到属性通知,列表也没有用到ObservableCollection而是List。是因为保存配置文件这个过程都是目标到数据源的过程,不存在程序在运行的过程中数据源变换通知到界面情况,所以普通的属性就能满足要求。(Bingding中目标到数据源的过程是默认的,界面的改变就会改变数据源,反之就需要加属性通知了)。
由于,我们已经把用户控件放入了ItemsControl的数据模板,所以暂且不管数据源具体是什么,现在只要list_seriaInfo 有几条数据,界面就会显示几个控件。一开始是不存在配置文件的,所以我们这里可以自己先构建一个(最后实现序列化和反序列化)。
public ConfigInfos? config_infos { get; set; } = new ConfigInfos()
{
list_seriaInfo = new List<SeriaInfo>()
{
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>1200</ComboBoxItem>
<ComboBoxItem>2400</ComboBoxItem>
<ComboBoxItem>4800</ComboBoxItem>
<ComboBoxItem>9600</ComboBoxItem>
<ComboBoxItem>19200</ComboBoxItem>
<ComboBoxItem>38400</ComboBoxItem>
<ComboBoxItem>115200</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="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>8</ComboBoxItem>
<ComboBoxItem>7</ComboBoxItem>
<ComboBoxItem>6</ComboBoxItem>
<ComboBoxItem>5</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="stopBitsComboBox" Width="120" Grid.Column="1" Padding="5"
IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
SelectedIndex="{Binding stopBits}"
>
<ComboBoxItem>1</ComboBoxItem>
<ComboBoxItem>1.5</ComboBoxItem>
<ComboBoxItem>2</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="encodingComboBox" Width="120" Grid.Column="1" Padding="5"
IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
SelectedIndex="{Binding encoding}"
>
<ComboBoxItem>Default</ComboBoxItem>
<ComboBoxItem>ASCII</ComboBoxItem>
<ComboBoxItem>Unicode</ComboBoxItem>
<ComboBoxItem>UTF-8</ComboBoxItem>
</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<ConfigInfos>();
// 当文件不存在时,直接构建数据,以确保界面生成
if (config_infos == null)
{
config_infos = new ConfigInfos()
{
list_seriaInfo = new List<SeriaInfo>()
{
new SeriaInfo(),
new SeriaInfo(),
new SeriaInfo()
}
};
}
}
反序列化调用
// 反序列化调用
Save<ConfigInfos>(config_infos);
序列化实现
/// <summary>
/// 序列化操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="obj"></param>
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();
}
反序列化实现
/// <summary>
/// 泛型反序列化操作
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
/// <exception cref="FileNotFoundException"></exception>
static public T? Read<T>()
{
if (!File.Exists(_filePath))
{
return default;
}
StreamReader yamlReader = File.OpenText(_filePath);
Deserializer yamlDeserializer = new Deserializer();
//读取持久化对象
try
{
T info = yamlDeserializer.Deserialize<T>(yamlReader);
yamlReader.Close();
return info;
}
catch (Exception)
{
return default;
}
}
最后
关于yaml的序列化内容,可以参考我的这篇文章:
C# 配置文件的最终解决方案, yaml的序列化,反序列化_code bean的博客-CSDN博客_yaml序列化与反序列化
小结
通过 ItemsControl + 控件模板的方式,还有一个好处就是,界面的控件可以动态的递增!
内容补充
这里ComboBox 的绑定可以做一个优化,不再绑定SelectedIndex,而是加上SelectedValuePath="Content" 之后直接绑定SelectedValue,这样写道配置里的就直接是ComboBoxItem的内容,而不是一个序号,而且后续要使用这些数据的时候也更加的方便。
<ComboBox SelectedValuePath="Content"
SelectedValue="{Binding ConfigVM.config_infos.SpectrumType}"
>
<ComboBoxItem>BSHP</ComboBoxItem>
<ComboBoxItem>Flame</ComboBoxItem>
</ComboBox>
具体细节可以参考文章:
作者:宋桓公
出处:http://www.cnblogs.com/douzi2/
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!