Handling a child view's Loaded event

Topics: Conventions, Framework Services, UI Architecture
Jun 15, 2012 at 3:35 AM

I'm working on an application which has a Visual Studio-like interface, with docking panels. Each docking panel is represented by a viewmodel on my ShellViewModel. The view for each docking panel has ribbon controls that I want to merge into the main ribbon control on the shell, so my code might look something like this:

<dxd:LayoutPanel Caption="Server Explorer">
	<local:ServerExplorerView
		Loaded="OnServerExplorerViewLoaded"
		Unloaded="OnMappingsViewUnloaded" />
</dxd:LayoutPanel>

and in the Loaded/Unloaded events, I could merge/unmerge the child ribbon controls:

protected virtual void OnServerExplorerViewLoaded(object sender, RoutedEventArgs e)
{
	var explorer = (ServerExplorerView) sender;
	ribbonControl.Merge(explorer.Ribbon);
}

However, in moving to CM, my code now looks like this:

<dxd:LayoutPanel Caption="Server Explorer">
	<ContentControl cal:View.Model="{Binding Path=ServerExplorer}"
		Loaded="OnServerExplorerViewLoaded"
		Unloaded="OnMappingsViewUnloaded" />
</dxd:LayoutPanel>
and when the Loaded/Unloaded event occurs, the sender is a ContentControl. How can I handle this properly in a CM fashion?

Jun 15, 2012 at 8:46 AM

The root elemnt in the ServerExplorerView has a loaded event. You can hook on that. For further information about wiring events in CM see the Cheatsheet (http://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet&referringTitle=Documentation).

But in my opinion, in this scenario it would make sense to utilize the ViewModel's activated/deactivated events or methods. See http://caliburnmicro.codeplex.com/wikipage?title=Screens%2c%20Conductors%20and%20Composition&referringTitle=Documentation and take a look at the paragraphs about the interfaces IActivate, IDeactivate and IViewAware, as well as the parts about OnInitialize, OnActivate, OnDeactivate and OnViewLoaded.

This should give you a starting point!

Roland

Jun 18, 2012 at 1:12 AM
RAuer wrote:

The root elemnt in the ServerExplorerView has a loaded event. You can hook on that. For further information about wiring events in CM see the Cheatsheet (http://caliburnmicro.codeplex.com/wikipage?title=Cheat%20Sheet&referringTitle=Documentation).

But in my opinion, in this scenario it would make sense to utilize the ViewModel's activated/deactivated events or methods. See http://caliburnmicro.codeplex.com/wikipage?title=Screens%2c%20Conductors%20and%20Composition&referringTitle=Documentation and take a look at the paragraphs about the interfaces IActivate, IDeactivate and IViewAware, as well as the parts about OnInitialize, OnActivate, OnDeactivate and OnViewLoaded.

This should give you a starting point!

Roland

So what you're saying in the first part of your answer is that, in my ServerExplorerView, I should have something like this:

<DockPanel x:Name="layoutRoot"
    cal:Message.Attach="[Event Loaded] = [Action Loaded($view)]">
    ...
</DockPanel>

and then in my ServerExplorerViewModel, implement the Loaded method and publish a message which the ShellView subscribes to? I'm not too concerned at this point about adding code to the view's code-behind, as the merging of toolbars is pretty much a UI concern only. If I ever find the time to implement the ribbon bars in a full-on MVVM way then I could move all that code into the viewmodel, but that's a bit further down the track.

Regarding the second approach, which is your preference, I'm experiencing the following problem. My ShellViewModel implements Conductor<DocumentViewModel>.Collection.OneActive, where DocumentViewModel implements screen, and is bound to a tab control. The docked panels, such as the ServerExplorerViewModel referred to previously, separate properties on the ShellViewModel. When I derive these view models from PropertyChangedBase, they work OK; however, when I derive them from Screen, they get picked up the Conductor, even though they don't implement DocumentViewModel. 

My view models are:

public class ShellViewModel : Conductor<DocumentViewModel>.Collection.OneActive
{
    public ServerExplorerViewModel ServerExplorer // Implementation of INPC
	
    public ClassExplorerViewModel ClassExplorer // Implementation of INPC
}

public class DocumentViewModel : Screen
{
}

public class ServerExplorerViewModel : PropertyChangedBase
{
}

while my ShellView is:

<dxd:LayoutGroup>
    <dxd:DocumentGroup ItemsSource="{Binding Path=Items}">
    <dxd:LayoutGroup>
        <dxd:LayoutPanel>
            <ContentControl cal:View.Model="{Binding Path=ServerExplorer}" />
        </dxd:LayoutPanel>
        <dxd:LayoutPanel>
            <ContentControl cal:View.Model="{Binding Path=ClassExplorer}" />
        </dxd:LayoutPanel>
    </dxd:LayoutGroup>
</dxd:LayoutGroup>

Is the conductor being too greedy and picking up all implementations of IScreen, rather than taking the Conductor<T> type?

 

 

Jun 18, 2012 at 1:20 PM

Attaching a loaded event to the layout root would work, but you don't need to do this, as every Screen has a OnViewLoaded(object view) virtual method you can override.

Regarding the code-behind: Of course you can manage this in the code-behind. CM provides you with the opportunity to delete the code-behind-file, and that's why I forget the possibility of it most of the time.

The last issue with the Conductor<DocumentViewModel> is strange. How do you activate a screen? If the shell is a Conductor<T>, it only takes Ts as children as parameter in ActivateItem(T item).

You have quite many possible solutions in your scenario. I had quite a similar one in a former project. To solve the add/remove of Ribbons (or buttons, or menus), I used the EventAggregator. In addition, I had a DockingManager (DM) and a MenuManager (or, in your case, a RibbonManager). The DM would manage adding and removing screens in the DockingControl. This is done quite "code-behind-like": the DM searches for the DockingControl in the assigned view, wraps every opened screen in a LayoutPanel, or removes every closed screen from the docking control. The same for the RibbonManager: The manager gets assigned to a view with a ribbon, and listens to EventAggregator-Messages to add/remove items. Each created RibbonButton points via an Action to a method in the sending VM.

That was my way, not entirely MVVM friendly, but as dev-friendly as it gets.