当绑定值更改时,突出显示WPF DataGrid中的单元格 [英] Highlighting cells in WPF DataGrid when the bound value changes

查看:299
本文介绍了当绑定值更改时,突出显示WPF DataGrid中的单元格的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个DataGrid,它的数据每15秒刷新一个后台进程。如果任何数据发生变化,我想运行一个动画,突出显示具有黄色变化值的单元格,然后淡出回到白色。我通过执行以下操作让它工作:



我在Binding.TargetUpdated上创建了一个带事件触发器的样式

 < Style x:Key =ChangedCellStyleTargetType =DataGridCell> 
< Style.Triggers>
< EventTrigger RoutedEvent =Binding.TargetUpdated>
< BeginStoryboard>
< Storyboard>
< ColorAnimation Duration =00:00:15
Storyboard.TargetProperty =
(DataGridCell.Background)。(SolidColorBrush.Color)
From =Yellow To =透明/>
< / Storyboard>
< / BeginStoryboard>
< / EventTrigger>
< /Style.Triggers>
< / Style>

然后将其应用于我想突出显示的列,如果值更改

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

如果数据库中状态字段的值更改,则单元格将以黄色显示,就像我想要的。但是,有一些问题。



首先,当数据网格最初加载时,整个列以黄色突出显示。这是有道理的,因为所有的值都是第一次加载,所以你期望TargetUpdated被触发。我确定有一些方法可以阻止这一点,但这是一个相对较小的一点。



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



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

解决方案

由于 TargetUpdated 基于UI更新的事件。发生更新无关紧要。在排序所有 DataGridCells 的地方,只有数据根据排序结果被更改,因此 TargetUpdated 被提升。因此我们必须依赖于WPF应用程序的数据层。为了实现这一点,我已经重新设置了 DataGridCell 的绑定,基于一个变量,如果更新在数据层发生,那么这种变量就是这样。



XAML:

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

< TextBox Horizo​​ntalAlignment =StretchVerticalAlignment =StretchBackground =Transparent
Name =myTxt>
< TextBox.Style>
< Style TargetType =TextBox>
< Style.Triggers>
< DataTrigger Binding ={Binding RelativeSource = {RelativeSource Mode = TemplatedParent},Path = DataContext.SourceUpdating}Value =True>
< Setter Property =TextValue ={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 =TextValue ={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 =NameBinding ={Binding Name}/>
< DataGridTextColumn Header =IDBinding ={Binding Id}/>
< /DataGrid.Columns>
< / DataGrid>
< Button Content =更改值Click =Button_Click/>
< / StackPanel>

后面的代码(Window的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>列表
{
get {return _list; }
set {
_list = value;
updateProperty(list);
}
}

随机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);
}

模型类: PropertyUpdate 属性设置为true(通过 DataTrigger将绑定设置为通知 TargetUpdate )当 MyClass updateProperty()方法和更新后的任何通知正在进行中通知 UI SourceUpdating 设置为false(然后将绑定重置为不通知 TargetUpdate 通过 DataTrigger )。

  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);
}
}

//当这个calss中的更新是离子进度时,vaiable必须设置为ture
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;
}
}

}

输出:



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





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




更新后,在排序或过滤发生时,绑定知道它不必调用 TargetUpdated
事件。只有当源集合的更新正在进行时,
绑定被重置才能调用 TargetUpdated 事件。此外,初始着色问题也得到处理。


然而,由于逻辑还有一些排序编辑器 TextBox 的逻辑是基于更复杂的数据类型和UI逻辑,代码将变得更加复杂,对于初始绑定重置,整行动画为 TargetUpdated 被提升为一行的所有单元格。


I have a DataGrid that has its data refreshed by a background process every 15 seconds. If any of the data changes, I want to run an animation that highlights the cell with the changed value in yellow and then fade back to white. I sort-of have it working by doing the following:

I created a style with event trigger on 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>

And then applied it to the columns I wanted to highlight if a value changes

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

If the value for the status field in the database changes, the cell highlights in yellow just like I want. But, there are a few problems.

First, when the data grid is initially loaded, the entire column is highlighted in yellow. This makes sense, because all of the values are being loaded for the first time so you would expect TargetUpdated to fire. I'm sure there is some way I can stop this, but it's a relatively minor point.

The real problem is the entire column is highlighted in yellow if the grid is sorted or filtered in any way. I guess I don't understand why a sort would cause TargetUpdated to fire since the data didn't change, just the way it is displayed.

So my question is (1) how can I stop this behavior on initial load and sort/filter, and (2) am I on the right track and is this even a good way to do this? I should mention this is MVVM.

解决方案

Since TargetUpdated is truly only UI update based event. It doesn't matter how update in happening. While sorting all the DataGridCells remain at their places only data is changed in them according to sorting result hence TargetUpdatedis raised. hence we have to be dependent on data layer of WPF app. To achieve this I've reset the Binding of DataGridCell based on a variable that kind of trace if update is happening at data layer.

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 object of Window):

 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);
    }

Model Class: SourceUpdating property is set to true(which set the binding to notify TargetUpdate via a DataTrigger) when any notification is in progress for MyClass in updateProperty() method and after update is notified to UI, SourceUpdating is set to false(which then reset the binding to not notify TargetUpdate via a DataTrigger).

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;                
        }
    }

}

Outputs:

Two simultaneous Updates/ Button is clicked once :

Many simultaneous Updates/ Button is clicked many times :

SO after update, when sorting or filtering is happening the bindings know that it doesn't have to invoke the TargetUpdated event. Only when the update of source collection is in progress the binding is reset to invoke the TargetUpdated event. Also initial coloring problem is also get handled by this.

However as the logic still has some sort comings as for editor TextBox the logic is based on with more complexity of data types and UI logic the code will become more complex also for initial binding reset whole row is animated as TargetUpdated is raised for all cells of a row.

这篇关于当绑定值更改时,突出显示WPF DataGrid中的单元格的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