VM in another VM: what about deactivation?

Feb 18, 2011 at 8:41 AM

Hello,

I have a little question: if I expose a view-model property inside another view-model, both being of type Screen, is the child screen deactivated when the parent screen is?

If it isn't, I guess I have only 2 options here : use a conductor as parent view-model, or manually subscribe to the OnDeactivated event of the parent screen, to close the child view-model?

Thanlks !

Mike

Coordinator
Feb 18, 2011 at 2:12 PM

It has to be inside a Conductor's collection or activated by the Bootstrapper or activated by the WindowManager. Those are the three things that handle screen lifecycles. So, your two options are correct.

Coordinator
Feb 18, 2011 at 2:16 PM

I've seen people asking this sort of thing a lot and I'm wondering...what if I created two methods on Screen: ParticipateInActivation(IActivate item) and ParticipateInDeactivation(IDeactivate item) which would just activate/deactivate respectively the item along with the screen it is registered with. Would that be useful?

Feb 18, 2011 at 2:23 PM

Definitely... I often prefer to avoid a Conductor if the number of children is already known...

Coordinator
Feb 18, 2011 at 2:29 PM

The is Conductor<T>.Collection.AllActive right now for this purpose too. But, maybe this would be better or simpler alternative.

Feb 18, 2011 at 2:38 PM

Yes Conductor<T>.Collection.AllActive is an alternative, but the solution you are proposing does not imply a constraint on the conducted types, and feels more elegant, in my opinion.

Coordinator
Feb 18, 2011 at 2:39 PM

The difference in the extension methods and Conductor<T>.Collection.AllActive would be that the extension methods would not hook the screen in such a way that it can participate in the CanClose check. It would just make sure it was activated/deactivated along with what it was attached to. The Conductor would do all that, plus manage a potentially dynamic list of screens and make them all participate in the CanClose check. I'm thinking of some extension methods that would look like this:

screen.ActivateWith(parent);

or

screen.DeactivateWith(parent);

or

screen.ConductWith(parent)

The last one would just call the first two methods.

Thoughts?

Feb 18, 2011 at 2:48 PM

I have extension methods that acts exactly this way in my current project, since I prefer to split a complex view-model in simpler parts.

AMmenu view-model is a nice candidate for this approach: menu items are not conducted, do not require closing strategy and should be lightweight.

A Ribbon, which is composed by tabs (containing groups and items), the quick access toolbar and the main menu is another candidate: the menu and the quick access toolbar are not really conducted, while tabs should be conducted with a OneActive conductor.

All in all, it seems to me a reasonable change to cope at least with all the cases where complex UI elements are composed by very simple elements.

Coordinator
Feb 18, 2011 at 2:52 PM

Here are the implementations of the extension methods:

public static void ActivateWith(this IActivate child, IActivate parent)
{
    parent.Activated += (s, e) => child.Activate();
}

public static void DeactivateWith(this IDeactivate child, IDeactivate parent)
{
    parent.Deactivated += (s, e) => child.Deactivate(e.WasClosed);
}

public static void ConductWith<TChild, TParent>(this TChild child, TParent parent)
    where TChild : IActivate, IDeactivate
    where TParent : IActivate, IDeactivate
{
    child.ActivateWith(parent);
    child.DeactivateWith(parent);
}

Would that do it? Do I need to unwire the deactivate event on close? 


Coordinator
Feb 18, 2011 at 2:56 PM

This would be a better version probably:

public static void DeactivateWith(this IDeactivate child, IDeactivate parent)
{
    EventHandler<DeactivationEventArgs> handler = null;
    handler = (s, e) => {
        child.Deactivate(e.WasClosed);
        if (e.WasClosed)
            parent.Deactivated -= handler;
    };
    parent.Deactivated += handler;
}
Feb 18, 2011 at 3:01 PM

Well, if such extensions are used with children that can be dynamically removed from the parent, I suppose both events should be detached to avoid children to leak in memory.

