I have a simple application: There's a DataGrid, and every time the user adds a new row and finished editing, the row turns yellow. In the background a thread tries to save the data and if this worked, it sets the property IsSaved to true. In the view the row then changes to a transparent background.
All this works fine. But now I want to use a storyboard for a nice fade out of the yellow color. That's almost working - but now the color transition is playing whenever a row in my DataGrid gets out of sight (for example when scrolling, or when resizing the window) and comes back into sight (scrolling back to the row).
It should only play once when the property is set to true. Any ideas?
<DataGrid.Resources>
<Storyboard x:Key="AnimateYellowToTransparent">
<ColorAnimation Storyboard.TargetProperty="Background.(SolidColorBrush.Color)" From="#FFF4F496" Duration="0:0:1" RepeatBehavior="1x" />
</Storyboard>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaved, UpdateSourceTrigger=PropertyChanged}" Value="false">
<Setter Property="Background" Value="#FFF4F496"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaved, UpdateSourceTrigger=PropertyChanged}" Value="true">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Saved" Storyboard="{StaticResource AnimateYellowToTransparent}" />
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
The code for the Property:
public bool IsSaved {
get {
return _isSaved; }
set
{
if (value != this._isSaved)
{
_isSaved = value;
OnPropertyChanged();
}
}
}
In fact, the OnPropertyChanged() is only called when the property is actually changed, as expected, so no events are fired from this side.
In my understanding, the DataTriggers with the UpdateSourceTrigger=PropertyChanged should only execute when the binded property actually fired the respective event. But they don't. They are getting invoked when the DataRow is displayed.
Any help is greatly appreciated, and many thanks in advance for your time reading this.
CodePudding user response:
Notes: I used a translator because of my bad English. I ask for your understanding.
The virtualization of DataGrid causes animation to repeat. You can turn off virtualization, but it can degrade performance, so I wrote the following example.
Disable virtualizing:
VirtualizingPanel.IsVirtualizing="False"
Example (XAML):
<Grid>
<DataGrid ItemsSource="{Binding Source={x:Static local:MainWindow.Obs}}" AutoGenerateColumns="False" Background="White" Margin="0,0,0,20" VirtualizingPanel.IsVirtualizing="False">
<DataGrid.Resources>
<Storyboard x:Key="AnimateYellowToTransparent" Completed="Storyboard_Completed">
<ColorAnimation Storyboard.TargetProperty="Background.(SolidColorBrush.Color)" From="#FFF4F496" Duration="0:0:1" RepeatBehavior="1x" />
</Storyboard>
</DataGrid.Resources>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow">
<Style.Triggers>
<DataTrigger Binding="{Binding IsSaved, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static local:Saved.False}">
<Setter Property="Background" Value="#FFF4F496"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsSaved, UpdateSourceTrigger=PropertyChanged}" Value="{x:Static local:Saved.True}">
<DataTrigger.EnterActions>
<BeginStoryboard Name="Saved" Storyboard="{StaticResource AnimateYellowToTransparent}"/>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</DataGrid.RowStyle>
<DataGrid.Columns>
<DataGridTextColumn Header="Text" Binding="{Binding Text}"/>
</DataGrid.Columns>
</DataGrid>
<Button Content="All Save" VerticalAlignment="Bottom" HorizontalAlignment="Stretch" Click="Button_Click" />
</Grid>
Example (CS):
namespace WpfApp1
{
public enum Saved
{
False,
True,
Completed
}
public class Data : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public string? Text { get; set; }
private Saved issaved;
public Saved IsSaved
{
get => issaved;
set
{
if (issaved != value)
{
issaved = value;
NotifyPropertyChanged(nameof(IsSaved));
}
}
}
}
public partial class MainWindow : Window
{
public static ObservableCollection<Data> Obs = new();
public MainWindow()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
var search = Obs.Where(p => p.IsSaved == Saved.False);
foreach (var item in search)
{
item.IsSaved = Saved.True;
}
}
private void Storyboard_Completed(object sender, EventArgs e)
{
var search = Obs.Where(p => p.IsSaved == Saved.True);
foreach (var item in search)
{
item.IsSaved = Saved.Completed;
}
}
}
}
Enter the contents in DataGrid, and click the button to change the IsSaved property.
I think it's a rather ugly code, but it works well.