当前位置:首页 > 技术 > 正文内容

WPF数据绑定格式化技术详解

访客 技术 2026年5月23日 3

WPF数据绑定格式化技术详解

数据绑定基础

数据绑定是WPF应用程序中的核心概念,它允许UI元素与数据源自动同步。通过绑定,开发者可以减少大量样板代码,实现更高效的应用程序开发。

数据转换技术

使用字符串格式化属性

字符串格式化属性主要用于将数值转换为特定格式的文本显示。在WPF中,Binding对象提供了StringFormat属性来实现这一功能:

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

StringFormat值开头的一对花括号是转义序列,表示这不是一个标记扩展。需要注意的是,只有当StringFormat值以花括号开头时,才需要使用{}转义序列。

WPF列表控件同样支持对列表项进行字符串格式化。要使用此功能,只需设置列表控件的ItemStringFormat属性(定义在ItemsControl类中)。以下是一个显示产品价格的示例:

<ListBox Name="产品列表" DisplayMemberPath="单价" ItemStringFormat="{}{0:C}">
</ListBox>

值转换器原理

值转换器负责在源数据和目标UI之间进行数据转换。在数据被显示之前,以及在进行双向绑定的情况下,在目标值被应用回源数据之前,都会进行转换。

创建值转换器需要四个步骤:

  1. 创建实现IValueConverter接口的类
  2. 在类声明上添加ValueConversion特性,指定目标数据类型
  3. 实现Convert()方法,将数据从原始格式转换为显示格式
  4. 实现ConvertBack()方法,将数据从显示格式转换回本地格式

以下是一个处理价格的完整值转换器类:

[ValueConversion(typeof(decimal), typeof(string))]
public class 价格转换器 : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var 价格 = (decimal) value;
        return 价格.ToString("C", culture);
    }
    
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var 价格 = value.ToString();
        
        decimal 结果;
        if (Decimal.TryParse(价格, NumberStyles.Any, culture, out 结果))
        {
            return 结果;
        }
        return value;
    }
}

应用转换器时,需要创建一个价格转换器实例,并将其赋值给绑定对象的转换器属性:

<TextBlock Margin="7" Grid.Row="2">单价:</TextBlock>
<TextBox Margin="5" Grid.Row="2" Grid.Column="1">
  <TextBox.Text>
    <Binding Path="单价">
      <Binding.Converter>
        <local:价格转换器></local:价格转换器>
      </Binding.Converter>
    </Binding>
  </TextBox.Text>
</TextBox>

当相同的转换器需要在多个绑定中重复使用时,为每个绑定创建转换器实例是不必要的。更好的方法是在Resources集合中创建转换器对象,如下所示:

<Window.Resources>
  <local:价格转换器 x:Key="价格转换器"></local:价格转换器>
</Window.Resources>

然后在XAML中引用资源:

<TextBox Margin="5" Grid.Row="2" Grid.Column="1"
 Text="{Binding Path=单价, Converter={StaticResource 价格转换器}}">
</TextBox>

创建对象转换器

值转换器作为数据类和UI显示之间的桥梁,可以实现复杂的数据类型转换。以下是一个将文件路径转换为BitmapImage对象的转换器:

public class 图片路径转换器 : IValueConverter
{
    private string 图片目录 = Directory.GetCurrentDirectory();

    public string 图片目录属性
    {
        get { return 图片目录; }
        set { 图片目录 = value; }
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var 图片路径 = Path.Combine(图片目录, (string)value);
        return new BitmapImage(new Uri(图片路径));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException("此方法未实现。");
    }
}

要使用这个转换器,首先将其添加到资源中:

<Window.Resources>
  <local:图片路径转换器 x:Key="图片路径转换器"></local:图片路径转换器>
</Window.Resources>

然后创建使用此转换器的绑定表达式:

<Image Margin="5" Grid.Row="2" Grid.Column="1" Stretch="None"
 水平对齐方式="左" 来源=
 "{Binding Path=产品图片路径, Converter={StaticResource 图片路径转换器}}">
</Image>

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

条件格式化应用

例如,您可能希望突出显示价格较高的项目,为它们设置不同的背景颜色:

