• 【wpf】实战 ItemsControl + 用户控件 + 绑定


    前言

            这次是对之前学习的内容的一次实战内容,背景如下,我写了一个串口控件(用户控件)

    界面上需要支持多个串口,每个串口的都有配置项,配置项需要保存到本地。

    思路

            首先准备一个ItemsControl,每个子项装一个串口控件。然后,构建数据结构,每个串口对应一个SeriaInfo,然后再构建一个SeriaInfo的数组,作为数据源。由于我不想横向摆放控件,所以需要修改ItemsControl的ItemsPanel。

    前台代码如下:

    1. <ItemsControl Grid.Row="1" ItemsSource="{Binding config_infos.list_seriaInfo}" >
    2. <ItemsControl.ItemsPanel>
    3. <ItemsPanelTemplate>
    4. <UniformGrid/>
    5. ItemsPanelTemplate>
    6. ItemsControl.ItemsPanel>
    7. <ItemsControl.ItemTemplate>
    8. <DataTemplate>
    9. <ctl:SerialControl/>
    10. DataTemplate>
    11. ItemsControl.ItemTemplate>
    12. ItemsControl>

    数据源结构如下:

    1. public class SeriaInfo
    2. {
    3. // Com 端口
    4. public int port { get; set; } = 0;
    5. //波特率
    6. public int baudRate { get; set; } = 0;
    7. //奇偶校验位
    8. public int parity { get; set; } = 0;
    9. //数据位
    10. public int dataBits { get; set; } = 0;
    11. //停止位
    12. public int stopBits { get; set; } = 1;
    13. //字节编码
    14. public int encoding { get; set; } = 0;
    15. public bool Enable { get; set; } = true;
    16. }
    17. public class ConfigInfos
    18. {
    19. ///
    20. /// 注意,一定要写成属性的形式,不然无法绑定
    21. ///
    22. public List list_seriaInfo { get; set; } = new List ();
    23. }

            虽然后面会用到绑定,但是这里并属性没有用到属性通知,列表也没有用到ObservableCollection而是List。是因为保存配置文件这个过程都是目标到数据源的过程,不存在程序在运行的过程中数据源变换通知到界面情况,所以普通的属性就能满足要求。(Bingding中目标到数据源的过程是默认的,界面的改变就会改变数据源,反之就需要加属性通知了)。

            由于,我们已经把用户控件放入了ItemsControl的数据模板,所以暂且不管数据源具体是什么,现在只要list_seriaInfo 有几条数据,界面就会显示几个控件。一开始是不存在配置文件的,所以我们这里可以自己先构建一个(最后实现序列化和反序列化)。

    1. public ConfigInfos? config_infos { get; set; } = new ConfigInfos()
    2. {
    3. list_seriaInfo = new List()
    4. {
    5. new SeriaInfo(),
    6. new SeriaInfo(),
    7. new SeriaInfo()
    8. }
    9. };

    这里,我们很容易犯的一个错误就是把 { get; set; } 忘记了,这样是绑定不成功的,因为绑定的话必须是属性,不能是字段

    有了这句之后,界面上就会显示三个串口。并且是横排列的。

    数据绑定

            接下来就是数据绑定,这里需要理解的重点是,每个控件已经是ItemsControl的一个子项了,这里ItemsControl的ItemsSource绑定的是config_infos.list_seriaInfo,那每个控件对应的就是list_seriaInfo的一个元素,即SeriaInfo这个数据结构

    ItemsSource="{Binding config_infos.list_seriaInfo}"

    理解这一点非常重要,于是我们就可以去串口用户控件做绑定了,直接指定的就是SeriaInfo中的属性。

    1. <UserControl x:Class="WpfTest.Control.SerialControl"
    2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    5. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6. xmlns:local="clr-namespace:WpfTest.Control"
    7. mc:Ignorable="d" Background="AliceBlue"
    8. d:DesignHeight="420" d:DesignWidth="250" >
    9. <Grid>
    10. <GroupBox Name="serialPortConfigPanel" Header="串口配置面板" Margin="5,5,0,5" BorderThickness="1" BorderBrush="#FF7199E0" Style="{x:Null}">
    11. <DockPanel LastChildFill="False">
    12. <Grid Margin="0,10" DockPanel.Dock="Top">
    13. <Grid.ColumnDefinitions>
    14. <ColumnDefinition Width="10*">ColumnDefinition>
    15. <ColumnDefinition Width="10*">ColumnDefinition>
    16. Grid.ColumnDefinitions>
    17. <TextBlock Text="可用端口" VerticalAlignment="Center" Grid.Column="0" Margin="0,0,25,0">TextBlock>
    18. <ComboBox Name="portsComboBox" Width="120" Grid.Column="1" Padding="5" IsEditable="True"
    19. SelectedIndex="{Binding port}"
    20. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    21. >ComboBox>
    22. Grid>
    23. <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
    24. <Grid.ColumnDefinitions>
    25. <ColumnDefinition Width="10*">ColumnDefinition>
    26. <ColumnDefinition Width="10*">ColumnDefinition>
    27. Grid.ColumnDefinitions>
    28. <TextBlock Text="通讯波特率" VerticalAlignment="Center" Grid.Column="0">TextBlock>
    29. <ComboBox Name="baudRateComboBox" Width="120" Grid.Column="1" IsEditable="True" Padding="5"
    30. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    31. SelectedIndex="{Binding baudRate}"
    32. >
    33. <ComboBoxItem>1200ComboBoxItem>
    34. <ComboBoxItem>2400ComboBoxItem>
    35. <ComboBoxItem>4800ComboBoxItem>
    36. <ComboBoxItem>9600ComboBoxItem>
    37. <ComboBoxItem>19200ComboBoxItem>
    38. <ComboBoxItem>38400ComboBoxItem>
    39. <ComboBoxItem>115200ComboBoxItem>
    40. ComboBox>
    41. Grid>
    42. <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
    43. <Grid.ColumnDefinitions>
    44. <ColumnDefinition Width="10*">ColumnDefinition>
    45. <ColumnDefinition Width="10*">ColumnDefinition>
    46. Grid.ColumnDefinitions>
    47. <TextBlock Text="奇偶校验位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
    48. <ComboBox Name="parityComboBox" Width="120" Grid.Column="1" Text="无(None)" Padding="5"
    49. SelectedIndex="{Binding parity}"
    50. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}">
    51. <ComboBoxItem Tag="None" >无(None)ComboBoxItem>
    52. <ComboBoxItem Tag="Even">偶校验(Even)ComboBoxItem>
    53. <ComboBoxItem Tag="Odd">奇校验(Odd)ComboBoxItem>
    54. <ComboBoxItem Tag="Space">保留为0(Space)ComboBoxItem>
    55. <ComboBoxItem Tag="Mark">保留为1(Mark)ComboBoxItem>
    56. ComboBox>
    57. Grid>
    58. <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
    59. <Grid.ColumnDefinitions>
    60. <ColumnDefinition Width="10*">ColumnDefinition>
    61. <ColumnDefinition Width="10*">ColumnDefinition>
    62. Grid.ColumnDefinitions>
    63. <TextBlock Text="数据位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
    64. <ComboBox Name="dataBitsComboBox" Width="120" Grid.Column="1" Padding="5"
    65. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    66. SelectedIndex="{Binding dataBits}"
    67. >
    68. <ComboBoxItem>8ComboBoxItem>
    69. <ComboBoxItem>7ComboBoxItem>
    70. <ComboBoxItem>6ComboBoxItem>
    71. <ComboBoxItem>5ComboBoxItem>
    72. ComboBox>
    73. Grid>
    74. <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
    75. <Grid.ColumnDefinitions>
    76. <ColumnDefinition Width="10*">ColumnDefinition>
    77. <ColumnDefinition Width="10*">ColumnDefinition>
    78. Grid.ColumnDefinitions>
    79. <TextBlock Text="停止位" VerticalAlignment="Center" Grid.Column="0">TextBlock>
    80. <ComboBox Name="stopBitsComboBox" Width="120" Grid.Column="1" Padding="5"
    81. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    82. SelectedIndex="{Binding stopBits}"
    83. >
    84. <ComboBoxItem>1ComboBoxItem>
    85. <ComboBoxItem>1.5ComboBoxItem>
    86. <ComboBoxItem>2ComboBoxItem>
    87. ComboBox>
    88. Grid>
    89. <Grid Margin="0,0,0,10" DockPanel.Dock="Top">
    90. <Grid.ColumnDefinitions>
    91. <ColumnDefinition Width="10*">ColumnDefinition>
    92. <ColumnDefinition Width="10*">ColumnDefinition>
    93. Grid.ColumnDefinitions>
    94. <TextBlock Text="字节编码" VerticalAlignment="Center" Grid.Column="0">TextBlock>
    95. <ComboBox Name="encodingComboBox" Width="120" Grid.Column="1" Padding="5"
    96. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    97. SelectedIndex="{Binding encoding}"
    98. >
    99. <ComboBoxItem>DefaultComboBoxItem>
    100. <ComboBoxItem>ASCIIComboBoxItem>
    101. <ComboBoxItem>UnicodeComboBoxItem>
    102. <ComboBoxItem>UTF-8ComboBoxItem>
    103. ComboBox>
    104. Grid>
    105. <GroupBox Header="数据接收" DockPanel.Dock="Top" Style="{x:Null}">
    106. <TextBox x:Name="txt_rev" Height="40" IsReadOnly="True"
    107. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    108. />
    109. GroupBox>
    110. <GroupBox Header="使能开关" DockPanel.Dock="Top" Style="{x:Null}">
    111. <CheckBox x:Name="cb_enable" Margin="5" HorizontalAlignment="Right" Click="cb_enable_Click"
    112. IsChecked="{Binding Enable}"
    113. />
    114. GroupBox>
    115. <Grid DockPanel.Dock="Bottom">
    116. <Grid.ColumnDefinitions>
    117. <ColumnDefinition Width="10*">ColumnDefinition>
    118. <ColumnDefinition Width="10*">ColumnDefinition>
    119. <ColumnDefinition Width="10*">ColumnDefinition>
    120. Grid.ColumnDefinitions>
    121. <Button Name="openClosePortButton" Click="OpenClosePortButton_Click" Grid.Column="1"
    122. IsEnabled="{Binding ElementName=portsComboBox, Path=IsEnabled}">打开Button>
    123. <Button Name="findPortButton" Click="FindPortButton_Click" Grid.Column="2"
    124. IsEnabled="{Binding ElementName=cb_enable, Path=IsChecked}"
    125. >查找Button>
    126. Grid>
    127. DockPanel>
    128. GroupBox>
    129. Grid>
    130. UserControl>

    这里我们主要关注ComboBox的SelectedIndex的绑定,这个和我们的保存像相关。由于串口的配置项是固定的,我直接写到的前台(就没有写到后台然后做绑定),这样的话我只用保存ComboBox的SelectedIndex即可。

    序列化和反序列化

    有了这个绑定,序列化和反序列换做起来是真的省心,省去了很多的不必要的代码

    比如:

    再比如:

    还有这种事件:

     这些统统不需要,因为界面的变化的同时,数据源已经随之更新了,我们省去赋值,直接序列化和反序列化就行啦

    序列化调用

    1. public ConfigInfoViewMode()
    2. {
    3. //序列化调用
    4. config_infos = Read();
    5. // 当文件不存在时,直接构建数据,以确保界面生成
    6. if (config_infos == null)
    7. {
    8. config_infos = new ConfigInfos()
    9. {
    10. list_seriaInfo = new List()
    11. {
    12. new SeriaInfo(),
    13. new SeriaInfo(),
    14. new SeriaInfo()
    15. }
    16. };
    17. }
    18. }

    反序列化调用

    1. // 反序列化调用
    2. Save(config_infos);

    序列化实现

    1. ///
    2. /// 序列化操作
    3. ///
    4. ///
    5. ///
    6. static public void Save<T>(T? obj)
    7. {
    8. FileInfo fi = new FileInfo(_filePath);
    9. if (!Directory.Exists(fi.DirectoryName))
    10. {
    11. Directory.CreateDirectory(fi.DirectoryName);
    12. }
    13. StreamWriter yamlWriter = File.CreateText(_filePath);
    14. Serializer yamlSerializer = new Serializer();
    15. yamlSerializer.Serialize(yamlWriter, obj);
    16. yamlWriter.Close();
    17. }

    反序列化实现

    1. ///
    2. /// 泛型反序列化操作
    3. ///
    4. ///
    5. ///
    6. ///
    7. static public T? Read()
    8. {
    9. if (!File.Exists(_filePath))
    10. {
    11. return default;
    12. }
    13. StreamReader yamlReader = File.OpenText(_filePath);
    14. Deserializer yamlDeserializer = new Deserializer();
    15. //读取持久化对象
    16. try
    17. {
    18. T info = yamlDeserializer.Deserialize(yamlReader);
    19. yamlReader.Close();
    20. return info;
    21. }
    22. catch (Exception)
    23. {
    24. return default;
    25. }
    26. }

     最后

            关于yaml的序列化内容,可以参考我的这篇文章:

    C# 配置文件的最终解决方案, yaml的序列化,反序列化_code bean的博客-CSDN博客_yaml序列化与反序列化

    小结

    通过 ItemsControl + 控件模板的方式,还有一个好处就是,界面的控件可以动态的递增!

  • 相关阅读:
    基础IO(文件读取写入,重定向,缓冲区)
    分布式缓存
    狂神springcloud速补笔记3
    typescript: Builder Pattern
    面向个性化需求的在线云数据库混合调优系统 | SIGMOD 2022入选论文解读
    高考专业抉择计算机专业热度不减,兴趣、实力与挑战并存。
    (C)一些错题
    3分钟让你学会axios在vue项目中的基本用法(建议收藏)
    netty 底层的工作原理
    C语言字符函数和字符串函数(1)
  • 原文地址:https://blog.csdn.net/songhuangong123/article/details/126319542