Skip to content
This repository was archived by the owner on Nov 27, 2024. It is now read-only.

Advanced scenarios and examples

Andreas Niedermair edited this page Dec 30, 2016 · 13 revisions

Example: Adapt the creation of the View Model

One key benefit of the introduction of a controller is that you can control the concrete screen that is created, and how it is going to be shaped.

The root of these variations is located in the signature of IControllerManager.ShowWindowAsync and IControllerManager.ShowDialogAsync:

Task<TController> ShowWindowAsync<TControll>(**object options**, object context, IDictionary<string, string> settings);
Task<TController> ShowDialogAsync<TControll>(**object options**, object context, IDictionary<string, string> settings);

The parameters context and settings are known from the underlying Caliburn.Micro implementation, but options is the parameter you can base your decisions on.

With overriding GetScreenType one can simply control the creation (and displaying) of screens according to options:

using Caliburn.Micro;
using Caliburn.Micro.Contrib.Controller;
using Jetbrains.Annotations;

public class MainController : ControllerBase<MainViewModel>
{
  public MainController([NotNull] IScreenFactory screenFactory,
                        [NotNull] [ItemNotNull] params IRoutine[] routines)
    : base(screenFactory,
           routines) {}

  [NotNull]
  public override Type GetScreenType([CanBeNull] object options = null)
  {
    return base.GetScreenType(options);
  }
}

Just be aware that the returned Type-instance shall not be an interface, and has to implement IScreen.

If you simply want to inject some properties, but leave the creation of the screen intact, override BuildUp:

using Caliburn.Micro;
using Caliburn.Micro.Contrib.Controller;
using Jetbrains.Annotations;

public class MainController : ControllerBase<MainViewModel>
{
  public MainController([NotNull] IScreenFactory screenFactory,
                        [NotNull] [ItemNotNull] params IRoutine[] routines)
    : base(screenFactory,
           routines) {}

  [NotNull]
  public override MainViewModel BuildUp([NotNull] MainViewModel screen,
                                        [CanBeNull] object options = null)
  {
    var creationOptions = options as CreationOptions;
    if (creationOptions != null)
    {
      screen.Name = creationOptions.Name;
    }

    return screen;
  }

  public sealed class CreationOptions
  {
    [CanBeNull]
    public string Name { get; set; }
  }
}

// Usage:
// var creationOptions = new MainController.CreationOptions
//                       {
//                         Name = "Joe Doe"
//                       };
// var controllerManager = IoC.Get<IControllerManager>();
// controllerManager.ShowWindowAsync<MainController>(creationOptions);

Example: Interact with a specific Controller routine

You can declare interest in a specific Controller routine in your Controller's constructor:

using Caliburn.Micro.Contrib.Controller;
using JetBrains.Annotations;

public class MainController : ControllerBase<MainViewModel>
{
  /// <exception cref="ArgumentNullException"><paramref name="defaultDisplayNameRoutine" /> is <see langword="null" /></exception>
  public MainController([NotNull] DefaultDisplayNameRoutine defaultDisplayNameRoutine,
                        [NotNull] IScreenFactory screenFactory,
                        [NotNull] [ItemNotNull] params IRoutine[] routines)
    : base(screenFactory,
           routines)
  {
    if (defaultDisplayNameRoutine == null)
    {
      throw new ArgumentNullException(nameof(defaultDisplayNameRoutine));
    }
    this.DefaultDisplayNameRoutine = this.RegisterRoutine(defaultDisplayNameRoutine);
  }

  [NotNull]
  private DefaultDisplayNameRoutine DefaultDisplayNameRoutine { get; }
}

Instead of calling the base-ctor with the Controller routine in question, set the instance to a local member and call this.RegisterRoutine(controllerRoutine).

Example: Mix in the View Model

Injecting additional behavior into the View Model can be useful for multiple reasons, such as writing a Controller routine that should automagically extend the View Model (see BlockingRoutine for an actual example).

Mixins can be defined on a Controller routine:

using System.Reflection.Emit;
using Caliburn.Micro.Contrib.Controller;
using JetBrains.Annotations;

public class SomeControllerRoutine : ControllerRoutineBase,
                                     IScreenMixin<SomeControllerRoutine.Mixin>,
                                     IScreenMixin<SomeControllerRoutine.IMixin>,
                                     IScreenAttributesMixin
{
  public interface IMixin
  {
    string SomeProperty { get; set; }
    void SomeMethod();
  }

  public class Mixin : IMixin
  {
    public string SomeProperty { get; set; }
    public void SomeMethod() { /* ... */ }
  }

  [Pure]
  [NotNull]
  [ItemNotNull]
  public CustomAttributeBuilder[] GetCustomAttributeBuilders()
  {
    /* ... */
  }
}

Caution: Please be aware, that you have to implement additional NotifyOfPropertyChange-calls in your Controller routine, as DynamicProxy cannot intercept events at this very moment.
Caution: You can define additional attributes to be injected on the class'-level but not on member-level. Read more about CustomAttributeBuilder.

Example: Injecting IHandle into the View Model

using Caliburn.Micro;
using Caliburn.Micro.Contrib.Controller;
using JetBrains.Annotations;

public abstract class MainViewModel : Screen
{
  public sealed class Close {}
}

public class MainController : ControllerBase<MainViewModel>,
                              Intercept<MainViewModel>.IHandle<MainViewModel.Close>
{
  public MainController([NotNull] IScreenFactory screenFactory,
                        [NotNull] [ItemNotNull] params IRoutine[] routines)
    : base(screenFactory,
           routines) {}

  [UsedImplicitly]
  [InterceptProxyMethod(CallBase = false)]
  public void Handle([NotNull] MainViewModel mainViewModel,
                     [NotNull] MainViewModel.Close message)
  {
    mainViewModel.TryClose();
  }
}
Clone this wiki locally