Phoenix.UI.Wpf.Architecture.VMFirst.Stylet 3.0.0

dotnet add package Phoenix.UI.Wpf.Architecture.VMFirst.Stylet --version 3.0.0                
NuGet\Install-Package Phoenix.UI.Wpf.Architecture.VMFirst.Stylet -Version 3.0.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Phoenix.UI.Wpf.Architecture.VMFirst.Stylet" Version="3.0.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add Phoenix.UI.Wpf.Architecture.VMFirst.Stylet --version 3.0.0                
#r "nuget: Phoenix.UI.Wpf.Architecture.VMFirst.Stylet, 3.0.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install Phoenix.UI.Wpf.Architecture.VMFirst.Stylet as a Cake Addin
#addin nuget:?package=Phoenix.UI.Wpf.Architecture.VMFirst.Stylet&version=3.0.0

// Install Phoenix.UI.Wpf.Architecture.VMFirst.Stylet as a Cake Tool
#tool nuget:?package=Phoenix.UI.Wpf.Architecture.VMFirst.Stylet&version=3.0.0                

Phoenix.UI.Wpf.Architecture.VMFirst

.NET Framework .NET Core .NET
✔️ 4.8 ✔️ 3.1 ✔️ 5.0 ✔️ 6.0

Collection of base projects for the View Model First architecture approach of WPF applications.


Table of content

[toc]


ViewModel Interfaces

ViewModel interfaces enhance simple view models to be able to handle certain commonly needed tasks by making them implement callback functions or properties. Standalone those interfaces don't do that much, but together with an IViewProvider like the DefaultViewProvider from the separate NuGet package Phoenix.UI.Wpf.Architecture.VMFirst.ViewProvider they can be very helpful.

When using an IViewProvider those viewmodel interfaces can automatically be hooked up via the IViewProvider.ViewLoaded event. This event provides the newly loaded viewmodel and its bound view via its ViewLoadedEventArgs. When the event is raised, callback methods can be executed that initialize all viewmodel interfaces the newly loaded viewmodel implements. Those callback methods can typically be found in a static helper class with a similar name to their viewmodel interface.

For example the IActivatedViewModel interface has a static helper class ActivatedViewModelHelper with the callback method simply named Callback. This method takes in a viewmodel and its bound view as parameters.

static void Callback(object viewModel, FrameworkElement view)

Since the event does provide those two parameters, all that needs to be done is attaching all callbacks methods of the viewmodel interfaces to the IViewProvider.ViewLoaded event. This should be done early on in an application.

IActivatedViewModel

This interface is for view models that need to know when their linked view has been loaded. This can then be used to perform some kind of initialization task.

It provides the following method:

void OnInitialActivate();

Usage with IViewProvider

IViewProvider provider = new DefaultViewProvider();
provider.ViewLoaded += (sender, args) => ActivatedViewModelHelper.Callback(args.ViewModel, args.View);

IDeactivatedViewModel

This interface is for view models that need to know when their linked view is about to close. This will only apply to view models that are bound to a Window as only those provide the necessary Closing event.

It provides the following method:

void OnClosing();

Usage with IViewProvider

IViewProvider provider = new DefaultViewProvider();
provider.ViewLoaded += (sender, args) => DeactivatedViewModelHelper.Callback(args.ViewModel, args.View);

IViewAwareViewModel

This interface is for view models that need to know about their view.

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #ff0000; background-color: #ff000020' > <span style='margin-left:1em; text-align:left'> <b>Warning</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> Please note, that directly accessing and interacting with the view from a view model defies common <b>MVVM</b> principles and should be an absolute last resort. </div> </div>

It provides the following property:

FrameworkElement View { get; }

Usage with IViewProvider

IViewProvider provider = new DefaultViewProvider();
provider.ViewLoaded += (sender, args) => ViewAwareViewModelHelper.Callback(args.ViewModel, args.View);

IBusyIndicatorViewModel

This interface is for view models that utilize an IBusyIndicatorHandler.

It provides the following property:

IBusyIndicatorHandler BusyIndicatorHandler { get; }

Usage with IViewProvider

The setup of an IBusyIndicatorViewModel differs a little bit from the other ones, as it additionally needs an IBusyIndicatorHandler besides the viewmodel and its bound view. There are two ways of setting this viewmodel interface up:

  • BusyIndicatorViewModelHelper.Callback
IViewProvider provider = new DefaultViewProvider();
provider.ViewLoaded += (sender, args) =>
{
	IBusyIndicatorHandler busyIndicator = new IBusyIndicatorHandler(); // This must be an implementing class.
	BusyIndicatorViewModelHelper.Callback(args.ViewModel, args.View, busyIndicator);
};
  • BusyIndicatorViewModelHelper.CreateCallback (this is the better option for IOC)