public class 价格转背景转换器 : IValueConverter
{
    public decimal 最低高亮价格 {get; set;}
    
    public Brush 高亮画笔{get; set;}
    
    public Brush 默认画笔{get; set;}

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        var 价格 = (decimal)value;
        if (价格 >= 最低高亮价格)
            return 高亮画笔;
        else
            return 默认画笔;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

定义资源对象:

<local:价格转背景转换器 x:Key="价格转背景转换器"
  默认画笔="{x:Null}" 高亮画笔="橙色" 最低高亮价格="50">
</local:价格转背景转换器>

在元素上应用值转换器:

<Border 背景=
 "{Binding Path=单价, Converter={StaticResource 价格转背景转换器}}"
 ... >

多属性值评估

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

结合多个属性的第一种方法是使用MultiBinding。以下示例展示了如何将名字和姓氏组合显示:

<TextBlock>
  <TextBlock.文本>
    <多绑定 字符串格式="{1}, {0}">
      <绑定 路径="名字"></绑定>
      <绑定 路径="姓氏"></绑定>
    </多绑定>
  </TextBlock.文本>
</TextBlock>

第二种方法是使用值转换器,实现IMultiValueConverter接口。

以下示例使用MultiBinding结合源对象的单价和库存数量,并通过一个值转换器计算它们的乘积:

<TextBlock>库存总价值: </TextBlock>
<文本框>
  <文本框.文本>
    <多绑定 转换器="{静态资源 库存价值转换器}">
      <绑定 路径="单价"></绑定>
      <绑定 路径="库存数量"></绑定>
    </多绑定>
  </文本框.文本>
</文本框>

注意,Convert()方法的values是一个对象数组,按照它们在MultiBinding中出现的顺序排列。在上一个示例中,首先出现单价,然后是库存数量。

public class 库存价值转换器 : IMultiValueConverter
{
    public object 转换(object[] 值, 类型 目标类型, object 参数, 文化信息 文化)
    {
        var 单价 = (decimal)值[0];
        var 库存数量 = (int)值[1];

        return 单价 * 库存数量;
    }

    public object[] 转换回(object 值, 类型[] 目标类型, object 参数, 文化信息 文化)
    {
        throw new NotSupportedException();
    }
}

列表控件基础

所有列表控件的基类是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,当项被创建时,样式将应用到列表控件的每个项上。对于列表框控件,每个项是一个ListBoxItem对象(在组合框中是ComboBoxItem,等等)。因此,使用ListBox.ItemContainerStyle属性应用的任何样式都将用于设置每个ListBoxItem对象的属性。

<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称">
  <列表框.项容器样式>
    <样式>
      <设置器 属性="列表框项.背景" 值="淡钢蓝色" />
      <设置器 属性="列表框项.边距" 值="5" />
      <设置器 属性="列表框项.内边距" 值="5" />
    </样式>
  </列表框.项容器样式>
</列表框>

带有触发器的样式允许在满足特定条件时设置控件属性:

<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称">
  <列表框.项容器样式>
    <样式 目标类型="{x:Type 列表框项}">
      <设置器 属性="背景" 值="淡钢蓝色" />
      <设置器 属性="边距" 值="5" />
      <设置器 属性="内边距" 值="5" />

      <样式.触发器>
        <触发器 属性="是否已选中" 值="真">
          <设置器 属性="背景" 值="深红色" />
          <设置器 属性="前景色" 值="白色" />
          <设置器 属性="边框画笔" 值="黑色" />
          <设置器 属性="边框粗细" 值="1" />
        </触发器>
      </样式.触发器>
    </样式>
  </列表框.项容器样式>
</列表框>

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

基本技术是修改代表每个列表项的控件模板。不要修改ListBox.Template属性,因为那是列表框本身的模板。需要修改ListBoxItem.Template属性。以下是带有单选按钮的模板:

<窗口.资源>
  <样式 x:键="单选按钮列表样式" 目标类型="{x:Type 列表框}">
    <设置器 属性="项容器样式">
      <设置器.值>
        <样式 目标类型="{x:Type 列表框项}" >
          <设置器 属性="边距" 值="2" />
          <设置器 属性="模板">
            <设置器.值>
              <控制模板 目标类型="{x:Type 列表框项}">
                <单选按钮 可聚焦="假"
                 是否选中="{绑定 路径=是否已选中, 模式=双向,
                             相对源={相对源 模板父级} }">
                  <内容展示器></内容展示器>
                </单选按钮>
              </控制模板>
            </设置器.值>
          </设置器>
        </样式>
      </设置器.值>
    </设置器>
  </样式>
