julian bechtold
julian bechtold

Reputation: 2251

clearing child controls from wrappanel does not clear memory and references

Issue

I have a wrappanel which is beeing used to apply tags to an image. Tags can be added and removed: enter image description here

The problem is the following: Upon removing an element, the memory is not released and also never garbage collected.

this is how I add the Control:

foreach (MetadataAttribute attribute in metadata.attributes)
{
    this.Attributes_StackPanel.Children.Add(new Attribute(attribute));
}

this is how i removed the elements initially:

this.Attributes_StackPanel.Children.Clear();

What I tried

I found multiple resources and questions about the topic, but none of them seemed to work for me:

I wandered through posts until I ended with an abomination like this:

parent:

/// <summary>
/// clears the attribute panel to prepare for new metadata to be loaded
/// </summary>
private void ClearAttributesPanel()
{
    for(int i = this.Attributes_StackPanel.Children.Count-1; i > 0; i--)
    {

        Attribute attr = (Attribute)this.Attributes_StackPanel.Children[i];
        attr.Delete();
        attr = null;
    }
}

xaml:

<UserControl x:Class="Minter_UI.Attribute"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:Minter_UI"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800" Padding="0.1cm, 0.1cm, 0, 0">
    <Grid x:Name="Main_Grid">
        <Grid.ColumnDefinitions>
            <ColumnDefinition MinWidth="1cm"></ColumnDefinition>
            <ColumnDefinition Width="0.5cm"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <StackPanel x:Name="Stack_Panel"  Orientation="Vertical" Grid.Row="0">
            <ComboBox x:Name="TraitType_ComboBox" IsEditable="True" Text="TraitType" SelectionChanged="TraitType_ComboBox_SelectionChanged"></ComboBox>
            <ComboBox x:Name="Value_ComboBox" IsEditable="True" Text="Value"></ComboBox>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition></RowDefinition>
                    <RowDefinition></RowDefinition>
                </Grid.RowDefinitions>
                <Label Content="MinValue" Grid.Column="0"></Label>
                <Label Content="MaxValue" Grid.Column="1"></Label>
                <TextBox x:Name="MinValue_TextBox" Grid.Row="1" Grid.Column="0"></TextBox>
                <TextBox x:Name="MaxValue_TextBox" Grid.Row="1" Grid.Column="1"></TextBox>
            </Grid>
            
        </StackPanel>
        
        <Button x:Name="Delete_Button" Content="X" Background="LightCoral" Grid.Column="1" Click="Delete_Button_Click"></Button>
    </Grid>
</UserControl>

Attribute delete:

public void Delete()
{
    // unregister events
    this.TraitType_ComboBox.SelectionChanged -= this.TraitType_ComboBox_SelectionChanged;
    this.Delete_Button.Click -= this.Delete_Button_Click;
    // main grid
    this.Main_Grid.Children.Clear();
    this.Main_Grid = null;
    // stack panel
    this.Stack_Panel.Children.Clear();
    this.Stack_Panel = null;
    // textboxes
    this.MinValue_TextBox.Text = null;
    this.MinValue_TextBox = null;
    this.MaxValue_TextBox.Text= null;
    this.MaxValue_TextBox = null;
    //comboboxes
    this.TraitType_ComboBox.ItemsSource= null;
    this.TraitType_ComboBox.SelectedItem= null;
    this.TraitType_ComboBox.Text= null;
    this.TraitType_ComboBox = null;
    this.Value_ComboBox.ItemsSource = null;
    this.Value_ComboBox.SelectedItem = null;
    this.Value_ComboBox.Text = null;
    this.Value_ComboBox = null;
    // delete button
    this.Delete_Button.Content = null;
    this.Delete_Button = null;
    // clear all x:name properties
    this.UnregisterName("Stack_Panel");
    this.UnregisterName("MinValue_TextBox");
    this.UnregisterName("MaxValue_TextBox");
    this.UnregisterName("TraitType_ComboBox");
    this.UnregisterName("Value_ComboBox");
    this.UnregisterName("Main_Grid");
    this.UnregisterName("Delete_Button");
    // remove self from parent
    ((Panel)this.Parent).Children.Remove(this);
}

Still, this doesnt work. I can find thousands of references to the comboboxes and textpanels as well as hashtables in heap after enough reloads. After a while, the garbage collector goes crazy but never actually clears any elements.

Upvotes: 1

Views: 209

Answers (1)

julian bechtold
julian bechtold

Reputation: 2251

I dont kow if this is the way to do it but it works:

  1. add a function to repopulate the properties to the child control xaml.cs:
public void SetAttribute(MetadataAttribute attribute)
{
    // you need to extend the function, its just an example
    this.TraitType_ComboBox.Text = attribute.trait_type.ToString();
    this.Value_ComboBox.Text = [email protected]();
    this.MinValue_TextBox.Text = attribute.min_value.ToString();
    this.MaxValue_TextBox.Text = attribute.max_value.ToString();
}
  1. to the parent which holds the collection, add a queue and adjust the remove function to add the elements to the queue instead of destroying them:
Queue<Attribute> AttributeReuseElements = new Queue<Attribute>();
private void ClearAttributesPanel()
{
    for(int i = this.Attributes_StackPanel.Children.Count-1; i > 0; i--)
    {
        AttributeReuseElements.Enqueue((Attribute)this.Attributes_StackPanel.Children[i]);
        this.Attributes_StackPanel.Children.RemoveAt(i);
    }
}
  1. upon loading of the elements, recycle the previously removed elements from the queue:
foreach (MetadataAttribute attribute in CollectionInformation.Information.LikelyAttributes)
{
    if (AttributeReuseElements.Count > 0)
    {
        Attribute attr = AttributeReuseElements.Dequeue();
        attr.SetAttribute(attribute);
        this.Attributes_StackPanel.Children.Add(attr);
    }
    else
    {
        this.Attributes_StackPanel.Children.Add(new Attribute(attribute));
    }
}

This will not neglect the memory leak from manually pressing the delete button of the control but this will likely be neglible. Whenever the information is reloaded however, elements get recycled instead of destroyed and created freshly.

This might have some implications if you do not make sure all fields and values are properly cleared. For example you might find values from the previus property.

Upvotes: 0

Related Questions