Search code examples
wpfstylesinotifypropertychangeddatatriggerivalueconverter

Updating a wpf style having infinite possible conditions


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.


Solution

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