【问题标题】:WPF TreeView leaking the selected itemWPF TreeView 泄漏所选项目
【发布时间】: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/…

标签: c# .net wpf memory memory-leaks


【解决方案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 中没有更多对象,并且该对象将一直保留到应用程序关闭为止。无论如何,谢谢你的回答,我无法解决我的问题,但至少,你证实了我的想法。
    猜你喜欢
    • 2016-11-20
    • 2021-12-22
    • 2010-11-05
    • 1970-01-01
    • 2015-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多