Skip to content

Commit

Permalink
Merge pull request #37 from KasperSK/master
Browse files Browse the repository at this point in the history
WIP Version 4.0 documentation
  • Loading branch information
nigel-sampson authored Apr 18, 2021
2 parents 0fa3612 + 7268852 commit 067d1af
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 124 deletions.
21 changes: 15 additions & 6 deletions documentation/actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ We briefly introduced actions in [Pt. 1](./configuration), but there is so much
<UserControl x:Class="Caliburn.Micro.Hello.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cal="http://www.caliburnproject.org">
<StackPanel>
<Label Content="Hello please write your name" />
<TextBox x:Name="Name" />
<Button Content="Click Me">
<i:Interaction.Triggers>
Expand All @@ -24,13 +25,19 @@ We briefly introduced actions in [Pt. 1](./configuration), but there is so much
</UserControl>
```

As you can see, the Actions feature leverages System.Windows.Interactivity for it’s trigger mechanism. This means that you can use anything that inherits from System.Windows.Interactivity.TriggerBase to trigger the sending of an ActionMessage.1 Perhaps the most common trigger is an EventTrigger, but you can create almost any kind of trigger imaginable or leverage some common triggers already created by the community. ActionMessage is, of course, the Caliburn.Micro-specific part of this markup. It indicates that when the trigger occurs, we should send a message of “SayHello.” So, why do I use the language “send a message” instead of “execute a method” when describing this functionality? That’s the interesting and powerful part. ActionMessage bubbles through the Visual Tree searching for a target instance that can handle it. If a target is found, but does not have a “SayHello” method, the framework will continue to bubble until it finds one, throwing an exception if no “handler” is found.2 This bubbling nature of ActionMessage comes in handy in a number of interesting scenarios, Master/Details being a key use case. Another important feature to note is Action guards. When a handler is found for the “SayHello” message, it will check to see if that class also has either a property or a method named “CanSayHello.” If you have a guard property and your class implements INotifyPropertyChanged, then the framework will observe changes in that property and re-evaluate the guard accordingly. We’ll discuss method guards in further detail below.
As you can see, the Actions feature leverages Microsoft.Xaml.Behaviors for it’s trigger mechanism. This means that you can use anything that inherits from Microsoft.Xaml.Behaviors.TriggerBase to trigger the sending of an ActionMessage.

1 Perhaps the most common trigger is an EventTrigger, but you can create almost any kind of trigger imaginable or leverage some common triggers already created by the community. ActionMessage is, of course, the Caliburn.Micro-specific part of this markup. It indicates that when the trigger occurs, we should send a message of “SayHello.” So, why do I use the language “send a message” instead of “execute a method” when describing this functionality? That’s the interesting and powerful part. ActionMessage bubbles through the Visual Tree searching for a target instance that can handle it. If a target is found, but does not have a “SayHello” method, the framework will continue to bubble until it finds one, throwing an exception if no “handler” is found.

2 This bubbling nature of ActionMessage comes in handy in a number of interesting scenarios, Master/Details being a key use case. Another important feature to note is Action guards. When a handler is found for the “SayHello” message, it will check to see if that class also has either a property or a method named “CanSayHello.” If you have a guard property and your class implements INotifyPropertyChanged, then the framework will observe changes in that property and re-evaluate the guard accordingly. We’ll discuss method guards in further detail below.

### Action Targets

Now you’re probably wondering how to specify the target of an ActionMessage. Looking at the markup above, there’s no visible indication of what that target will be. So, where does that come from? Since we used a Model-First approach, when Caliburn.Micro (hereafter CM) created the view and bound it to the ViewModel using the ViewModelBinder, it set this up for us. Anything that goes through the ViewModelBinder will have its action target set automatically. But, you can set it yourself as well, using the attached property Action.Target. Setting this property positions an ActionMessage “handler” in the Visual Tree attached to the node on with you declare the property. It also sets the DataContext to the same value, since you often want these two things to be the same. However, you can vary the Action.Target from the DataContext if you like. Simply use the Action.TargetWithoutContext attached property instead. One nice thing about Action.Target is that you can set it to a System.String and CM will use that string to resolve an instance from the IoC container using the provided value as its key. This gives you a nice way of doing View-First MVVM if you so desire. If you want Action.Target set and you want Action/Binding Conventions applied as well, you can use the Bind.Model attached property in the same way.

#### View First
#### View First

//Skip this section.

Let’s see how we would apply this to achieve MVVM using a View-First technique (gasp!) Here’s how we would change our bootstrapper:

Expand Down Expand Up @@ -95,10 +102,8 @@ Notice the use of the Bind.Model attached property. This resolves our VM by key
Now, let’s take a look at another interesting aspect of ActionMessage: Parameters. To see this in action, let’s switch back to our original ViewModel-First bootstrapper, etc. and begin by changing our ShellViewModel to look like this:

``` csharp
using System.ComponentModel.Composition;
using System.Windows;

