[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:
<TabControl>
<TabItem Header="Addresses">
<my:TestUserControl GridItems="{Binding Addresses}" />
</TabItem>
<TabItem Header="Phone numbers">
<my:TestUserControl GridItems="{Binding PhoneNumbers}" />
</TabItem>
<TabItem Header="Websites">
<my:TestUserControl GridItems="{Binding Websites}" />
</TabItem>
</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:
public class GridItem : NotifyObject
{
private int _Id;
public int Id
{
get { return _Id; }
set
{
_Id = value;
this.OnPropertyChanged(() => this.Id);
}
}
private string _Name;
public string Name
{
get { return _Name; }
set
{
_Name = value;
this.OnPropertyChanged(() => this.Name);
}
}
}
(NotifyObject is just a base class implementing INotifyPropertyChanged which easily handles raising the PropertyChanged event)
The MainWindow has this XAML code:
xml Code:
<Window x:Class="UserControlGridTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:my="clr-namespace:UserControlGridTest"
Title="MainWindow" Height="350" Width="525">
<Grid>
<my:TestUserControl GridItems="{Binding TestItems}" />
</Grid>
</Window>
It's DataContext is set to the following ViewModel:
csharp Code:
public class MainViewModel
{
public MainViewModel()
{
_TestItems = new List<GridItem>
{
new GridItem {Id = 1, Name = "Item 1"},
new GridItem {Id = 2, Name = "Item 2"},
new GridItem {Id = 3, Name = "Item 3"},
new GridItem {Id = 4, Name = "Item 4"},
};
}
private List<GridItem> _TestItems;
public List<GridItem> TestItems
{
get
{
return _TestItems;
}
}
}
The code for the TestUserControl is this:
xml Code:
<UserControl x:Class="UserControlGridTest.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">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Text="Items:" Grid.Row="0" Margin="5"/>
<DataGrid ItemsSource="{Binding GridItems}" Grid.Row="1" Margin="5" AutoGenerateColumns="true" />
</Grid>
</UserControl>
csharp Code:
public partial class TestUserControl : UserControl
{
private readonly TestUserControlViewModel viewModel;
public TestUserControl()
{
InitializeComponent();
viewModel = new TestUserControlViewModel(this);
this.DataContext = viewModel;
}
public static DependencyProperty ItemsProperty =
DependencyProperty.Register("GridItems", typeof (List<GridItem>), typeof (TestUserControl));
public List<GridItem> GridItems
{
get { return (List<GridItem>) GetValue(ItemsProperty); }
set { SetValue(ItemsProperty, value); }
}
}
The DataGrid binds to the GridItems property on the ViewModel, which in turn returns the DependencyProperty in the TestUserControl:
csharp Code:
public class TestUserControlViewModel
{
private readonly TestUserControl _Control;
public TestUserControlViewModel(TestUserControl control)
{
_Control = control;
}
public List<GridItem> GridItems
{
get { return _Control.GridItems; }
}
}
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...
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?
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.
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>
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