65.9K
CodeProject 正在变化。 阅读更多。
Home

MVVM 项目中的动态 DataGrid 单元格样式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.33/5 (5投票s)

2019 年 11 月 18 日

CPOL

6分钟阅读

viewsIcon

21167

downloadIcon

1238

在 MVVM 项目中动态更改 DataGrid 单元格样式

引言

这个小程序演示了一种根据单元格内容动态修改 DataGrid 单元格样式的方法。动态样式的一个例子是:如果单元格中的值变为负数,您可能希望将单元格的背景颜色更改为红色。此样式以模型-视图-视图模型 (MVVM) 模式为重点进行了演示。我在此处提供了完整的 Visual Studio 源代码和其他项目文件 此处。项目的可运行示例可从 此处 下载。在应用程序中,每次用户单击 [更改值] 时,DataGrid 中的单元格都会填充 1 到 9 之间的新的随机整数。单元格的背景颜色会根据单元格的新内容而变化。

必备组件

该解决方案使用 Visual Studio 2019 Community Edition(版本 16.3.9)和 .NET 4.7.2 构建。它还需要 Expression.Blend.Sdk 版本 1.0.2,但此 SDK 已随项目文件一起打包。

假定读者对 C# WPF 项目和 MVVM 模式有基本了解。

Using the Code

构建并运行代码后,将出现以下窗口

注意:单元格中的整数由随机生成器生成,每次运行代码时以及每次单击 [更改值] 按钮时都会有所不同。我意识到颜色有点刺眼,但目的是清楚地展示 DataGrid 单元格的背景颜色在内容更改时是如何改变的。它无意遵循微软对用户界面样式的平淡指南。

动态单元格样式是通过一个 MultiValueConverter 完成的。此 Converter 是项目 View 中名为 CellColorConverter.cs 的文件的一部分。这些转换器要求将一个对象数组传递给它们,转换器将基于该数组进行处理。在我们的例子中,这个对象数组将包含两个对象:一个 DataGridCell 和包含该单元格的 DataGrid 中的 DataRow

每个单元格包含一个 1 到 9 之间的随机整数。每次单击 [更改值] 时,这些数字都会随机变化。单元格的背景颜色也会根据下面的详细信息而变化。

在上面的示例中,"First" 列中所有单元格的背景颜色都设置为系统颜色 Colors.LightGoldenrodYellow,而与单元格内容无关。对于 "Second" 列到 "Eighth" 列中的所有单元格,背景颜色设置如下:

如果单元格包含数字 123,背景颜色将设置为系统颜色 Colors.LightGreen

如果单元格包含数字 456,背景颜色将设置为系统颜色 Colors.LightSteelBlue

如果单元格包含数字 789,背景颜色将设置为系统颜色 Colors.LightSalmon

对于 "First" 列中的单元格,字体大小设置为 20,字体样式设置为斜体。当然,这可以通过更简单的方式实现,但这里的目的是展示 Converter 如何影响字体设置等其他属性的更改。

首先,我们需要了解 Converter 如何与项目关联。

查看主 XAML 文件: MainWindow.xaml,在 <Window.Resources> 标签下。在这里,转换器被列为一个资源,其 Key 为 "ColorConverter"

<local:CellColorConverter x:Key="ColorConverter" />

现在向下看一点,DataGrid 样式定义所在的位置。

        <Style x:Key="BlueDataGridStyle" TargetType="{x:Type DataGrid}">
            <Setter Property="CellStyle" Value="{DynamicResource BlueDataGridCellStyle}" />

.................................

BlueDataGridCellStyle 中的单元格背景颜色定义如下:

             <Setter Property="Background">
                <Setter.Value>
                    <MultiBinding Converter="{StaticResource ColorConverter}">
                        <MultiBinding.Bindings>
                            <Binding RelativeSource="{RelativeSource Self}" />
                            <Binding Path="Row" />
                        </MultiBinding.Bindings>
                    </MultiBinding>
                </Setter.Value>
            </Setter>

在这里,您可以看到两个对象被传递给了 Converter,首先是单元格本身,然后是包含该单元格的行。

<Binding RelativeSource="{RelativeSource Self}" />
<Binding Path="Row" />

关于单元格的说明:传递给 Converter 的单元格不带其内容。您可能会在 Converter 中尝试访问单元格的内容,如下所示:Cell.Content,这在语法上是正确的,但它将始终返回 null。稍后将详细介绍这一点。

因此,当系统需要渲染单元格的背景时,它会发现需要将单元格及其行传递给 Converter,而 Converter 将返回用于背景的颜色。

转换器

现在是时候看看这个非常重要的 Converter 了。任何 MultiValueConverter 都必须遵守 IMultiValueConverter 接口规定的约定。此接口规定 Converter 代码应包含两个方法:

object Convert(object[] values, Type targetType, 
               object parameter, System.Globalization.CultureInfo culture)

object[] ConvertBack(object value, Type[] targetType, 
                     object parameter, System.Globalization.CultureInfo culture)

通常,ConvertBack 方法不做任何事情,但必须提供以满足接口。Convert(...) 方法的第一个参数 object[] 将由正在渲染的单元格和包含该单元格的 DataGrid 行组成。此方法的其他参数在此处未使用。

