WPF_15_格式化绑定的数据

为了得到更人性化的外观,需要设计如何修剪数据列表和数据字段。

数据转换

在基本绑定中,信息从源到目标传递过程没有任何变化。但有时候希望将信息转换到更友好的内容再呈现到界面上。WPF提供了两个工具:

  • 字符串格式化
  • 值转换器

单个属性

Binding.StringFormat 属性针对简单的,标准的格式化数字和日期而创建的。

<TextBox Text="{Binding Path=UnitCost, StringFormat={}{0:C}}"/>
<TextBox Text="{Binding Path=UnitCost, StringFormat=The value is {0:C}.}"/>
<ListBox DisplayMemberPath="UnitCost" ItemStringFormat="{0:C}"/>

值转换器功能更强大,创建值转换器需要4个步骤:

  1. 创建一个实现了 IValueConverter 接口的类
  2. 为该类声明添加 ValueConversion 特性,并指定目标数据类型
  3. 实现 Convert() 方法
  4. 实现 ConvertBack() 方法
[ValueConversion(typeof(decimal), typeof(string))]
public class PriceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        decimal price = (decimal)value;
        return price.ToString("C", culture);
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        string price = value.ToString(cultere);
        decimal result;
        if(Decimal.TryParse(price, NumberStyles.Any, cultere, out result))
            return result;
        return value;
    }
}
<!--在Resources中创建转换器对象,可以用于多个绑定-->
<Window.Resources>
    <local:PriceConverter x:Key="PriceConverter"/>
</Window.Resources>

<TextBox Text="{Binding Path=UnitCost, Converter={StaticResource PriceConverter}}"/>

多个属性

<TextBlock>
    <TextBlock.Text>
        <!--使用 MultiBinding 替换 Binding-->
        <MultiBinding StringFromat="{1}, {0}">
            <Binding Path="FirstName"/>
            <Binding Path="LastName"/>
        </MultiBinding>
    </TextBlcok.Text>
</TextBlock>

如果希望完成更复杂的工作,需要使用值转换器:

<TextBox>
    <TextBox.Text>
        <MultiBinding Converter="{StaticResource ValueInStockConverter}">
            <Binding Path="UnitCost"/>
            <Binding Path="UnitsInStock"/>
        </MultiBinding>
    </TextBox.Text>
</TextBox>
public class VallueInStockConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        decimal unitCost = (decimal)values[0];
        int unitsInStock = (int)value[1];
        return unitCost * unitsInStock;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

列表控件

ItemsControl 类为封装列表中的控件定义了基本功能,所有列表控件都继承自该类。

属性名 说明
ItemsSource 数据源
DisplayMemberPath 期望数据项显示的属性 (更复杂的显示使用ItemTemplate)
ItemStringFormat 为每个项格式化文本
ItemContainerStyle 通过样式可以设置封装每个项的容器的多个属性。自动创建这些封装器对象
ItemContainerStyleSelector 为每项的封装器选择样式的StyleSelector对象
AIternationCount 在数据中设置的交替集合数量
ItemTemplate 模板从绑定的对象提取合适的数据并安排到合适的控件组合中
ItemTemplateSelector 为每个项选择模板的 DataTemplateSelector 对象
ItemsPanel 用于包含列表中项的面板,所有封装器都添加到这个容器中
GroupStyle 定义应当如何格式化每个分组
GroupStyleSelector 为每个分组选择样式的 StyleSelector 对象

列表样式

ItemContainerStyle

当创建列表项时,列表控件会将其向下传递 ItemContainerStyle 属性,每个列表项都将应用该样式。

<ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName">
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}">
            <Setter Property="Background" Value="LightSteelBlue"/>
            <Setter Property="Margin" Value="5"/>
            <Setter Property="Padding" Value="5"/>
            <!--触发器使得样式更加精彩-->
            <style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Background" Value="DarkRed"/>
                    <Setter Property="Forground" Value="White"/>
                    <Setter Property="BorderBrush" Value="Blcak"/>
                    <Setter Property="BorderThickness" Value="1"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    <ListBox.ItemContainerStyle>
</ListBox>

可以让每个 ListBoxItem 对象在项文本的旁边显示单选按钮或复选框

