1

I have a custom control to show items with checkboxes inside a ComboBox. To realize this, I used a DataTemplate with a CheckBox. The ItemSource of the ComboBox uses a binding to a ObserableCollection<FilterValue> which contains my filter values. FilterValue is a custom class implementing INotifyPropertyChanged. The properties Content and IsChecked of the CheckBox use bindings as well to use the values of my list. This control will be used in Silverlight.

Binding itself works fine, as seen here:
Binding itself works fine, as seen here:

The problem appears when I register the Checked or Unchecked event.

As soon as one of the check boxes changed its state, the event is fired as expected but at this moment, the value in the bound list is still not updated. What I saw while debugging is that the Checked/Unchecked events are firing before the PropertyChanged event of the FilterValue. This means that at the time the event is firing, I can't ask the list for all active (checked) filters. What could I do to achieve this?

FilterControl.xaml:

<UserControl
    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:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
    xmlns:local="clr-namespace:Controls" x:Class="Controls.FilterControl"
    mc:Ignorable="d"
    d:DesignHeight="45" d:DesignWidth="140">

    <StackPanel x:Name="LayoutRoot">
        <sdk:Label x:Name="LblFilterDescription" Content="-" />
        <ComboBox x:Name="Filter"  Width="120" ItemsSource="{Binding AvailableFilters, RelativeSource={RelativeSource FindAncestor, AncestorType=local:FilterControl}}">
            <ComboBox.ItemTemplate>
                <DataTemplate>
                    <CheckBox Content="{Binding Path=Text}" IsChecked="{Binding Path=IsChecked, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Checked="FilterChanged" Unchecked="FilterChanged" />
                </DataTemplate>
            </ComboBox.ItemTemplate>
        </ComboBox>
    </StackPanel>
</UserControl>

FilterControl.xaml.cs:

public partial class FilterControl : UserControl
{
    public delegate void FilterChangedHandler(object sender);
    public event FilterChangedHandler OnFilterChanged;

    public ObservableCollection<FilterValue> AvailableFilters { get; set; }
    public List<string> AppliedFilters
    {
        get
        {
            return new List<string>(AvailableFilters.Where(filter => filter.IsChecked).Select(filter => filter.Text));
        }
    }

    public FilterControl()
    {
        InitializeComponent();

        AvailableFilters = new ObservableCollection<FilterValue>();
    }

    public bool AddFilterValue(string filterValue)
    {
        bool found = false;

        foreach (FilterValue f in AvailableFilters)
        {
            if (f.Text == filterValue)
            {
                found = true;
                break;
            }
        }

        if (!found)
            AvailableFilters.Add(new FilterValue() { IsChecked = false, Text = filterValue });

        return found;
    }

    private void FilterChanged(object sender, RoutedEventArgs e)
    {
        //Here if I check AvailableFilters, the value is not changed yet.
        //PropertyChanged allways fires after this, what makes me unable to
        //get all currently applied filters (checked items)...
    }
}

FilterValue:

public class FilterValue : INotifyPropertyChanged
{
    private bool _IsChecked;
    private string _Text;

    public bool IsChecked
    {
        get { return _IsChecked; }
        set
        {
            _IsChecked = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("IsChecked"));
        }
    }
    public string Text
    {
        get { return _Text; }
        set
        {
            _Text = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Text"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}
poke
  • 369,085
  • 72
  • 557
  • 602

1 Answers1

1

So, as I tried to reproduce this behavior, I realized that this appears to be a behavior that only occurs like that in Silverlight. If you try this example on WPF, the Changed fires after the bound property is updated. So you can just access your AppliedFilters property in the FilterChanged method and it will reflect the actual current situation. On Silverlight though, not so much. Even worse, this behavior didn’t even appear to be consistent to me. I did encounter situations in which the event fired after the property has been updated (resulting in the expected output).

A way to get around this is to clean up your component logic. If you look at it, you are mixing two different concepts: Event-driven UI logic, and clear data binding. Of course, doing it “properly” has multiple effects you likely cannot just ensure in an existing project, but you can at least try to get in the right direction here which should then also solve this issue.

So your logic right now uses data binding to provide the data for the view, and to reflect changes of the displayed items. But you are using events on the item level to perform additional logic depending on the former changes. As we have seen, the order of execution appears not be guaranteed across platforms, so it’s best to avoid having to rely on it.

In this case, you should have your data be the source of truth and make changes in the data tell you when applied filters change. You’re already halfway there by having an ObservableCollection and items that implement INotifyPropertyChanged. Unfortunately, an observable collection will only notify you about changes to the collection but not to changes to the contained items. But there are multiple solutions to expand the collection to also look at the items inside the collection.

This related question covers exactly that topic and there are multiple ideas on how to expand the observable collection for exactly that behavior. In my case, I have used the FullyObservableCollection implementation by Bob Sammers.

All you have to do for that is to change your ObservableCollection<FilterValue> into a FullyObservableCollection<FilterValue> and subscribe to the ItemPropertyChanged event:

AvailableFilters = new FullyObservableCollection<FilterValue>();
AvailableFilters.ItemPropertyChanged += AvailableFilters_ItemPropertyChanged;

In that event handler, you will then correctly see the proper behavior.

poke
  • 369,085
  • 72
  • 557
  • 602
  • I used a similar approach to the linked `ObservableCollection`, which solved my issues after a bit of tinkering. Will be very helpful for future projects as well, thank you! –  Jul 10 '17 at 09:50