如何根据用户输入更改 DataGrid 前景色?

Posted

技术标签:

【中文标题】如何根据用户输入更改 DataGrid 前景色?【英文标题】:How can I change the DataGrid Foreground color based on user input? 【发布时间】:2021-12-03 10:27:12 【问题描述】:

我有一个DataGridDataGrid 中有 12 列。这些列中的每一列都代表 0-255 之间的 byte 值。我想根据用户输入的范围为它们着色。

我不知道如何根据用户输入更改这些值。用户可以为每列指定不同的范围。下面是我手动实现的应用程序。如何将此应用绑定到用户登录。

转换器

public class DataGridColorConverter : IValueConverter

    public object Convert(
        object value, Type targetType,
        object parameter, CultureInfo culture)
    
        byte data = (byte)value;

        if (data <= 30)
            return 0;
        else if (data <= 60)
            return 1;
        else if (data <= 90)
            return 2;
        else
            return 3;
    

    public object ConvertBack(
        object value, Type targetType,
        object parameter, CultureInfo culture)
    
        throw new NotImplementedException();
    

Xaml

<UserControl.Resources>
    <my:DataGridColorConverter x:Key="DGCConverter"/>
</UserControl.Resources>

<DataGrid Name="MyDataGrid"
          Grid.Row="1"
          AutoGenerateColumns="False" 
          ItemsSource="Binding JobsCollectionView , IsAsync=True"
          VirtualizingStackPanel.VirtualizationMode="Recycling"
          IsReadOnly="True"
          Height="480">
    <DataGrid.Columns>
        <DataGridTextColumn Header="ID" Binding="Binding ID"/>
        <DataGridTextColumn Header="RTR" Binding="Binding RTR"/>
        <DataGridTextColumn Header="IDE" Binding="Binding IDE"/>
        <DataGridTextColumn Header="DLC" Binding="Binding DLC"/>
        <DataGridTextColumn Header="BYTE-0" Binding="Binding Byte0"/>
        <DataGridTextColumn Header="BYTE-1" Binding="Binding Byte1"/>
        <DataGridTextColumn Header="BYTE-2" Binding="Binding Byte2"/>
        <DataGridTextColumn Header="BYTE-3" Binding="Binding Byte3"/>
        <DataGridTextColumn Header="BYTE-4" Binding="Binding Byte4"/>
        <DataGridTextColumn Header="BYTE-5" Binding="Binding Byte5"/>
        <DataGridTextColumn Header="BYTE-6" Binding="Binding Byte6"/>
        <DataGridTextColumn Header="BYTE-7" Binding="Binding Byte7">
            <DataGridTextColumn.ElementStyle>
                <Style TargetType="x:Type TextBlock">

                    <Setter Property="Foreground" Value="Green" />
                    <Style.Triggers>
                        <DataTrigger Binding="Binding Byte7,Converter=StaticResource DGCConverter" Value="1">
                            <Setter Property="Foreground" Value="Yellow" />
                        </DataTrigger>
                        <DataTrigger Binding="Binding Byte7,Converter=StaticResource DGCConverter" Value="2">
                            <Setter Property="Foreground" Value="Orange" />
                        </DataTrigger>
                        <DataTrigger Binding="Binding Byte7,Converter=StaticResource DGCConverter" Value="3">
                            <Setter Property="Foreground" Value="Red" />
                        </DataTrigger>
                    </Style.Triggers>
                </Style>
            </DataGridTextColumn.ElementStyle>
        </DataGridTextColumn>
        <DataGridTextColumn Header="TIME" Binding="Binding Time, StringFormat=\0:dd.MM.yy HH:mm:ss\"/>
    </DataGrid.Columns>
</DataGrid>

注意:我正在使用 MVVM。

注意2:我没有共享用户可以输入值范围的字段,以免在XAML中拥挤。

例如,用户希望Blue 介于 0-100 之间,Red 介于 100-150 之间,Green 介于 150-255 之间。

【问题讨论】:

【参考方案1】:

我不知道如何根据用户输入更改这些值。用户可以为每一列指定不同的范围。

如果用户可以指定范围,例如通过Sliders 或TextBoxes,需要绑定这些值才能对更改做出反应。让我们假设这些值映射到的画笔不需要绑定(还)。 IValueConverter 只能绑定一个值。您可以改为创建一个IMultiValueConverter,正如其名称所暗示的那样,它可以绑定多个值。具体转换器的外观有多种选择,具体取决于您的视图、视图模型和其他要求。

