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?