Completely Remove a DataGrid and All its Bindings

Aug 27, 2009 at 3:40 PM

AKA: "Die, DataGrid, Die!"

In short:

How can I completely remove a DataGrid, including any bindings it may have?

In full:

My project consists of a TabControl, which the user can add tabs to or remove tabs from. Each tab contains its own DataGrid. When a tab is removed it and its contents are removed permanently, so its DataGrid and any bindings, etc. should permanently cease to exist.

However, I cannot get this to happen. After removing a tab, if I change any property to which the DataGrid was bound, the bound value updates normally - obviously you cannot see it normally, but the code passes through any associated value converter and there you can see that all the bindings (it is a MultiBinding) are able to find their target properties - even if those target properties should have been destroyed with the rest of the TabItem's content.

I am experiencing this with the Binding properties of DataGridColumns (e.g. the text of a DataGridTextColumn), hence why I ask here. That said, I fear it may well apply to other bound values, but it was easier for me to create a DataGrid testbed than a simpler one as the bulk of my work with data binding has been using DataGrid.

Full code given. To see the problem, run the code and remove any existing tabs. Put a breakpoint at the start of NameConverter, then toggle the checkbox (sorry, I should have given this some text). You can see that not only does the code still enter this converter once for each item that has even been displayed, but it also has full access to data in items in IC, which also should no longer exist.

In my real project I have played with removing events (such as PropertyChanged) and clearing and nulling the ObservableCollection when the TabItem is removed, however I have left such steps out here both for clarity and as they appeared to make no difference.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;

namespace BadBinding
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window, INotifyPropertyChanged
    {
        private Boolean japanese = true;

        public Boolean Japanese
        {
            get { return japanese; }
            set
            {
                if (japanese != value)
                {
                    japanese = value;
                    OnNotify("Japanese");
                }
            }
        }

        public Window1()
        {
            InitializeComponent();
        }

        private void addBtn_Click(Object sender, RoutedEventArgs e)
        {
            TabItem ti = new TabItem();
            ti.Content = new TestDataGrid();
            ti.Header = (tc.Items.Count + 1).ToString();
            tc.Items.Add(ti);
            tc.SelectedItem = ti;
        }

        private void removeBtn_Click(Object sender, RoutedEventArgs e)
        {
            try
            {
                TabItem ti = (TabItem)tc.Items[tc.Items.Count - 1];
                tc.Items.Remove(ti);
                ti = null;
                GC.Collect();
            }
            catch
            {

            }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnNotify(String parameter)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(parameter));
            }
        }

        #endregion
    }
}
<Window x:Class="BadBinding.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BadBinding"
    xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
    x:Name="window"
    Title="Window1" Height="300" Width="300">
    <StackPanel>
        <CheckBox x:Name="japaneseCB" IsChecked="{Binding ElementName=window, Path=Japanese}"/>
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
            <Button x:Name="addBtn" Grid.Column="0" Content="Add" Click="addBtn_Click"/>
            <Button x:Name="removeBtn" Grid.Column="1" Content="Remove" Click="removeBtn_Click"/>
        </Grid>
        <TabControl x:Name="tc">
            <TabItem Header="Default">
                <local:TestDataGrid/>
            </TabItem>
        </TabControl>
    </StackPanel>
</Window>
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Controls;
using System.Windows.Data;

namespace BadBinding
{
    /// <summary>
    /// Interaction logic for TestDataGrid.xaml
    /// </summary>
    public partial class TestDataGrid : UserControl, INotifyPropertyChanged
    {
        private ObservableCollection<Bob> oc = new ObservableCollection<Bob>();

        public ObservableCollection<Bob> OC
        {
            get { return oc; }
            set
            {
                if (oc != value)
                {
                    oc = value;
                    OnNotify("OC");
                }
            }
        }

        public TestDataGrid()
        {
            InitializeComponent();
            OC.Add(new Bob("0"));
            OC.Add(new Bob("1"));
            OC.Add(new Bob("2"));
            OC.Add(new Bob("3"));
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnNotify(String parameter)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(parameter));
            }
        }

        #endregion
    }

    public class Bob : INotifyPropertyChanged
    {
        private String name;

        public String Name
        {
            get { return name; }
            set
            {
                if (name != value)
                {
                    name = value;
                    OnNotify("Name");
                }
            }
        }

        public Bob(String name)
        {
            Name = name;
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnNotify(String parameter)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(parameter));
            }
        }

        #endregion
    }

    public class NameConverter : IMultiValueConverter
    {
        #region IValueConverter Members

        public Object Convert(Object[] values, Type targetType, Object parameter, System.Globalization.CultureInfo culture)
        {
            String str = "-";

            if ((Boolean)values[1])
            {
                switch ((String)values[0])
                {
                    case "1":
                        str = "ichi";
                        break;
                    case "2":
                        str = "ni";
                        break;
                    case "3":
                        str = "san";
                        break;
                    case "4":
                        str = "shi";
                        break;
                    default:
                        str = "shirimasen";
                        break;
                }
            }
            else
            {
                switch ((String)values[0])
                {
                    case "1":
                        str = "one";
                        break;
                    case "2":
                        str = "two";
                        break;
                    case "3":
                        str = "three";
                        break;
                    case "4":
                        str = "four";
                        break;
                    default:
                        str = "iunno";
                        break;
                }
            }

            return str;
        }

        public Object[] ConvertBack(Object value, Type[] targetTypes, Object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}
<UserControl x:Class="BadBinding.TestDataGrid"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:BadBinding"
    xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit"
    x:Name="tdg"
    Height="300" Width="300">

    <UserControl.Resources>
        <local:NameConverter x:Key="nameConverter"/>
    </UserControl.Resources>

    <dg:DataGrid x:Name="dg" ItemsSource="{Binding ElementName=tdg, Path=OC}" AutoGenerateColumns="False">
        <dg:DataGrid.Columns>
            <dg:DataGridTextColumn Header="Name">
                <dg:DataGridTextColumn.Binding>
                    <MultiBinding Converter="{StaticResource nameConverter}">
                        <Binding Path="Name"/>
                        <Binding RelativeSource="{RelativeSource AncestorType={x:Type local:Window1}}" Path="Japanese"/>
                    </MultiBinding>
                </dg:DataGridTextColumn.Binding>
            </dg:DataGridTextColumn>
        </dg:DataGrid.Columns>
    </dg:DataGrid>

</UserControl>
Aug 27, 2009 at 4:18 PM

Okay, it turns out this applies to binding on even the simplest controls, so I will go ask the question on MSDN. (Although if anybody here would care to post the answer... ;) )