FreezableCollection 在子属性更改时不提供更改通知

发布于 2024-10-18 05:57:11 字数 2087 浏览 0 评论 0原文

我有一个 FreezableCollection,我想监视其子属性的更改。下面是代码的一小部分:

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>();
    }
}

然后,我们为其中一个 FieldHeadings 的 Layout 分配一个新值,不会生成任何更改通知。显然我错过了一些重要的事情。 HeadingChanged 永远不会被调用。

有关 FreezableCollection 的 MSDN 帮助可在此处找到:FreezableCollection,指出:

事件已更改... 当 Freezable 或其包含的对象被修改时发生。 (继承自 Freezable。)

预先感谢您的帮助。

〜卡梅伦

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

临风闻羌笛 2024-10-25 05:57:11

实际上,您可以做您想做的事情。这正是 FreezableCollection 存在的原因!您需要做的就是将 FieldHeading 更改为派生自 Freezable 而不是 DependencyObject,并且对集合中的项目的更改将给出与以下相同的更改通知:如果整个项目已被更换。

这是一个非常有用但鲜为人知的 WPF 功能。

来自 Charles Petzold 本人

当然,每当向集合添加或从集合中删除项目时,这些可冻结集合类都会触发更改通知,而且当集合中任何项目的依赖属性发生更改时也会触发更改通知。这是一个极其强大的机制。

下面是一个小示例,演示如何使用 FreezableCollection。我在 Visual Studio 中创建了一个新的 WPF 项目。以下是 MainWindow.xaml.cs 的 XAML 及其背后的 C# 代码:

<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();
    }
}

该示例显示一个包含单个 CheckBox 的列表。单击它可切换数据绑定 MyFreezableCollection 中项目的 IsNice 属性。即使列表中没有添加或删除任何内容,也会调用依赖属性更改回调

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.

逆光下的微笑 2024-10-25 05:57:11

更改通知处理程序只会在属性值更改时通知您,因此在这种情况下,如果可冻结集合更改为新集合。在属性更改处理程序中,您需要订阅 CollectionChanged 事件,在该事件中,您需要订阅新项目的 PropertyChanged 事件。现在,您终于有了一个事件,该事件允许您对属于可冻结集合(即依赖属性)的项的属性更改做出反应。请记住取消订阅旧收藏和旧项目的活动。

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.

岁月无声 2024-10-25 05:57:11

问题是,您仅订阅 FieldHeadings property 更改,这意味着仅当有人分配集合本身的新实例(例如,使用FeildHedings 属性设置器。

为了在 Layout 属性更改时接收通知,您必须在 FieldHeading 的每个单独实例上订阅它。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文