这次是对之前学习的内容的一次实战内容,背景如下,我写了一个串口控件(用户控件)
界面上需要支持多个串口,每个串口的都有配置项,配置项需要保存到本地。
首先准备一个ItemsControl,每个子项装一个串口控件。然后,构建数据结构,每个串口对应一个SeriaInfo,然后再构建一个SeriaInfo的数组,作为数据源。由于我不想横向摆放控件,所以需要修改ItemsControl的ItemsPanel。
前台代码如下:
数据源结构如下:
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中的属性。
1200
2400
4800
9600
19200
38400
115200
无(None)
偶校验(Even)
奇校验(Odd)
保留为0(Space)
保留为1(Mark)
8
7
6
5
1
1.5
2
Default
ASCII
Unicode
UTF-8
这里我们主要关注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? 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 + 控件模板的方式,还有一个好处就是,界面的控件可以动态的递增!
这里ComboBox 的绑定可以做一个优化,不再绑定SelectedIndex,而是加上SelectedValuePath="Content" 之后直接绑定SelectedValue,这样写道配置里的就直接是ComboBoxItem的内容,而不是一个序号,而且后续要使用这些数据的时候也更加的方便。
BSHP
Flame
具体细节可以参考文章:
【WPF绑定1】 ListBox/ComboBox基础绑定_code bean的博客-CSDN博客_listbox绑定下面是ListBox的基础绑定设置: