在 WPF 的虚拟化数据网格中重置单元格颜色



我有一个简单的WPF应用程序,它在数据网格中显示大约30-35,000行数据,如果用户更改任何单元格值,则应通过更改其背景颜色来突出显示该单元格。列在运行时根据数据生成。考虑到它所包含的数据量,虚拟化数据网格是必须的。这是我的数据网格:

<DataGrid x:Name="dgPropertyData" HeadersVisibility="Column" ColumnHeaderHeight="25" 
BorderThickness="0" AutoGenerateColumns="false" 
HorizontalGridLinesBrush="#FFC7C5C5" 
VerticalGridLinesBrush="#FFC7C5C5" 
MinRowHeight="20" CanUserResizeRows="False" 
CanUserDeleteRows="False" CanUserReorderColumns="False" 
EnableRowVirtualization="true" 
AutoGeneratedColumns="dgPropertyData_AutoGeneratedColumns"                      
CellEditEnding="dgPropertyData_CellEditEnding">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="FontWeight" Value="Bold"/>
</Style>
</DataGrid.ColumnHeaderStyle>
</DataGrid>

我正在代码隐藏方法中更改单元格背景颜色:

private void dgPropertyData_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
string oldValue = (e.Row.Item as BindableDynamicDictionary)[e.Column.Header as string] as string;
string newValue = (e.EditingElement as System.Windows.Controls.TextBox).Text;
if ((oldValue != null && oldValue.CompareTo(newValue) != 0) || (oldValue == null && string.IsNullOrEmpty(newValue) == false))
{         
(e.Column.GetCellContent(e.Row).Parent as DataGridCell).Background = Brushes.Orange;
IsCellEdited = true;
}
}

我的问题:当我编辑单元格时,背景会很好地变化以通知值已更改。直到....我滚动网格视图。在滚动时, 编辑过的单元格会被擦除,错误的单元格会被绘制(如果有的话)。

我通过以下问题得到了一些想法:

  1. 更改 WPF 数据网格中单个单元格的颜色
  2. WPF 数据网格虚拟化 设置单元格颜色时出现的问题
  3. 虚拟化导致行的背景颜色不正确

但是还没有完全找到适合我的目的的解决方案。作为WPF世界的新手,我经常遇到如何解决这个问题的思维障碍。我知道丢失单元格背景颜色的问题是因为回收行,如果我禁用虚拟化,性能下降对于我拥有的数据量来说是无法忍受的。

与其在视图中处理CellEditEnding事件,不如处理行视图模型中的逻辑,这在您的情况下似乎是一个BindableDynamicDictionary

在单元格中显示其值的属性的资源库中,可以执行比较(当前在视图的代码隐藏中执行的比较),并设置IsCellEdited属性以指示单元格是否已修改。

然后,在视图中,应使用具有绑定到此属性的DataTriggerCellStyle,并根据单元格的值更改单元格的背景颜色。

此设计模式称为模型-视图-视图模型 (MVVM),建议在开发基于 XAML 的 UI 应用程序时使用这种模式。这是有原因的。你刚刚遇到了其中一个。这种逻辑不应该在视图中处理,正如你已经注意到的,如果你禁用 UI 虚拟化,它甚至不起作用。

正如我最初的帖子中提到的,我仍然是WPF和MVVM的新手,所以我仍然经常使用代码隐藏语言。有一天,我将在纯 XAML 中实现它。但在那之前,我提出了代码隐藏解决方案。我知道,很多人不会喜欢它,但这不仅仅是被卡住。

我有一本字典来跟踪编辑的单元格

Dictionary<int, List<int>> editedCells = new Dictionary<int, List<int>>(); // Row No, List<Column No>

我在更改背景时将编辑的单元格位置保存在此字典中:

private void dgPropertyData_CellEditEnding(object sender, DataGridCellEditEndingEventArgs e)
{
string oldValue = (e.Row.Item as BindableDynamicDictionary)[e.Column.Header as string] as string;
string newValue = (e.EditingElement as System.Windows.Controls.TextBox).Text;
if ((oldValue != null && oldValue.CompareTo(newValue) != 0) || (oldValue == null && string.IsNullOrEmpty(newValue) == false))
{         
(e.Column.GetCellContent(e.Row).Parent as DataGridCell).Background = Brushes.Orange;
IsCellEdited = true;
// Mark this cell has been edited.
var columnIndex = e.Column.DisplayIndex;
var rowIndex = dgPropertyData.Items.IndexOf(e.Row.Item);
if (editedCells.ContainsKey(rowIndex) == false)
editedCells.Add(rowIndex, new List<int>());
editedCells[rowIndex].Add(columnIndex);                                   
}
}

当用户滚动数据网格时,一些行将被隐藏,一些行将被显示。隐藏行时,我清除编辑的单元格背景,并在显示时重新着色编辑的单元格。

private void dgPropertyData_LoadingRow(object sender, DataGridRowEventArgs e)
{
var rowIndex = dgPropertyData.Items.IndexOf(e.Row.Item);
if(editedCells.ContainsKey(rowIndex) == true)
{
editedCells[rowIndex].ForEach( columnIndex =>
{
var column = dgPropertyData.Columns[columnIndex];
(column.GetCellContent(e.Row).Parent as DataGridCell).Background = Brushes.Orange;
});                
}
}
private void dgPropertyData_UnloadingRow(object sender, DataGridRowEventArgs e)
{
var rowIndex = dgPropertyData.Items.IndexOf(e.Row.Item);
if (editedCells.ContainsKey(rowIndex) == true)
{
editedCells[rowIndex].ForEach(columnIndex =>
{
var column = dgPropertyData.Columns[columnIndex];
(column.GetCellContent(e.Row).Parent as DataGridCell).ClearValue(DataGridCell.BackgroundProperty);
});
}
}

对我来说效果很好。

最新更新