以下是IMultiValueConverter 的示例,它允许绑定多个byte 属性,其中第一个是要比较的原始data 值,其余的代表每个范围的边界。这些范围的Brushes 作为parameter(集合)传递并且不可绑定。如果您希望它们也可绑定,它们也将通过 values 数组传递。此转换器假定所有值都是bytes,而parameterBrushes 的集合。此外,必须有 不同 范围边界(无重复)和相等数量的 Brushes(可以重复),否则不存在 1:1 映射(抛出异常)。如果没有绑定值或没有可能的映射,则不会返回画笔。

public class DataGridColorConverter : IMultiValueConverter

   public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
   
      if (values == null)
         return Binding.DoNothing;

      var byteValues = values.Cast<byte>().ToList();
      var brushes = ((IEnumerable)parameter).Cast<Brush>().ToList();

      var data = byteValues[0];
      var bounds = byteValues.Skip(1).Distinct().ToList();

      if (bounds.Count != brushes.Count)
         throw new ArgumentException("The number of distinct stops must be equal to the number of stop brushes.");

      foreach (var (boundary, brush) in bounds.Zip(brushes))
      
         if (data <= boundary)
            return brush;
      

      return Binding.DoNothing;
   

   public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
   
      throw new NotImplementedException();
   

在 XAML 中,您可以使用新的转换器通过 MultiBinding 调整您的样式。

<Style TargetType="x:Type TextBlock">
   <Setter Property="Foreground">
      <Setter.Value>
         <MultiBinding Converter="StaticResource DataGridColorConverter">
            <MultiBinding.Bindings>
               <!-- ...binds the current data context, your BYTE 7. -->
               <Binding/>
               <!-- ...these bindings may vary depending on the source for binding, view model, text block, ... -->
               <Binding Path="MyBlueRangeEndProperty"/>
               <Binding Path="MyRedRangeEndProperty"/>
               <Binding Path="MyGreenRangeEndProperty"/>
            </MultiBinding.Bindings>
            <MultiBinding.ConverterParameter>
               <!-- ...this array could also be shared (moved to resource and refernce via StaticResource ...). -->
               <x:Array Type="x:Type Brush">
                  <SolidColorBrush Color="Blue"/>
                  <SolidColorBrush Color="Red"/>
                  <SolidColorBrush Color="Green"/>
               </x:Array>
            </MultiBinding.ConverterParameter>
         </MultiBinding>
      </Setter.Value>
   </Setter>
</Style>

如上所述,有很多选项可以实现这一点。这对于您的用例来说应该足够了,并且如果颜色数组对于所有列都相同或单独定义,您甚至可以共享颜色数组。


更新关于Zip 编译错误的评论。此扩展方法并非在所有目标框架上都可用。这同样适用于 C# 语言版本和元组解构。但是,在这些情况下,您可以改用简单的 for 循环。

for (var i = 0; i < bounds.Count; i++)

   if (data <= bounds[i])
      return brushes[i];

【讨论】:

太好了,我得出了接近这个答案的结论。但我心里还是有问号。问号不见了。谢谢。 我在转换函数中的 foreach 中遇到了一些错误。 “方法'Zip'没有重载需要1个参数”和“不包含隐式类型解构变量('boundart')或('brush')的类型”我不明白问题是什么。 我解决了这个问题。我写了一个 for 循环,它返回颜色和值而不是 foreach。据我了解,这是 C# 版本的情况。我应该编辑答案吗? @saklanmaz 谢谢。查看我的最新编辑,我为旧框架和 C# 语言版本添加了解释和替代方案。 另外;如果有非常密集的数据流,我认为这个过程很慢。如果 Binding.DoNothing 返回,它会在 UI 中混淆。如果使用 Brushes.Transparent,则背景没有问题。

以上是关于如何根据用户输入更改 DataGrid 前景色?的主要内容,如果未能解决你的问题,请参考以下文章

使用背景色更改 TextBlock 前景色

根据 JasperReports 中的条件更改文本字段数据颜色(前景色)

所选行的 WPF DataGrid RowStyle 不改变背景和前景色

如何更改一个Dialogbar上控件的前景色和背景色?

如何更改 ListView 控件中单个子项的前景色?

如何在视图中更改图像的前景色