Func<IBusyIndicatorHandler> busyIndicatorFactory = () => new IBusyIndicatorHandler(); // This must be an implementing class.
var callback = BusyIndicatorViewModelHelper.CreateCallback(busyIndicatorFactory);
IViewProvider provider = new DefaultViewProvider();
provider.ViewLoaded += (sender, args) => callback.Invoke(args.ViewModel, args.View);

IBusyIndicatorHandler

The IBusyIndicatorHandler interface and its implementing class BusyIndicatorHandler can be used by view models that want to inform their bound view, that currently some work is being executed. It provides the bindable signal properties IsBusy and BusyMessage that the view can use and show some kind of waiting animation (BusyIndicator) or lock certain parts of the UI.

The signal properties can be changed by directly calling the below methods of the BusyIndicatorHandler.

  • Activates the busy indicator by setting the IsBusy property to True and sets the BusyMessage to the defined message. Internally this pushes the new message onto a stack of messages and displays the topmost one.
void Show(string message = null);

<div style='padding:0.1em; border-style: solid; border-width: 0px; border-left-width: 10px; border-color: #ff0000; background-color: #ff000020' > <span style='margin-left:1em; text-align:left'> <b>Warning</b> </span> <br> <div style='margin-left:1em; margin-right:1em;'> Please note, that every message that is pushed with the <i>Show</i> method must be removed. Otherwise the <i>IsBusy</i> property won't be set to <i>false</i>. Either use one <i>Revoke</i> call for every message, or call <i>Close</i> if the workload has finished. </div> </div>

  • Overrides the currently displayed BusyMessage.
void Override(string message = null);
  • Removes the topmost BusyMessage in the stack of all currently displayed messages. If this is the last one, then the busy indicator will be hidden.
void Revoke();
  • Completely disables the busy indicator by setting the IsBusy property to False.
void Close();

Normally the usage of the following methods is better than manually calling above methods, as below ones are made to encapsulate workload and implicitly handle changing the signal properties.

Those methods will run in the calling thread and therefore block further execution.

  • Activates the busy indicator while executing the passed method.
void Execute(Action method, Action doneCallback = null);
void Execute(string message, Expression<Func<bool>> toggle, Action method);
void Execute(string message, Action method, Action doneCallback = null);

Those methods will await the passed function and therefore not block the calling thread.

  • Activates the busy indicator while executing the passed asynchronous method.
Task ExecuteAsync(Func<Task> asyncMethod, Action doneCallback = null);
Task ExecuteAsync(string message, Expression<Func<bool>> toggle, Func<Task> asyncMethod);
Task ExecuteAsync(string message, Func<Task> asyncMethod, Action doneCallback = null);
Task<T> ExecuteAsync<T>(Func<Task<T>> asyncFunction, Action doneCallback = null);
Task<T> ExecuteAsync<T>(string message, Expression<Func<bool>> toggle, Func<Task<T>> asyncFunction);
Task<T> ExecuteAsync<T>(string message, Func<Task<T>> asyncFunction, Action doneCallback = null);

Those methods will await the passed function and therefore not block the calling thread. They additionally provide cancellation support.

  • Activates the busy indicator while executing the passed asynchronous method with cancellation support.
Task ExecuteAsync(Func<CancellationToken, Task> asyncMethod, Action doneCallback = null, CancellationToken cancellationToken = default);
Task ExecuteAsync(string message, Expression<Func<bool>> toggle, Func<CancellationToken, Task> asyncMethod, CancellationToken cancellationToken = default);
Task ExecuteAsync(string message, Func<CancellationToken, Task> asyncMethod, Action doneCallback = null, CancellationToken cancellationToken = default);

Those methods will wrap the passed method in a task that will be awaited, therefore not blocking the calling thread and guaranteeing execution in another thread.

  • Activates the busy indicator while executing the passed method that will be wrapped within its own Task.

⇒ Wraps simple methods within their own awaited task.

Task ExecuteTaskAsync(Action method, Action doneCallback = null, CancellationToken cancellationToken = default);
Task ExecuteTaskAsync(string message, Expression<Func<bool>> toggle, Action method, CancellationToken cancellationToken = default);
Task ExecuteTaskAsync(string message, Action method, Action doneCallback = null, CancellationToken cancellationToken = default);

⇒ Wraps asynchronous tasks within their own awaited task. This is useful, if it is unknown, whether the underlying task really runs synchronous or not.

Task ExecuteTaskAsync(Func<Task> asyncMethod, Action doneCallback = null);
Task ExecuteTaskAsync(string message, Expression<Func<bool>> toggle, Func<Task> asyncMethod);
Task ExecuteTaskAsync(string message, Func<Task> asyncMethod, Action doneCallback = null);

⇒ Wraps asynchronous tasks within their own awaited and cancelable task. This is useful, if it is unknown, whether the underlying task really runs synchronous or not.

