2012-10-04 16 views
10

現在、WPF TreeViewで奇妙なメモリリークが発生しています。 TreeViewでアイテムを選択すると、対応するバインドされたViewModelがTreeView EffectiveValueEntry []コレクションで強く保持されます。問題は、ViewModelが親コレクションから削除されたときに解放されないことです。ここでWPFツリービューで選択したアイテムが漏洩しています

は、問題を再現するための簡単なコードです:

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はまだ存在し、ガベージコレクションのフラグが設定されていません。

+0

あなたは何の.Netバージョンを使用している次のようにありますか? – JleruOHeP

+0

私は.NET 3.5と4.0で問題があります。私はそれについて言及することを完全に忘れました、申し訳ありません。私は今すぐ4.5でテストします。 – Sisyphe

+1

.NET 4.5にはまだ問題があります – Sisyphe

答えて

3

まあ私はやっと暴力的な解決策を思いついた。 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 
} 
+0

'effectiveValues [index + 1]'には、 'lastObjectDeleted'(つまり(BindingExpression)value).ParentBinding.Source == lastObjectDeletedも参照する' BindingExpression'があるので、それも削除しました。 –

1

あなたのツリービューをOneTimeモードでバインドしたため、あなたのコレクションは「スナップショット」されています。述べたように:UPDATED

EffectiveValueEntryDependencyObjectsが自分DependencyPropertiesの値を格納する方法についてです。このコレクションは、treeViewがアイテムを選択している限り、オブジェクトを保持します。あなたが何かを選択するとすぐにコレクションが更新されます。

+0

実際には、 OneTime "バインディングは、別のスレッドで見つかった問題を解決しようとして失敗しました。それを削除しても何も変わらず、問題の原因でもありません。編集:OneWayバインディングを試して、問題が解決したかどうかを確認します。 EDIT2:動作しません、エントリはまだ生きています。 – Sisyphe

+0

元の投稿を更新してOneTimeバインディングを削除しました。 – Sisyphe

+0

はい、それは依存関係プロパティーストレージです。最後のオブジェクトが選択されていた場合、そのオブジェクトが削除されたときにメモリが解放されないのは面倒です。この場合、ツリービューにはオブジェクトがなくなり、オブジェクトはアプリケーションが閉じられるまで保持されます。とにかくあなたの答えをありがとう、私は私の問題を解決することはできませんが、少なくとも、あなたは私が考えていたことを確認した。 – Sisyphe

1

私は同じ問題を持っていたし、(トム・ゴフにより投稿)このlink上のソリューションのいずれかを使用して、それを解決しました。

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; 
      } 
     } 
    } 
} 
関連する問題