WPF TreeView 泄漏所选项目
Posted
技术标签:
【中文标题】WPF TreeView 泄漏所选项目【英文标题】:WPF TreeView leaking the selected item 【发布时间】:2012-09-25 05:25:54 【问题描述】:我目前有一个奇怪的 WPF TreeView 内存泄漏。当我在 TreeView 中选择一个项目时,对应的绑定 ViewModel 强烈地保存在 TreeView EffectiveValueEntry[] 集合中。问题是当 ViewModel 从它的父集合中移除时它没有被释放。
这是重现问题的简单代码:
MainWindow.xaml
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls.Primitives;
namespace TreeViewMemoryLeak
public partial class MainWindow : Window
public MainWindow()
InitializeComponent();
DataContext = this;
public ObservableCollection<Entry> Entries
get
if (entries == null)
entries = new ObservableCollection<Entry>() new Entry() DisplayName = "First Entry" ;
return entries;
private void Button_Click(object sender, RoutedEventArgs e) entries.Clear();
private ObservableCollection<Entry> entries;
public class Entry : DependencyObject
public string DisplayName get; set;
MainWindow.xaml.cs
<Window x:Class="TreeViewMemoryLeak.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TreeViewMemoryLeak"
Title="MainWindow" Height="350" Width="250">
<Window.Resources>
<DataTemplate DataType="x:Type local:Entry">
<TextBlock Text="Binding DisplayName" />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="delete item" Click="Button_Click" Grid.Row="0" Margin="10"/>
<TreeView x:Name="treeView" ItemsSource="Binding Entries" Grid.Row="1" Margin="10" BorderBrush="Black" BorderThickness="1" />
</StackPanel>
</Window>
重现问题
选择项目,然后单击按钮清除 ObservableCollection。现在检查 TreeView 控件上的 EffectiveValueEntry[]:ViewModel 仍然存在并且没有标记为垃圾回收。
【问题讨论】:
您使用的是什么 .Net 版本? 我遇到了 .NET 3.5 和 4.0 的问题。我完全忘了提,对不起。我现在用 4.5 进行测试。 .NET 4.5 仍然存在问题 我对 TreeView 有同样的问题。我在 Microsoft Connect 上发布了一个错误条目:connect.microsoft.com/VisualStudio/feedback/details/778557/… 【参考方案1】:我遇到了同样的问题,并使用link
(由 Tom Goff 发布)上的解决方案之一解决了它。执行以下操作:
ClearSelection(this.treeView);
this.treeView.SelectedValuePath = ".";
this.treeView.ClearValue(TreeView.SelectedValuePathProperty);
this.treeView.ItemsSource = null;
...
public static void ClearSelection(TreeView treeView)
if (treeView != null)
ClearSelection(treeView.Items, treeView.ItemContainerGenerator);
private static void ClearSelection(ItemCollection collection, ItemContainerGenerator generator)
if ((collection != null) && (generator != null))
for (int i = 0; i < collection.Count; i++)
TreeViewItem treeViewItem = generator.ContainerFromIndex(i) as TreeViewItem;
if (treeViewItem != null)
ClearSelection(treeViewItem.Items, treeViewItem.ItemContainerGenerator);
treeViewItem.IsSelected = false;
【讨论】:
【参考方案2】:好吧,我终于想出了一个相当暴力的解决方案。在删除 TreeView 中的最后一个对象时,我自己从 EffectiveValues 集合中删除了引用。这可能有点矫枉过正,但至少它有效。
public class MyTreeView : TreeView
protected override void OnSelectedItemChanged(RoutedPropertyChangedEventArgs<object> e)
base.OnSelectedItemChanged(e);
if (Items.Count == 0)
var lastObjectDeleted = e.OldValue;
if (lastObjectDeleted != null)
var effectiveValues = EffectiveValuesGetMethod.Invoke(this, null) as Array;
if (effectiveValues == null)
throw new InvalidOperationException();
bool foundEntry = false;
int index = 0;
foreach (var effectiveValueEntry in effectiveValues)
var value = EffectiveValueEntryValueGetMethod.Invoke(effectiveValueEntry, null);
if (value == lastObjectDeleted)
foundEntry = true;
break;
index++;
if (foundEntry)
effectiveValues.SetValue(null, index);
protected MethodInfo EffectiveValueEntryValueGetMethod
get
if (effectiveValueEntryValueGetMethod == null)
var effectiveValueEntryType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes()).Where(t => t.Name == "EffectiveValueEntry").FirstOrDefault();
if (effectiveValueEntryType == null)
throw new InvalidOperationException();
var effectiveValueEntryValuePropertyInfo = effectiveValueEntryType.GetProperty("Value", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValueEntryValuePropertyInfo == null)
throw new InvalidOperationException();
effectiveValueEntryValueGetMethod = effectiveValueEntryValuePropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValueEntryValueGetMethod == null)
throw new InvalidOperationException();
return effectiveValueEntryValueGetMethod;
protected MethodInfo EffectiveValuesGetMethod
get
if (effectiveValuesGetMethod == null)
var dependencyObjectType = typeof(DependencyObject);
var effectiveValuesPropertyInfo = dependencyObjectType.GetProperty("EffectiveValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.DeclaredOnly | System.Reflection.BindingFlags.Instance);
if (effectiveValuesPropertyInfo == null)
throw new InvalidOperationException();
effectiveValuesGetMethod = effectiveValuesPropertyInfo.GetGetMethod(nonPublic: true);
if (effectiveValuesGetMethod == null)
throw new InvalidOperationException();
return effectiveValuesGetMethod;
#region Private fields
private MethodInfo effectiveValueEntryValueGetMethod;
private MethodInfo effectiveValuesGetMethod;
#endregion
【讨论】:
我们发现在effectiveValues[index + 1]
有一个BindingExpression
也引用了lastObjectDeleted
(即((BindingExpression)value).ParentBinding.Source == lastObjectDeleted
),所以我们也删除了它。【参考方案3】:
这是因为您将树视图绑定到OneTime
模式,所以您的收藏被“快照”了。如前所述:
更新:
EffectiveValueEntry
是关于DependencyObjects
如何存储DependencyProperties
的值。只要 treeView 选择了项目,这个集合就会保存对象。只要您选择其他内容,集合就会更新。
【讨论】:
实际上,“OneTime”绑定是解决我在另一个线程上发现的问题的失败尝试。删除它不会改变任何东西,也不是问题的原因。编辑:我将尝试使用 OneWay 绑定来查看问题是否已解决。 EDIT2:不起作用,条目仍然存在。 是的,它是关于依赖属性存储的。如果选择了最后一个对象,则删除最后一个对象时不会释放内存,这很烦人。在这种情况下,treeView 中没有更多对象,并且该对象将一直保留到应用程序关闭为止。无论如何,谢谢你的回答,我无法解决我的问题,但至少,你证实了我的想法。以上是关于WPF TreeView 泄漏所选项目的主要内容,如果未能解决你的问题,请参考以下文章
带有 ExtendedSelection 的 Qml TreeView 中的 selectedRows() 落后了一步
bootstrap treevie只展开一个节点,关闭其他节点