Friday, 30 May 2014

Caliburn Micro and UserControls

The things that took me a while to understand were:
  • The UserControl library needs a bootstrapper
  • How to bind the view-model to the view
  • How to reference the view-model  in XAML instead of the view

The UserControl library needs a bootstrapper

Just like a Caliburn Micro app the library needs a bootstrapper so that Caliburn can play nicely.
Here are my updates to the 'standard' bootstrapper.

internal class UserControlBootstrapper : BootstrapperBase
{
  private CompositionContainer container;
 
  /// <summary>
  /// The singleton bootstrapper
  /// </summary>
  private static BootstrapperBase bootstrapper;
 
  /// <summary>
  /// As assemblies don't have a well known entry point we need to initialise the bootstrapper on view construction AND view-model construction as we don't have control on how the user may access us
  /// </summary>
  internal static void Initialise()
  {
    if (null == bootstrapper)
    {
      bootstrapper = new UserControlBootstrapper();
    }
  }
 
  /// <summary>
  /// Call through to BootstrapperBase indicating that this isn't an application
  /// </summary>
  [UsedImplicitly]
  private UserControlBootstrapper() : base(false)
  {
    this.Initialize();
  }

  /// <summary>Override this to include this usercontrol assembly</summary>
  /// <returns>Enumeration of 'Assembly's which include this assembly</returns>
  protected override IEnumerable<Assembly> SelectAssemblies()
  {
    var baseAssemblies = new List<Assembly>(base.SelectAssemblies());
    var thisAssembly = Assembly.GetAssembly(typeof(UserControlBootstrapper));
    if (!baseAssemblies.Contains(thisAssembly))
    {
      baseAssemblies.Add(thisAssembly);
    }


    // If this library is being accessed from a Caliburn enabled app then
    // this assembly may already be 'known' in the AssemblySource.Instance collection.
    // We need to remove these otherwise we'll get:
    //  "An item with the same key has already been added." (System.ArgumentException)
    // which (for my scenario) eventually manifested itself as a:
    //  "" (System.ComponentModel.Composition.CompositionException)
    foreach (var assembly in baseAssemblies.ToList().Where(newAssembly => AssemblySource.Instance.Contains(newAssembly)))
    {
      baseAssemblies.Remove(assembly);
    }
  return baseAssemblies; }


  ...



How to bind the the view-model to the view

Use cal:Bind.Model

e.g.

<Window 
...
        xmlns:controls="clr-namespace:My.Controls;assembly=My.Controls"
  >
...
  <!-- Reference the view and bind to the view-model -->
  <controls:MyView cal:Bind.Model="My.Controls.MyViewModel" />

How to reference the view-model  in XAML instead of the view

In a CM app simply use an instance of the VM as the source of a ContentControl.

e.g.

MyView.xaml

<ContentControl Name="FractalControl"/>

MyViewModel.cs

/// <summary>
/// Return a view-model to use and allow CM to join it up with the relevant view
/// </summary>
[UsedImplicitly]
public FractalControlViewModel FractalControl
{
  get { return new FractalControlViewModel(); }
}

No comments: