• 20 WPF格式化绑定数据


    数据绑定回顾

    见601页。

    数据转换

    使用StringFormat属性

    StringFormat属性适用于显示数字为文本。

    使用Binding对象的StringFormat属性,可以将绑定对象的属性转化为指定格式的文本:

    <TextBox Margin="5" Grid.Row="2" Grid.Column="1"
     Text="{Binding Path=UnitCost, StringFormat={}{0:C}}">
    </TextBox>

    StrngFormat值开始的成对花括号,是逃跑序列,表式StringFormat值不是标记扩展。

    顺便,只有StringFormat值以花括号开头时,才需要{}逃跑序列。

    WPF列表控件也支持字符串格式化列表项目。为使用它,你简单地设置列表的ItemStringFormat属性(定义在ItemsControl类)。这是一个例子,列出产品价格:

    <ListBox Name="lstProducts" DisplayMemberPath="UnitCost" ItemStringFormat="{}{0:C}">
    </ListBox>

    介绍值转换器

    值转换器负责源数据和目标的转换。恰好在源数据被显示之前转换,以及在两路绑定的情况下,恰好在目标值被应用回源数据之前转换。

    创造值转换器,需要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)
        {
            var price = (decimal) value;
            return price.ToString("C", culture);
        }
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var price = value.ToString();
            
            decimal result;
            if (Decimal.TryParse(price, NumberStyles.Any, culture, out result))
            {
                return result;
            }
            return value;
        }
    }

    应用转换器,创建一个PriceConverter实例,将它赋值给绑定对象的转换器属性:

    <TextBlock Margin="7" Grid.Row="2">Unit Cost:</TextBlock>
    <TextBox Margin="5" Grid.Row="2" Grid.Column="1">
      <TextBox.Text>
        <Binding Path="UnitCost">
          <Binding.Converter>
            <local:PriceConverter></local:PriceConverter>
          </Binding.Converter>
        </Binding>
      </TextBox.Text>
    </TextBox>

    在许多情况下,相同的转换器被用于多个绑定。在这种情况下,为每个绑定创造转换器的一个实例毫无意义。而是,在Resources集合创造转换器对象,如下所示:

    <Window.Resources>
      <local:PriceConverter x:Key="PriceConverter"></local:PriceConverter>
    </Window.Resources>

    然后引用资源:

    <TextBox Margin="5" Grid.Row="2" Grid.Column="1"
     Text="{Binding Path=UnitCost, Converter={StaticResource PriceConverter}}">
    </TextBox>

    用值转换器创建对象

    值转换器是连接数据类和窗口显示的桥梁。

    这是实现文件路径到BitmapImage对象转换的类:

    public class ImagePathConverter : IValueConverter
    {
        private string imageDirectory = Directory.GetCurrentDirectory();
    
        public string ImageDirectory
        {
            get { return imageDirectory; }
            set { imageDirectory = value; }
        }
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var imagePath = Path.Combine(ImageDirectory, (string)value);
            return new BitmapImage(new Uri(imagePath));
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException("The method or operation is not implemented.");
        }
    }

    为了使用这个转换器,添加它到资源:

    <Window.Resources>
      <local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter>
    </Window.Resources>

    创造一个使用此转换器的绑定表达式:

    <Image Margin="5" Grid.Row="2" Grid.Column="1" Stretch="None"
     HorizontalAlignment="Left" Source=
     "{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}">
    </Image>

    注意,Image.Source属性期待一个ImageSource对象,而BitmapImage类派生自ImageSource类。

    应用条件格式

    例如,你希望标记高价的项目,给他们一个不同的背景颜色:

    public class PriceToBackgroundConverter : IValueConverter
    {
        public decimal MinimumPriceToHighlight {get; set;}
    
        public Brush HighlightBrush{get; set;}
    
        public Brush DefaultBrush{get; set;}
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            var price = (decimal)value;
            if (price >= MinimumPriceToHighlight)
                return HighlightBrush;
            else
                return DefaultBrush;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

    定义资源对象:

    <local:PriceToBackgroundConverter x:Key="PriceToBackgroundConverter"
      DefaultBrush="{x:Null}" HighlightBrush="Orange" MinimumPriceToHighlight="50">
    </local:PriceToBackgroundConverter>

    在元素上应用值转换器:

    <Border Background=
     "{Binding Path=UnitCost, Converter={StaticResource PriceToBackgroundConverter}}"
     ... >

    估值多个属性

    这个过程是不可逆的,也就是只能将多个值合并为一个结果。而不能将结果分解为多个属性。

    结合多个属性的第一个方法是使用MultiBinding。下面例子:

    <TextBlock>
      <TextBlock.Text>
        <MultiBinding StringFormat="{1}, {0}">
          <Binding Path="FirstName"></Binding>
          <Binding Path="LastName"></Binding>
        </MultiBinding>
      </TextBlock.Text>
    </TextBlock>

    第二个方法是使用值转换器,实现的是IMultiValueConverter。

    MultiBinding使用源对象的UnitCost和UnitsInStock属性,并且使用一个值转换器结合他们:

    <TextBlock>Total Stock Value: </TextBlock>
    <TextBox>
      <TextBox.Text>
        <MultiBinding Converter="{StaticResource ValueInStockConverter}">
          <Binding Path="UnitCost"></Binding>
          <Binding Path="UnitsInStock"></Binding>
        </MultiBinding>
      </TextBox.Text>
    </TextBox>

    注意Convert()方法的values是一个对象数组,按照他们在MultiBinding中的顺序放置这些值。在前一个示例中,首先出现UnitCost,然后是UnitsInStock。

    public class ValueInStockConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            var unitCost = (decimal)values[0];
            var unitsInStock = (int)values[1];
    
            return unitCost * unitsInStock;
        }
    
        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotSupportedException();
        }
    }

    列表类控件

    所有列表控件的基类是ItemsControl。

    ItemsControl

    ItemsControl类的格式化相关的属性

    名称 描述
    ItemsSource 绑定数据源(你希望显示在列表的集合或DataView)。
    DisplayMemberPath 你希望为每个数据项目显示的属性。对于一个更世故的表示法或为使用属性的一个联合,使用ItemTemplate代替。
    ItemStringFormat .NET格式字符串,如果设置,将被用于格式化每个项目的文本。通常,这技术被用来转换数字或日期值到一个合适的显示表示法,确切地为Binding.StringFormat属性。
    ItemContainerStyle 一个样式允许你设置包裹每个项目容器的属性。容器取决于列表的类型(例如,对于ListBox类它是ListBoxItem,对于ComboBox类ComboBoxItem)。当列表被填充时,这些包裹器对象被自动地创造。
    ItemContainerStyleSelector StyleSelector。使用代码为每个列表项目的包裹器选择一个样式。这允许你为不同的列表项目给出不同的样式。你必须自己创造一个自定义StyleSelector。
    AlternationCount 在你的数据中交替集合的数目。例如,2交替2行样式,3交替3行样式,等等。
    ItemTemplate 一个模板,从你的绑定对象提取合适的数据,并且排列它到合适的控件联合里。
    ItemTemplateSelector DataTemplateSelector,使用代码为每个列表项目选择一个模板。这允许你为不同的项目给出不同的模板。你必须自己创造一个自定义DataTemplateSelector类。
    ItemsPanel 定义被创造持有列表项目面板。所有的项目包装被添加到这容器。通常,VirtualizingStackPanel带有一个垂直的(自顶到底)方向被使用。
    GroupStyle 如果你使用分组,这是一个样式那定义每个组应该如何被格式化。当使用分组时,项目包装(ListBoxItem,ComboBoxItem,等等)被添加在GroupItem包装那表示每个组,和这些组是然后添加到列表。
    GroupStyleSelector StyleSelector。使用代码为每个组选择一个样式。这允许你为不同的组给不同的样式。你必须自己创造一个自定义StyleSelector。

    ItemsControl类的下一级是Selector类,它添加了一组属性描述被选择的项目。

    Selector类添加的属性包括:SelectedItem,SelectedIndex,SelectedValue,SelectedValuePath。

    注意,Selector类不支持多选。ListBox通过SelectionMode和SelectedItems属性支持多选。

    列表样式

    ItemContainerStyle

    如果ItemContainerStyle被设置,当项目被创造时,样式将向下传递到列表控件的每个项目。在列表框控件的情况,每个项目是一个ListBoxItem对象。(在组合框,它是ComboBoxItem,等等。)因而,你用ListBox.ItemContainerStyle属性应用任何样式被用来设置每个ListBoxItem对象的属性。

    <ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName">
      <ListBox.ItemContainerStyle>
        <Style>
          <Setter Property="ListBoxItem.Background" Value="LightSteelBlue" />
          <Setter Property="ListBoxItem.Margin" Value="5" />
          <Setter Property="ListBoxItem.Padding" Value="5" />
        </Style>
      </ListBox.ItemContainerStyle>
    </ListBox>

    带触发器的样式,样式中的触发器是在满足某个前提条件的情况下,设置控件的属性:

    <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="Foreground" Value="White" />
              <Setter Property="BorderBrush" Value="Black" />
              <Setter Property="BorderThickness" Value="1" />
            </Trigger>
          </Style.Triggers>
        </Style>
      </ListBox.ItemContainerStyle>
    </ListBox>

    带复选框或单选按钮的列表框

    基本技术是修改代表每个列表项目容器的控件模板。不要修改ListBox.Template属性,因为它是列表框的模板。你需要修改ListBoxItem.Template属性。这里是带有单选按钮的模板:

    <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>
                  </ControlTemplate>
                </Setter.Value>
              </Setter>
            </Style>
          </Setter.Value>
        </Setter>
      </Style>
    </Window.Resources>

    直接设置列表框的样式:

    <ListBox Style="{StaticResource RadioButtonListStyle}" Name="lstProducts"
     DisplayMemberPath="ModelName">

    复选框样式的列表框建立方法与单选列表框基本相同。只是允许列表框多选:

    <Style x:Key="CheckBoxListStyle" TargetType="{x:Type ListBox}">
      <Setter Property="SelectionMode" Value="Multiple"></Setter>
      <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}">
                  <CheckBox Focusable="False"
                   IsChecked="{Binding Path=IsSelected, Mode=TwoWay,
                              RelativeSource={RelativeSource TemplatedParent} }">
                    <ContentPresenter></ContentPresenter>
                  </CheckBox>
                </ControlTemplate>
              </Setter.Value>
            </Setter>
          </Style>
        </Setter.Value>
      </Setter>
    </Style>

    交替项目样式

    AlternationCount是形成一个序列的项目数,在此数目以后样式交替。默认情况下,AlternationCount被设置为0,并且不使用交替格式化。如果你设置AlternationCount为1,列表将在每个项目以后交替,这允许你应用偶奇格式化模式。

    给每个ListBoxItem一个AlternationIndex属性,允许你决定它在交替项目的序列如何编号。假设你设置AlternationCount为2,第一ListBoxItem获得一个AlternationIndex的0,第二获得一个AlternationIndex的1,第三获得一个AlternationIndex的0,第四获得一个AlternationIndex的1,等等。

    <ListBox Name="lstProducts" Margin="5" DisplayMemberPath="ModelName"
     AlternationCount=”2”>
      <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="ItemsControl.AlternationIndex" Value="1">
                <Setter Property="Background" Value="LightBlue" />
              </Trigger>
              <Trigger Property="IsSelected" Value="True">
                <Setter Property="Background" Value="DarkRed" />
                <Setter Property="Foreground" Value="White" />
                <Setter Property="BorderBrush" Value="Black" />
                <Setter Property="BorderThickness" Value="1" />
              </Trigger>
            </Style.Triggers>
          </Style>
        </ListBox.ItemContainerStyle>
    </ListBox>

    样式选择器

    必须用代码方式实现,从StyleSelector派生一个专用的类,覆盖SelectStyle()方法,选择合适的样式。

    这里是一个基本的选择器,二样式选择一个:

    public class ProductByCategoryStyleSelector : StyleSelector
    {
        public override Style SelectStyle(object item, DependencyObject container)
        {
            var product = (Product)item;
            var window = Application.Current.MainWindow;
            if (product.CategoryName == "Travel")
            {
                return (Style)window.FindResource("TravelProductStyle");
            }
            else
            {
                return (Style)window.FindResource("DefaultProductStyle");
            }
        }
    }

    下面代码使用了反射,更为通用的一个样式选择器:

    public class SingleCriteriaHighlightStyleSelector : StyleSelector
    {
        public Style DefaultStyle
        {
            get; set;
        }
        public Style HighlightStyle
        {
            get; set;
        }
        public string PropertyToEvaluate
        {
            get; set;
        }
        public string PropertyValueToHighlight
        {
            get; set;
        }
        public override Style SelectStyle(object item, DependencyObject container)
        {
            Product product = (Product)item;
            
            // 使用反射获得要检测的属性
            Type type = product.GetType();
            PropertyInfo property = type.GetProperty(PropertyToEvaluate);
            
            // 根据属性值决定是否这个产品应该被高亮
            if (property.GetValue(product, null).ToString() == PropertyValueToHighlight)
            {
                return HighlightStyle;
            }
            else
            {
                return DefaultStyle;
            }
        }
    }

    定义代码中用到的两个样式:

    <Window.Resources>
      <Style x:Key="DefaultStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="Background" Value="LightYellow" />
        <Setter Property="Padding" Value="2" />
      </Style>
    
      <Style x:Key="HighlightStyle" TargetType="{x:Type ListBoxItem}">
        <Setter Property="Background" Value="LightSteelBlue" />
        <Setter Property="FontWeight" Value="Bold" />
        <Setter Property="Padding" Value="2" />
      </Style>
    </Window.Resources>

    内联使用样式选择器:

    <ListBox Name="lstProducts" HorizontalContentAlignment="Stretch">
      <ListBox.ItemContainerStyleSelector>
        <local:SingleCriteriaHighlightStyleSelector
          DefaultStyle="{StaticResource DefaultStyle}"
          HighlightStyle="{StaticResource HighlightStyle}"
          PropertyToEvaluate="CategoryName"
          PropertyValueToHighlight="Travel"
        >
        </local:SingleCriteriaHighlightStyleSelector>
      </ListBox.ItemContainerStyleSelector>
    </ListBox>

    样式选择过程,当第一次绑定列表时,被执行一次。如果,由于修改了决定样式的数据,样式不会自动改变。只能用暴力的方法强制样式更新:

    var selector = lstProducts.ItemContainerStyleSelector;
    lstProducts.ItemContainerStyleSelector = null;
    lstProducts.ItemContainerStyleSelector = selector;

    数据模板

    每个ListBoxItem只能绑定一个字段,没有办法结合多个字段。如果要显示多个字段,这就需要用到数据模板。数据模板是一块XAML标记。它定义了应该如何显示一个绑定数据对象。二类型的控件支持数据模板:

    • 内容控件通过ContentTemplate属性支持数据模板。内容模板被用来显示Content属性。
    • 列表控件通过ItemTemplate属性支持数据模板。这模板被用来显示集合每个项目。集合通过ItemsSource提供。

    列表项目是一个内容控件。

    数据模板应该包括数据绑定表达式。

    这是一个例子,用一个导圆角的边界包裹每个项目,显示二片信息,并且使用粗体的格式化高亮模型数:

    <ListBox Name="lstProducts" HorizontalContentAlignment="Stretch">
      <ListBox.ItemTemplate>
        <DataTemplate>
          <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4">
            <Grid Margin="3">
              <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition></RowDefinition>
              </Grid.RowDefinitions>
    
              <TextBlock FontWeight="Bold"
               Text="{Binding Path=ModelNumber}"></TextBlock>
              <TextBlock Grid.Row="1"
               Text="{Binding Path=ModelName}"></TextBlock>
            </Grid>
          </Border>
        </DataTemplate>
      </ListBox.ItemTemplate>
    </ListBox>

    分离和重用模板

    提取前一个例子的模板:

    <Window.Resources>
      <DataTemplate x:Key="ProductDataTemplate">
        <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
         CornerRadius="4">
          <Grid Margin="3">
            <Grid.RowDefinitions>
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
    
            <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}">
            </TextBlock>
            <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}">
            </TextBlock>
          </Grid>
        </Border>
      </DataTemplate>
    </Window.Resources>

    现在,你能添加你的数据模板到列表,使用一个静态资源:

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

    自动地在不同的类型的控件重用同一个数据模板。设置DataTemplate.DataType属性为绑定数据的类型。例如,前一个例子,移除Key并指定绑定Product对象:

    <Window.Resources>
      <DataTemplate DataType="{x:Type local:Product}">
      </DataTemplate>
    </Window.Resources>

    使用更高级的模板

    第一个例子,

    <Window.Resources>
      <local:ImagePathConverter x:Key="ImagePathConverter"></local:ImagePathConverter>
      <DataTemplate x:Key="ProductTemplate">
        <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
         CornerRadius="4">
          <Grid Margin="3">
            <Grid.RowDefinitions>
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
    
            <TextBlock FontWeight="Bold" Text="{Binding Path=ModelNumber}"></TextBlock>
            <TextBlock Grid.Row="1" Text="{Binding Path=ModelName}"></TextBlock>
            <Image Grid.Row="2" Grid.RowSpan="2" Source=
    "{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}">
            </Image>
          </Grid>
        </Border>
      </DataTemplate>
    </Window.Resources>

    直接一个模板内部放置控件。例如,一列种类。挨着每个种类是一个View按钮。你能使用按钮发射另一个窗口恰好匹配在那种类产品。

    处理按钮点击。明显地,所有的按钮将被链接到同样的事件处理器,你在模板内部定义它。但是,你需要决定哪一个列表项目被点击。一个解决方案是存储一些额外的识别信息在按钮的Tag属性,如下所示:

    <DataTemplate>
      <Grid Margin="3">
        <Grid.ColumnDefinitions>
          <ColumnDefinition></ColumnDefinition>
          <ColumnDefinition Width="Auto"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Text="{Binding Path=CategoryName}"></TextBlock>
        <Button Grid.Column="2" HorizontalAlignment="Right" Padding="2"
          Click="cmdView_Clicked" Tag="{Binding Path=CategoryID}">View ...</Button>
      </Grid>
    </DataTemplate>

    你能取回Tag属性,在事件处理器中:

    private void cmdView_Clicked(object sender, RoutedEventArgs e)
    {
        var cmd = (Button)sender;
        var categoryID = (int)cmd.Tag;
        ...
    }

    当你定义绑定时,遗漏Path属性,你能抓取整个数据对象:

    <Button HorizontalAlignment="Right" Padding="1"
      Click="cmdView_Clicked" Tag="{Binding}">View ...</Button>

    传递整个的对象使更新列表选择更容易。当点击View按钮之前,移动选择到按钮被点击的列表项目。如下所示:

    var cmd = (Button)sender;
    var product = (Product)cmd.Tag;
    lstCategories.SelectedItem = product;

    不同的模板

    以不同的方式呈现列表项目:

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

    数据触发器。基于数据项目的一个属性,设置模板元素的一个属性。例如,基于相应产品对象的CategoryName属性,你能改变包裹每个列表项目的自定义边界的背景。这是一个例子,用红字高亮在工具目录中的产品:

    <DataTemplate x:Key="DefaultTemplate">
      <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Path=CategoryName}" Value="Tools">
         <Setter Property="ListBoxItem.Foreground" Value="Red"></Setter>
        </DataTrigger>
      </DataTemplate.Triggers>
      <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
       CornerRadius="4">
        <Grid Margin="3">
          <Grid.RowDefinitions>
            <RowDefinition></RowDefinition>
            <RowDefinition></RowDefinition>
          </Grid.RowDefinitions>
          <TextBlock FontWeight="Bold"
           Text="{Binding Path=ModelNumber}"></TextBlock>
          <TextBlock Grid.Row="1"
           Text="{Binding Path=ModelName}"></TextBlock>
        </Grid>
      </Border>
    </DataTemplate>

    值转换器方法:

    <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue" CornerRadius="4"
     Background=
     "{Binding Path=CategoryName, Converter={StaticResource CategoryToColorConverter}">

    模板选择器

    你需要创造一个派生自DataTemplateSelector类。检查绑定对象并且使用你提供逻辑选择一个合适的模板。

    public class SingleCriteriaHighlightTemplateSelector : DataTemplateSelector
    {
        public DataTemplate DefaultTemplate
        {
            get; set;
        }
        public DataTemplate HighlightTemplate
        {
            get; set;
        }
        public string PropertyToEvaluate
        {
            get; set;
        }
        public string PropertyValueToHighlight
        {
            get; set;
        }
        public override DataTemplate SelectTemplate(object item,
          DependencyObject container)
        {
            Product product = (Product)item;
            // Use reflection to get the property to check.
            Type type = product.GetType();
            PropertyInfo property = type.GetProperty(PropertyToEvaluate);
            // Decide if this product should be highlighted
            // based on the property value.
            if (property.GetValue(product, null).ToString() == PropertyValueToHighlight)
            {
                return HighlightTemplate;
            }
            else
            {
                return DefaultTemplate;
            }
        }
    }

    这里是创建两个数据模板的标记:

    <Window.Resources>
      <DataTemplate x:Key="DefaultTemplate">
        <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
          CornerRadius="4">
          <Grid Margin="3">
            <Grid.RowDefinitions>
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock
             Text="{Binding Path=ModelNumber}"></TextBlock>
            <TextBlock Grid.Row="1"
             Text="{Binding Path=ModelName}"></TextBlock>
          </Grid>
        </Border>
      </DataTemplate>
      
      <DataTemplate x:Key="HighlightTemplate">
        <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
         Background="LightYellow" CornerRadius="4">
          <Grid Margin="3">
            <Grid.RowDefinitions>    
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
              <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <TextBlock FontWeight="Bold"
             Text="{Binding Path=ModelNumber}"></TextBlock>
            <TextBlock Grid.Row="1" FontWeight="Bold"
             Text="{Binding Path=ModelName}"></TextBlock>
            <TextBlock Grid.Row="2" FontStyle="Italic" HorizontalAlignment="Right">
             *** Great for vacations ***</TextBlock>
          </Grid>
        </Border>
      </DataTemplate>
    </Window.Resources>    

    这里是应用模板选择器的标记:

    <ListBox Name="lstProducts" HorizontalContentAlignment="Stretch">
      <ListBox.ItemTemplateSelector>
        <local:SingleCriteriaHighlightTemplateSelector
          DefaultTemplate="{StaticResource DefaultTemplate}"
          HighlightTemplate="{StaticResource HighlightTemplate}"
          PropertyToEvaluate="CategoryName"
          PropertyValueToHighlight="Travel"
        >
        </local:SingleCriteriaHighlightTemplateSelector>
      </ListBox.ItemTemplateSelector>
    </ListBox>

     模板与选择

    例1,修改选中项目的背景色:

    Foreground属性使用属性继承,所以你添加到模板任何元素自动地获得白颜色。Background属性不使用属性继承,但是默认背景颜色是透明。

    首先,设置项目容器的样式:

    <ListBox Name="lstProducts" HorizontalContentAlignment="Stretch">
      <ListBox.ItemContainerStyle>
        <Style>
          <Setter Property="Control.Padding" Value="0"></Setter>
          <Style.Triggers>
            <Trigger Property="ListBoxItem.IsSelected" Value="True">
              <Setter Property="ListBoxItem.Background" Value="DarkRed" />
            </Trigger>
          </Style.Triggers>
        </Style>
      </ListBox.ItemContainerStyle>
    </ListBox>

    然后,修改数据模板:

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

    例2,选中项目后,显示此项目的细节:

    在数据模板中,使用Binding的RelativeSource属性搜索目前的ListBoxItem。如果它没有被选择,设置额外信息的Visibility属性,你能隐藏它。

    使用一个数据触发器,当ListBoxItem的IsSelected属性被改变,修改容器的Visibility属性。

    数据触发器只需要放在要隐藏的容器内。

    这是还没有自动扩展功能的简化版:

    <DataTemplate>
      <Border Margin="5" BorderThickness="1" BorderBrush="SteelBlue"
       CornerRadius="4">
        <StackPanel Margin="3">
          <TextBlock Text="{Binding Path=ModelName}"></TextBlock>
          <StackPanel>
            <TextBlock Margin="3" Text="{Binding Path=Description}"
             TextWrapping="Wrap" MaxWidth="250" HorizontalAlignment="Left"></TextBlock>
            <Image Source="{Binding Path=ProductImagePath, Converter={StaticResource ImagePathConverter}}">
            </Image>
            <Button FontWeight="Regular" HorizontalAlignment="Right" Padding="1"
             Tag="{Binding}">View Details...</Button>
          </StackPanel>
        </StackPanel>
      </Border>
    </DataTemplate>

    最内层的StackPanel包含着只对选中项目显示的内容,所以设置这个容器的样式:

    <StackPanel>
      <StackPanel.Style>
        <Style>
          <Style.Triggers>
            <DataTrigger
              Binding="{Binding Path=IsSelected, RelativeSource={
                                 RelativeSource
                                 Mode=FindAncestor,
                                 AncestorType={x:Type ListBoxItem}
                              }}"
              Value="False">
              <Setter Property="StackPanel.Visibility" Value="Collapsed" />
            </DataTrigger>
          </Style.Triggers>
        </Style>
      </StackPanel.Style>
    </StackPanel>

    在这个例子中,你需要使用DataTrigger而不是一个普通的触发器,因为你需要的属性在祖先元素中(ListBoxItem),并且唯一访问它的办法是使用数据绑定表达式。

    改变项目布局

    使用任何从System.Windows.Controls.Panel派生的类,设置ListBox的ItemsPanelTemplate属性,可以改变项目布局。

    下面例子,使用WrapPanel改变项目布局:

    <ListBox Margin="7,3,7,10" Name="lstProducts"
     ItemTemplate="{StaticResource ItemTemplate}"
     ScrollViewer.HorizontalScrollBarVisibility="Disabled">
      <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
          <WrapPanel></WrapPanel>
        </ItemsPanelTemplate>
      </ListBox.ItemsPanel>
    </ListBox>

    你必须也设置ScrollViewer.HorizontalScrollBarVisibility附加属性为Disabled。这确保ScrollViewer从不使用水平的scrollbar。

    VirtualizingStackPanel对于大量数据项目有更好的性能。

    组合框

    默认,ComboBox是只读的。当设置IsReadOnly属性为假并且IsEditable属性为真,选择框变成文本框,你能键入任何文本。

    组合框有自动完成的功能。要关闭它,设置ComboBox.IsTextSearchEnabled属性为假。此属性位于ItemsControl类。

    如果IsEditable属性是假(这是默认值),选择框将显示一个项目的精确视觉副本。

    重要的细节是组合框显示的内容是什么,而不是它的数据源是什么。例如,用Product对象填充一个组合框控件,并且设置DisplayMemberPath属性为ModelName。如此,组合框显示每个项目的ModelName属性。即组合框从一组Product对象获取信息,你的标记创造的是一个普通的文本列表。它将显示当前产品的ModelName,并且如果IsEditable是真并且IsReadOnly是假,它将允许你编辑那值。

    如果IsEditable属性是真,选择框显示一个它的一个逐字的表示法。就是简单地调用ToString()到项目。一般情况下,显示一个类名的全称。

    纠正这个问题最简单的方法是,设置TextSearch.TextPath附着属性指明组合框应该使用的内容。

    <ComboBox IsEditable="True" IsReadOnly="True" TextSearch.TextPath="ModelName" ...>
  • 相关阅读:
    C# 生成随机索引列表
    QQ音乐MP3下载
    微信Dat文件解码
    C#工作常用关键字
    C#左移运算符
    C#中datatable操作
    html 显示 pdf
    framework7 下拉刷新、无限滚动
    framework7 总结之前遇到的问题和踩过的坑
    HTML 引用大全
  • 原文地址:https://www.cnblogs.com/cuishengli/p/3134229.html
Copyright © 2020-2023  润新知