【问题标题】:WPF DataGrid Cells Being Randomly Highlighted When Passed Through A ConverterWPF DataGrid 单元格在通过转换器时随机突出显示
【发布时间】:2024-04-25 20:05:02
【问题描述】:

我试图在用户输入的文本框中突出显示以给定值开头的所有单元格。我查看了几个示例,生成了以下代码,在您开始垂直滚动之前,它可以正常工作。

首先我们有 XAML,它利用了多重绑定:

<DataGrid.CellStyle>
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="Background">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource CellColor}">
                    <Binding RelativeSource="{RelativeSource Mode=Self}"/>
                    <Binding Mode="OneWay" ElementName="contactFilterTextBox" Path="Text"/>
                </MultiBinding>
            </Setter.Value>
        </Setter>
    </Style>
</DataGrid.CellStyle>

接下来是我创建的类,它继承自 IMultiValueConverter 类:

public class ColorBasedOnFilterConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        System.Windows.Media.SolidColorBrush cellColor = Brushes.White;
        DataGridCell cellToAnalyze = (DataGridCell)values[0];
        TextBlock cellContents = (TextBlock)cellToAnalyze.Content;
        if (cellContents != null)
        {
            string cellValue = (string)cellContents.Text;
            string filterValue = values[1].ToString();
            if (!(String.IsNullOrEmpty(filterValue)))
            {
                if (cellValue.ToUpper().StartsWith(filterValue.ToUpper()))
                {
                    cellColor = Brushes.LightSalmon;
                }
            }
        }
        return cellColor;
    }
    public object[] ConvertBack(
        object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

最初,它的工作方式与我期望的一样,如下所示:

那么问题是当我滚动时,显然选择了如下图所示的值?

我对 WPF 很陌生,当然我做错了什么,但我对它是什么感到困惑。任何帮助将不胜感激。

【问题讨论】:

    标签: c# wpf xaml datagrid


    【解决方案1】:

    默认情况下 Virtualizing 为 dataGrid 开启,这意味着容器,即 DataGridRow 只会为可见项目生成。

    VirtualizationMode 的默认值为 Recycling,这意味着当您滚动时,已移出焦点的单元格将用于托管新的可见项目为什么你看到不正确的项目被着色,因为它可能使用了同一行。

    要克服这个问题,您可以将 VirtualizationMode 设置为标准,以便始终生成新行来托管新的可见项目。

    <DataGrid VirtualizingStackPanel.VirtualizationMode="Standard">
       .......
    </DataGrid>
    

    【讨论】:

    • 这似乎是朝着正确方向迈出的一步。现在的问题是文本框值的绑定仅在转换器第一次运行时才有效。如中所示,突出显示适用于显示的网格,但是一旦涉及需要突出显示的另一行,该函数就会失败并出现错误:无法将“MS.Internal.NamedObject”类型的对象转换为“System.String”类型'。如果我查看转换器参数的值数组中分配的值,它是“{DependencyProperty.UnsetValue}”我是否需要为此注册一个依赖属性,如果需要,如何?
    • 这里的转换器存在问题 - string filterValue = (string)values[1];。将其替换为此 - string filterValue = values[1].ToString();DependencyProperty.UnsetValue 表示未设置绑定值。在盲目转换值之前,您应该始终在转换器中检查此值。
    • 对不起,我真的不清楚。我应该进行逻辑测试以确定该值是否已设置。在这种情况下,我知道未设置绑定值,但我不明白为什么。转换器第一次通过可见单元格时,过滤器值被设置并且它应该突出显示,但是当转换器再次运行时(比如在滚动之后),它不再是(没有修改文本中的条目盒子)。这是我不明白的部分。
    • ElementName 在绑定中查找遍历可视树的元素。因此,这两个控件都应该附加到 Visual 树,但是当您滚动网格时,由于虚拟化,dataGrid 将销毁不在视图中的项目。因此,第二个绑定作为 Unset 值传递,因为无法找到 textBox,因为单元格不再是 Visual 树的一部分。此外,如果您看到第一个单元格出现错误。只需执行 ToString() ,因为无论如何您对看不见的项目不感兴趣。希望现在清楚了。
    • 那么,现在回答你原来的问题。是现在解决了还是在找别的?
    【解决方案2】:

    根据我(诚然非常有限)的经验,由于 RowVirtualization,在 CodeBehind 中更改 DataGridCell 的属性经常会出错。我尽可能避免它像瘟疫一样。更改单元格的背景颜色将是属于该类别的一个很好的例子。

    另一种(可以说更好)选项是根本不在 CodeBehind 中设置颜色,而是使用 DependencyProperty 来确定单元格是否符合条件,然后使用触发器设置背景颜色(或其他) .你可以从this answer 和从this answer 到我最近问的一个问题中获得更多可能有用的信息,特别是如果你想“找到”所有细胞如果它们没有被虚拟化,突出显示。这是一个代码摘录,其目标与原始问题非常相似,可以帮助您入门(主要是根据提供的链接改编的代码),以防它在某个时候对某人有所帮助。

    Xaml:

    <DataGrid local:DataGridTextSearch.SearchValue="{Binding ElementName=txtSearch, Path=Text, UpdateSourceTrigger=PropertyChanged}">
    <DataGrid.Resources>
    <local:SearchValueConverter x:Key="SearchValueConverter"/>
    <Style TargetType="{x:Type DataGridCell}">
        <Setter Property="local:DataGridTextSearch.IsTextMatch">
            <Setter.Value>
                <MultiBinding Converter="{StaticResource SearchValueConverter}">
                    <Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
                    <Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
                </MultiBinding>
            </Setter.Value>
        </Setter>
        <Style.Triggers>
            <Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
                <Setter Property="Background" Value="Orange" />
                <Setter Property="Tag" Value="1" />
            </Trigger>
        </Style.Triggers>
    </Style>
    </DataGrid.Resources>
    </DataGrid>
    

    依赖属性:

    Public NotInheritable Class DataGridTextSearch
    Private Sub New()
    End Sub
    Public Shared ReadOnly SearchValueProperty As DependencyProperty = DependencyProperty.RegisterAttached("SearchValue", GetType(String), GetType(DataGridTextSearch), New FrameworkPropertyMetadata(String.Empty, FrameworkPropertyMetadataOptions.[Inherits]))
    Public Shared Function GetSearchValue(obj As DependencyObject) As String
        Return DirectCast(obj.GetValue(SearchValueProperty), String)
    End Function
    Public Shared Sub SetSearchValue(obj As DependencyObject, value As String)
        obj.SetValue(SearchValueProperty, value)
    End Sub
    Public Shared ReadOnly IsTextMatchProperty As DependencyProperty = DependencyProperty.RegisterAttached("IsTextMatch", GetType(Boolean), GetType(DataGridTextSearch), New UIPropertyMetadata(False))
    Public Shared Function GetIsTextMatch(obj As DependencyObject) As Boolean
        Return CBool(obj.GetValue(IsTextMatchProperty))
    End Function
    Public Shared Sub SetIsTextMatch(obj As DependencyObject, value As Boolean)
        obj.SetValue(IsTextMatchProperty, value)
    End Sub
    End Class
    

    还有转换器(这绝对可以充实,但你明白了):

    Public Class SearchValueConverter
    Implements IMultiValueConverter
    Public Function Convert(values() As Object, targetType As Type, parameter As Object, culture As Globalization.CultureInfo) As Object Implements IMultiValueConverter.Convert
        Dim cellText As String = If(values(0) Is Nothing, String.Empty, values(0).ToString())
        Dim searchText As String = TryCast(values(1), String)
    
        If Not String.IsNullOrEmpty(searchText) AndAlso Not String.IsNullOrEmpty(cellText) Then
            If cellText.ToLower().Contains(searchText.ToLower()) Then
                Return True 
            Else
                Return False
            End If
        End If
        Return False
    End Function
    Public Function ConvertBack(value As Object, targetTypes() As Type, parameter As Object, culture As Globalization.CultureInfo) As Object() Implements IMultiValueConverter.ConvertBack
        Return Nothing
    End Function
    End Class
    

    【讨论】: