2012-10-04 17 views
10

Mam obecnie dziwny wyciek pamięci z TreeView WPF. Kiedy wybieram element w TreeView, odpowiedni związany ViewModel jest mocno trzymany w kolekcji TreeView EffectiveValueEntry []. Problem polega na tym, że nie jest on wypuszczany, gdy ViewModel zostanie usunięty z jego kolekcji nadrzędnej.WPF TreeView wyciek z wybranego elementu

Oto prosty kod do odtworzenia problemu:

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> 

Aby odtworzyć problem

Wybierz element, a następnie kliknij przycisk, aby wyczyścić ObservableCollection. Teraz sprawdź EffectiveValueEntry [] na formantu TreeView: ViewModel nadal tam jest i nie jest oflagowany do usuwania śmieci.

+0

Co .Net wersja używasz? – JleruOHeP

+0

Mam problem z .NET 3.5 i 4.0. Całkowicie zapomniałem o tym wspomnieć, przepraszam. Będę testować z 4.5 już teraz. – Sisyphe

+1

Problem nadal występuje w .NET 4.5 – Sisyphe

Odpowiedz

3

W końcu wpadłem na dość gwałtowne rozwiązanie. Usuwam odwołanie z kolekcji EffectiveValues ​​podczas usuwania ostatniego obiektu w TreeView. Może to być przesada, ale przynajmniej działa.

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

Znaleźliśmy, że w 'effectiveValues ​​[index + 1]' było 'BindingExpression', które również odnosiło się do' lastObjectDeleted' (tj. '((BindingExpression) value. ParentBinding.Source == lastObjectDeleted'), więc również to usunęliśmy. –

1

To dlatego, że zbindowanych swoją katalogów z trybem OneTime, więc kolekcja została „snapshotted”. Jak stwierdził:

AKTUALIZACJA:

EffectiveValueEntry jest o tym, jak DependencyObjects przechowywać wartości ich DependencyProperties. Ta kolekcja będzie przechowywać obiekt tak długo, jak treeView wybrał element. Jak tylko wybierzesz coś innego, kolekcja zostanie zaktualizowana.

+0

Właściwie " Wiązanie OneTime było nieudaną próbą rozwiązania problemu, który znalazłem w innym wątku. Usunięcie go nie zmienia niczego i nie jest przyczyną problemu. EDYCJA: spróbuję powiązania OneWay, aby sprawdzić, czy problem został rozwiązany. EDIT2: Nie działa, wpis wciąż żyje. – Sisyphe

+0

Zaktualizowałem oryginalny wpis, aby usunąć powiązanie OneTime. – Sisyphe

+0

Tak, chodzi o przechowywanie własności zależnej. Jest to po prostu denerwujące, że pamięć nie jest zwalniana, gdy ostatni obiekt zostanie usunięty, jeśli został wybrany. W takim przypadku nie mam już żadnych obiektów w treeView, a obiekt będzie utrzymywany do czasu zamknięcia aplikacji. W każdym razie dziękuję za odpowiedź, nie mogę rozwiązać mojego problemu, ale przynajmniej potwierdziłeś, o czym myślałem. – Sisyphe

1

miałem ten sam problem i rozwiązać go za pomocą jednego z rozwiązań w tej link (Wysłany przez Toma Goff). Wykonaj następujące czynności:

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; 
      } 
     } 
    } 
} 
Powiązane problemy