【问题标题】:Where to unsubscribe events in an attached property?在哪里取消订阅附加属性中的事件?
【发布时间】:2017-12-30 16:34:32
【问题描述】:

我有一个附加属性可在数据网格中使用,以便可以在我的视图模型中使用 SelectedItems。代码是这样的:

public class DataGridSelectedItemsAttachedProperty
    {
        #region SelectedItems
        ///
        /// SelectedItems Attached Dependency Property
        ///
        public static readonly DependencyProperty SelectedItemsProperty =
        DependencyProperty.RegisterAttached("SelectedItems", typeof(IList),
        typeof(DataGridSelectedItemsAttachedProperty),
        new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
        new PropertyChangedCallback(OnSelectedItemsChanged)));

        public static IList GetSelectedItems(DependencyObject d)
        {
            return (IList)d.GetValue(SelectedItemsProperty);
        }

        public static void SetSelectedItems(DependencyObject d, IList value)
        {
            d.SetValue(SelectedItemsProperty, value);
        }

        private static void OnSelectedItemsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            DataGrid miDg = (DataGrid)d;
            miDg.SelectionChanged += dataGrid_SelectionChanged;
            miDg.Unloaded += dataGrid_Unloaded;
        }

        private static void dataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            DataGrid miDg = (DataGrid)sender;
            //Get list box's selected items.
            IEnumerable miDgSelectedItems = miDg.SelectedItems;
            //Get list from model
            IList ModelSelectedItems = GetSelectedItems(miDg);

            //Update the model
            ModelSelectedItems.Clear();

            if (miDg.SelectedItems != null)
            {
                foreach (var item in miDg.SelectedItems)
                    ModelSelectedItems.Add(item);
            }
            SetSelectedItems(miDg, ModelSelectedItems);
        }


        private static void dataGrid_Unloaded(object sender, RoutedEventArgs e)
        {
            DataGrid miDg = sender as DataGrid;
            miDg.SelectionChanged -= dataGrid_SelectionChanged;
            miDg.Unloaded -= dataGrid_Unloaded;
        }
        #endregion
    }

问题是这个数据网格在一个选项卡控件中,事件卸载被触发,所以事件被取消订阅,然后 SelectedItems 不再通知给视图模型。

所以我想知道如何解决这个问题,也许在另一个地方取消订阅事件而不是卸载事件?

谢谢。

【问题讨论】:

  • 为什么要在这种情况下取​​消订阅?
  • 对。真的,这是我发现的一个例子,我认为这是一个很好的解决方案。但我认为在这种情况下不需要取消订阅,因为附加属性必须与用户控件同时存在,当我关闭用户控件时,附加属性将被重新收集,因为没有对象引用它.所以在这种情况下,我想它也是 unneded。

标签: c# wpf datagridview attached-properties


【解决方案1】:

我遇到了同样的问题,但得出的结论是,在这种情况下没有必要取消订阅事件(感谢 Álvaro García 和 Blechdose 的 cmets 为我指明了这个方向)。

实际上,由于事件处理程序导致的内存泄漏是单向问题。此处描述了此问题的原因:https://stackoverflow.com/a/4526840/12797700。通过使用此代码miDg.SelectionChanged += dataGrid_SelectionChanged;,您可以向将dataGrid_SelectionChanged 方法存储到miDg 对象的对象添加一个链接。因此,当 miDg 对象处于活动状态时,GC 无法删除存储 dataGrid_SelectionChanged 方法的对象。

但是,静态对象对 miDg 对象一无所知,即使处理了事件,GC 也可以删除 miDg 对象。

您可以使用下一个链接下载演示此行为的测试项目。它还演示了如何通过处理事件来复制内存泄漏问题。

https://github.com/Drreamer/AttachedPropertyMemoryTest

【讨论】:

    【解决方案2】:

    当我关闭用户控件时,附加属性将被重新收集,因为没有对象引用它。

    这是错误的。如果您删除取消注册事件的代码,任何使用附加属性的控件都将永远存在。为什么?因为您注册的事件处理程序是静态的。这意味着控件将包含对静态内容的引用,以防止垃圾收集器收集它。

    此问题的第一个潜在解决方案是在注册事件时使用弱事件模式。正是由于上述原因,我在为自己的附加属性注册事件时总是使用弱事件模式。

    这个解决方案的烦人之处在于它需要大量的样板代码。您必须为每种新类型的事件创建一个新的WeakEventManager 实现。然后要接收弱事件,您必须实现一个接口(编辑:除非您使用.NET 4.5 或更高版本),这意味着您不能拥有静态处理程序。因此,您需要实现IWeakEventListner 接口的类,并在附加的属性事件中创建和管理该类的实例。

    因此,我向您推荐的解决方案是实际继承 DataGrid 类并将此功能添加为正常的依赖属性。如果您这样做,则根本不必注册事件(您可以覆盖受保护的方法),并且不必担心潜在的内存泄漏。我推荐此解决方案的原因是,根据我的经验,由于许多其他原因,我需要覆盖 DataGrid 类,其中许多可以通过附加属性来实现,但其中一些不能。

    真正的问题是 WPF DataGrid 实现相当不成熟(我个人认为)。有错误、我不喜欢的默认行为以及不完整或未实现的功能(例如支持复制,但不支持粘贴;或者我认为您正在尝试解决的特定问题:可绑定的 SelectedItems)。只需将 DataGrid 子类化,就可以最轻松地解决所有这些问题。

    【讨论】:

    • 如果您使用在 .net 4.5 中添加的 AddHandler 方法,它会删除所有样板并将其转换为单行语句以添加或删除弱事件。您不再需要实现 IWeakEventListener
    • 好点子,但是您仍然需要为每个事件创建 WeakEventManager 实现,总体观点仍然成立:子类化 DataGrid 要容易得多。
    • 不,你没有。 WeakEventManager<DataGrid, SelectionChangedEventArgs>.AddHandler(miDg, "SelectionChanged", dataGrid_SelectionChanged); 是 .net 4.5 或更新版本中唯一需要使用弱事件订阅的代码行。从 4.5 开始,您不再需要创建实现。
    • 这听起来不对,还是?为什么发布者(控件)在引用静态事件处理程序(订阅者)时会保持活动状态?事件处理程序是静态的,因此甚至没有向发布者提供对象引用。但即便如此,也无所谓。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-08
    • 1970-01-01
    • 2018-01-05
    • 1970-01-01
    • 2020-11-11
    • 2011-08-14
    相关资源
    最近更新 更多