目录
介绍
先决条件
使用代码
转换器
数据网格绑定
按钮绑定
结论
- 下载源代码和项目文件-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不参与这些操作。