This article will provide a basic platform from which you can delve deeper into other aspects of Stylet.
Introduction
Stylet is a small MVVM framework that supports development of modular WPF applications. It aims to simplify the process of creating WPF-MVVM applications by reducing the amount of code required to implement certain MVVM features. In this article, I will briefly cover some of those features but do note that this article is not an introduction to WPF or MVVM. You should have knowledge of the two if you are to comfortably follow along.
Background
I've previously written about Prism and MvvmCross using a sample application that displays cards representing some imaginary employees. This article follows the same trend: The sample app has cards with some employee details and clicking on a card navigates to a page displaying more details of a specific employee.
The sample application has three projects: A .NET Core WPF application project and two .NET Core class library projects; one containing shared code and the other a Stylet module.
You can clone or download the sample project from GitHub.
Stylet
Roots & Conductors
To use Stylet in your WPF application project you have to reference the Stylet NuGet package. You then have to create a root view and root view model, which will associated with each other based on the naming convention. The root view, which has to be a Window
, acts as the shell of your application. In the sample project, ShellViewModel
is the root view model.
using Stylet;
namespace StaffStuff.ViewModels
{
public class ShellViewModel : Conductor<IScreen>.StackNavigation
{
public ShellViewModel(StaffViewModel staffViewModel)
{
this.DisplayName = string.Empty;
this.ActivateItem(staffViewModel);
}
}
}
ShellViewModel
derives from Stylet's Conductor<T>.StackNavigation
. A conductor manages the lifecycle of the view model, or view models, it owns. It determines whether a view model is activated, deactivated or closed. The Conductor<T>.StackNavigation
is a conductor that provides stack based navigation. In the sample application, the conductor will enable navigation from the cards view to the details view.
In ShellViewModel
's constructor, a view model is passed as a dependency and set as the active item. Since ShellViewModel
is a conductor, it now owns StaffViewModel
and will manage its lifecycle. In ShellView
, the active item, or rather the view associated with the active item, will be placed in a ContentControl
.
<Window x:Class="StaffStuff.Views.ShellView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:s="https://github.com/canton7/Stylet"
Background="#FF0D2738"
Height="480" Width="800"
WindowStartupLocation="CenterScreen">
<Grid>
<ContentControl s:View.Model="{Binding ActiveItem}"/>
</Grid>
</Window>
ActiveItem
is a property of the conductor and is set by calling the conductor's ActivateItem()
method.
Bootstrapper
With your roots in place, Stylet requires that you create a bootsrapper, where you specify your root view model.
namespace StaffStuff
{
public class Bootstrapper : Bootstrapper<ShellViewModel>
{
protected override void ConfigureIoC(IStyletIoCBuilder builder)
{
builder.AddModule(new ServicesModule());
}
}
}
You then add Stylet's ApplicationLoader
as an app resource and set the bootstrapper to be loaded.
<Application x:Class="StaffStuff.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:StaffStuff"
xmlns:s="https://github.com/canton7/Stylet">
<Application.Resources>
<s:ApplicationLoader>
<s:ApplicationLoader.Bootstrapper>
<local:Bootstrapper/>
</s:ApplicationLoader.Bootstrapper>
</s:ApplicationLoader>
</Application.Resources>
</Application>
Apart from setting your root view model, the bootstrapper is also where you can register types with Stylet's IoC container. This is done in ConfigureIoC()
using the StyleIoCBuilder
, so you can do something like builder.Bind<IStaffData>().To<StaffData>()
. In the bootstrapper for the sample project I'm adding a module to the builder, builder.AddModule(new ServicesModule())
.
Modules
As mentioned in the introduction, Stylet enables development of modular WPF-MVVM applications. To create a Stylet module, you create a .NET Core class library project, that references the Stylet NuGet package, and add a class that derives from StyletIoCModule
at the root of the project. Types can then be registered with the IoC container in the module's Load()
method.
using StaffStuff.Common.Interfaces;
using StaffStuff.Services.Services;
using StyletIoC;
namespace StaffStuff.Services
{
public class ServicesModule : StyletIoCModule
{
protected override void Load()
{
Bind<IStaffData>().To<StaffData>().InSingletonScope();
}
}
}
Screens
A Screen
provides validation, property change notification and active status monitoring to view models that derive from it. It also includes a Parent
property which enables a view model to know which Conductor
is managing it and allows it to request the conductor to close it or activate a different view model. In the sample project StaffViewModel
, which is the first view model activated by ShellViewModel
, derives from the Screen
class.
using StaffStuff.Common.Interfaces;
using StaffStuff.Common.Models;
using Stylet;
using System.Collections.Generic;
namespace StaffStuff.ViewModels
{
public class StaffViewModel : Screen
{
private readonly IStaffData _staffData;
public StaffViewModel(IStaffData staffData)
{
_staffData = staffData;
}
private List<Employee> _employees;
public List<Employee> Employees
{
get => _employees;
set => SetAndNotify(ref _employees, value);
}
protected override void OnInitialActivate()
{
Employees = _staffData.GetEmployees();
}
public void StaffDetails(Employee employee)
{
var staffDetailsVM = new StaffDetailsViewModel { Employee = employee };
((ShellViewModel)this.Parent).ActivateItem(staffDetailsVM);
}
}
}
OnInitialActivate()
is called only once when the view model is first activated. StaffViewModel'
s StaffDetails()
method calls the conductor's ActivateItem()
method requesting it to activate the StaffDetailsViewModel
triggering navigation to its associated view.
using StaffStuff.Common.Models;
using Stylet;
namespace StaffStuff.ViewModels
{
public class StaffDetailsViewModel : Screen
{
public StaffDetailsViewModel() { }
private Employee _employee;
public Employee Employee
{
get => _employee;
set => SetAndNotify(ref _employee, value);
}
public void GoBack()
{
this.RequestClose();
}
}
}
GoBack()
calls the Screen
's RequestClose()
method which in turn causes the conductor to close the view model. This triggers navigation back to the previous view by re-activating the conductor's previous active item – Since the conductor is of type Conductor<T>.StackNavigation
, navigation back to the previous active item can also be triggered by calling the conductor's GoBack()
method.
public void GoBack()
{
((ShellViewModel)this.Parent).GoBack();
}
Actions
You may have noticed that both StaffViewModel
and StaffDetailsViewModel
do not have any ICommand
properties. This is because Stylet does away with such properties and instead enables a method in your view model to be set as the value of a Button
's Command
property. Stylet does this using Action
s.
<Button Command="{s:Action GoBack}">
...
</Button>
<DataTemplate x:Key="StaffDataTemplate" DataType="{x:Type models:Employee}">
<Border Margin="10" Cursor="Hand" BorderThickness="1"
Background="#FF16394F" BorderBrush="#FF3F5666"
CornerRadius="8" Width="200" Height="240">
...
<behaviors:Interaction.Triggers>
<behaviors:EventTrigger EventName="MouseLeftButtonUp">
<behaviors:InvokeCommandAction Command="{s:Action StaffDetails}"
CommandParameter="{Binding}"/>
</behaviors:EventTrigger>
</behaviors:Interaction.Triggers>
</Border>
</DataTemplate>
Notice that you can also pass the value of a CommandParameter
to your method if it requires a parameter to be passed to it.
Conclusion
That's it! That's all that's required to get the sample application running. I think Stylet is really neat and the documentation is quite comprehensive, though it would be much better if it had Visual Studio templates for both application and module project setup – Currently there's no Visual Studio extension that enables this, like with Prism, but hopefully they'll be there in future. If you have an interest in learning more about the framework, I encourage you to go through the documentation and check out the sample projects in Stylet's repository.
History
- 21st July 2020: Initial post