当WPF DataGrid绑定的值改变时,如何在单元格中突出显示?

14

我有一个DataGrid,它的数据每隔15秒就会被后台进程刷新一次。如果任何数据发生变化,我想运行一个动画来将更改值的单元格突出显示为黄色,然后再淡回白色。我通过以下方式实现了这个功能:

我创建了一个样式,并在Binding.TargetUpdated事件触发时使用它。

<Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
    <Style.Triggers>
        <EventTrigger RoutedEvent="Binding.TargetUpdated">
            <BeginStoryboard>
                <Storyboard>
                    <ColorAnimation Duration="00:00:15"
                        Storyboard.TargetProperty=
                            "(DataGridCell.Background).(SolidColorBrush.Color)" 
                        From="Yellow" To="Transparent" />
                </Storyboard>
            </BeginStoryboard>
        </EventTrigger>
    </Style.Triggers>
</Style>

然后将其应用到我想要突出显示的列,如果值发生了改变

<DataGridTextColumn Header="Status" 
    Binding="{Binding Path=Status, NotifyOnTargetUpdated=True}" 
    CellStyle="{StaticResource ChangedCellStyle}" />

如果数据库中状态字段的值发生更改,单元格就会像我想要的那样以黄色突出显示。 但是,存在一些问题。

首先,在数据网格最初加载时,整个列都会以黄色突出显示。这是有道理的,因为首次加载所有值,所以您希望TargetUpdated触发。我相信我可以找到一种方法来阻止这种情况,但这是一个相对较小的问题。

真正的问题是,如果对网格进行了排序或过滤,则整个列都将以黄色突出显示。 我猜我不明白为什么排序会导致TargetUpdated触发,因为数据没有改变,只是显示方式改变了。

因此,我的问题是(1)如何停止初始加载和排序/过滤上的此行为,以及(2)我是否走在正确的轨道上,这是一个好方法吗?我应该提到这是MVVM。


为了对您提出的解决方案做出替代安排,以下是两个问题:1)您是否希望列表很大?(在这种情况下指大于等于100个项目);2)您是否预计列表中的项目数量经常发生变化? - Luis Quijada
这实际上是一个帮助台队列应用程序,它列出交易中的错误,并允许人们接管特定的错误并将其标记为已解决。这些值不应经常更改,我希望它们在生产中每天少于100个错误。 - Paul Abbott
好的,我认为当单元格内容改变时平滑地显示不同单元格的背景色是一个很有趣的想法,例如当任务状态或任务的受托人发生变化时...但是我对此进行了一些研究,我没有找到一种只通过编写Xaml就可以实现这一点的方法。我的建议是在内存中编写您的域对象集合,并且每次从服务器检索列表时,实现实用程序方法来执行刚刚检索到的数据与已经在数据网格中的数据进行比较并执行样式更改。 - Luis Quijada
你可以尝试将其设置为模板列,并直接附加到基础控件上的绑定(而不是使用列绑定机制)。这就是我能想到的为什么它会在排序或筛选时触发的所有内容。另一个选择可能是检查ViewModel上的通知事件,看看它是否在排序/筛选期间被触发,以及哪些属性可能会触发它。 - Jacob Proffitt
@PaulAbbott 你有没有检查答案?对你来说是否可接受,还是我需要寻找其他方法(因为我发现这是WPF中最有趣的问题之一)? - Kylo Ren
3个回答

0

由于TargetUpdated事件仅基于UI更新。无论更新如何进行,所有的DataGridCells在排序时都保持不变,只有其中的数据根据排序结果发生了更改,因此会触发TargetUpdated事件。因此,我们必须依赖于WPF应用程序的数据层来实现这一点。为了实现这一点,我根据一个变量重置了DataGridCell的绑定,以跟踪是否在数据层进行更新。

XAML:

<Window.Resources>
    <Style x:Key="ChangedCellStyle" TargetType="DataGridCell">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="DataGridCell">
                    <ControlTemplate.Triggers>
                        <EventTrigger RoutedEvent="Binding.TargetUpdated">
                            <BeginStoryboard>
                                <Storyboard>
                                    <ColorAnimation Duration="00:00:04" Storyboard.TargetName="myTxt"
                                        Storyboard.TargetProperty="(DataGridCell.Background).(SolidColorBrush.Color)" 
                                        From="Red" To="Transparent" />
                                </Storyboard>
                            </BeginStoryboard>
                        </EventTrigger>                           
                    </ControlTemplate.Triggers>

                    <TextBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Transparent"
                             Name="myTxt" >
                        <TextBox.Style>
                            <Style TargetType="TextBox">
                                <Style.Triggers>
                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="True">
                                        <Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text,NotifyOnSourceUpdated=True,NotifyOnTargetUpdated=True}" />
                                    </DataTrigger>
                                    <DataTrigger Binding="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=DataContext.SourceUpdating}" Value="False">
                                        <Setter Property="Text" Value="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},Path=Content.Text}" />                                            
                                    </DataTrigger>                                       
                                </Style.Triggers>                                    
                            </Style>
                        </TextBox.Style>
                    </TextBox>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</Window.Resources>

