views:

532

answers:

2

Hi, I'm trying to get my first WPF app working using MVVM, and I've hit a little binding problem.

The setup is that I have a view & viewModel which holds User details (the parent), and to try and keep things simple I've put a section of that view into a separate view & viewModel (the child). The child view is defined as a UserControl.

The issue I'm having is how to set the DataContext of the child view (the UserControl). My parent ViewModel has a property which exposes the child ViewModel, like so:

class ParentViewModel: INotifyPropertyChanged
{
    public ChildViewModel childViewModel { get; set; }
    //...
}

In the XAML for my parent view (which has it's DataContext set to the ParentViewModel), I try to set the DataContext of the child view as follows:

<views:ChildView 
    x:Name="ChildView"
    DataContext="{Binding childViewModel}"/>

However, this doesn't work. The DataContext of the child view is set to the same DataContext as the parent view (i.e. the ParentViewModel), as if I wasn't setting it at all. I also tried setting the DataContext in the child view itself, which also doesn't work:

<UserControl x:Class="DietRecorder.Client.View.ChildView"
    DataContext="childViewModel"

I have found a couple of ways around this. In the child view, I can bind everything by including the ChildViewModel in the path:

<SomeControl Visibility="{Binding Path=childViewModel.IsVisible}">

but I don't want the child view to have this level of awareness of the hierarchy. Setting the DataContext in code also works - however, I have to do this after showing the parent view, otherwise the DataContext just gets overwritten when I call Show():

parentView.Show();
parentView.ChildView.DataContext = parentViewModel.childViewModel;

This code also makes me feel uneasy, what with the LOD violation and all.

It's just the DataContext that seems to be the problem - I can bind other things in the child, for example I tried binding the FontSize to an int property just to test it:

<views:ChildView 
    x:Name="ChildView"
    FontSize="{Binding Path=someVal}"/>

And that works fine.

But I'm sure binding the DataContext should work - I've seen similar examples of this kind of thing. Have I missed something obvious here? Is there a reason this won't work? Is there a spelling mistake somewhere? (I renamed things for your benefit so you won't be able to help me there anyway).

Any thoughts welcome.

Edit

Reviewing this code again, it seems I've made a mistake somewhere, as the following XAML in the parent view does now appear to work:

<views:ChildView 
    x:Name="ChildView"
    DataContext="{Binding childViewModel}"/>

I'm not sure why I couldn't get it to work originally, or what I might have changed to make it work. Perhaps it was the INotifyPropertyChanged issue like one of the answers suggests. Oh well, onwards and upwards..

+1  A: 

Hi,

I suspect it is because the childViewModel property does not raise the PropertyChanged event. When the binding is evaluated, it is possible that this property is null (in which case the DataContext will just fall back to the parent's one). When the childViewModel is instantiated later, no PropertyChanged event is raised, and the binding is never informed that there is now a DataContext.

Try to raise PropertyChanged event in the childViewModel property.

Cheers, Laurent

LBugnion
Good thinking, but no joy I'm afraid. I was thinking it could be something to do with the order, but the child viewModel should be available when the binding happens - and it works when I set the font size using the int property, which happens at the same time, and that doesn't use PropertyChanged.
Grant Crofton
+1  A: 

I was looking for the same thing and I found one way of doing it. Basically, I bound the DataContext of the child to the DataContext of some parent (the window) using

DataContext="{Binding ElementName=_topLevel, Path=DataContext.childViewModel}"

where I've set x:Name="_topLevel" on some parent control (the window).

I tried to use RelativeSource/FindAncestor instead of ElementName but it didn't work - but that was probably my fault.

Feels like a hack to me, but better than binding to the top level view model.

stevesp4644
Although I haven't tried it, that should work, so I'll give you +1 for the effort after all this time - but this solution has the same problem as one of the solutions I suggested above, which is that the child knows about the parent. That's either just a bad smell or a real problem, depending on whether you use the child in different parent views or not.When checking my code for this answer, I discovered it now works, although I'm not sure why - see the edit for details.
Grant Crofton