0

I'm creating a list of checkboxes "On the fly" from a Flagged Enum using a converter, in MVVM WPF application. The bound property is an int, that represents a flagged enum. This is my converter that converts from the int, to an ObservableDictionary<int, bool>.

public class AccessListConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        int realValue;
        switch (value)
        {
            case int intValue:
                realValue = intValue;
                break;
            case AccessEnum enumValue:
                realValue = (int)enumValue;
                break;
            default:
                return Binding.DoNothing;
        } 

        List<AccessEnum> accessComponents = Enum.GetValues(typeof(AccessEnum)).Cast<AccessEnum>().Where(r => ((AccessEnum)realValue & r) == r).Select(r => r).ToList();
        ObservableDictionary<int, bool> accessDictionary = new ObservableDictionary<int, bool>(Enum.GetValues(typeof(AccessEnum)).Cast<int>().Select(i => i).ToDictionary(i => i, i => accessComponents.Contains((AccessEnum) i)));
        accessDictionary.Remove(0);
        return accessDictionary;
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (!(value is Dictionary<int, bool> intDict))
        {
            throw new NotImplementedException();
        }
        return intDict.Sum(x => x.Value ? x.Key : 0);
    }
}

The point is that the user can pick and choose which enums to enable for the property. I figured using a converter would make sense, but I'm having a hard time getting the converter to trigger it's "ConvertBack" method, when a checkbox is ticked/unticked.

Here are my bindings in the Xaml:

<ItemsControl ItemsSource="{Binding Access, UpdateSourceTrigger=PropertyChanged, Converter={StaticResource AccessListConverter}}" Margin="87,0,10,0">
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel CanVerticallyScroll="False" Margin="0"/>
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
    <DataTemplate>
        <CheckBox VerticalAlignment="Center" Margin="6,0,0,0" Content="{Binding Key, Converter={StaticResource AccessStringConverter}, UpdateSourceTrigger=PropertyChanged}" 
                    IsChecked="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
    </DataTemplate>
</ItemsControl.ItemTemplate>

And now we get to the Observable Dictionary This is what my implementation of OBservableDictionary looks like (Irrelevant parts removed and the rest shrunk):

 [Serializable]
public class ObservableDictionary<TKey, TValue> : 
    ObservableCollection<ObservableKeyValuePair<TKey, TValue>>, IDictionary<TKey, TValue>
{
    public ObservableDictionary() {}
    public ObservableDictionary(Dictionary<TKey, TValue> dictionary) : this()
    {
        foreach (TKey key in dictionary.Keys) { Add(key, dictionary[key]); }
    }
    public ObservableDictionary(ObservableDictionary<TKey, TValue> dictionary) : this()
    {
        foreach (TKey key in dictionary.Keys) { Add(key, dictionary[key]); }
    }

    public void Add(TKey key, TValue value)
    {
        if (ContainsKey(key)) { throw new ArgumentException("The dictionary already contains the key"); }
        Add(new ObservableKeyValuePair<TKey, TValue>() { Key = key, Value = value });
    }

    public ICollection<TKey> Keys => (from i in ThisAsCollection() select i.Key).ToList();
    public ICollection<TValue> Values => (from i in ThisAsCollection() select i.Value).ToList();
    public TValue this[TKey key]
    {
        get { if (!TryGetValue(key, out TValue result)) {throw new ArgumentException("Key not found"); } return result;  }
        set { if (ContainsKey(key)) { GetKvpByTheKey(key).Value = value; } else { Add(key, value); } }
    }
}

I then implemented a collectionChanged code snippet form StackOverflow

void TrulyObservableCollection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems != null) foreach (object item in e.NewItems){ ((INotifyPropertyChanged) item).PropertyChanged += item_PropertyChanged; }
        if (e.OldItems != null) foreach (object item in e.OldItems) { ((INotifyPropertyChanged) item).PropertyChanged -= item_PropertyChanged;}
    }
    void item_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        NotifyCollectionChangedEventArgs a = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
        OnCollectionChanged(a);
    }

And added CollectionChanged += TrulyObservableCollection_CollectionChanged; to the empty constructor.

But no dice. With hours of searching, I've still come up empty. ConvertBack never gets triggered. I gather it may be because the collection itself isn't actually changing, but how then can I force an update? Any help much appreciated :)

Edit: I might have discovered the answer to my own question in the form of this comment: https://stackoverflow.com/a/59637445/3390519 I'll give it a go!

Frederik
  • 79
  • 1
  • 8
  • 1
    Have you tried adding Mode=TwoWay on ItemsSource binding? As MSDN states: that it is OneWay binding by default. And read the [remarks](https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.itemscontrol.itemssource?view=net-5.0#remarks). – XAMlMAX Apr 29 '21 at 09:25
  • Good idea! Didn't work though, unfortunately. – Frederik Apr 29 '21 at 10:02
  • 1
    Ok, I have read your question again and I think you are approaching this from wrong angle. You need a standard ObservableCollection of type tuple or custom class, that would have IsSelected property, then your VM wouold know which items are selected and which are not, obviously you would have a property with enum value as well. Will make most of your code a lot smaller and less messing about with custom collections. – XAMlMAX Apr 29 '21 at 10:57
  • I can try, but I don't see how that would fix it? It still wouldn't notify the converter. ObservableDictionary is already just an ObservableCollection, so what's the difference? I woul really like to not have a collection in the viewmodel and stick to an int property. That's kind of the point of this question, but I guess it is a good backup solution :) – Frederik Apr 29 '21 at 11:54

1 Answers1

0

I solved my own problem, by changing my search parameters! Instead of search for a solution to "Triggering “ConvertBack” on a converter when a property in a collection changes?", I started search for a solution to "wpf - converting flag enum to checkbox list" and found a decent solution! It's not as dynamic (Enum values have to be updated manually), but good enough for my purposes :) https://stackoverflow.com/a/59637445/3390519

Thanks for your time XAMIMAX :)

Frederik
  • 79
  • 1
  • 8