【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>

具体细节可以参考文章:

【WPF绑定1】 ListBox/ComboBox基础绑定_code bean的博客-CSDN博客_listbox绑定下面是ListBox的基础绑定设置:<ListBox Name="list_axis" DisplayMemberPath="Name" SelectedValuePath="Num" SelectionChanged="list_axis_SelectionChanged"/>DisplayMemberPath属性:显示的值SelectedValuePath属性:在选中某个Item时我们可以通过ListBox的SelectedValue属性获取的值的类型我们先构建一个listhttps://blog.csdn.net/songhuangong123/article/details/121118824?csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22121118824%22%2C%22source%22%3A%22songhuangong123%22%7D#:~:text=%E5%8F%91%E5%B8%83-,%E3%80%90WPF%E7%BB%91%E5%AE%9A1%E3%80%91%20ListBox/ComboBox%E5%9F%BA%E7%A1%80%E7%BB%91%E5%AE%9A,-code%20bean

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

code bean

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值