Results 1 to 5 of 5

Thread: [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

  1. #1

    Thread Starter
    PowerPoster
    Join Date
    Apr 2007
    Location
    The Netherlands
    Posts
    5,070

    [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

    Hi,

    I have a UserControl 'TestUserControl' that contains a DataGrid amongst other things. On my MainWindow I now have several of these UserControls (in different TabItems) and each of the grids in the UserControls have to bind to a different set of data. I want to control this binding in the MainWindow.

    The idea is basically this:
    xml Code:
    1. <TabControl>
    2.             <TabItem Header="Addresses">
    3.                 <my:TestUserControl GridItems="{Binding Addresses}" />
    4.             </TabItem>
    5.             <TabItem Header="Phone numbers">
    6.                 <my:TestUserControl GridItems="{Binding PhoneNumbers}" />
    7.             </TabItem>
    8.             <TabItem Header="Websites">
    9.                 <my:TestUserControl GridItems="{Binding Websites}" />
    10.             </TabItem>
    11.         </TabControl>


    For testing purposes I've simplified it a bit: a single TestUserControl on the MainWindow that binds to a single List<GridItem>, where GridItem is a simple test class:
    csharp Code:
    1. public class GridItem : NotifyObject
    2.     {
    3.         private int _Id;
    4.         public int Id
    5.         {
    6.             get { return _Id; }
    7.             set
    8.             {
    9.                 _Id = value;
    10.                 this.OnPropertyChanged(() => this.Id);
    11.             }
    12.         }
    13.  
    14.         private string _Name;
    15.         public string Name
    16.         {
    17.             get { return _Name; }
    18.             set
    19.             {
    20.                 _Name = value;
    21.                 this.OnPropertyChanged(() => this.Name);
    22.             }
    23.         }
    24.     }
    (NotifyObject is just a base class implementing INotifyPropertyChanged which easily handles raising the PropertyChanged event)

    The MainWindow has this XAML code:
    xml Code:
    1. <Window x:Class="UserControlGridTest.MainWindow"
    2.         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3.         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4.         xmlns:my="clr-namespace:UserControlGridTest"
    5.         Title="MainWindow" Height="350" Width="525">
    6.     <Grid>
    7.         <my:TestUserControl GridItems="{Binding TestItems}" />
    8.     </Grid>
    9. </Window>
    It's DataContext is set to the following ViewModel:
    csharp Code:
    1. public class MainViewModel
    2.     {
    3.         public MainViewModel()
    4.         {
    5.             _TestItems = new List<GridItem>
    6.                            {
    7.                                new GridItem {Id = 1, Name = "Item 1"},
    8.                                new GridItem {Id = 2, Name = "Item 2"},
    9.                                new GridItem {Id = 3, Name = "Item 3"},
    10.                                new GridItem {Id = 4, Name = "Item 4"},
    11.                            };
    12.         }
    13.  
    14.         private List<GridItem> _TestItems;
    15.         public List<GridItem> TestItems
    16.         {
    17.             get
    18.             {
    19.                 return _TestItems;
    20.             }
    21.         }
    22.     }

    The code for the TestUserControl is this:
    xml Code:
    1. <UserControl x:Class="UserControlGridTest.TestUserControl"
    2.              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    3.              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    4.              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    5.              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    6.              mc:Ignorable="d"
    7.              d:DesignHeight="300" d:DesignWidth="300">
    8.     <Grid>
    9.         <Grid.RowDefinitions>
    10.             <RowDefinition Height="Auto" />
    11.             <RowDefinition Height="*" />
    12.         </Grid.RowDefinitions>
    13.        
    14.         <TextBlock Text="Items:" Grid.Row="0" Margin="5"/>
    15.         <DataGrid ItemsSource="{Binding GridItems}" Grid.Row="1" Margin="5" AutoGenerateColumns="true" />
    16.     </Grid>
    17. </UserControl>
    csharp Code:
    1. public partial class TestUserControl : UserControl
    2.     {
    3.         private readonly TestUserControlViewModel viewModel;
    4.  
    5.         public TestUserControl()
    6.         {
    7.             InitializeComponent();
    8.  
    9.             viewModel = new TestUserControlViewModel(this);
    10.             this.DataContext = viewModel;
    11.         }
    12.  
    13.         public static DependencyProperty ItemsProperty =
    14.             DependencyProperty.Register("GridItems", typeof (List<GridItem>), typeof (TestUserControl));
    15.  
    16.         public List<GridItem> GridItems
    17.         {
    18.             get { return (List<GridItem>) GetValue(ItemsProperty); }
    19.             set { SetValue(ItemsProperty, value); }
    20.         }
    21.     }

    The DataGrid binds to the GridItems property on the ViewModel, which in turn returns the DependencyProperty in the TestUserControl:
    csharp Code:
    1. public class TestUserControlViewModel
    2.     {
    3.         private readonly TestUserControl _Control;
    4.  
    5.         public TestUserControlViewModel(TestUserControl control)
    6.         {
    7.             _Control = control;
    8.         }
    9.  
    10.         public List<GridItem> GridItems
    11.         {
    12.             get { return _Control.GridItems; }
    13.         }
    14.     }

    As far as I can see, this should work. In the MainWindow, the GridItems property of the TestUserControl is bound to the list of items. This GridItems property is a DependencyProperty (otherwise I cannot bind to it?). The DataGrid on the TestUserControl in turn binds to the GridItems property of its own ViewModel, which returns the value of the DependencyProperty (which should be set to the list of test items).

    Yet, the grid remains empty. There's no errors or warnings, just nothing seems to happen...

    What am I doing wrong?

    Thanks!


    Edit:
    As a bonus, I know I'm horribly breaking the MVVM pattern here by letting the TestUserControlViewModel have a reference to the TestUserControl, but I don't see how else I'm going to achieve what I want... Any idea's?

    The only other option I can see is to get rid of the UserControl altogether and just putting the grids on the MainWindow directly. But that doesn't seem like a very good solution, for one because it's a lot of repeated XAML and second because a lot more will be going on later, such as a toolbar for Add/Edit/Delete buttons and such. That's a LOT of repeated code that would fit very nicely into a single UserControl...

  2. #2
    Fanatic Member Megalith's Avatar
    Join Date
    Oct 2006
    Location
    Secret location in the UK
    Posts
    879

    Re: [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

    Hi Nick,

    From what i have studied so far using a user control is a last resort,the way to do this is with templates and existing controls.would you have some kind of screenshot/mockup of what you are trying to achieve?
    If debugging is the process of removing bugs, then programming must be the process of putting them in.

  3. #3
    You don't want to know.
    Join Date
    Aug 2010
    Posts
    4,578

    Re: [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

    Hogwash. UserControls are for the case where you want a composite control and don't want to have to fool with the complexity of a template. It's tough to write a good templated control. (I soften this stance later...)

    I don't see anything staring me in the face but I'll look over it. It'd obviously be helpful if I had the project. Have you turned on binding/dependency property tracing in the VS debugging options? That outputs diagnostic information to the output window and is invaluable when trying to figure out what's going wrong with bindings.

    *update*

    List<GridItem> doesn't raise any events when it changes. Your user control defines GridItems as a dependency property (footnote about that later), but the VM doesn't. When you make this binding:
    Code:
    <DataGrid ItemsSource="{Binding GridItems}"...
    You're not binding to the dependency property TestUserControl.GridItems. You're binding to the ViewModel's plain old CLR property GridItems. When the window sets GridItems:
    Code:
    <my:TestUserControl GridItems="{Binding TestItems}" />
    It's binding the plain CLR property MainViewModel.TestItems (source) to the Dependency Property TestUserControl.GridItems (target). This is legal; the target of a binding *has* to be a DP but the source can be a plain property.

    But what happens is you set TestUserControl's TestItems DP, which raises its change notifications. But no one's listening. TestUserControlViewModel can't tell the DP has changed, and even if it could it can't tell the DataGrid that GridItems has changed since there's no notification.

    What's the way out? I'm working on that. I've done some rudimentary research and it looks like the general opinion is it's a toughie. Something I'm seeing as a popular option is making the UserControl be its own VM:
    Code:
    public TestUserControl()
    {
        InitializeComponent();
        this.DataContext = this;
    }
    This would certainly work; the DataGrid would be binding to the same DependencyProperty as the Window. I'm not sure how much I like it. I'm going to back off of Megalith a bit because the more I read about this the more I see people claiming it's a case that's better served with a templated control. No one's generous enough to provide an example though, so it'd be worth some experimentation.
    Last edited by Sitten Spynne; May 19th, 2011 at 05:30 PM.

  4. #4
    PowerPoster Evil_Giraffe's Avatar
    Join Date
    Aug 2002
    Location
    Suffolk, UK
    Posts
    2,555

    Re: [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

    The answer is not to bind the GridItems via the containing control, your viewmodel should supply the List directly to where it is needed (the DataGrid).

    Okay, so that probably requires extensive surgery to your model structure. If you just want to get it working, bind the DataGrid to the UserControl's GridItems dependency property:

    Code:
    <UserControl x:Class="DataGrid.TestUserControl"
                 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" 
                 mc:Ignorable="d" 
                 d:DesignHeight="300" d:DesignWidth="300"
                 x:Name="testUserControl">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
    
            <TextBlock Text="Items:" Grid.Row="0" Margin="5"/>
            <DataGrid ItemsSource="{Binding GridItems, ElementName=testUserControl}" Grid.Row="1" Margin="5" AutoGenerateColumns="true" />
        </Grid>
    </UserControl>

  5. #5
    New Member
    Join Date
    Oct 2012
    Posts
    1

    Re: [WPF] DataGrid in UserControl - binding the ItemsSource on the containing Window

    This thread is now over one year old, and I started with WPF a few weeks ago. I am interested if the original poster has a working solution for his problem? If so, can you eventually post your project?
    Regards, rainer

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width