This serves as a quick guide to the most frequently used conventions and features in the Caliburn.Micro project.
This is automatically wiring events on controls to call methods on the ViewModel.
<Button x:Name="Save">
This will cause the Click event of the Button to call "Save" method on the ViewModel.
<Button cal:Message.Attach="Save">
This will again cause the "Click" event of the Button to call "Save" method on the ViewModel.
Different events can be used like this:
<Button cal:Message.Attach="[Event MouseEnter] = [Action Save]">
Different parameters can be passed to the method like this:
<Button cal:Message.Attach="[Event MouseEnter] = [Action Save($this)]">
- $eventArgs
- Passes the EventArgs or input parameter to your Action. Note: This will be null for guard methods since the trigger hasn’t actually occurred.
- $dataContext
- Passes the DataContext of the element that the ActionMessage is attached to. This is very useful in Master/Detail scenarios where the ActionMessage may bubble to a parent VM but needs to carry with it the child instance to be acted upon.
- $source
- The actual FrameworkElement that triggered the ActionMessage to be sent.
- $view
- The view (usually a UserControl or Window) that is bound to the ViewModel.
- $executionContext
- The action's execution context, which contains all the above information and more. This is useful in advanced scenarios.
- $this
- The actual UI element to which the action is attached. In this case, the element itself won't be passed as a parameter, but rather its default property.
<UserControl x:Class="Caliburn.Micro.CheatSheet.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:cal="http://www.caliburnproject.org">
<StackPanel>
<TextBox x:Name="Name" />
<Button Content="Save">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<cal:ActionMessage MethodName="Save">
<cal:Parameter Value="{Binding ElementName=Name, Path=Text}" />
</cal:ActionMessage>
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</StackPanel>
</UserControl>
This syntax is Expression Blend friendly.
This is automatically binding dependency properties on controls to properties on the ViewModel.
<TextBox x:Name="FirstName" />
Will cause the "Text" property of the TextBox to be bound to the "FirstName" property on the ViewModel.
<TextBox Text="{Binding Path=FirstName, Mode=TwoWay}" />
This is the normal way of binding properties.
The three different methods on the Event Aggregator are:
public interface IEventAggregator {
void Subscribe(object instance);
void Unsubscribe(object instance);
void Publish(object message, Action<System.Action> marshal);
}
An event can be a simple class such as:
public class MyEvent {
public MyEvent(string myData) {
this.MyData = myData;
}
public string MyData { get; private set; }
}
- Add a reference to the
Microsoft.Extensions.DependencyInjection
nuget package - In Bootstrapper.cs, add the following property:
public static IHost Host { get; private set; }
- In the constructor, add the following code:
public Bootstrapper()
{
Host = Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
ConfigureServices(services);
})
.Build();
... code elided
}
- Add a method named
ConfigureServices
and set up your DI wire up as appropriate for your project.
protected void ConfigureServices(IServiceCollection serviceCollection)
{
FrameSet = serviceCollection.AddCaliburnMicro();
serviceCollection.AddLogging();
serviceCollection.AddLocalization();
}
- Override the appropriate Caliburn.Micro dependency injection methods:
protected override object GetInstance(Type service, string key)
{
return Host.Services.GetService(service);
}
protected override IEnumerable<object> GetAllInstances(Type service)
{
return Host.Services.GetServices(service);
}
- Add a class named
Frameset
public class FrameSet
{
public FrameSet()
{
Frame = new Frame
{
NavigationUIVisibility = NavigationUIVisibility.Hidden
};
FrameAdapter = new FrameAdapter(Frame);
}
public Frame Frame { get; private set; }
public FrameAdapter FrameAdapter { get; private set; }
public INavigationService NavigationService => FrameAdapter as INavigationService;
}
- Add an extension method which creates a Frame and sets up the Caliburn.Micro infratructure.
public static FrameSet AddCaliburnMicro(this IServiceCollection serviceCollection)
{
var frameSet = new FrameSet();
// wire up the interfaces required by Caliburn.Micro
serviceCollection.AddSingleton<IWindowManager, WindowManager>();
serviceCollection.AddSingleton<IEventAggregator, EventAggregator>();
// Register the FrameAdapter which wraps a Frame as INavigationService
serviceCollection.AddSingleton<INavigationService>(sp => frameSet.NavigationService);
// wire up all of the view models in the project.
typeof(Bootstrapper).Assembly.GetTypes()
.Where(type => type.IsClass)
.Where(type => type.Name.EndsWith("ViewModel"))
.ToList()
.ForEach(viewModelType => serviceCollection.AddTransient(viewModelType));
return frameSet;
}
- In the Bootstrapper class store an instance of FrameSet in the ConfigureServices method:
protected void ConfigureServices(IServiceCollection serviceCollection)
{
FrameSet = serviceCollection.AddCaliburnMicro();
serviceCollection.AddClearDashboardDataAccessLayer();
serviceCollection.AddLogging();
serviceCollection.AddLocalization();
}
- Add a method to add the Frame to the visual tree:
private void AddFrameToMainWindow(Frame frame)
{
Logger.LogInformation("Adding Frame to ShellView grid control.");
var mainWindow = Application.MainWindow;
if (mainWindow == null)
{
throw new NullReferenceException("'Application.MainWindow' is null.");
}
if (mainWindow.Content is not Grid grid)
{
throw new NullReferenceException("The grid on 'Application.MainWindow' is null.");
}
Grid.SetRow(frame, 1);
Grid.SetColumn(frame, 0);
grid.Children.Add(frame);
}
- Then override OnStartup and call the method to add the Frame to the visual tree:
protected override async void OnStartup(object sender, StartupEventArgs e)
{
// Allow the ShellView to be created.
await DisplayRootViewForAsync<ShellViewModel>();
// Now add the Frame to be added to the Grid in ShellView
AddFrameToMainWindow(FrameSet.Frame);
... code elided
}
- Inject INavigationService into the contructor of your view model:
private readonly INavigationService _navigationService
public YourViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
}
- To navigate to a different viewmodel/view:
_navigationService.NavigateToViewModel<AnotherViewModel>();
- To pass data to a view model... define properties on the target view model, i.e.
public int ProductId { get ;set;}
public int CategoryId { get; set;}
navigationService.For<ProductViewModel>()
.WithParam(v => v.ProductId, 42)
.WithParam(v => v.CategoryId, 2)
.Navigate();
- This also works with more complex objects (in WPF at least)
_navigationService.For<AnotherViewModel>()
.WithParam(v => v.ComplexObject, new ComplexObject()).Navigate();
- And finally multiple parameters may be passed by chaining WithParam:
_navigationService.For<AnotherViewModel>()
.WithParam(v => v.ComplexObject, new ComplexObject())
.WithParam(v => v.SomeString, _theString)
.WithParam(v => v.ComplexObject2, new ComplexObject2()).Navigate();