</窗口.资源>

直接设置列表框的样式:

<列表框 样式="{静态资源 单选按钮列表样式}" 名称="产品列表"
 显示成员路径="型号名称">

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

<样式 x:键="复选框列表样式" 目标类型="{x:Type 列表框}">
  <设置器 属性="选择模式" 值="多选"></设置器>
  <设置器 属性="项容器样式">
    <设置器.值>
      <样式 目标类型="{x:Type 列表框项}" >
        <设置器 属性="边距" 值="2" />
        <设置器 属性="模板">
          <设置器.值>
            <控制模板 目标类型="{x:Type 列表框项}">
              <复选框 可聚焦="假"
               是否选中="{绑定 路径=是否已选中, 模式=双向,
                          相对源={相对源 模板父级} }">
                <内容展示器></内容展示器>
              </复选框>
            </控制模板>
          </设置器.值>
        </设置器>
      </样式>
    </设置器.值>
  </设置器>
</样式>

交替项样式

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

给每个ListBoxItem一个AlternationIndex属性,允许您决定它在交替项序列中的编号。假设您设置AlternationCount为2,第一个ListBoxItem获得AlternationIndex为0,第二个获得1,第三个获得0,第四个获得1,以此类推。

<列表框 名称="产品列表" 边距="5" 显示成员路径="型号名称"
 交替计数="2">
  <列表框.项容器样式>
    <样式 目标类型="{x:Type 列表框项}">
      <设置器 属性="背景" 值="淡钢蓝色" />
      <设置器 属性="边距" 值="5" />
      <设置器 属性="内边距" 值="5" />
        <样式.触发器>
          <触发器 属性="项目控件.交替索引" 值="1">
            <设置器 属性="背景" 值="淡蓝色" />
          </触发器>
          <触发器 属性="是否已选中" 值="真">
            <设置器 属性="背景" 值="深红色" />
            <设置器 属性="前景色" 值="白色" />
            <设置器 属性="边框画笔" 值="黑色" />
            <设置器 属性="边框粗细" 值="1" />
          </触发器>
        </样式.触发器>
      </样式>
    </列表框.项容器样式>
</列表框>

样式选择器

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

以下是一个基本的双样式选择器:

public class 按类别产品样式选择器 : 样式选择器
{
    public override 样式 选择样式(object 项, 依赖对象 容器)
    {
        var 产品 = (产品)项;
        var 窗口 = 应用程序.当前.主窗口;
        if (产品.类别名称 == "旅行")
        {
            return (样式)窗口.查找资源("旅行产品样式");
        }
        else
        {
            return (样式)窗口.查找资源("默认产品样式");
        }
    }
}

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

public class 单条件高亮样式选择器 : 样式选择器
{
    public 样式 默认样式
    {
        get; set;
    }
    public 样式 高亮样式
    {
        get; set;
    }
    public string 要评估的属性
    {
        get; set;
    }
    public string 高亮值
    {
        get; set;
    }
    public override 样式 选择样式(object 项, 依赖对象 容器)
    {
        产品 产品 = (产品)项;
        
        // 使用反射获取要检测的属性
        类型 类型 = 产品.GetType();
        属性信息 属性 = 类型.GetProperty(要评估的属性);
        
        // 根据属性值决定是否应该高亮此产品
        if (属性.获取值(产品, null).ToString() == 高亮值)
        {
            return 高亮样式;
        }
        else
        {
            return 默认样式;
        }
    }
}

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