这是 Converter 的完整 Convert(...) 方法:

  public object Convert(object[] values, Type targetType, 
                        object parameter, System.Globalization.CultureInfo culture)
  {
   if (values[0] is DataGridCell cell && values[1] is DataRow row)
   {
    try
    {
     string columnName = (string)cell.Column.Header;
     int content = row.Field<int>(columnName); // Header must be same as column name
     int columnIndex = cell.Column.DisplayIndex;

     if (columnIndex == 0)
     {
      cell.FontStyle = FontStyles.Italic;
      cell.FontWeight = FontWeights.Bold;
      cell.FontSize = 20;
      return new SolidColorBrush(Colors.LightGoldenrodYellow);
     }

     if (content < 4)
     {
      return new SolidColorBrush(Colors.LightGreen);
     }

     if (content > 6)
     {
      return new SolidColorBrush(Colors.LightSalmon);
     }

     return new SolidColorBrush(Colors.LightSteelBlue);
    }
    catch (Exception)
    {
     return new SolidColorBrush(Colors.Black); // Error! An Exception was thrown
    }
   }
   return new SolidColorBrush(Colors.DarkRed); // Error! object[] is invalid.
  }

首先,我们需要验证传递给 Converterobject[] 是否确实是由 DataGridCellDataRow 组成的,并同时为这两个对象指定名称。

if (values[0] is DataGridCell cell && values[1] is DataRow row)

由于我们无法直接访问单元格的 content,因此我们从与该单元格对应的行的 Field 获取它。

     string columnName = (string)cell.Column.Header;
     int content = row.Field<int>(columnName); // Header must be same as column name

content 现在包含传递给 ConverterDataGrid 单元格中的整数。

注意:为了使此方法奏效,DataGrid 列标题必须与列名相同。这通常不是问题。

接下来,Converter 获取 ColumnDisplayIndex,如果索引为零(最左边的列),Converter 会对单元格字体进行一些更改,并返回系统颜色 Colors.LightGoldenrodYellow 作为单元格背景。这就是将整列设置为相同的背景颜色的方法。

     int columnIndex = cell.Column.DisplayIndex;

     if (columnIndex == 0)
     {
      cell.FontStyle = FontStyles.Italic;
      cell.FontWeight = FontWeights.Bold;
      cell.FontSize = 20;
      return new SolidColorBrush(Colors.LightGoldenrodYellow);
     }

接下来,对于列索引大于零的情况,当 content 小于 4 时,Converter 返回 Colors.LightGreen;当 content 大于 6 时,返回 Colors.LightSalmon;对于值 456,返回 Colors.LightSteelBlue

     if (content < 4)
     {
      return new SolidColorBrush(Colors.LightGreen);
     }

     if (content > 6)
     {
      return new SolidColorBrush(Colors.LightSalmon);
     }

     return new SolidColorBrush(Colors.LightSteelBlue);

DataGrid 绑定

我们如何将整数放入 DataGrid 的单元格中?如果您查看 ViewModel (MainViewModel.cs),您会看到一个名为 ValuesArrayDataTable,其列名与 DataGrid 的列名相同。在 View (MainWindow.xaml) 中,您将看到窗口的 DataContext 设置为: MainViewModel:

   xmlns:viewmodel="clr-namespace:DataGridProject.ViewModel"

    <Window.DataContext>
        <viewmodel:MainViewModel />
    </Window.DataContext>

同样,在 MainWindow.xamlDataGrid 的定义中:

 ItemsSource="{Binding Path=ValuesArray}"

这将 DataGrid 单元格中的值绑定到 DataTable ValuesArray 中相应单元格的值。换句话说,ViewViewModel 之间的耦合非常松散。ViewModelView 中的任何控件都没有直接的了解,这符合 MVVM 模式的原则。

按钮绑定

同样,这两个按钮通过绑定与 ViewModel 中的方法松散耦合。当您单击 [更改值] 时,通过绑定将执行 ViewModel 中的以下方法:

  private void ChangeValues()
  {
   DataRow tableRow;
   int row;
   Random rnd = new Random();

   ValuesArray.Rows.Clear();

   for (row = 0; row < 16; row++)
   {
    tableRow = ValuesArray.NewRow();

    tableRow.SetField<int>("First", rnd.Next(1, 10));
    tableRow.SetField<int>("Second", rnd.Next(1, 10));
    tableRow.SetField<int>("Third 3", rnd.Next(1, 10));
    tableRow.SetField<int>("Fourth", rnd.Next(1, 10));
    tableRow.SetField<int>("Fifth", rnd.Next(1, 10));
    tableRow.SetField<int>("Sixth", rnd.Next(1, 10));
    tableRow.SetField<int>("Seventh", rnd.Next(1, 10));
    tableRow.SetField<int>("Eighth", rnd.Next(1, 10));

    ValuesArray.Rows.Add(tableRow);
   }
  }

ViewModel 将整数保存在 DataTable ValuesArray 中。它“不知道”这些值通过绑定传输到 View

结论

我花了一些时间才弄清楚如何使用 MultiValueConverter。我的目标是将所有与 DataGrid 单元格样式相关的操作都放在 View 中执行,并让 ViewModel 不参与这些操作。

我希望这篇文章能对你们中的一些人有所帮助。

历史

  • 2019 年 11 月 18 日:初版
© . All rights reserved.