R – Silverlight Relating Two Datagrids

datagridsilverlightsilverlight-3.0

My question: How do I bind the SelectedItem from a primary datagrid to the ItemsSource for a secondary datagrid?

In detail:
I have two datagrids on my view. The first shows a collection of teams and the second shows as list of people in the selected team.

When I select a team from the grid I can see that the SelectedTeam property is getting updated correctly, but the People grid is not getting populated.

Note: I am not able to use nested grids, or the cool master-detail features provided in the SL data-grid.

UPDATE: Replacing the parent datagrid with a ComboBox gives completely different results and works perfectly. Why would ComboBox.SelectedItem and DataGrid.SelectedItem behave so differently?

Thanks,
Mark


Simple Repro:

VIEW:

<UserControl x:Class="NestedDataGrid.MainPage"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             mc:Ignorable="d"
             xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data">
    <StackPanel x:Name="LayoutRoot">
        <TextBlock Text="Teams:" />
        <data:DataGrid ItemsSource="{Binding Teams}"
                       SelectedItem="{Binding SelectedTeam, Mode=TwoWay}"
                       AutoGenerateColumns="False">
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Id" Binding="{Binding TeamId}" />
                <data:DataGridTextColumn Header="Desc" Binding="{Binding TeamDesc}" />
            </data:DataGrid.Columns>
        </data:DataGrid>
        <TextBlock Text="Peeps:" />
        <data:DataGrid ItemsSource="{Binding SelectedTeam.People}"
                       AutoGenerateColumns="False">
            <data:DataGrid.Columns>
                <data:DataGridTextColumn Header="Id"
                                         Binding="{Binding PersonId}" />
                <data:DataGridTextColumn Header="Name"
                                         Binding="{Binding Name}" />
            </data:DataGrid.Columns>
        </data:DataGrid>
    </StackPanel>
</UserControl>

CODE_BEHIND:

using System.Windows.Controls;
namespace NestedDataGrid
{
    public partial class MainPage : UserControl
    {
        public MainPage()
        {
            InitializeComponent();
            this.LayoutRoot.DataContext = new ViewModel();
        }

    }
}

VIEWMODEL:

using System.Collections.ObjectModel;
namespace NestedDataGrid
{
    public class ViewModel: ObjectBase
    {
        public ViewModel()
        {
            ObservableCollection<Person> RainbowPeeps = new ObservableCollection<Person>()
            {
                new Person(){ PersonId=1, Name="George"}, 
                new Person(){ PersonId=2, Name="Zippy"}, 
                new Person(){ PersonId=3, Name="Bungle"}, 
            };

            ObservableCollection<Person> Simpsons = new ObservableCollection<Person>()
            {
                new Person(){ PersonId=4, Name="Moe"}, 
                new Person(){ PersonId=5, Name="Barney"}, 
                new Person(){ PersonId=6, Name="Selma"}, 
            };

            ObservableCollection<Person> FamilyGuyKids = new ObservableCollection<Person>()
            {
                new Person(){ PersonId=7, Name="Stewie"}, 
                new Person(){ PersonId=8, Name="Meg"}, 
                new Person(){ PersonId=9, Name="Chris"}, 
            };


            Teams = new ObservableCollection<Team>()
            {
                new Team(){ TeamId=1, TeamDesc="Rainbow", People=RainbowPeeps},
                new Team(){ TeamId=2, TeamDesc="Simpsons", People=Simpsons},
                new Team(){ TeamId=3, TeamDesc="Family Guys", People=FamilyGuyKids },
            };
        }


        private ObservableCollection<Team> _teams;
        public ObservableCollection<Team> Teams
        {
            get { return _teams; }
            set
            {
                SetValue(ref _teams, value, "Teams");
            }
        }


        private Team _selectedTeam;
        public Team SelectedTeam
        {
            get { return _selectedTeam; }
            set
            {
                SetValue(ref _selectedTeam, value, "SelectedTeam");
            }
        }


    }
}

ASSOCIATED CLASSES:

using System;
using System.ComponentModel;

namespace NestedDataGrid
{
    public abstract class ObjectBase : Object, INotifyPropertyChanged
    {
        public ObjectBase()
        { }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void _OnPropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler pceh = PropertyChanged;
            if (pceh != null)
            {
                pceh(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected virtual bool SetValue<T>(ref T target, T value, string propertyName)
        {
            if (Object.Equals(target, value))
            {
                return false;
            }

            target = value;
            _OnPropertyChanged(propertyName);

            return true;
        }

    }


    public class Person: ObjectBase
    {
        private int  _personId;
        public int PersonId
        {
            get { return _personId; }
            set
            {
                SetValue(ref _personId, value, "PersonId");
            }
        }

        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                SetValue(ref _name, value, "Name");
            }
        }

    }

    public class Team : ObjectBase
    {

        private int _teamId;
        public int TeamId
        {
            get { return _teamId; }
            set
            {
                SetValue(ref _teamId, value, "TeamId");
            }
        }

        private string _teamDesc;
        public string TeamDesc
        {
            get { return _teamDesc; }
            set
            {
                SetValue(ref _teamDesc, value, "TeamDesc");
            }
        }


        private ObservableCollection<Person> _people;
        public ObservableCollection<Person> People
        {
            get { return _people; }
            set
            {
                SetValue(ref _people, value, "People");
            }
        }

    }
}

UPDATE

Replacing the first datagrid with a combobox and eveything works OK. Why would DataGrid.SelectedItem and ComboBox.SelectedItem behave so differently?

<StackPanel x:Name="LayoutRoot">        
    <TextBlock Text="Teams:" />
    <ComboBox SelectedItem="{Binding SelectedTeam, Mode=TwoWay}"
                    ItemsSource="{Binding Teams}"/>
    <TextBlock Text="{Binding SelectedTeam}" />    
    <TextBlock Text="Peeps:" />
    <data:DataGrid ItemsSource="{Binding SelectedTeam.People}" />    
</StackPanel>

Best Answer

Having done some tests.

First I just wanted to confirm that the Binding itself is working. It works quite happly when the second DataGrid is swapped out for a ListBox. I've gone so far to confirm that the second DataGrid is having its ItemsSource property changed by the binding engine.

I've also swapped out the first DataGrid for a ListBox and then the second DataGrid starts working quite happly.

In addition if you wire up the SelectionChanged event on the first datagrid and use code to assign directly to the second datagrid it starts working.

I've also removed the SelectedItem binding on the first Grid and set up an ElementToElement bind to it from the on the ItemsSource property of the second Grid. Still no joy.

Hence the problem is narrowed down to SelectedItem on one DatGrid to the ItemsSource of another via the framework binding engine.

Reflector provides a possible clue. The Data namespace contains an Extensions static class targeting DependencyObject which has an AreHandlersSuspended method backed bye a static variable. The which the code handling changes to the ItemsSource property uses this method and does nothing if it returns true.

My unconfirmed suspicion is that in the process of the first Grid assigning its SelectedItem property it has turned on the flag in order to avoid an infinite loop. However since this flag is effectively global any other legitmate code running as a result of this SelectedItem assignment is not being executed.

Anyone got SL4 and fancy testing on that?
Any MSFTers lurking want to look into?

If SL4 still has it this will need reporting to Connect as a bug.