Adding basic logic to WPF data binding

Ever tried to bind a control to a data source in WPF? Usually the procedure looks this way: You use or create a control (let's say a ListView), create a data source and point the control to the data source.

But there are some circumstances when you need to add certain behavior to your control based on the data that it's bound to, like marking it red when the value is larger than certain value. .NET framework provides us with the DataTrigger trigerring mechanism, but the limitation of this mechanism is that it could only be triggered when the data is equal to a certain value. It cannot accept other comparisons. You could always do this in the code-behind of your UI, but this won't be a good practice in general (with some exceptions).

 

Now, what I want to create is a ListView, that will contain Label Controls in it's ListViewItem and when the value of the data item is larger than 100 I want to set the foreground to Red brush.

 

Let's start with a basic ListView:

        <ListView Name="listView">
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Value1" />
                    <GridViewColumn Header="Value2" />
                </GridView>
            </ListView.View>
        </ListView>

 

Next, let's say that we want to bind to it a List of our custom classe

 

class OurCustomClass
{
    public int Value1 { get; set; }
    public string Value2 { get; set; }
}
 
List<OurCustomClass> data = new List<OurCustomClass>();
Random rand = new Random();
for(int i = 0; i < 100; i++)
    data.Add(new OurCustomClass {Value1 = rand.Next(1, 1000), Value2 = rand.Next(3000, 30000).ToString()});

:

listView.ItemsSource = data;

 

What .NET Framework enables us to do is adding a DataTrigger do a DataTemplate:

 

<ListView>
    <ListView.Resources>
        <DataTemplate x:Key="OurTemplate">
            <Label Name="val1Label" />
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=Value1}"  Value="SomeValue">
                    <Setter ... />
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>
    </ListView.Resources>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Value1" />
            <GridViewColumn Header="Value2" />
        </GridView>
    </ListView.View>
</ListView>

 

Here as you see the DataTrigger checks if the bound data is equal to the value defined in the Value property, if yes, the trigger is fired and the setters alter the content and attributes of the item. But when we need to check, let's say, if the value is grater than 100 we have to use a small trick.

The binding mechanism provides something called Converter, it is used to convert the bound data to another type and the syntax for it is  Binding="{Binding Path=Value1, Converter=OurConverter}".

When there is a converter assigned to the bound data, they are first passed to it, converted and the comparison is done on the converted data and here we could trick the mechanism with a small hack, that works in a way, that it will convert the int to string. It will convert it to a string of "Hi" if it is higher than 100, or convert it to "Low" if lower. We could write it in a yet more elegant way in this particular example as we are having only two possible options (either it is bigger than 100 or not) so we could return a Boolean value.

In other situations, where there are several possible results of comparison and we should react to each of the results in a different way, a good idea is to create an enum.

 

Ahh, and I forgot to mention that the Converter class should implement the IValueConverter interface and should be added to the resources .

 

The code below illustrates how to set the color of the label in the items' template basing on the data value (the condition is that the value should be > 100):

 

First, the converter:

 

class OurConverter : IValueConverter
    {
        private int originalValue;
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            originalValue = (int)value;
            if (originalValue > 100)
                return true;
            return false;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            return originalValue;
        }
    }

 

Next we add it to the resources:

 

<UserControl.Resources>
        <MyNamespace:OurConverter x:Key="outConverter" />
</UserControl.Resources>

 

Finally we add the converter to the binding and the final resulting XAML will be:

 

        <ListView>
            <ListView.Resources>
                <DataTemplate x:Key="OurTemplate">
                    <Label Name="val1Label" />
                    <DataTemplate.Triggers>
                        <DataTrigger Binding="{Binding Path=Value1, Converter={StaticResource ourConverter}}"  Value="True">
                            <Setter TargetName="val1Label" Property="Foreground" Value="Red" />
                        </DataTrigger>
                    </DataTemplate.Triggers>
                </DataTemplate>
            </ListView.Resources>
            <ListView.View>
                <GridView>
                    <GridViewColumn Header="Value1" CellTemplate="{StaticResource OurTemplate}" />
                    <GridViewColumn Header="Value2" />
                </GridView>
            </ListView.View>
        </ListView>

 

Enjoy!

©2008 Karim Agha. All rights reserved.