XAML ListView–Styling Items Based on Data (Windows 8.1 XAML)

I was reading Tim’s post about ListViews over breakfast about his experiments in styling XAML list items based on the data itself and thought I’d add a couple more suggestions.

Let’s say that I have some data source/view model;

class NumericalViewModel
  {
    public IEnumerable<int> Numbers
    {
      get
      {
        Random r = new Random();

        return (
          Enumerable
            .Range(1, 10)
            .Select(
              i => r.Next(-5, 6)));
      }
    }
  }

and then I might display those in a ListView;

    <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
    </ListView>

We’ve (sort of) got 2 view models in play here – the view model which is providing the property Numbers to the ListView and then we have 10 instances of a second view model which is simply providing an integer value for each item in the list.

Tim took the approach of replacing the control template and data-binding some pieces within it whereas I’d usually produce an item template and data-bind some property on elements within that template. For example;

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
      <local:IntegerBrushConverter x:Key="converter" PositiveBrush="Red" NegativeBrush="Green"/>
    </Grid.Resources>
    <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding}"
                     Foreground="{Binding Converter={StaticResource converter}}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>

where the IntegerBrushConverter class is a quickly hacked-together;

  class IntegerBrushConverter : IValueConverter
  {
    public object Convert(object value, Type targetType, object parameter, string language)
    {
      // NB: not properly checking types.
      int integer = (int)value;

      Brush brush = integer >= 0 ? this.PositiveBrush : this.NegativeBrush;
      return (brush);
    }
    public Brush PositiveBrush
    {
      get
      {
        return (this.positiveBrush);
      }
      set
      {
        this.positiveBrush = value;
      }
    }
    Brush positiveBrush;

    public Brush NegativeBrush
    {
      get
      {
        return (this.negativeBrush);
      }
      set
      {
        this.negativeBrush = value;
      }
    }
    Brush negativeBrush;

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
      throw new NotImplementedException();
    }
  }

Now, if the view model for each item was just slightly more complicated;

  class ItemViewModel : INotifyPropertyChanged
  {
    public int Value
    {
      get
      {
        return (this.value);
      }
      set
      {
        this.value = value;
        this.RaiseMyPropertyChanged();
      }
    }
    int value;

    void RaiseMyPropertyChanged()
    {
      var handlers = this.PropertyChanged;
      if (handlers != null)
      {
        handlers(this, new PropertyChangedEventArgs("Value"));
      }
    }
    public event PropertyChangedEventHandler PropertyChanged;
  }

and the main view model was updated to produce instances of those items;

  class NumericalViewModel
  {
    public IEnumerable<ItemViewModel> Numbers
    {
      get
      {
        Random r = new Random();

        List<ItemViewModel> list =
          Enumerable.Range(1, 10).Select(
            i => new ItemViewModel() { Value = r.Next(-5, 6) }).ToList();

        this.UpdateLater(list);

        return (list);
      }
    }
    async Task UpdateLater(List<ItemViewModel> integers)
    {
      await Task.Delay(5000);

      foreach (var item in integers)
      {
        item.Value++;
      }
    }
  }

and the XAML updated to bind correctly;

 <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
      <local:IntegerBrushConverter x:Key="converter" PositiveBrush="Red" NegativeBrush="Green"/>
    </Grid.Resources>
    <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Value}"
                     Foreground="{Binding Value,Converter={StaticResource converter}}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>

then that will correctly display red/green items based on whether the values are positive/negative and after 5 seconds any value that flips negative->positive will be correctly coloured – i.e. the binding engine will notice the property change events and will pull new values from the source objects and will run those values through conversion to make sure that they are still the correct colours.

It’s possible to skip the converter here and simply put this responsibility into the view model itself. For example, we can change the item view model;

  class ItemViewModel : INotifyPropertyChanged
  {
    static ItemViewModel()
    {
      positiveBrush = new Lazy<SolidColorBrush>(() =>
        {
          return (new SolidColorBrush(Colors.Red));
        });

      negativeBrush = new Lazy<SolidColorBrush>(() =>
        {
          return (new SolidColorBrush(Colors.Green));
        });
    }
    public int Value
    {
      get
      {
        return (this.value);
      }
      set
      {
        this.value = value;
        this.RaiseMyPropertyChanged();
      }
    }
    int value;

    public Brush Foreground
    {
      get
      {
        return (this.value >= 0 ? positiveBrush.Value : negativeBrush.Value);
      }
    }
    static Lazy<SolidColorBrush> positiveBrush;
    static Lazy<SolidColorBrush> negativeBrush;

    void RaiseMyPropertyChanged()
    {
      var handlers = this.PropertyChanged;
      if (handlers != null)
      {
        handlers(this, new PropertyChangedEventArgs("Value"));
        handlers(this, new PropertyChangedEventArgs("Foreground"));
      }
    }
    public event PropertyChangedEventHandler PropertyChanged;
  }

