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

Posted

技术标签:

【中文标题】如果绑定属性具有特定值,如何用圆圈覆盖单元格的内容?【英文标题】:How to override contents of a cell with a circle if the bound property has a certain value? 【发布时间】:2021-10-10 08:08:01 【问题描述】:

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

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

有时如果值无效,我只显示-。如果绑定属性的值为-,我想要做的是显示一个圆形。

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

这也许可以通过触发器实现,还是有更好的方法来做到这一点?

【问题讨论】:

【参考方案1】:

文本值转换器

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

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>

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

如果您需要最大的灵活性,可以将模板列与数据模板选择器结合使用。请注意,数据网格模板列存在限制,例如传递给列的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>

自动数据模板选择

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

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

最后,一个更老套的解决方案是在数据模板中同时显示 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>

【讨论】:

非常感谢,我正在使用您的最后一种方法并且它有效,但是当显示圆圈时,它似乎会缩放整行的高度。不知道为什么,因为我在另一列中使用具有相同宽度和高度 8 的相同形状,而他们不这样做。我将 Collapsed 更改为 Hidden 但结果仍然相同。是因为使用了 StackPanel 吗?

以上是关于如果绑定属性具有特定值,如何用圆圈覆盖单元格的内容?的主要内容,如果未能解决你的问题,请参考以下文章

如何用最少 30 英里半径的圆圈覆盖美国地图?

如何为绑定的DataGridView的单元格的标签设置值?

如果数据库表格单元格的内容是一个列表,我如何检查该列表是不是包含 T-SQL 中的特定值?

excel表格中如何用一个值查询表格中的相关值

Excle数据透视如何用含有单元格的数据来创建数据透视

在excel中如何用vba来实现查找特定的字符串?