<StackPanel Orientation="Vertical">
    <DataGrid ItemsSource="{Binding list}" CellStyle="{StaticResource ChangedCellStyle}" AutoGenerateColumns="False"
              Name="myGrid"  >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name" Binding="{Binding Name}" />
            <DataGridTextColumn Header="ID" Binding="{Binding Id}" />
        </DataGrid.Columns>
    </DataGrid>
    <Button Content="Change Values" Click="Button_Click" />
</StackPanel>

窗口的Code Behind(DataContext对象):

 public MainWindow()
    {
        list = new ObservableCollection<MyClass>();
        list.Add(new MyClass() { Id = 1, Name = "aa" });
        list.Add(new MyClass() { Id = 2, Name = "bb" });
        list.Add(new MyClass() { Id = 3, Name = "cc" });
        list.Add(new MyClass() { Id = 4, Name = "dd" });
        list.Add(new MyClass() { Id = 5, Name = "ee" });
        list.Add(new MyClass() { Id = 6, Name = "ff" });   
        InitializeComponent();
    }

    private ObservableCollection<MyClass> _list;
    public ObservableCollection<MyClass> list
    {
        get{ return _list; }
        set{   
            _list = value;
            updateProperty("list");
        }
    }
   
    Random r = new Random(0);
    private void Button_Click(object sender, RoutedEventArgs e)
    {

        int id = (int)r.Next(6);
        list[id].Id += 1;
        int name = (int)r.Next(6);
        list[name].Name = "update " + r.Next(20000);
    }

模型类:MyClassupdateProperty()方法中发出任何通知并且更新已通知到UI后,将SourceUpdating属性设置为true(这将通过DataTrigger将绑定设置为通知TargetUpdate),然后将SourceUpdating设置为false(这将重置绑定以不通过DataTrigger通知TargetUpdate)。

public class MyClass : INotifyPropertyChanged
{
    private string name;
    public string Name
    {
        get { return name; }
        set { 
            name = value;updateProperty("Name");
        }
    }

    private int id;
    public int Id
    {
        get { return id; }
        set 
        { 
            id = value;updateProperty("Id");
        }
    }

    //the vaiable must set to ture when update in this calss is ion progress
    private bool sourceUpdating;
    public bool SourceUpdating
    {
        get { return sourceUpdating; }
        set 
        { 
            sourceUpdating = value;updateProperty("SourceUpdating");
        }
    }        

    public event PropertyChangedEventHandler PropertyChanged;
    public void updateProperty(string name)
    {
        if (name == "SourceUpdating")
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }
        }
        else
        {
            SourceUpdating = true;               
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(name));
            }               
           SourceUpdating = false;                
        }
    }

}

输出:

两个同时更新/按钮被点击一次:

update1

许多同时更新/按钮被点击多次:

update2

更新后,当进行排序或筛选时,绑定知道它不必调用TargetUpdated事件。只有在源集合的更新正在进行时,绑定才会重置以调用TargetUpdated事件。同时,这也解决了初始着色问题。
然而,由于逻辑仍然存在一些缺陷,对于编辑器TextBox,逻辑基于更复杂的数据类型和UI逻辑,代码将变得更加复杂,对于初始绑定重置,整个行都会被动画化,因为TargetUpdated被触发了一整行的所有单元格。

0
我建议在您的ViewModel中为每个属性使用OnPropertyChanged,并更新相关的UI元素(启动动画或其他操作),这样您的问题就会得到解决(在加载、排序、过滤等情况下),用户也可以看到哪个单元格发生了变化!

0

对于第(1)个点,我的想法是通过编写代码来处理。一种方法是处理DataGridTextColumn的TargetUpdated事件,并在旧值与新值之间进行额外检查,仅在值不同时应用样式;另一种方法是根据代码中的其他事件(如初始加载、刷新等)以编程方式创建和删除绑定。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接