<窗口.资源>
  <样式 x:键="默认样式" 目标类型="{x:Type 列表框项}">
    <设置器 属性="背景" 值="淡黄色" />
    <设置器 属性="内边距" 值="2" />
  </样式>

  <样式 x:键="高亮样式" 目标类型="{x:Type 列表框项}">
    <设置器 属性="背景" 值="淡钢蓝色" />
    <设置器 属性="字体粗细" 值="粗体" />
    <设置器 属性="内边距" 值="2" />
  </样式>
</窗口.资源>

内联使用样式选择器:

<列表框 名称="产品列表" 水平内容对齐="拉伸">
  <列表框.项容器样式选择器>
    <local:单条件高亮样式选择器
      默认样式="{静态资源 默认样式}"
      高亮样式="{静态资源 高亮样式}"
      要评估的属性="类别名称"
      高亮值="旅行"
    >
    </local:单条件高亮样式选择器>
  </列表框.项容器样式选择器>
</列表框>

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

var 选择器 = 产品列表.项容器样式选择器;
产品列表.项容器样式选择器 = null;
产品列表.项容器样式选择器 = 选择器;

数据模板技术

每个ListBoxItem只能绑定一个字段,无法直接组合多个字段。要显示多个字段,需要使用数据模板。数据模板是一段XAML标记,它定义了应该如何显示绑定数据对象。两种类型的控件支持数据模板:

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

列表项是一个内容控件。

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

以下示例使用带圆角的边框包裹每个项,显示两片信息,并使用粗体格式化高亮型号:

<列表框 名称="产品列表" 水平内容对齐="拉伸">
  <列表框.项模板>
    <数据模板>
      <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色" 圆角="4">
        <网格 边距="3">
          <网格.行定义>
            <行定义></行定义>
            <行定义></行定义>
          </网格.行定义>

          <文本框 字体粗细="粗体"
           文本="{绑定 路径=型号编号}"></文本框>
          <文本框 网格.行="1"
           文本="{绑定 路径=型号名称}"></文本框>
        </网格>
      </边框>
    </数据模板>
  </列表框.项模板>
</列表框>

分离和重用模板

提取前一个示例的模板:

<窗口.资源>
  <数据模板 x:键="产品数据模板">
    <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
     圆角="4">
      <网格 边距="3">
        <网格.行定义>
          <行定义></行定义>
          <行定义></行定义>
        </网格.行定义>

        <文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}">
        </文本框>
        <文本框 网格.行="1" 文本="{绑定 路径=型号名称}">
        </文本框>
      </网格>
    </边框>
  </数据模板>
</窗口.资源>

现在,您可以将数据模板作为静态资源添加到列表中:

<列表框 名称="产品列表" 水平内容对齐="拉伸"
 项模板="{静态资源 产品数据模板}"></列表框>

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

<窗口.资源>
  <数据模板 数据类型="{x:Type local:产品}">
  </数据模板>
</窗口.资源>

使用高级模板

以下示例是一个更复杂的数据模板,包含图片和按钮:

<窗口.资源>
  <local:图片路径转换器 x:键="图片路径转换器"></local:图片路径转换器>
  <数据模板 x:键="产品模板">
    <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
     圆角="4">
      <网格 边距="3">
        <网格.行定义>
          <行定义></行定义>
          <行定义></行定义>
          <行定义></行定义>
        </网格.行定义>

        <文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}"></文本框>
        <文本框 网格.行="1" 文本="{绑定 路径=型号名称}"></文本框>
        <图片 网格.行="2" 网格.行跨距="2" 来源=
"{绑定 路径=产品图片路径, 转换器={静态资源 图片路径转换器}}">
        </图片>
      </网格>
    </边框>
  </数据模板>
</窗口.资源>

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

<数据模板>
  <网格 边距="3">
    <网格.列定义>
      <列定义></列定义>
      <列定义 宽度="自动"></列定义>
    </网格.列定义>
    <文本框 文本="{绑定 路径=类别名称}"></文本框>
    <按钮 网格.列="2" 水平对齐方式="右" 内边距="2"
      点击="查看按钮_点击" 标签="{绑定 路径=类别ID}">查看 ...</按钮>
  </网格>
</数据模板>

您可以在事件处理器中获取Tag属性:

private void 查看按钮_点击(object 发送者, 路由事件参数 e)
{
    var 按钮 = (按钮)发送者;
    var 类别ID = (int)按钮.标签;
    ...
}

当定义绑定时,省略Path属性,您可以获取整个数据对象:

<按钮 水平对齐方式="右" 内边距="1"
  点击="查看按钮_点击" 标签="{绑定}">查看 ...</按钮>

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

var 按钮 = (按钮)发送者;
var 产品 = (产品)按钮.标签;
产品列表.选中项 = 产品;

不同模板的实现

以不同方式呈现列表项有三种主要方法:

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

数据触发器示例:基于数据项的属性,设置模板元素的属性。例如,基于相应产品对象的CategoryName属性,可以更改包装每个列表项的自定义边框的背景。以下示例用红色文本突出显示工具目录中的产品:

<数据模板 x:键="默认模板">
  <数据模板.触发器>
    <数据触发器 绑定="{绑定 路径=类别名称}" 值="工具">
     <设置器 属性="列表框项.前景色" 值="红色"></设置器>
    </数据触发器>
  </数据模板.触发器>
  <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
   圆角="4">
    <网格 边距="3">
      <网格.行定义>
        <行定义></行定义>
        <行定义></行定义>
      </网格.行定义>
      <文本框 字体粗细="粗体"
       文本="{绑定 路径=型号编号}"></文本框>
      <文本框 网格.行="1"
       文本="{绑定 路径=型号名称}"></文本框>
    </网格>
  </边框>
</数据模板>

值转换器方法:

<边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色" 圆角="4"
 背景=
 "{绑定 路径=类别名称, 转换器={静态资源 类别转颜色转换器}">

模板选择器实现

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

public class 单条件高亮模板选择器 : 数据模板选择器
{
    public 数据模板 默认模板
    {
        get; set;
    }
    public 数据模板 高亮模板
    {
        get; set;
    }
    public string 要评估的属性
    {
        get; set;
    }
    public string 高亮值
    {
        get; set;
    }
    public override 数据模板 选择模板(object 项,
      依赖对象 容器)
    {
        产品 产品 = (产品)项;
        // 使用反射获取要检查的属性
        类型 类型 = 产品.GetType();
        属性信息 属性 = 类型.GetProperty(要评估的属性);
        // 根据属性值决定是否应该高亮此产品
        if (属性.获取值(产品, null).ToString() == 高亮值)
        {
            return 高亮模板;
        }
        else
        {
            return 默认模板;
        }
    }
}

以下是创建两个数据模板的标记:

<窗口.资源>
  <数据模板 x:键="默认模板">
    <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
      圆角="4">
      <网格 边距="3">
        <网格.行定义>
          <行定义></行定义>
          <行定义></行定义>
        </网格.行定义>
        <文本框
         文本="{绑定 路径=型号编号}"></文本框>
        <文本框 网格.行="1"
         文本="{绑定 路径=型号名称}"></文本框>
      </网格>
    </边框>
  </数据模板>
  
  <数据模板 x:键="高亮模板">
    <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
     背景="淡黄色" 圆角="4">
      <网格 边距="3">
        <网格.行定义>    
          <行定义></行定义>
          <行定义></行定义>
          <行定义></行定义>
        </网格.行定义>
        <文本框 字体粗细="粗体"
         文本="{绑定 路径=型号编号}"></文本框>
        <文本框 网格.行="1" 字体粗细="粗体"
         文本="{绑定 路径=型号名称}"></文本框>
        <文本框 网格.行="2" 字体样式="斜体" 水平对齐方式="右">
         *** 适合度假 ***</文本框>
      </网格>
    </边框>
  </数据模板>
</窗口.资源>    

以下是应用模板选择器的标记:

<列表框 名称="产品列表" 水平内容对齐="拉伸">
  <列表框.项模板选择器>
    <local:单条件高亮模板选择器
      默认模板="{静态资源 默认模板}"
      高亮模板="{静态资源 高亮模板}"
      要评估的属性="类别名称"
      高亮值="旅行"
    >
    </local:单条件高亮模板选择器>
  </列表框.项模板选择器>