and then change the XAML to take away any use of a converter;

 <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Value}"
                     Foreground="{Binding Foreground}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>

You could argue though about whether you want objects like Brush in your view models and certainly today you would struggle for instance to build such a class into a portable class library that spanned (e.g.) Windows 8.1 and Windows Phone 8.0 as you’re taking dependencies on types that are UI specific.

Another variation on this theme is to use some kind of data template selector. That is, we can have a couple of data-templates defined in our XAML as below;

    <ListView ItemsSource="{Binding Numbers}">
      <ListView.ItemTemplateSelector>
        <local:MySelector>
          <local:MySelector.PositiveTemplate>
            <DataTemplate>
              <TextBlock Text="{Binding Value}"
                         Foreground="Red" />
            </DataTemplate>
          </local:MySelector.PositiveTemplate>
          <local:MySelector.NegativeTemplate>
            <DataTemplate>
              <TextBlock Text="{Binding Value}"
                         Foreground="Green" />
            </DataTemplate>
          </local:MySelector.NegativeTemplate>
        </local:MySelector>
      </ListView.ItemTemplateSelector>
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
    </ListView>

with that class MySelector defined as;

  class MySelector : DataTemplateSelector
  {
    public DataTemplate PositiveTemplate { get; set; }
    public DataTemplate NegativeTemplate { get; set; }

    protected override DataTemplate SelectTemplateCore(object item)
    {
      ItemViewModel viewModel = (ItemViewModel)item;
      return (viewModel.Value >= 0 ? this.PositiveTemplate : this.NegativeTemplate);
    }
  }

The problem here is that this won’t dynamically re-apply the templates to the ListViewItems as the item values changed so colours would not dynamically update and so while it seems a bit more elegant to me, it wouldn’t be the solution in this particular case.

A very similar approach would be to use an ItemContainerStyleSelector which might look something like this;

    <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
      <ListView.ItemContainerStyleSelector>
        <local:MyStyleSelector>
          <local:MyStyleSelector.PositiveStyle>
            <Style TargetType="ListViewItem">
              <Setter Property="Foreground"
                      Value="Red" />
            </Style>
          </local:MyStyleSelector.PositiveStyle>
          <local:MyStyleSelector.NegativeStyle>
            <Style TargetType="ListViewItem">
              <Setter Property="Foreground"
                      Value="Green" />
            </Style>
          </local:MyStyleSelector.NegativeStyle>
        </local:MyStyleSelector>
      </ListView.ItemContainerStyleSelector>
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Value}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>

with the MyStyleSelector class being;

  class MyStyleSelector : StyleSelector
  {
    public Style PositiveStyle { get; set; }
    public Style NegativeStyle { get; set; }

    protected override Style SelectStyleCore(object item, DependencyObject container)
    {
      ItemViewModel viewModel = (ItemViewModel)item;
      return (viewModel.Value >= 0 ? this.PositiveStyle : this.NegativeStyle);
    }
  }

which works fine in the first instance but won’t (like the DataTemplateSelector) re-apply the styles should something in the underlying data change and that leads back to having a style which is data-bound as in something like this;

  <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.Resources>
      <local:IntegerBrushConverter x:Key="converter"
                                   PositiveBrush="Red"
                                   NegativeBrush="Green" />
    </Grid.Resources>
    <ListView ItemsSource="{Binding Numbers}">
      <ListView.DataContext>
        <local:NumericalViewModel />
      </ListView.DataContext>
      <ListView.ItemContainerStyle>
        <Style TargetType="ListViewItem">
          <Setter Property="Foreground"
                  Value="{Binding Value,Converter={StaticResource converter}}" />
        </Style>
      </ListView.ItemContainerStyle>
      <ListView.ItemTemplate>
        <DataTemplate>
          <TextBlock Text="{Binding Value}" />
        </DataTemplate>
      </ListView.ItemTemplate>
    </ListView>
  </Grid>

which, for me, is probably the best solution without going as far as Tim did and actually reproducing the control template and setting up binding inside of there.

However…that doesn’t work at all Sad smile 

Binding inside of style setters isn’t supported for Windows 8.1 apps as per this bug report and so while that feels like the way I’d like to do things it looks like I’d have to revert back to one of the previous methods in order to actually get this done.

I’m sure you have other methods for doing this kind of thing – feel free to link to them from the bottom of this post.