I have a DataGrid
with rows representing a host I'm doing pings to and a column called Lost
which represents lost ICMP packets which over time increases in value. I have the whole INotifyPropertyChanged
thing down and I'm seeing value increase. What I want to do is write a Style
that'll change a row's background color from white to dark red progressively relative to the Lost
column's value.
I would like, if it were possible, to write a Trigger
or DataTrigger
with a setter value set to a ValueConverter
which would calculate the color needed, but so far I've been unsuccessful in writing a style that will update every time a Lost cell's value changes. I only see a difference in color when I load a new data context and switch back (just for testing).
Here's what I've tried:
<Style x:Key="DownStyle" TargetType="{x:Type DataGridRow}">
<Setter Property="Background" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</Style>
I can't see this working with a DataTrigger
since you have to specify a value anyways and the values are infinite (or Int32.MaxValue I guess), really. Although I also tried specifying a ValueConverter
for the value property and that didn't work either.
Btw, I want to try to avoid code-behind if possible.
Edit: Rick: I had tried doing something like:
<Style x:Key="DownStyle" TargetType="{x:Type DataGridRow}">
<Style.Triggers>
<DataTrigger Binding="{Binding Converter={StaticResource LostColumnValueConverter}}" Value="Somenumber">
<Setter Property="Background" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</DataTrigger>
</Style.Triggers>
</Style>
I think I understand the need to have the Trigger
actually bind to something that's going to be changing (in this case I feel I'm forced to also use a ValueConverter
to get the column's value, is there a more direct way?), but even if I do this, what do I specify as the DataTrigger
's value?
Edit:
So in my case I went ahead and did the following (currently this would only modify the TextBlock
's background):
<DataGridTemplateColumn Header="Lost" Width="Auto">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Lost, NotifyOnTargetUpdated=True}">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="Binding.TargetUpdated">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0" Storyboard.TargetProperty="Background">
<DiscreteObjectKeyFrame KeyTime="0" Value="{Binding Converter={StaticResource BackgroundConverter}}" />
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Which to me seems right, but only works once for some reason. I added a seperate TargetUpdated
event handler right on the TextBlock
definition to see if the event was indeed being called on every change and it is.
Something must be missing on my EventTrigger
. Probably something to do with the Storyboard
.
Anyways, this all seems incredibly verbose for something so simple, so I went ahead and went with the code-behind route.
I suppose Triggers and DataTriggers can't help in this case because datagrigger's value is unstable. And worse, it's not dependency property and you can't bind to it. It seems to me the better way is to use EventTrigger.
<Window.Resources>
<BeginStoryboard x:Key="bsbPing">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" Storyboard.TargetProperty="FontSize" To="{Binding Path=PingValue}" />
</Storyboard>
</BeginStoryboard>
</Window.Resources>
<Grid>
<StackPanel>
<TextBox Name="txbPingValue" Text="{Binding Path=PingValue}">
<TextBox.Triggers>
<EventTrigger RoutedEvent="TextBox.TextChanged">
<EventTrigger.Actions>
<StaticResource ResourceKey="bsbPing" />
</EventTrigger.Actions>
</EventTrigger>
</TextBox.Triggers>
</TextBox>
<Button Name="btnPing" Click="btnPing_Click">Ping</Button>
</StackPanel>
</Grid>
and code:
public partial class Window7 : Window
{
public Ping MyPing { get; set; }
public Window7()
{
InitializeComponent();
MyPing = new Ping { PingValue = 20.0 };
this.DataContext = MyPing;
}
private void btnPing_Click(object sender, RoutedEventArgs e)
{
MyPing.PingValue += 10;
}
}
public class Ping : INotifyPropertyChanged
{
private double pingValue;
public double PingValue
{
get
{
return pingValue;
}
set
{
pingValue = value;
NotifyPropertyChanged("PingValue");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
}