Should I Add Conductor<T>.Collection.AllActive

Coordinator
Jan 7, 2011 at 7:51 PM

It's a pretty common use case and I've seen more occurrences with CM users where they could benefit from this. Should I bring this class over from the full version of Caliburn?

Jan 7, 2011 at 8:41 PM

Of curiosity, which use cases are you thinking of Rob?

 

Br Christoffer

Jan 8, 2011 at 1:01 AM
Edited Jan 8, 2011 at 2:58 AM

I think that Conductor<T>.Collection.AllActive is tipically used where a complex root view-model is broken into multiple view-models.

Think about a shell composed by a window having a ribbon, a content and a status bar (a scenario I am currently dealing with, except for the fact that the content is indeed a multi-document dockable area). In this case, it can be useful to define the shell view-model as a composition of 3 sub-view-models that are activated and deactivated together with the shell itself.

That said, I don't think that Conductor<T>.Collection.AllActive should be part of Caliburn.Micro, since it does not have a clear role. What I mean is that a Conductor<T>.Collection.AllActive is not really a conductor, it is just a shortcut to activate/deactivate a collection of associated members, considering that an important element of the IConductor interface (i.e. IConductor.ActiveItem) is meaningless in such implementation. The real value added by this implementation is the capability of activate/deactivate public members (that could be provided with an extension method).

I suppose that the real 'issue' is that the framework provides an interface to define a 'child-parent relationship' (i.e. IChild) but does not provide a way to define a 'parent-child relationship'. What I mean is that the IConductor interface is sometimes used in place of a more proper IParent interface, that provides the collection of its children, something like:

/// <summary>
///   Interface used to define an object associated to a collection of children.
/// </summary>
public interface IParent
{
    /// <summary>
    ///   Gets the children.
    /// </summary>
    /// <returns>
    ///   The collection of children.
    /// </returns>
    IEnumerable GetChildren();
}

/// <summary>
/// Interface used to define a specialized parent.
/// </summary>
/// <typeparam name="T">The type of children.</typeparam>
public interface IParent<out T> : IParent
{
    /// <summary>
    ///   Gets the children.
    /// </summary>
    /// <returns>
    ///   The collection of children.
    /// </returns>
    new IEnumerable<T> GetChildren();
}

If such an interface was available, an extension method could take care of activating/deactivating children or public members and Conductor<T>.Collection.AllActive would be meaningless.

Moreover, I think that IConductor should just define a way to manage the lifecycle of its conducted children, without implying the need of an ActiveItem (which is meaningful only for certain types of conductors)

/// <summary>
/// Interface used to define an object used to conduct items.
/// </summary>
public interface IConductor
{
    /// <summary>
    /// Activates the item.
    /// </summary>
    /// <param name="item">The item.</param>
    void ActivateItem(object item);

    /// <summary>
    /// Deactivates the item.
    /// </summary>
    /// <param name="item">The item.</param>
    /// <param name="close">If set to <c>true</c>, the item will be deactivated and closed.</param>
    void DeactivateItem(object item, bool close = false);

    /// <summary>
    /// Gets the collection of conducted items.
    /// </summary>
    /// <returns>The collection of conducted items.</returns>
    IEnumerable GetConductedItems();
}

/// <summary>
/// Interface used to define an object used to conduct specialized child items.
/// </summary>
/// <typeparam name="T">The type of child items.</typeparam>
public interface IConductor<T> : IConductor
{
    /// <summary>
    /// Activates the item.
    /// </summary>
    /// <param name="item">The item.</param>
    void ActivateItem(T item);

    /// <summary>
    /// Deactivates the item.
    /// </summary>
    /// <param name="item">The item.</param>
    /// <param name="close">If set to <c>true</c>, the item will be deactivated and closed.</param>
    void DeactivateItem(T item, bool close = false);

    /// <summary>
    /// Gets the collection of conducted items.
    /// </summary>
    /// <returns>The collection of conducted items.</returns>
    new IEnumerable<T> GetConductedItems();
}

Note that I changed the CloseItem method with a more generic DeactivateItem providing optional closing (which is a missing feature in IConductor).

 Removing the requirement for an ActiveItem allows for a cleaner and reusable ConductorBase implementation.

I hope I didn't bother anyone with my babbling... --'

Coordinator
Jan 8, 2011 at 1:51 AM

That's all really good feedback. I think it's too late to change things that drastically in this version. However, I'll seriously consider these ideas for a future version.

Jan 8, 2011 at 3:11 AM
Edited Jan 8, 2011 at 3:14 AM

Yes, I fear it could be troublesome fort people that are already in production and want to keep the CM runtime up-to-date.

Nevertheless the required modifications are just at the interface level and, as long as real classes are not affected from this (e.g. the Conductor<T>.Collection.OneActive is not changed at all), the transition should be quite smooth.

Moreover, if the default value for DeactivateItem is set to true, the interface change would be just a simple method rename.

From the ConductorBase point of view, it would be just a matter of removing some code related to ActiveItem (the property and the ChangeActiveItem function) and moving it to Conductor<T>.Collection.OneActive, or just state that the class is the base class for 'one-active' conductors without changing the code at all.

Again, I agree with you: it is probably not the right time, but I wanted to point out the sections to be changed just for future reference.

Coordinator
Jan 8, 2011 at 2:37 PM

Hmm. On second thought. I really like these changes. I just might implement them this weekend and make people deal with it ;) It won't change much, as you said; probably only the CloseItem => DeactivateItem, but that could be also improved with a CloseItem extension method. I'l probably experiment with it in the next few days and see what I think.

Coordinator
Jan 10, 2011 at 2:20 AM

I've committed the proposed changes.

Jan 20, 2011 at 12:58 PM
Edited Jan 20, 2011 at 2:15 PM

How would the binding look if you wanted to add an applicationbar (e.g for example Conductor<IApplicationBarButton>.Collection.AllActive ) in the shell that is a conductor itself?

How do I bind to the "ApplicationBarButtonConductorViewModel" Items?

/Christoffer

EDIT - Thinking a bit more about it, should only be to add a contentcontrol with the x:Name set to "IApplicationBarManager". Will try it

Coordinator
Jan 20, 2011 at 3:20 PM

Remember, every screen or conductor needs to be hosted inside of a conductor (or the window manager or bootstrapper) in order for activation/deactivation to work properly. You can just use the content control technique, but you will need to manually activate/deactivate your AppBarButtonConductor... 

Jan 20, 2011 at 6:12 PM
Edited Jan 20, 2011 at 7:30 PM

It seems to work quite nice. I tried this

 

protected override void OnInitialize()
        {
            base.OnInitialize();
            ActivateItem(_login);
        }

 public IAppBarManager AppBarManager
        {
            get { return _isLoggedIn ? _appBarManager : null; }
        }

        #region IHandle<LoginEntity> Members

        public void Handle(LoginEntity message)
        {
            //DoLogin
            DeactivateItem(_login, true);
            _isLoggedIn = true;
            NotifyOfPropertyChange(() => AppBarManager);
            ActivateItem(_main);
        }

 

So when the app starts i have a dataform asking for login credentials. If it's possible to login deactive it and notify to show the appbar and activate main vm. :)

EDIT - Looking in the documentation about Screens,Conductors,Composition and was wondering what the "DSL" means

BONUS: Create a DSL for doing this which doesn’t require explicit code in the OnDeactivate override. HINT: Use the events.