Unfortunally, this means that only the last method can be safely added, since there is no way to detach the Activated event otherwise.

        public static void ConductWith<TChild, TParent>(this TChild child, TParent parent)
            where TChild : IActivate, IDeactivate
            where TParent : IActivate, IDeactivate
        {
            EventHandler<ActivationEventArgs> activated = (s, e) => child.Activate();
            EventHandler<DeactivationEventArgs> deactivated = null;
            deactivated = (s, e) =>
                          {
                              child.Deactivate(e.WasClosed);
                              if (e.WasClosed)
                              {
                                  var p = (TParent)s;
                                  p.Activated -= activated;
                                  p.Deactivated -= deactivated;
                              }
                          };
            
            parent.Activated += activated;
            parent.Deactivated += deactivated;
        }

Coordinator
Feb 18, 2011 at 3:06 PM

What if I just change the ActivateWith function to this:

public static void ActivateWith(this IActivate child, IActivate parent) {
            EventHandler<ActivationEventArgs> handler = (s, e) => child.Activate();
            parent.Activated += handler;

            var deactivator = parent as IDeactivate;
            if(deactivator != null) {
                EventHandler<DeactivationEventArgs> handler2 = null;
                handler2 = (s, e) => {
                    if (e.WasClosed) {
                        parent.Activated -= handler;
                        deactivator.Deactivated -= handler2;
                    }
                };
                deactivator.Deactivated += handler2;
            }
        }
Feb 18, 2011 at 3:09 PM

Well it would definitely do the trick, didn't think about it! :)

Coordinator
Feb 18, 2011 at 3:18 PM

Added in fd7f0e34a9e8

Feb 18, 2011 at 3:22 PM

Nice addition.  I implemented something similar recently, but this is better.

Feb 24, 2011 at 8:38 PM

Hi

I am new to Caliburn Micro and am currently trying to build a shell with a ribbon menu similar to what you described. Does someone have an example of a viewmodel split into smaller

parts for the tabs, quick access and menu.

Regards

Feb 24, 2011 at 8:45 PM

I've got some view-model implementations, but honestly I had to write a lot of code in the views to make them work nicely. I use Divelements SandRibbon, and I must say it is definitely not MVVM friendly.

I could post the view-model classes, but I dubt it will be of any use, since they are just Screen-derived (more or less) classes, with bindable collections to store ribbon tabs, groups and items. The real issue with a ribbon is the integration with the view... and it depends on the ribbon library.

Feb 25, 2011 at 9:18 AM

I use the official Microsoft Ribbon control suite, which is pretty full MVVM.

I had a few light adjustments to do, but it works well

Feb 25, 2011 at 11:24 AM
Edited Feb 26, 2011 at 10:17 AM

I had a lot of issues with Divelements' one:

  • Ribbon is not an ItemsControl so ribbon tabs cannot be databound to view-models. I was forced to explicitly manage them on datacontext changed and populate the tabs with views.
  • Handling the shortcuts toolbar have been a pain, expecially the ones associated to the ribbon menu (the Ribbon control required reference to views and strange behaviours happened when dealing with nested menu items). Moreover I had to use reflection on internal methods to be able to modify the ribbon customize menu.

 

Honestly, I had issues even with standard context menus, that are definitely *not* MVVM friendly. Unless your menu items are very simple (image + text + command) and you can use the ItemTemplate to define the menu item looks, you could find quite a hard time to be able to dynamically inject a menu item view inside the context menu view (I was forced to define a special ViewCollection class that can be databound to the ItemsSource and is responsible to locate views).

Feb 26, 2011 at 6:12 AM

We are using the Telerik ribbon and as far as I am aware the ribbon is an ItemsControl. Can someone maybe just list the general steps involved with do this. I believe the implementation for the ribbon tabs are very similar to that of a tab control. My views would have to inherit from RadRibbonTab.