FreezableCollection not providing change notification when sub properties change
I have a FreezableCollection for which I want to monitor the changes to sub properties. Here is a subsection of the code:
public class FieldHeading : DependencyObject
{
public static readonly DependencyProperty LayoutProperty = DependencyProperty.Register("Layout", typeof(FieldHeadingLayout), typeof(FieldHeading),
new FrameworkPropertyMetadata(FieldHeadingLayout.Above,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure));
public FieldHeadingLayout Layout
{
get { return (FieldHeadingLayout) GetValue(LayoutProperty); }
set { SetValue(LayoutProperty, value); }
}
}
public class FieldPanel : FrameworkElement
{
private static readonly DependencyProperty FieldHeadingProperty = DependencyProperty.Register("FieldHeading", typeof(FreezableCollection<FieldHeading>), typeof(FieldPanel),
new FrameworkPropertyMetadata(null,
开发者_开发百科FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsParentMeasure |
FrameworkPropertyMetadataOptions.AffectsRender, HeadingChanged));
private static void HeadingChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Hello");
}
public FreezableCollection<FieldHeading> FieldHeadings
{
get
{ return (FreezableCollection<FieldHeading>) GetValue(FieldHeadingProperty); }
set { SetValue(FieldHeadingProperty, value);}
}
public FieldPanel()
{
AddVisual(_contentVisual = new DrawingVisual());
FieldHeadings = new FreezableCollection<FieldHeading>();
}
}
Then we assign a new value to Layout for one of the FieldHeadings, no change notification is generated. Obviously I'm missing something important. HeadingChanged is never called.
The MSDN help on FreezableCollection, which can be found here: FreezableCollection, states:
Event changed... Occurs when the Freezable or an object it contains is modified. (Inherited from Freezable.)
Thanks in advance for any help.
~ Cameron
Actually, you can do what you're trying to do. This is exactly why FreezableCollection<T>
exists! All you need to do is change FieldHeading
to derive from Freezable
instead of DependencyObject
and changes to items in the collection will give the same change notification as if the entire item had been replaced.
This is an incredibly useful and little-known WPF feature.
From Charles Petzold himself,
These freezable collection classes fire change notifications whenever items are added to or removed from the collection, of course, but also when a dependency property of any item within the collection changes. This is an extremely powerful mechanism.
Here is a small sample that demonstrates how to use FreezableCollection<>
. I made a new WPF project in Visual Studio. Here is the XAML for MainWindow.xaml.cs and its C# code behind:
<Window x:Class="FreezableCollection.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:FreezableCollection"
x:Name="Root"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate DataType="{x:Type local:MyFreezable}">
<CheckBox IsChecked="{Binding IsNice}" Content="Check me!"/>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox ItemsSource="{Binding ElementName=Root, Path=MyFreezables}" />
</Grid>
</Window>
using System.Windows;
namespace FreezableCollection
{
public partial class MainWindow : Window
{
public static readonly DependencyProperty MyFreezablesProperty =
DependencyProperty.Register("MyFreezables", typeof(MyFreezableCollection), typeof(MainWindow), new FrameworkPropertyMetadata(null, FreezablesChangedCallback));
public MyFreezableCollection MyFreezables
{
get => (MyFreezableCollection)GetValue(MyFreezablesProperty);
set => SetValue(MyFreezablesProperty, value);
}
public MainWindow()
{
InitializeComponent();
MyFreezables = new MyFreezableCollection { new MyFreezable() };
}
private static void FreezablesChangedCallback(object sender, DependencyPropertyChangedEventArgs args)
{
MessageBox.Show("Changed!");
}
}
public class MyFreezableCollection : FreezableCollection<MyFreezable>
{
protected override Freezable CreateInstanceCore() => new MyFreezableCollection();
}
public class MyFreezable : Freezable
{
public static readonly DependencyProperty IsNiceProperty =
DependencyProperty.Register("IsNice", typeof(bool), typeof(MyFreezable), new PropertyMetadata(false));
public bool IsNice
{
get => (bool)GetValue(IsNiceProperty);
set => SetValue(IsNiceProperty, value);
}
protected override Freezable CreateInstanceCore() => new MyFreezable();
}
}
The sample displays a list containing a single CheckBox
. Click on it to toggle the IsNice
property on an item in the data-bound MyFreezableCollection
. Even though nothing is added to or removed from the list, the dependency property change callback is invoked.
The change notification handler will only notify you when the value of the property changes, so in this case if the freezable collection changes to a new collection. In your property changed handler you need to subscribe to the CollectionChanged
event and in that event you need to subscribe to the PropertyChanged
event on the new item. Now, finally, you have an event that will allow you to react to changes in properties of items belonging to a freezable collection that is a dependency property. Remember to unsubscribe to the old collection's and old item's events.
The thing is that you subscribe only for the FieldHeadings
property changes, meaning you will receive notifications only if someone assigns a new instance of the collection itself, using, for example, the FeildHedings
property setter.
In order to receive notifications when the Layout
property changes, you have to subscribe to it on each individual instance of FieldHeading
.
精彩评论