0

I have several shapes where I want to change the opacity when I click on it, additionally I need to know in code which shapes were selected and I need to set the opacity from code behind.

My idea is to use a Dictionary<int, bool> with an index and if selected or not. I know a Dictionary does not implement INotifyCollectionChanged or INotifyPropertyChanged, but I found an example of an observable dictionary, which triggers both events very well.

For each shape (rectangle) I set following binding:

<Rectangle Height="25" Stroke="Black" Margin="10" Fill="AliceBlue" StrokeThickness="3">
    <Rectangle.Style>
        <Style TargetType="Rectangle">
            <EventSetter Event="PreviewMouseDown" Handler="Rectangle_PreviewMouseDown"></EventSetter>
            <Setter Property="local:IndexProperty.SectorIndex" Value="1"></Setter>
            <Setter Property="local:SelectSectorProperty.SelectedSector" Value="{Binding SelectedRect, Mode=OneWay, NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged}"></Setter>
        </Style>
    </Rectangle.Style>
</Rectangle>

Within Rectangle_PreviewMouseDown I toggle the bool value of ObservableDictionary<int, bool> using SectorIndex.

Attached property looks like this:

public class SelectSectorProperty : DependencyObject
{
    public static ObservableDictionary<int, bool> GetSelectedSector(DependencyObject obj)
    {
        return (ObservableDictionary<int, bool>)obj.GetValue(SelectedSectorProperty);
    }

    public static void SetSelectedSector(DependencyObject obj, ObservableDictionary<int, bool> value)
    {
        obj.SetValue(SelectedSectorProperty, value);
    }

    // Using a DependencyProperty as the backing store for SectorList.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty SelectedSectorProperty =
        DependencyProperty.RegisterAttached("SelectedSector", typeof(ObservableDictionary<int, bool>),
            typeof(SelectSectorProperty), new PropertyMetadata(OnStatusChanged));

    private static void OnStatusChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("OnStatusChanged");
        if (obj is Rectangle element)
        {
            if (e.NewValue?.GetType() == typeof(ObservableDictionary<int, bool>))
            {
                var index = (int)obj.GetValue(IndexProperty.SectorIndexProperty);
                GetSelectedSector(obj).TryGetValue(index, out bool selected);
                if (selected)
                {
                    element.Opacity = 0.8;
                }
                else
                {
                    element.Opacity = 0.1;
                }
            }
        }
    }
}

My ViewModel is this:

public class ViewModel : ViewModelBase
{
    public ViewModel()
    {
        SelectedRect.PropertyChanged += SelectedSectors_PropertyChanged;
    }

    private void SelectedSectors_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        OnPropertyChanged(nameof(SelectedRect));
    }

    private ObservableDictionary<int, bool> _SelectedRect = new ObservableDictionary<int, bool>() {
        { 1, false},
        { 2, false},
        { 3, false},
    };
    public ObservableDictionary<int, bool> SelectedRect
    {
        get { return _SelectedRect; }
        set
        {
            _SelectedRect = value;
            OnPropertyChanged(nameof(SelectedRect));
        }
    }
}

Once I update my ObservableDictionary, the SelectedRect.PropertyChanged event is triggered and also the OnPropertyChanged event, which is implemented in the ViewModelBase class, is called with the exact same parameters which would be called from the SelectedRect property itself. But OnStatusChanged of my attached property is not triggerd, only if I change the reference of my SelectedRect property (e.g. setting to null and back to it).

This differs from my expectations, I would really like to know why, calling OnPropertyChanged with the correct parameters should be enough, but it seems there is more validation going on behind the scenes.

I guess updating the Binding explicitly is the way to go?

nalka
  • 1,894
  • 11
  • 26
Marcel B.
  • 1
  • 1
  • The PropertyChanged callback of a dependency property is only called when the value of the property has actually changed. Modifying a collection object is not a value change of a property that holds a reference to the collection object. Even if the view model fires a change notification like the PropertyChanged event, that doesn't mean the property value has actually changed. The notication is just silently ignored, so "*calling OnPropertyChanged with the correct parameters should be enough*" is a wrong expectation. – Clemens Jan 23 '20 at 13:08
  • You should perhaps also think about having a view model (e.g. like [shown here](https://stackoverflow.com/a/22325266/1136211)) to manage you collection of rectangles. Them visualize them in a ListBox (instead of ItemsControl) and bind the ListBoxItem's IsSelected state to a property in the rectangle item class in the view model. – Clemens Jan 23 '20 at 13:21
  • @Clemens thanks for clarification. This was just an isolated example, in reality the rectangles are polylines which create a track with different sectors inside a Canvas. Storing them in my ViewModel doesn't make sense, especially because it was a svg-file converted to a xaml-file. – Marcel B. Jan 23 '20 at 13:27
  • It would however make perfect sense to store the selected/opacity state in some kind of Sector object, call it a view model or not. – Clemens Jan 23 '20 at 13:29

0 Answers0