Access ParentViewModel from XAML

Jun 17, 2011 at 8:14 AM

Hi Geert and others,

I have a setup in which a big UserControl<T> ServiceItemMainCtrl control contains a smaller UserControl<T> subcontrols called BrowseActivities. Both the main control and the subcontrol have their own viewmodels (ServiceItemMainCtrlViewModel and BrowseActivitiesViewModel respectively), but I often need to access properties and commands of the main control from the subcontrol's XAML.

Up till now I have been doing this construct:


<TextBlock Text="{Binding DataContext.ServiceItem.Summary, RelativeSource={RelativeSource AncestorType=local:ServiceItemMainCtrl}}"/>

which works fine, but does not seem very elegant and forces the view to be more tied to its parent than is ideal.

I therefore investigated whether the XAML could not be bound directly to the parent viewmodel. I tried using the ParentViewModel member, changing the above to:

<TextBlock Text="{Binding ParentViewModel.ServiceItem.Summary}"/>

which did not work due to the ParentViewModel being a protected property, not a public one.

I then modified your sourcecode slightly, to change the access level in the ViewModelBaseWithoutServices class thus:

public IViewModel ParentViewModel { get; private set; } 

and bingo, it all works perfectly.

I am worried, however, that I am not following best practice here or that I may break something with this approach.

Please can you advise whether this is OK?

Coordinator
Jun 17, 2011 at 9:23 AM

Hi Bob,

I won't say your design is wrong, but there is a reason why we choose to make the ParentViewModel private. The reason is that you should not "assume" that the control is used in a specific environment. A user control stands on its own, that's why it's a user control in the end. This is one of the reasons we invented the UserControl<TViewModel>, to prevent the user from having to declare a list of child view models so the view model cannot be used in any other location.

The problem is that if you write code that assumes that there is a ServiceItemMainCtrlViewModel, the BrowseActivities control can never function on it's own and in another location then beneath a ServiceItemMainCtrl.

If you need information in a child view-model, we have 2 options that you can use:

  1. Get them out of the model (the data is probably available in the model, no need to search for the data in the parent view model)
  2. Create a DTO object and inject that into the view model (thus the DTO becomes the model)

The BrowseActivitiesViewModel probably accepts one or more activities. There must be some kind of link the the ServiceItem, right?

If this sounds too complex, just send us a small example and we will show you how we think it should be done.

Jun 17, 2011 at 9:44 AM

Hi Geert,

Thank you for your response.

You are correct in your assumption that there is a link: the ServiceItem model contains a list of Activities, which is what the BrowseActivities control is displaying. You are also correct in your hypothesis that the BrowseActivities control may in future be used in scenarios where the activity list is not parented by a ServiceItem.

However, the reason why I need this link is that I wish to have a separate subcontrol (called ActivityCtrl) of ServiceItemMainCtrl which displays details of the activity currently selected in the BrowseActivities pane, and dynamically changes this whenever the selection is changed. I have therefore set up a SelectedActivity property in the ServiceItemMainCtrlViewModel, and linked to it in BrowseActivities with:

<ListView SelectedItem="{Binding Path=ParentViewModel.SelectedActivity, Mode=TwoWay}"
This SelectedActivity property is then used from ServiceItemMainCtrl as the primary model for its ActivityCtrl pane.

If you can recommend an alternative way of doing this without using ParentViewModel I would be grateful.

Thanks.

Coordinator
Jun 17, 2011 at 9:47 AM

The selection should not be handled by a child control, but the list that shows. To give you a good example:

If you have a list of textboxes, the state of the selection is never handled by the textbox. It's the containing listview (items control) that will handle this. When developing a control, you will never know (you do, but that shouldn't change anything) whether the control is used stand-alone or inside an items control.

What behavior do you want to implement based on the selection?

Jun 17, 2011 at 12:52 PM

Hi again,

I guess what I'm trying to do here is a Master/Details control on Activities, but with the master table in a subcontrol. So the ServiceItemMainCtrl has these two subcontrols:

 

<Controls:BrowseActivities Grid.Row="3" Grid.Column="0" DataContext="{Binding TheServiceItem.ActivityList}"/>
<Controls:ActivityViewer Grid.Row="3" Grid.Column="2" DataContext="{Binding SelectedActivity}"/>

Within the BrowseActivities control is a listview, whose selected item is bound to the parent viewmodel's SelectedActivity member (as shown in my post above).

I want the ActivityViewer control to take that selected activity as its model. And whenever the user changes selection within the BrowseActivities listview, the ActivityViewer should update with the newly selected Activity.

Is this a scenario you've encountered, and do you have any advice on how to solve it within the Catel framework?

Many thanks.

 

Coordinator
Jun 17, 2011 at 3:24 PM

Hi Bob,

In my opinion, AcitivityList should be a direct property of the service item view model. TheServiceItem is a model, which should not be exposed and use like this. But, this is not a big issue as long as you use it as read-only. The ServiceItemMainCtrl should have a SelectedActivity property. The ServiceItemMainCtrl is the control that handles this, and thus should be responsible for this.

The cool thing about Catel and user controls with view-models is that you can define custom dependency properties on your control. You can automatically map these properties to/from your view-model via the ControlToViewModelMapping attribute. This is a great way to develop custom controls that any user NOT using MVVM can still use your control developed using MVVM (Catel). So, if you want the BrowseActivities to publish custom dependency properties, it's perfectly possible using Catel.

When using this technique, you can bind the SelectedActivity of the ServiceItemMainCtrlViewModel to the BrowseActivities.SelectedActivity control dependency property.

I hope you get the idea. If not, just let us know!