WPF ItemsSource not updating when items added to ObservableCollection?

Let’s say our WPF application has an ItemsControl whose ItemsSource is bound to an ObservableCollection.

<UserControl x:Class="Lexico.View.UserControl1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Lexico.View"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary Source="pack://application:,,,/Lexico.WPF;component/Resources/MasterDictionary.xaml" />
    </UserControl.Resources>
    <Grid>
        <ItemsControl
            ItemsSource="{Binding Items}" 
            Visibility="{Binding Items, Converter={StaticResource NonEmptyContainerVisCollapseConverter}}">
        </ItemsControl>
        <TextBlock 
            Visibility="{Binding Items, Converter={StaticResource EmptyContainerVisibilityCollapseConverter}}">
            Looks like this document doesn't contain any questionnaire items.
        </TextBlock>
    </Grid>
</UserControl>

The Visibility property of the ItemsControl is bound to a custom IValueConverter that returns Visibility.Visible if the ItemsSource is non-empty, or Visibility.Collapsed if empty. Additionally, we have a TextBox with a reverse IValueConverter

In other words, if our ObservableCollection is empty, we display the TextBox to notify the user there’s nothing there, otherwise we show the ItemsControl.

If everything goes to plan, on first load, on first load, we should see our “empty” message.

screenshot1

So far so good.

Now if we add an item to the ObservableCollection, the ItemsControl should update and we should see our newly added object.

screenshot1

No bueno - the UI isn’t updating; seemingly nothing has been added.

If we step through the debugger, though, we can verify that everything is wired up correctly. The item is definitely added to the collection.

screenshot1

So what’s going on?

The key here is the Visibility binding:

        <ItemsControl
            ItemsSource="{Binding Items}" 
            Visibility="{Binding Items, Converter={StaticResource NonEmptyContainerVisCollapseConverter}}">
        </ItemsControl>
        <TextBlock 
            Visibility="{Binding Items, Converter={StaticResource EmptyContainerVisibilityCollapseConverter}}">
            Looks like this document doesn't contain any questionnaire items.
        </TextBlock>

If we remove this, suddenly everything clicks!

screenshot1

The logic is pretty straightforward - feel free to kick yourself like I did when I figured it out. ObservableCollection raises a CollectionChanged event whenever an item is added or removed - the ItemsSource binding will listen for this event and refresh appropriately.

The Visibility binding, however, will only listen to PropertyChanged event, which is not raised when the collection changes.

This means that, although the ItemsControl itself is being updated, its Visibility (and the Visibility of the overlayed TextBlock) are not.

A simple solution is to add an event handler to the CollectionChanged event on the ObservableCollection:

Items.CollectionChanged += Items_CollectionChanged;

private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
            NotifyPropertyChanged("Items");
}

This will then refresh the Visibility bindings whenever the ObservableCollection changes, ensuring the updated ItemsControl is actually visible.

Depending on the size of the collection, this may be quite resource-intensive, so YMMV.