您当前的位置: 首页 > 

寒冰屋

暂无认证

  • 0浏览

    0关注

    2286博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

寒冰屋 发布时间:2020-01-08 21:00:38 ,浏览量:0

目录

介绍

先决条件

使用代码

转换器

数据网格绑定

按钮绑定

结论

  • 下载源代码和项目文件-397.4 KB
介绍

这个小应用程序演示了一种基于DataGrid单元格内容动态修改单元格样式的方法。动态样式的一个示例:如果单元格中的值变为负数,则可能需要将单元格的背景色更改为红色。尤其强调了Model-View-ViewModel(MVVM)模式来演示此样式。我提供完整的Visual Studio源代码和其他项目文件。可以从本文头部下载该项目的工作示例。在应用程序中,每当用户单击[CHANGE VALUES]时, DataGrid中的单元格就会填充1到9的新随机整数。单元格的背景颜色会根据单元格的新内容而变化。

先决条件

该解决方案是使用Visual Studio 2019社区版16.3.9版和.NET 4.7.2构建的。它还需要Expression.Blend.Sdk版本1.0.2,但是此SDK与项目文件打包在一起。

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

使用代码

当您生成并运行代码时,将出现以下窗口:

注意:单元格中的整数是由随机生成器生成的,每次运行代码时以及每次单击[CHANGE VALUES]按钮时都会不同。我意识到颜色有点扎眼,但目的是清楚地显示当DataGrid单元格内容更改时如何更改单元格的背景色。它不旨在符合Microsoft关于用户界面样式的平淡准则。

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

每个单元格包含一个介于1到9之间的随机整数。每次单击[CHANGE VALUES]时,这些数字都会随机变化。单元格的背景颜色也会发生变化,如下所述。

在上面的示例中,单元格的背景色设置为“First” 列中所有单元格的系统颜色Colors.LightGoldenrodYellow,而不考虑单元格的内容。对于“Second”列至“Eighth”列中的所有单元格,背景颜色设置如下:

如果单元格包含一个数1,2或3,背景颜色设置为系统颜色Colors.LightGreen。

如果单元格包含一个数4,5或6,背景颜色设置为系统颜色Colors.LightSteelBlue。

如果单元格包含一个数7,8或9,背景颜色设置为系统颜色Colors.LightSalmon。

对于“First” 列中的单元格,字体大小设置为20,字体样式设置为斜体。当然,这可以通过更简单的方法来完成,但是这里的目的是显示Converter可以如何影响其他属性(例如字体设置)的更改。

首先,我们需要知道Converter是如何与项目联系在一起的:

看一下标签下的XAML主文件:MainWindow.xaml。在这里,转换器被列为带有Key的资源:"ColorConverter":

现在在DataGrid定义样式的位置向下看一点:


            

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

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


    
        
            
                
                
            
        
    

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


关于单元格的注意事项:单元格将传递至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)

通常, ConvertBac方法不执行任何操作,但必须提供该方法才能满足该接口。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(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.
}

首先,我们需要验证传递给 Converter的object[]是否由DataGridCell和DataRow组成,同时为这两个对象指定名称:

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

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

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

content现在将DataGrid单元格中的整数传递给Converter。

注意:为了使这种方法起作用,DataGrid列标题必须与列名称相同。这通常不是问题。

接着, Converter得到Column的DisplayIndex,如果索引是零(最左边的列)时,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);
}

接着,在列索引大于零的情况下,Converter将返回Colors.LightGreen(content小于4),Colors.LightSalmon(content大于6)和Colors.LightSteelBlue(值4,5以6):

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

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

return new SolidColorBrush(Colors.LightSteelBlue);
数据网格绑定

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

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

和:


    

另外,在MainWindow.xaml中的DataGrid定义中:

ItemsSource="{Binding Path=ValuesArray}"

这会将DataGrid单元格中的值绑定到的相应DataTable ValuesArray单元格的值。换言之,View和ViewModel之间的耦合非常松散。正如MVVM模式的原则要求的那样,ViewModel对View中的任何控件都没有直接的知识。

按钮绑定

同样,这两个按钮通过绑定松散地连接到ViewModel中的方法。单击[CHANGE VALUES]时,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("First", rnd.Next(1, 10));
  tableRow.SetField("Second", rnd.Next(1, 10));
  tableRow.SetField("Third 3", rnd.Next(1, 10));
  tableRow.SetField("Fourth", rnd.Next(1, 10));
  tableRow.SetField("Fifth", rnd.Next(1, 10));
  tableRow.SetField("Sixth", rnd.Next(1, 10));
  tableRow.SetField("Seventh", rnd.Next(1, 10));
  tableRow.SetField("Eighth", rnd.Next(1, 10));

  ValuesArray.Rows.Add(tableRow);
 }
}

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

结论

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

关注
打赏
1665926880
查看更多评论
立即登录/注册

微信扫码登录

0.0779s