views:

25

answers:

1

I have a data bound UI. When I change the underlying data the UI updates fine but the controls appear to remain subscribed to my PropertyChanged events.

I have the following:

  • An ItemsControl bound to ListA

  • Each item ListA contains a sub list ListB that is displayed using another ItemsControl

  • Each item ListB is displayed using a TextBox bound to a string property via INotifyPropertyChanged

Here is the XAML:

<UserControl x:Class="SilverlightBindingTest.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;

    <UserControl.Resources>

        <DataTemplate x:Key="DataTempl" >
            <TextBox Text="{Binding Value, Mode=TwoWay}"/>
        </DataTemplate>

        <DataTemplate x:Key="PageTempl" >
            <ItemsControl ItemsSource="{Binding Datas}" ItemTemplate="{StaticResource DataTempl}"></ItemsControl>
        </DataTemplate>

    </UserControl.Resources>
    <Grid x:Name="LayoutRoot" Background="White">
        <StackPanel>
            <ItemsControl ItemsSource="{Binding Pages}" ItemTemplate="{StaticResource PageTempl}"></ItemsControl>
            <Button Content="Swap list" Click="ButtonClick1"></Button>
            <Button Content="Change base data" Click="ButtonClick2"></Button>
        </StackPanel>
    </Grid>
</UserControl>

Here is the code:

using System.ComponentModel;
using System.Diagnostics;
using System.Windows;
using System.Collections.Generic;

namespace SilverlightBindingTest
{
   public partial class MainPage : INotifyPropertyChanged
   {
      private List<DataGroupClass> pages1 = new List<DataGroupClass>();
      private List<DataGroupClass> pages2 = new List<DataGroupClass>();

      public MainPage()
      {
         InitializeComponent();
         List<DataClass> group = new List<DataClass>();
         group.Add(new DataClass("value 1"));
         group.Add(new DataClass("value 2"));
         group.Add(new DataClass("value 3"));
         pages1.Add(new DataGroupClass(group));

         group = new List<DataClass>();
         group.Add(new DataClass("value 4"));
         group.Add(new DataClass("value 5"));
         group.Add(new DataClass("value 6"));
         pages2.Add(new DataGroupClass(group));
         DataContext = this;
      }

      private List<DataGroupClass> pages = new List<DataGroupClass>();
      public List<DataGroupClass> Pages
      {
         get { return pages; }
         set
         {
            pages = value;
            PropertyChangedEventHandler h = PropertyChanged;
            if (h != null)
            {
               h(this, new PropertyChangedEventArgs("Pages"));
            }
         }
      }

      private void ButtonClick1(object sender, RoutedEventArgs e)
      {
         Debug.WriteLine("-------------------\n");
         if (Pages == pages1)
         {
            Pages = pages2;
         }
         else
         {
            Pages = pages1;
         }
      }

      private bool toggle;
      private void ButtonClick2(object sender, RoutedEventArgs e)
      {
         if (toggle)
         {
            pages1[0].Datas[0].Value = "tim";
            pages2[0].Datas[0].Value = "lajos";
         }
         else
         {
            pages1[0].Datas[0].Value = "joe";
            pages2[0].Datas[0].Value = "james";
         }
         toggle = !toggle;
      }

      #region INotifyPropertyChanged Members

      public event PropertyChangedEventHandler PropertyChanged;

      #endregion
   }

   public class DataClass : INotifyPropertyChanged
   {
      private string value;
      public string Value
      {
         get
         {
            Debug.WriteLine("Get Value:" + value);
            return value;
         }
         set
         {
            Debug.WriteLine("Set Value: " + this.value + " => " + value);
            this.value = value;
            PropertyChangedEventHandler h = PropertyChanged;
            if (h != null)
            {
               h(this, new PropertyChangedEventArgs("Value"));
            }
         }
      }

      public DataClass(string value)
      {         
         Value = value;
      }

      public event PropertyChangedEventHandler PropertyChanged;
   }

   public class DataGroupClass
   {
      private List<DataClass> datas;
      public List<DataClass> Datas
      {
         get
         {
            Debug.WriteLine("Get Datas");
            return datas;
         }
         set
         {
            Debug.WriteLine("Set Datas");
            datas = value;
         }
      }

      public DataGroupClass(List<DataClass> datas)
      {         
         Datas = datas;
      }
   }
}

Here is the trace output:

Click "Swap list" ten times (all looks OK):

 -------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3
-------------------

 Get Datas
 Get Value:value 4
 Get Value:value 5
 Get Value:value 6
-------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3
-------------------

 Get Datas
 Get Value:value 4
 Get Value:value 5
 Get Value:value 6
 -------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3
 -------------------

 Get Datas
 Get Value:value 4
 Get Value:value 5
 Get Value:value 6
 -------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3
 -------------------

 Get Datas
 Get Value:value 4
 Get Value:value 5
 Get Value:value 6
 -------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3
 -------------------

 Get Datas
 Get Value:value 4
 Get Value:value 5
 Get Value:value 6
 -------------------

 Get Datas
 Get Value:value 1
 Get Value:value 2
 Get Value:value 3

Click "Change base data" (See multiple calls into Get Value):

    -------------------

    Get Datas 
    Get Value:value 1 
    Get Value:value 2 
    Get Value:value 3 
    Get Datas 
    Set Value: value 1 => joe 
    Get Value:joe  
    Get Value:joe 
    Get Value:joe 
    Get Value:joe 
    Get Value:joe 
    Get Value:joe 
    Get Datas 
    Set Value: value 4 => james 
    Get Value:james 
    Get Value:james 
    Get Value:james 
    Get Value:james 
    Get Value:james 

I assume I have missed something out but what?

Any help greatly appreciated!

+1  A: 

Nope you haven't missed something out. There are still some UI elements that remain uncollected by the garbage collector at the time you press "change base data". The UI elements still hold on to their binding expression which in turn holds on the property changed event.

Eventually those unused UI elements will be collected and will cease to listen to the property changed events.

You can prove this (all though I certainly wouldn't recommend that you do this real code) by placing GC.Collect() at the end of your button1 click code. By forcing the collection you'll see you never get more than one read of the Value property.

AnthonyWJones