Click here to Skip to main content
15,867,568 members
Articles / Desktop Programming / WPF

Stylet: A Basic Primer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (4 votes)
21 Jul 2020CPOL4 min read 9.2K   3  
An overview of the Stylet MVVM framework
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.

C#
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.

XML
<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.

C#
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.

XML
<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.

C#
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.

C#
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.

C#
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.

C#
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 Actions.

XML
<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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Kenya Kenya
Experienced C# software developer with a passion for WPF.

Awards,
  • CodeProject MVP 2013
  • CodeProject MVP 2012
  • CodeProject MVP 2021

Comments and Discussions

 
-- There are no messages in this forum --