如果绑定 属性 具有特定值,如何用圆圈覆盖单元格的内容?

How to override contents of a cell with a circle if the bound property has a certain value?

我正在使用 DataGrid 来显示资产价格,所以我有很多行和列。例如,我这样显示当前价格:

<DataGridTextColumn Width="50" SortMemberPath="Price" Binding="{Binding Path=Price}">
    <DataGridTextColumn.Header>
        <TextBlock Text="{Binding Path=Price}"/>
    </DataGridTextColumn.Header>
</DataGridTextColumn>

有时如果值无效,我只显示 -。如果绑定 属性 的值为 -.

,我想要做的是显示一个圆形

我可以通过添加一个圆圈来做到这一点,该圆圈的可见性绑定到一个新的 属性 以检查价格是否无效,而上述文本显示则相反。但问题是,这将要求我为每个 属性 创建新的绑定,而我试图避免这种情况。

这是否可以通过触发器实现,或者有更好的方法吗?

文本值转换器

一种方法是创建一个 returns 其参数的值转换器,如果值不可用 (-)。

public class ValueNotAvailableConverter : IValueConverter
{
   public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
   {
      return (value is string str) && str == "-" ? parameter : value;
   }

   public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
   {
      throw new InvalidOperationException("This is a one-way conversion.");
   }
}

然后,您可以将此转换器用于您的绑定,并指定一个符合您要求的字形作为转换器参数。要使其正常工作,请确保您使用的字体包含字形。在我的例子中,Segoe UI 包含一个实心圆,这可能正是您想要的。

<DataGrid ItemsSource="{Binding YourItemsSource}" AutoGenerateColumns="False">
   <DataGrid.Resources>
      <local:ValueNotAvailableConverter x:Key="ValueNotAvailableConverter"/>
   </DataGrid.Resources>
   <DataGrid.Columns>
      <!-- ...other columns. -->
      <DataGridTextColumn Width="50" SortMemberPath="Price" Binding="{Binding Price, Converter={StaticResource ValueNotAvailableConverter}, ConverterParameter=●}">
         <DataGridTextColumn.Header>
            <TextBlock Text="{Binding Path=Price}"/>
         </DataGridTextColumn.Header>
      </DataGridTextColumn>
      <!-- ...other columns. -->
   </DataGrid.Columns>
   <!-- ...other markup. -->
</DataGrid>

带数据触发器的模板列

同样可以使用模板列、样式和数据触发器。

<DataGrid ItemsSource="{Binding YourItemsSource}" AutoGenerateColumns="False">
   <DataGrid.Columns>
      <DataGridTemplateColumn Width="50" SortMemberPath="Price">
         <DataGridTemplateColumn.Header>
            <TextBlock Text="{Binding Path=DataContext.Price, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
         </DataGridTemplateColumn.Header>
         <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
               <TextBlock>
                  <TextBlock.Style>
                     <Style TargetType="{x:Type TextBlock}">
                        <Setter Property="Text" Value="{Binding Price}"/>
                        <Style.Triggers>
                           <DataTrigger Binding="{Binding Price}" Value="-">
                              <Setter Property="Text" Value="●"/>
                           </DataTrigger>
                        </Style.Triggers>
                     </Style>
                  </TextBlock.Style>
               </TextBlock>
            </DataTemplate>
         </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
</DataGrid>

带有数据模板选择器的模板列

如果您需要最大的灵活性,您可以将模板列与数据模板结合使用 select 或者。请注意,数据网格模板列中存在限制,例如传递给列的 itemnull,这需要 workaround。由于模板列的数据上下文是 ItemsSource 的整个数据项,您必须在此处检查 Price 属性。

public class PriceNotAvailableTemplateSelector : DataTemplateSelector
{
   public string PriceAvailableTemplateKey { get; set; }

   public string PriceNotAvailableTemplateKey { get; set; }

   public override DataTemplate SelectTemplate(object item, DependencyObject container)
   {
      if (container is ContentPresenter contentPresenter &&
          contentPresenter.Parent is DataGridCell dataGridCell)
      {
         if (dataGridCell.DataContext is YourDataType data && data.Price == "-")
            return contentPresenter.FindResource(PriceNotAvailableTemplateKey) as DataTemplate;

         return contentPresenter.FindResource(PriceAvailableTemplateKey) as DataTemplate;
      }

      return base.SelectTemplate(item, container);
   }
}

现在您可以在价格可用和不可用时创建不同的数据模板。

<DataGrid ItemsSource="{Binding YourItemsSource}" AutoGenerateColumns="False">
   <DataGrid.Resources>
      <local:PriceNotAvailableTemplateSelector x:Key="PriceNotAvailableTemplateSelector"
                                               PriceAvailableTemplateKey="PriceAvailableTemplate"
                                               PriceNotAvailableTemplateKey="PriceNotAvailableTemplate"/>
      <DataTemplate x:Key="PriceAvailableTemplate">
         <TextBlock x:Name="ValueAvailable"  Text="{Binding}"/>
      </DataTemplate>
      <DataTemplate x:Key="PriceNotAvailableTemplate">
         <Ellipse x:Name="ValueNotAvailable" Width="5" Height="5" Fill="Red"/>
      </DataTemplate>
   </DataGrid.Resources>
   <DataGrid.Columns>
      <DataGridTemplateColumn Width="50"
                              SortMemberPath="Price"
                              CellTemplateSelector="{StaticResource PriceNotAvailableTemplateSelector}">
         <DataGridTemplateColumn.Header>
            <TextBlock Text="{Binding Path=DataContext.Price, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
         </DataGridTemplateColumn.Header>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
</DataGrid>

自动数据模板选择

如您所见,数据模板select或方法非常死板和不灵活。类似的方法是 select 按类型自动生成数据模板。但是,要使其工作,您必须为 price 创建一个专用类型,并为 no price 创建一个类型。您可以参考 this related question 了解更多信息,因为 DataGrid 也有它的怪癖,正如您所期望的那样。

带有数据模板和触发器的模板列

最后,一个更 hackish 的解决方案是在数据模板中同时显示 TextBlock 和替代元素,并根据 Price 值更改它们的可见性。

<DataGrid ItemsSource="{Binding YourItemsSource}" AutoGenerateColumns="False">
   <DataGrid.Columns>
      <DataGridTemplateColumn Width="50"
                              SortMemberPath="Price">
         <DataGridTemplateColumn.Header>
            <TextBlock Text="{Binding Path=DataContext.Price, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
         </DataGridTemplateColumn.Header>
         <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
               <StackPanel>
                  <TextBlock x:Name="ValueAvailable" Text="{Binding Price}"/>
                  <Ellipse x:Name="ValueNotAvailable" Visibility="Collapsed" Width="5" Height="5" Fill="Red"/>
               </StackPanel>
               <DataTemplate.Triggers>
                  <DataTrigger Binding="{Binding Price}" Value="-">
                     <Setter TargetName="ValueAvailable" Property="Visibility" Value="Collapsed"/>
                     <Setter TargetName="ValueNotAvailable" Property="Visibility" Value="Visible"/>
                  </DataTrigger>
               </DataTemplate.Triggers>
            </DataTemplate>
         </DataGridTemplateColumn.CellTemplate>
      </DataGridTemplateColumn>
   </DataGrid.Columns>
</DataGrid>