Search code examples
c#wpflistviewstyled-componentslistviewitem

Set WPF ListViewItem format based on item properties


While there are many posts relating to doing this with a ListBox, the ListView control is proving quite resistant.

In a nutshell, I'd like to highlight certain rows in the ListView as 'disabled' but still allow the user to search/see them listed.

In the commented out XAML sections, I can explicitly set the Background for all ListViewItems just fine. However, if I try to bind it, it fails...though it doesn't give any warnings and runs just fine - it simply doesn't work.

I could explicitly create a TextBlock for each column and set the Background there but that would result in quite a bit of copypasta which I'm looking to avoid as I've quite a few columns in my real-world example.

I've tried setting a DataTemplate but can't figure how to allow the bindings to happen per column - all the examples I've seen bind specifically to the item property (in my case, a column). I'd need such a template to set the style for all columns, but specify the column content elsewhere.

I think I'm close but I'm missing some small piece somewhere or have something not wired up correctly.

For berivity, the code to reproduce is as follows;

XAML

<Window x:Class="WpfApp1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfApp1"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200">
  <ListView Margin="5" VerticalAlignment="Stretch" 
              SelectionMode="Single" ItemsSource="{Binding Path=Items}"
              SelectedItem="{Binding SelectedItem,Mode=TwoWay}">
    <ListView.Resources>
      <Style TargetType="{x:Type ListViewItem}">
        <!--<Setter Property="Background" Value="{Binding Background}" />-->
        <!--<Setter Property="Background" Value="Blue" />-->
      </Style>
      <!--<DataTemplate DataType="{x:Type local:FooItemViewModel}">
        <TextBlock Background="{Binding Background}"></TextBlock>
      </DataTemplate>-->
    </ListView.Resources>
    <ListView.View>
      <GridView AllowsColumnReorder="True">
        <GridView.ColumnHeaderContainerStyle>
          <Style>
            <Setter Property="GridViewColumnHeader.HorizontalContentAlignment" Value="Left" />
            <Setter Property="GridViewColumnHeader.Padding" Value="10 0" />
          </Style>
        </GridView.ColumnHeaderContainerStyle>
        <GridViewColumn Header="ID" Width="Auto" DisplayMemberBinding="{Binding Id}" />
        <GridViewColumn Header="Barcode" Width="Auto" DisplayMemberBinding="{Binding Barcode}" />
        <GridViewColumn Header="Check" Width="Auto">
          <GridViewColumn.CellTemplate>
            <DataTemplate>
              <CheckBox IsChecked="{Binding Selected}"/>
            </DataTemplate>
          </GridViewColumn.CellTemplate>
        </GridViewColumn>
      </GridView>
    </ListView.View>
  </ListView>
</Window>

Code behind for XAML

using System.Collections.ObjectModel;
using System.Windows;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        private FooViewModel _viewModel;

        public MainWindow()
        {
            InitializeComponent();
            _viewModel = new FooViewModel();
            DataContext = _viewModel;
            Loaded += MainWindow_Loaded;
        }

        public void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _viewModel.Items = new ObservableCollection<FooItemViewModel>
            {
                new FooItemViewModel { Id = 1, Barcode = "test1"},
                new FooItemViewModel { Id = 2, Barcode = "test2", Selected = true},
                new FooItemViewModel { Id = 3, Barcode = "test3"},
            };
        }
    }
}

View Models

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Runtime.CompilerServices;

namespace WpfApp1
{
    internal class BaseViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected void UpdateProperty<T>(ref T toSet, T value, [CallerMemberName] string propertyName = null)
        {
            if ((toSet == null && value != null) || !toSet.Equals(value))
            {
                toSet = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }

    internal class FooItemViewModel : BaseViewModel
    {
        private int _id;
        private string _barcode;
        private bool _selected;

        public int Id
        {
            get => _id; 
            set => UpdateProperty(ref _id, value);
        }

        public string Barcode
        {
            get => _barcode; 
            set => UpdateProperty(ref _barcode, value);
        }

        public bool Selected
        {
            get => _selected; 
            set => UpdateProperty(ref _selected, value);
        }

        public Color Background => Color.Blue;
    }

    internal class FooViewModel : BaseViewModel
    {
        private FooItemViewModel _selectedItem;
        private ObservableCollection<FooItemViewModel> _items = new ObservableCollection<FooItemViewModel>();

        public FooItemViewModel Selecteditem
        {
            get => (FooItemViewModel)_selectedItem; 
            set => UpdateProperty(ref _selectedItem, value);
        }

        public ObservableCollection<FooItemViewModel> Items
        {
            get => _items; 
            set => UpdateProperty(ref _items, value);
        }
    }
}

Solution

  • The Background property should should return a Brush instead of a Color for the binding to work:

    public Brush Background => Brushes.Blue;