Dynamic Actions and parent Datacontext

Apr 6, 2011 at 10:07 AM

Confusing title, I know :)

I've got a DockingLibrary and some ToolBars in my ShellView.
One of this toolbars shall be bound to the "ActiveViewModel", selected via the DockingLibrary.
More or less this works, but not as smooth as with ICommands. The problem is neither the DockingLib,
nor the ActiveViewModel, but some other things.

Let's see how it works with ICommands: (Code-feature not working with FF4)

<ToolBar Name="tbEditor"
            Band="1"
            DataContext="{Binding ElementName=dockManager, Path=ActiveViewModel}"
            BandIndex="3"
            Height="33">

    <Button Command="{Binding TestCommand, Mode=Default}" Name="SaveDocument">
        <Image Source="{StaticResource mediafloppy}" />
    </Button>
</ToolBar

I can bind the Button to my Command, but I'm using the DataContext of the parent UI-Element, the toolbar.
With Caliburn, I'm not able to do this. Probably, it's my fault :)

<ToolBar Name="tbEditor"
            Band="1"
            BandIndex="3"
            Height="33">
    <Button cb:Action.Target="{Binding ElementName=dockManager, Path=ActiveViewModel}" Name="SaveDocument">
        <Image Source="{StaticResource mediafloppy}" />
    </Button>
</ToolBar>

This works, and it will bind the vois SaveDocument correctly to the current active ViewModel.
BUT additionally, the ShellViewModel must have at least an Empty void SaveDocument, otherwise the later binding to my ActiveViewModel does not work.
I already tried  Action.TargetWithoutContext and Message.Attach, but using ICommand still remains more convenient.

Probably I'm doing something wrong here. Finally I guess, that just having one Binding to the current ViewModel in the Toolbar-Object
might be more performant than having 10 single dynamic Bindings for each of the 10 Toolbar-Buttons. But I'm not quite sure if this would be a matter.

Any help would be appreciated. Thanks a lot in advance.
Jens

 

 

Apr 6, 2011 at 10:33 AM

Ok, if I get it straigth you need to change the action to perform depending on the currently selected item (or something along these lines).

First of all, can't you avoid the naming convention and do something like defining a converter used to push the correct action (or null)?

<ToolBar Name="tbEditor"
            Band="1"
            BandIndex="3"
            Height="33">
    <Button DataContext="{Binding ElementName=dockManager, Path=ActiveViewModel}" cb:Message.Attach="{Binding Converter={StaticResource ObjectToActionConverter}, ConverterParameter='SaveDocument'}">
        <Image Source="{StaticResource mediafloppy}" />
    </Button>
</ToolBar>

 

Otherwise (and this is the approach I would use), I suppose you coul probably target the same shell VM function and define some logic there:

<ToolBar Name="tbEditor"
            Band="1"
            BandIndex="3"
            Height="33">
    <Button Name="SaveDocument">
        <Image Source="{StaticResource mediafloppy}" />
    </Button>
</ToolBar>

public class ShellViewModel : Screen
{
     public Screen ActiveViewModel { get; }

     public Screen CanSaveDocument { get; private set; }
     ...

     public void SaveDocument()
     {
         if(ActiveViewModel is ICanSaveDocument)
                ((ICanSaveDocument)ActiveViewModel).SaveDocument();
     }

     ...

     private void OnActiveViewModelChanged()
     {
          CanSaveDocument = ActiveViewModel is ICanSaveDocument;
          NotifyPropertyChanged(()=>CanSaveDocument);
     }
}

Could this be a spossible solution?

Apr 6, 2011 at 10:51 AM

Hi BladeWise,

thanks for this very quick reply. The second solution was my first intention. But I thought it might be bad code if the ShellViewModel
must implement several Methods just in order to execute a simular Method in another Viewmodel.

For the time being I have only 5 Buttons there. But that's not the thing, I was looking vor a general and elegant solution.
So you wouldn't be worried about having all Methods twice (in ShellViewModel and WhateverViewModel)?

Jens

PS: Originally I had a "public Screen ActiveViewModel { get; }" in my ShellViewModel. But meantime I extended my Docking-Class to return ActiveViewModel.
Ah, by the way, I'm using AvalonDock with your base for a DockAwareWindowManager  :) And meantime im inheriting vom DockingManger and added some
Functions like ActiveViewModel (instead of ActiveDocument)


Apr 6, 2011 at 12:38 PM

The reason you have duplicated methods is that you are concentrating different actions under the same 'logical group'; in other words, you are providing a single access point to multiple actions.

In such a scenario, duplication will always occur (in the first sample you proposed, you have duplication of Commands, for example).

 

I am not bothered with multiple methods, since I tend to isolate the application logic among different view models: from my point of view, the shell VM exposes some action, the children expose other actions and, the application logic links such different actions together.

I suppose that a proper interface and some extension methods could help you to deal with this scenario.

Apr 6, 2011 at 12:47 PM

Hm....I have to think about it :)

Meantime I tried a binding at Message.Attach as suggested (btw. returning a simple String 'SaveDocument' would be enoguh.)
But in Caliburn.Micro.Message you'll find

        private static void OnAttachChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if(e.NewValue == e.OldValue)
                return;

Setting the same Action twice does not work because of this matter.
So it would be neccessary to delete the comparison for equality, and that's not the way I would like to go.

Probably I will take a look in the Toolbar-Thread some lines below. Seems to be interesting anyway.