<Window.Resources>
    <Style x:Key="RadioButtonListStyle" TargetType="{x:Type ListBox}">
        <Setter Property="ItemContainerStyle">
            <Setter.Value>
                <Style TargetType="{x:Type ListBoxItem">
                    <Setter Property="Margin" Value="2"/>
                    <Setter Property="Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type ListBoxItem}">
                                <RadioButton Focusable="False" IsChecked="{Binding Path=IsSelected,Mode=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
                                    <!--ContentPresenter获取最初在项中显示的内容-->
                                    <!--可能是文本,也可能是复杂的表示形式-->
                                    <ContentPresenter/>
                                </RadioButton>
                                <!-- 多选框
                                    <CheckBox Focusable="False" IsChecked="{Binding Path=IsSelected, Model=TwoWay,RelativeSource={RelativeSource TemplatedParent}}">
                                        <ContentPresenter/>
                                    </CheckBox>
                                -->
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Style>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>
交替条目样式

AlternationCount指定序列中项的数量,经过改数量后交替样式。如果设置为2,第一个ListBoxItem的 AlternationIndex=0,第二个为1,第三个为0,第四个为1……。

<Style.Triggers>
    <Trigger Property="ItemsControl.AlternationIndex" Value="1">
        <Setter Property="Background" Value="LightBlue"/>
    </Trigger>
</Style.Triggers>

数据模板

样式提供了基本的格式化能力,但不管如何修改ListBoxItem,它都只是ListBoxItem.数据模板是一块定义如何显示绑定的数据对象的XAML,有两种类型的控件支持数据模板:

  • 内容控件通过 ContentTemplate 属性支持数据模板
  • 列表控件通过 ItemTemplate 属性支持数据模板

分离和重用模板

与样式类似,通常也将模板声明为窗口或程序的资源。

<Window.Resources>
    <DataTemplate x:Key="ProductDataTemplate">
        <Border Margin="5" BorderThickness="1" BorderBrush="StellBlue" CornerRadius="4"
            Background="{Binding Path=Background, RelativeSource={
                RelativeSource Mode=FindAncestor, AncestorType={x:Type ListBoxItem}}}">
            <Grid Margin="3">
                <Grid.RowDefinitions>
                    <RowDefinition/>
                    <RowDefinition/>
                </Grid.RouDefinition>
                <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}"/>
                <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}"/>
            </Grid>
        </Border>
    </DataTemplate>
</Window.Resources>

通过 StaticResource 引用来为列表添加数据模板:

<ListBox Name="lstProducts" HorizontalContentAlignment="Stretch"
    ItemTemplate="{StaticResource ProductDataTemplate}"/>

如果希望在不同类型的控件中自动重用相同的模板,可以通过设置 DataTemplate.DataType 属性来确定使用模板的绑定数据的类型。

<Window.Resources>
    <!--模板将用于窗口中任何绑定到Product对象的列表控件或内容控件-->
    <DataTemplate DataType="{x:Type local:Product}">
    ...
    </DataTemplate>
</Window.Resources>

改变模板

目前只能为整个列表使用一个模板,如果希望采用不同方式灵活展示不同的数据:

  • 使用数据触发器
  • 使用值转换器
  • 使用模板选择器

模板选择器检查绑定对象并使用提供的逻辑选择合适的模板,需要创建继承自 DataTemplateSelector 的类。

ComboBox控件

与ListBox类不同的是,ComboBox类增加了另外两个部分:显示当前选择项的选择框和用于选择项的下拉列表。

ComboBox提供了自动完成输入功能,当键入内容时,WPF使用第一个匹配自动完成建议的项填充选择框中的剩余内容。可以通过设置 ComboBox.IsTextSearchEnabled 属性设置为 false 禁用该功能。

如果IsEditable属性为 true,ComboBox控件不是显示选择项的副本,而是显示选择项的文本形式表示,WPF简单调用ToString()方法。可以通过设置 TextSearch.TextPaht 附加属性来定义选择框显示的内容:

<ComboBox IsEditable="True" IsReadOnly="True" TextSearch.TextPath="ModelName">
...
</ComboBox>

我的公众号

WPF_15_格式化绑定的数据

上一篇:ThreadX NetXDUO测试200个TCP客户端连接没问题,分享工程和测试效果(2022-01-15)


下一篇:第一次会议记录(2022.1.15)