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?