Task ExecuteTaskAsync(Func<CancellationToken, Task> asyncMethod, Action doneCallback = null, CancellationToken cancellationToken = default);
Task ExecuteTaskAsync(string message, Expression<Func<bool>> toggle, Func<CancellationToken, Task> asyncMethod, CancellationToken cancellationToken = default);
Task ExecuteTaskAsync(string message, Func<CancellationToken, Task> asyncMethod, Action doneCallback = null, CancellationToken cancellationToken = default);

If a view model wants to use the IBusyIndicatorHandler it should implement the IBusyIndicatorViewModel interface.


Stylet

The Phoenix.UI.Wpf.Architecture.VMFirst.Stylet package provides some assets to be used together with Stylet.

StyletViewManager

This is an implementation of Stylet.IViewManager where view model to view resolving is handled by a Phoenix.UI.Wpf.Architecture.VMFirst.ViewProvider.IViewProvider.

By default Autofac is responsible to resolve the StyletViewManager and all its requirements. If not configured otherwise, the IOC container will create it with Phoenix.UI.Wpf.Architecture.VMFirst.ViewProvider.DefaultViewProvider as its default IViewProvider. This IViewProvider is not accessible and setting up any viewmodel interfaces won't work. Therefore it is recommended to manually register this or any other IViewProvider with the IOC container. Following is an example how to do this:

  • Register the services:
private static void RegisterViewModelFirst(ContainerBuilder builder)
{
	// Register the view loaded callbacks that will be used by the DefaultViewProvider.
	builder
		.Register<Action<object, FrameworkElement>>(_ => ActivatedViewModelHelper.Callback)
		.SingleInstance()
		;
	builder
		.Register<Action<object, FrameworkElement>>(_ => DeactivatedViewModelHelper.Callback)
		.SingleInstance()
		;
	builder
		.Register<Action<object, FrameworkElement>>(_  => ViewAwareViewModelHelper.Callback)
		.SingleInstance()
		;
	builder
		.Register<Action<object, FrameworkElement>>(context => BusyIndicatorViewModelHelper.CreateCallback(context.Resolve<Func<IBusyIndicatorHandler>>()))
		.SingleInstance()
		;

	// Register the view provider. 
	builder.RegisterType<DefaultViewProvider>().As<IViewProvider>().SingleInstance();

	// Register the special view provider needed by the DialogProvider to display (metro) dialogs.
	builder.RegisterType<MetroDialogAssemblyViewProvider>().As<DialogAssemblyViewProvider>().SingleInstance();

	// Register the DefaultDialogManager that uses the applications main window to display dialogs.
	builder.RegisterType<DefaultDialogManager>().As<IDefaultDialogManager>().SingleInstance();

	// Register the busy handler.
	builder.RegisterType<BusyIndicatorHandler>().As<IBusyIndicatorHandler>();
}
  • Override StyletBootstrapper.BeforeLaunch in your custom bootstrapper implementation to setup handling of viewmodel interfaces.
/// <inheritdoc />
protected override void BeforeLaunch
(
	IContainer container,
	string applicationName,
	Version assemblyVersion,
	Version fileVersion,
	string informationalVersion
)
{
	// Get the callbacks and ALL view providers.
	var callbacks = container.Resolve<ICollection<Action<object, FrameworkElement>>>();
	var viewProviders = container.Resolve<ICollection<IViewProvider>>();
	
	// Hook the callbacks up to the ViewLoaded event of the view providers.
	foreach (var viewProvider in viewProviders)
	{
		viewProvider.ViewLoaded += (sender, args) =>
		{
			foreach (var callback in callbacks)
			{
				callback.Invoke(args.ViewModel, args.View);
			}
		};
	}
}

StyletBootstrapper

A custom bootstrapper inheriting from Stylet.BootstrapperBase that uses Autofac as IOC container and the StyletViewManager as view manager. This should be the base class of the bootstrapper for each project.

Below is template for a custom bootstrapper.

internal class MyBootstrapper : StyletBootstrapper<MyWindow>
{
	/// <inheritdoc />
	protected override void OnStart() { }

	/// <inheritdoc />
	protected override void RegisterServices(ContainerBuilder builder) { }

	/// <inheritdoc />
	protected override void BeforeLaunch
	(
		IContainer container,
		string applicationName,
		Version assemblyVersion,
		Version fileVersion,
		string informationalVersion
	) { }

	/// <inheritdoc />
	protected override void AfterLaunch(IContainer container) { }

	/// <inheritdoc />
	protected override void OnClosing(IContainer container, ExitEventArgs args) { }
}

Authors

  • Felix Leistner: v1.x - v3.x
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net5.0-windows7.0 is compatible.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net6.0-windows7.0 is compatible.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp3.1 is compatible. 
.NET Framework net48 is compatible.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
3.0.0 474 8/15/2023
2.1.0 169 8/15/2023
2.0.1 154 8/15/2023
2.0.0 153 8/15/2023