当绑定值更改时,突出显示WPF DataGrid中的单元格 [英] Highlighting cells in WPF DataGrid when the bound value changes
问题描述
我在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 HorizontalAlignment =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 TargetUpdated
is 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 theTargetUpdated
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屋!