在哪里取消订阅附件中的事件 属性?
Where to unsubscribe events in an attached property?
我有一个附加的 属性 可以在数据网格中使用,以便可以在我的视图模型中使用 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 不再通知视图模型。
所以我想知道如何解决这个问题,也许取消订阅另一个地方的事件而不是卸载事件?
谢谢。
when I close the user control is when the attached property will be recolected because no object is referencing it.
这是错误的。如果删除注销事件的代码,使用附加 属性 的任何控件将永远存在。为什么?因为你注册的事件处理器是静态的。这意味着该控件将包含对某些静态内容的引用,从而阻止垃圾收集器收集它。
这个问题的第一个潜在解决方案是在注册事件时使用弱事件模式。由于上述原因,我在为自己的附加属性注册事件时总是使用弱事件模式。
此解决方案的烦人之处在于它需要相当大量的样板代码。您必须为每种新类型的事件创建一个新的 WeakEventManager
实现。然后要接收弱事件,您必须实现一个接口(编辑:除非您使用的是 .NET 4.5 或更高版本),这意味着您不能拥有静态处理程序。因此,您需要 class 实现 IWeakEventListner
接口,并在附加的 属性 事件中创建和管理该 class 的实例。
因此,我为您推荐的解决方案实际上是 class DataGrid
class 并将此功能添加为正常依赖项 属性。如果你这样做,你根本不需要注册事件(有你可以覆盖的受保护方法),并且不用担心潜在的内存泄漏。我推荐此解决方案的原因是,根据我的经验,出于许多其他原因,我需要覆盖 DataGrid
class,其中许多可以通过附加属性实现,但其中一些不能。
真正的问题是 WPF DataGrid 实现相当不成熟(我个人的看法)。存在错误、我不喜欢的默认行为以及不完整或未实现的功能(例如支持复制,但不支持粘贴;或者我认为您正在尝试解决的特定问题:可绑定的 SelectedItems)。通过简单地 subclassing DataGrid 可以最轻松地解决所有这些问题。
我也遇到过同样的问题,但得出的结论是在这种情况下没有必要取消订阅事件(感谢 Álvaro García 和 Blechdose 的评论指出了我的方向)。
实际上,由于事件处理程序导致的内存泄漏是一种单向问题。此处描述了此问题的原因:。通过使用此代码 miDg.SelectionChanged += dataGrid_SelectionChanged;
,您可以将 link 添加到将 dataGrid_SelectionChanged 方法存储到 miDg 对象中的对象。因为当 miDg 对象还活着时,GC 无法删除存储 dataGrid_SelectionChanged 方法的对象。
然而,静态对象对miDg对象一无所知,即使处理了事件,GC也可以删除miDg对象。
您可以使用下一个 link 下载演示此行为的测试项目。它还演示了如何通过处理事件来复制内存泄漏问题。
我有一个附加的 属性 可以在数据网格中使用,以便可以在我的视图模型中使用 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 不再通知视图模型。
所以我想知道如何解决这个问题,也许取消订阅另一个地方的事件而不是卸载事件?
谢谢。
when I close the user control is when the attached property will be recolected because no object is referencing it.
这是错误的。如果删除注销事件的代码,使用附加 属性 的任何控件将永远存在。为什么?因为你注册的事件处理器是静态的。这意味着该控件将包含对某些静态内容的引用,从而阻止垃圾收集器收集它。
这个问题的第一个潜在解决方案是在注册事件时使用弱事件模式。由于上述原因,我在为自己的附加属性注册事件时总是使用弱事件模式。
此解决方案的烦人之处在于它需要相当大量的样板代码。您必须为每种新类型的事件创建一个新的 WeakEventManager
实现。然后要接收弱事件,您必须实现一个接口(编辑:除非您使用的是 .NET 4.5 或更高版本),这意味着您不能拥有静态处理程序。因此,您需要 class 实现 IWeakEventListner
接口,并在附加的 属性 事件中创建和管理该 class 的实例。
因此,我为您推荐的解决方案实际上是 class DataGrid
class 并将此功能添加为正常依赖项 属性。如果你这样做,你根本不需要注册事件(有你可以覆盖的受保护方法),并且不用担心潜在的内存泄漏。我推荐此解决方案的原因是,根据我的经验,出于许多其他原因,我需要覆盖 DataGrid
class,其中许多可以通过附加属性实现,但其中一些不能。
真正的问题是 WPF DataGrid 实现相当不成熟(我个人的看法)。存在错误、我不喜欢的默认行为以及不完整或未实现的功能(例如支持复制,但不支持粘贴;或者我认为您正在尝试解决的特定问题:可绑定的 SelectedItems)。通过简单地 subclassing DataGrid 可以最轻松地解决所有这些问题。
我也遇到过同样的问题,但得出的结论是在这种情况下没有必要取消订阅事件(感谢 Álvaro García 和 Blechdose 的评论指出了我的方向)。
实际上,由于事件处理程序导致的内存泄漏是一种单向问题。此处描述了此问题的原因:。通过使用此代码 miDg.SelectionChanged += dataGrid_SelectionChanged;
,您可以将 link 添加到将 dataGrid_SelectionChanged 方法存储到 miDg 对象中的对象。因为当 miDg 对象还活着时,GC 无法删除存储 dataGrid_SelectionChanged 方法的对象。
然而,静态对象对miDg对象一无所知,即使处理了事件,GC也可以删除miDg对象。
您可以使用下一个 link 下载演示此行为的测试项目。它还演示了如何通过处理事件来复制内存泄漏问题。