我有一个在数据网格中使用的附加属性,可以在我的视图模型中使用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不再被通知给视图模型。
所以我想知道如何解决这个问题,或许取消订阅其他地方的事件而不是卸载事件?
感谢。
答案 0 :(得分:2)
我也遇到了同样的问题,但得出的结论是,在这种情况下无需退订事件(感谢ÁlvaroGarcía和Blechdose的评论指出了我的方向)。
由于事件处理程序,内存泄漏实际上是一种单向问题。此处描述了此问题的原因:https://stackoverflow.com/a/4526840/12797700。通过使用此代码miDg.SelectionChanged += dataGrid_SelectionChanged;
,您可以向该对象添加一个链接,该链接将dataGrid_SelectionChanged方法存储到miDg对象中。因此,在miDg对象处于活动状态时,GC无法删除存储dataGrid_SelectionChanged方法的对象。
但是,静态对象对miDg对象一无所知,即使事件得到处理,GC也可以删除miDg对象。
您可以使用下一个链接下载测试项目,以证明此行为。它还演示了如何通过处理事件来复制内存泄漏问题。
答案 1 :(得分:1)
当我关闭用户控件时,将重新选择附加属性,因为没有对象引用它。
这是错误的。如果删除取消注册事件的代码,则使用附加属性的任何控件都将永久存在。为什么?因为您注册的事件处理程序是静态的。这意味着控件将包含对静态的引用,以防止垃圾收集器收集它。
此问题的第一个可能解决方案是在注册事件时使用弱事件模式。出于上述原因,我在为自己的附加属性注册事件时总是使用弱事件模式。
这个解决方案的烦人之处在于它需要相当大量的样板代码。您必须为每种新类型的事件创建一个新的WeakEventManager
实现。然后,要接收弱事件,您必须实现一个接口(编辑:除非您使用的是.NET 4.5或更高版本),这意味着您不能拥有静态处理程序。那么你需要实现IWeakEventListner
接口的类,并在附加的属性事件中创建和管理该类的实例。
因此,我建议您使用的解决方案是实际上对DataGrid
类进行子类化,并将此功能添加为普通依赖项属性。如果你这样做,你根本不必注册事件(你可以覆盖受保护的方法),并且不用担心潜在的内存泄漏。我推荐这个解决方案的原因是因为根据我的经验,我需要覆盖DataGrid
类有很多其他原因,其中许多可以使用附加属性来实现,但其中一些不能。
真正的问题是WPF DataGrid实现相当不完整(我的个人意见)。存在我不喜欢的错误,默认行为,以及不完整或未实现的功能(例如支持复制,但不支持粘贴;或者我认为您尝试解决的特定问题:可绑定的SelectedItems)。通过简单地继承DataGrid,可以最轻松地解决所有这些问题。