</列表框>

模板与选择交互

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

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

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

<列表框 名称="产品列表" 水平内容对齐="拉伸">
  <列表框.项容器样式>
    <样式>
      <设置器 属性="控件.内边距" 值="0"></设置器>
      <样式.触发器>
        <触发器 属性="列表框项.是否已选中" 值="真">
          <设置器 属性="列表框项.背景" 值="深红色" />
        </触发器>
      </样式.触发器>
    </样式>
  </列表框.项容器样式>
</列表框>

然后,修改数据模板:

<数据模板>
  <网格 边距="0" 背景="白色">
    <边框 边距="5" 边框粗细="1"
     边框画笔="钢蓝色" 圆角="4"
     背景="{绑定 路径=背景, 相对源={
                             相对源
                             模式=查找上级,
                             上级类型={x:Type 列表框项}
                          }}" >
      <网格 边距="3">
        <网格.行定义>
          <行定义></行定义>
          <行定义></行定义>
        </网格.行定义>
        <文本框 字体粗细="粗体" 文本="{绑定 路径=型号编号}"></文本框>
        <文本框 网格.行="1" 文本="{绑定 路径=型号名称}"></文本框>
      </网格>
    </边框>
  </网格>
</数据模板>

示例2:选中项后显示详细信息:

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

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

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

这是简化版,尚未实现自动展开功能:

<数据模板>
  <边框 边距="5" 边框粗细="1" 边框画笔="钢蓝色"
   圆角="4">
    <堆栈面板 边距="3">
      <文本框 文本="{绑定 路径=型号名称}"></文本框>
      <堆栈面板>
        <文本框 边距="3" 文本="{绑定 路径=描述}"
         文本换行="自动" 最大宽度="250" 水平对齐方式="左"></文本框>
        <图片 来源="{绑定 路径=产品图片路径, 转换器={静态资源 图片路径转换器}}">
        </图片>
        <按钮 字体粗细="常规" 水平对齐方式="右" 内边距="1"
         标签="{绑定}">查看详情...</按钮>
      </堆栈面板>
    </堆栈面板>
  </边框>
</数据模板>

最内层的StackPanel包含仅对选中项显示的内容,因此为此容器设置样式:

<堆栈面板>
  <堆栈面板.样式>
    <样式>
      <样式.触发器>
        <数据触发器
          绑定="{绑定 路径=是否已选中, 相对源={
                             相对源
                             模式=查找上级,
                             上级类型={x:Type 列表框项}
                          }}"
          值="假">
          <设置器 属性="堆栈面板.可见性" 值="折叠" />
        </数据触发器>
      </样式.触发器>
    </样式>
  </堆栈面板.样式>
</堆栈面板>

在此示例中,需要使用DataTrigger而不是普通触发器,因为所需的属性在祖先元素中(ListBoxItem),唯一访问它的方法是使用数据绑定表达式。

改变项布局

通过设置ListBox的ItemsPanelTemplate属性为任何派生自System.Windows.Controls.Panel的类,可以改变项布局。

以下示例使用WrapPanel改变项布局:

<列表框 边距="7,3,7,10" 名称="产品列表"
 项模板="{静态资源 项模板}"
 滚动查看器.水平滚动条可见性="禁用">
  <列表框.项面板>
    <项面板模板>
      <环绕面板></环绕面板>
    </项面板模板>
  </列表框.项面板>
</列表框>

``

还必须设置ScrollViewer.HorizontalScrollBarVisibility附加属性为Disabled。这确保ScrollViewer从不使用水平滚动条。

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

组合框高级用法
---

默认情况下,ComboBox是只读的。当设置IsReadOnly属性为false且IsEditable属性为true时,选择框会变成文本框,允许您输入任何文本。

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

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

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

如果IsEditable属性为true,选择框显示它的逐字表示。简单地说,就是对项目调用ToString()。通常,它会显示类名的全称。

解决此问题最简单的方法是设置TextSearch.TextPath附加属性,指示组合框应该使用的内容。

<组合框 可编辑="真" 只读="真" 文本搜索.文本路径="型号名称" ...>

标签: WPF

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。