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

How can I change the DataGrid Foreground color based on user input?

我有一个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 之间。

I don't know how I can change these values based on user input. The user can specify different ranges for each column.

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

下面是一个IMultiValueConverter的例子,它允许绑定多个byte属性,其中第一个是要比较的原始data值,其余代表的边界每个范围。这些范围的 Brushes 作为 parameter(集合)传递并且不可绑定。如果您希望它们也可绑定,它们也将通过 values 数组传递。此转换器假定所有值都是 byte,而 parameterBrush 的集合。此外,必须有 distinct 范围边界(无重复)和相等数量的 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];
}