Reputation: 11680
I have a form with two different UserControls - one that contains a Telerik RadGridView and the other that that contains a Telerik DataForm.
The grid usercontrol is bound to a ViewModel that includes a property that exposes the Items collection that the grid is bound to.
When I bind the form to that property, everything works fine.
But I need to access additional info in the form control that really doesn't belong in the grid control's viewmodel.
So I thought I'd add a property to the form usercontrol, and bind it to the items collection:
<local:FormControl x:Name="formControl"
ItemsSource="{Binding items}"
/>
In the form's code-behind, I added a normal property:
private object itemsSource;
public object ItemsSource
{
get { return this.itemsSource; }
set { this.itemsSource = value; }
}
And this didn't work, of course. I got errors about having to use a DependencyProperty. Which I thought was reassuring - the page was actually trying to bind to the property I thought it should.
So I converted this to a DependencyProperty:
public static DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(object), typeof(FormControl));
public object ItemsSource
{
get { return GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
That compiled and ran without errors. Except, of course, that the control's viewmodel didn't have any items. So next was to try to pass the ItemsSource property to the form control's viewmodel:
public FormControl()
{
InitializeComponent();
this.DataContext = new FormControlVM(this.ItemsSource);
...
And this didn't work. ItemsSource was null when FormControl() was constructed. So I added a setItemsSource() method to the viewmodel, and called it in the ItemsSource property's set function. This didn't work, either. The xaml is apparently binding to the property, but it seems to do it without calling the property's set function.
So I decided to listen to the ValueChanged event, on the DependencyProperty:
public FormControl()
{
InitializeComponent();
this.DataContext = new FormControlVM();
DependencyPropertyDescriptor prop =
DependencyPropertyDescriptor.FromProperty(FormControl.ItemsSourceProperty, this.GetType());
prop.AddValueChanged(this, delegate
{
FormControlVM formControlVM = (FormControlVM)this.DataContext;
formControlVM.setItemsSource(this.ItemsSource);
});
...
And it's still not working. The ValueChanged delegate never seems to get called.
This seems like it should be a simple thing, but I've not been able to find examples on-line, and I've tried every combination I can conceive of.
Any ideas as to how I should handle this?
============ Additional info on the binding ============
Will was asking for info on the xaml that does the binding.
If I put this in page that contains the user control, binding the user control to the viewmodel of the page:
<local:FormControl x:Name="formControl"
Grid.Column="2"
DataContext="{Binding}"
/>
And then this in the user control, binding the form in the user control to the itemsCollection of the page's viewmodel:
<telerik:RadDataForm
ItemsSource="{Binding itemsCollection}"
Header="View Item:"
CommandButtonsVisibility="None"
AutoGenerateFields="False"
/>
Then everything works fine.
But the problem is that I can't bind the user control to the viewmodel of the page. I need to have the user control's viewmodel expose information that the page's viewmodel should not be seeing.
And I can't have the form in the user control reaching outside of the user control, binding to a property on the page's viewmodel. It's a violation of encapsulation, that will make using the user control on a different page much more complicated, and severely limit how I might modify the internals of the control in the future. (The page should not know anything about the controls within the user control, the controls within the user control should not know anything about the page.)
When I include the user control on a page, I want to bind the user control to a property of the page's viewmodel, and I want any controls within the user control to bind to properties of the user control, or of the user control's viewmodel.
I'd think this was a fairly common occurrence. But I've not been able to find any examples of how it might be done.
Upvotes: 2
Views: 3822
Reputation: 11680
To restate the problem: I have a UserControl, that contains an embedded Telerik DataForm control. I need to bind the ItemsSource property of the embedded DataForm to a property of the DataContext of the page on which my UserControl is placed.
If the UserControl did not have its DataContext set, it would inherit the DataContext of the page, and I could easily bind the embedded DataForm's ItemsSource property to a property of it, but the UserControl has it's own DataContext.
If the UserControl was written to be used only on this page, I could bind the embedded DataForm's ItemsSource property to a property of the page, using RelativeSource binding. But this UserControl is intended to be used in a number of places, and cannot have silent dependencies on properties outside of the UserControl. I need to make the dependency explicit.
Creating a DependencyProperty on the UserControl is the right approach, but there's no need to try to replicate the property in the UserControl's viewmodel.
What I need to do is to
1: Add a DependencyProperty to the UserControl:
public QueryableCollectionView ItemsSource
{
get { return (QueryableCollectionView)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public static readonly DependencyProperty ItemsSourceProperty =
DependencyProperty.Register("ItemsSource", typeof(QueryableCollectionView), typeof(FormControl));
Note: I do not need to implement a call-back function.
2: Bind the embedded DataForm's ItemsProperty to this new DependencyProperty of the UserControl, using RelativeSource binding:
<UserControl
...>
<telerik:RadDataForm
ItemsSource="{Binding Path=ItemsSource, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
/>
</UserControl>
3: Make the viewModel of the page visible, with the proper type (DataContext has type object):
public partial class MainWindow : Window
{
public MainWIndow()
{
this.InitializeComponent();
this.DataContext = new MainWindowVM();
}
public MainWindowVM viewModel
{ get { return this.DataContext as MainWindowVM; } }
}
4: On the page, bind the UserControl's new ItemsProperty DependencyProperty to the appropriate property of the page's viewmodel, again using RelativeSource binding:
<Window
...>
<local:FormControl
ItemsSource="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type telerik:RadPane}},Path=viewModel.items}"
/>
</Window>
Upvotes: 4