[Export(typeof(IShell))]
public class ShellViewModel : IShell
{
public bool CanSayHello(string name)
Expand All @@ -119,7 +124,7 @@ There are a few things to note here. First, we are now working with a completely
<UserControl x:Class="Caliburn.Micro.HelloParameters.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
xmlns:cal="http://www.caliburnproject.org">
<StackPanel>
<TextBox x:Name="Name" />
Expand Down Expand Up @@ -190,6 +195,10 @@ public class MyViewModel
}
```

#### Xamarin Forms

For Xamarin Forms only the $this parameter works, this is because traversing the visual tree is a bit different in Xamarin Forms.

##### Word to the Wise
Parameters are a convenience feature. They are very powerful and can help you out of some tricky spots, but they can be easily abused. Personally, I only use parameters in the simplest scenarios. One place where they have worked nicely for me is in login forms. Another scenario, as mentioned previously is Master/Detail operations.

Expand Down
113 changes: 31 additions & 82 deletions documentation/bootstrapper.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,72 +3,61 @@ layout: page
title: Customizing The Bootstrapper
---

In the [last part](./configuration) we discussed the most basic configuration for Caliburn.Micro and demonstrated a couple of simple features related to Actions and Conventions. In this part, I would like to explore the Bootstrapper class a little more. Let’s begin by configuring our application to use an IoC container. We’ll use MEF for this example, but Caliburn.Micro will work well with any container. First, go ahead and grab the code from Part 1. We are going to use that as our starting point. Add two additional references: System.ComponentModel.Composition and System.ComponentModel.Composition.Initialization. Those are the assemblies that contain MEF’s functionality. Now, let’s create a new Bootstrapper called MefBootstrapper. Use the following code:
In the [last part](./configuration) we discussed the most basic configuration for a Caliburn.Micro WPF Application and demonstrated a couple of simple features related to Actions and Conventions. In this part, I would like to explore the Bootstrapper class a little more. Let’s begin by configuring our application to use an IoC container. We’ll use the built in container for this example, but Caliburn.Micro will work well with any container. First, go ahead and grab the code from Part 1. We are going to use that as our starting point. Now, let’s create a new Bootstrapper called SimpleBootstrapper. Use the following code:

``` csharp
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
using System.Linq;
using System.Reflection;
using System.Windows;

public class MefBootstrapper : BootstrapperBase
public class SimpleBootstrapper : BootstrapperBase
{
private CompositionContainer container;
private SimpleContainer container;

public MefBootstrapper()
public SimpleBootstrapper()
{
Initialize();
}

protected override void Configure()
{
container = CompositionHost.Initialize(
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
)
);
container = new SimpleContainer();

var batch = new CompositionBatch();
container.Singleton<IWindowManager, WindowManager>();
container.Singleton<IEventAggregator, EventAggregator>();

batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);

container.Compose(batch);
container.PerRequest<ShellViewModel>();
}

protected override object GetInstance(Type serviceType, string key)
protected override object GetInstance(Type service, string key)
{
string contract = string.IsNullOrEmpty(key) ? AttributedModelServices.GetContractName(serviceType) : key;
var exports = container.GetExportedValues<object>(contract);

if (exports.Any())
return exports.First();

throw new Exception(string.Format("Could not locate any instances of contract {0}.", contract));
return container.GetInstance(service, key);
}

protected override IEnumerable<object> GetAllInstances(Type serviceType)
protected override IEnumerable<object> GetAllInstances(Type service)
{
return container.GetExportedValues<object>(AttributedModelServices.GetContractName(serviceType));
return container.GetAllInstances(service);
}

protected override void BuildUp(object instance)
{
container.SatisfyImportsOnce(instance);
container.BuildUp(instance);
}

protected override void OnStartup(object sender, StartupEventArgs e)
{
DisplayRootViewFor<IShell>();
DisplayRootViewFor<ShellViewModel>();
}

protected override IEnumerable<Assembly> SelectAssemblies()
{
return new[] { Assembly.GetExecutingAssembly() };
}
}
```
*Note: We define IShell down below.*

That’s all the code to integrate MEF. First, we override the Configure method of the Bootstrapper class. This gives us an opportunity to set up our IoC container as well as perform any other framework configuration we may want to do, such as customizing conventions. In this case, I’m taking advantage of Silverlight’s CompositionHost to setup the CompositionContainer. You can just instantiate the container directly if you are working with .NET. Then, I’m creating an AggregateCatalog and populating it with AssemblyCatalogs; one for each Assembly in AssemblySource.Instance. So, what is AssemblySoure.Instance? This is the place that Caliburn.Micro looks for Views. You can add assemblies to this at any time during your application to make them available to the framework, but there is also a special place to do it in the Bootstrapper. Simply override SelectAssemblies like this:
That’s all the code to use the built in container. First, we override the Configure method of the Bootstrapper class. This gives us an opportunity to set up our IoC container as well as perform any other framework configuration we may want to do, such as customizing conventions. Here we create the SimpleContainer and add the WindowManager and EventAggregator and ofcourse the ShellViewModel, but not the ShellView Becase we have Assembly.Source.Instance. So, what is AssemblySoure.Instance? This is the place that Caliburn.Micro looks for Views. You can add assemblies to this at any time during your application to make them available to the framework, but there is also a special place to do it in the Bootstrapper. Simply override SelectAssemblies like this:


``` csharp
Expand All @@ -86,65 +75,25 @@ After creating the container and providing it with the catalogs, I make sure to

After we configure the container, we need to tell Caliburn.Micro how to use it. That is the purpose of the three overrides that follow. “GetInstance” and “GetAllInstances” are required by the framework. “BuildUp” is optionally used to supply property dependencies to instances of IResult that are executed by the framework.

### Word to the Wise
While Caliburn.Micro does provide ServiceLocator functionality through the Bootstrapper’s overrides and the IoC class, you should avoid using this directly in your application code. ServiceLocator is considered by many to be an anti-pattern. Pulling from a container tends to obscure the intent of the dependent code and can make testing more complicated. In future articles I will demonstrate at least one scenario where you may be tempted to access the ServiceLocator from a ViewModel. I’ll also demonstrate some solutions.2

Besides what is shown above, there are some other notable methods on the Bootstrapper. You can override OnStartup and OnExit to execute code when the application starts or shuts down respectively and OnUnhandledException to cleanup after any exception that wasn’t specifically handled by your application code. The last override, DisplayRootView, is unique. Let’s look at how it is implemented in Bootstrapper<TRootModel>

``` csharp
protected override void DisplayRootView()
{
var viewModel = IoC.Get<TRootModel>();
#if SILVERLIGHT
var view = ViewLocator.LocateForModel(viewModel, null, null);
ViewModelBinder.Bind(viewModel, view, null);
Finally, make sure to update your App.xaml and change the HelloBootstrapper to SimpleBootstrapper. That’s it! Your up and running with MEF and you have a handle on some of the other key extension points of the Bootstrapper as well.

var activator = viewModel as IActivate;
if (activator != null)
activator.Activate();
You can of course use any IoC container you desire as long as you provide implementations for “GetInstance” and “GetAllInstances”.

Application.RootVisual = view;
#else
IWindowManager windowManager;

try
{
windowManager = IoC.Get<IWindowManager>();
}
catch
{
windowManager = new WindowManager();
}

windowManager.Show(viewModel);
#endif
}
```

The Silverlight version of this method resolves your root VM from the container, locates the view for it and binds the two together. It then makes sure to “activate” the VM if it implements the appropriate interface. The WPF version does the same thing by using the WindowManager class, more or less. DisplayRootView is basically a convenience implementation for model-first development. If you don’t like it, perhaps because you prefer view-first MVVM, then this is the method you want to override to change that behavior.

#### v1.1 Changes
In v1.1 we removed the DisplayRootView override and placed it's functionality in a helper method named DisplayRootViewFor. The generic bootstrapper now calls this method from the OnStartup override. To change this behavior, just override OnStartup, and instead of calling the base implementation, write your own activation code. This provides better support for splash screens, login screens and access to startup parameters.

Now that you understand all about the Bootstrapper, let’s get our sample working. We need to add the IShell interface. In our case, it’s just a marker interface. But, in a real application, you would have some significant shell-related functionality baked into this contract. Here’s the code:
### Word to the Wise
While Caliburn.Micro does provide ServiceLocator functionality through the Bootstrapper’s overrides and the IoC class, you should avoid using this directly in your application code. ServiceLocator is considered by many to be an anti-pattern. Pulling from a container tends to obscure the intent of the dependent code and can make testing more complicated.

``` csharp
public interface IShell
{
}
```
Besides what is shown above, there are some other notable methods on the Bootstrapper. You can override OnStartup and OnExit to execute code when the application starts or shuts down respectively and OnUnhandledException to cleanup after any exception that wasn’t specifically handled by your application code.

Now, we need to implement the interface and decorate our ShellViewModel with the appropriate MEF attributes:
#### v4.0 Changes
In 4.0 the bootstrapper has seen some changes and that is the the DisplayRootViewFor methods return a task and they can be awaited.

``` csharp
[Export(typeof(IShell))]
public class ShellViewModel : PropertyChangedBase, IShell
protected Task DisplayRootViewFor<TViewModel>(IDictionary<string, object> settings = null)
{
...implementation is same as before...
return DisplayRootViewForAsync(typeof(TViewModel), settings);
}
```

Finally, make sure to update your App.xaml and change the HelloBootstrapper to MefBootstrapper. That’s it! Your up and running with MEF and you have a handle on some of the other key extension points of the Bootstrapper as well.

### Using Caliburn.Micro in Office and WinForms Applications

Expand Down
Loading

0 comments on commit 067d1af

Please sign in to comment.