Horizon.MvvmFramework

Simple and lightweight MVVM framework

View on GitHub

logo

Horizon.MvvmFramework

The MvvmFramework is a simple and lightweight framework supposed to speed-up the development of MVVM (Model-View-ViewModel) applications as well as to reduce code of common operations such as change notifications.

The main component is build as a portable class library so it can be used “view” independently. The Wpf submodule of this framework contains WPF specific behaviors and extensions. This framework was extracted from a working productive application. It will be extendend if a code snippet is worth to be added and might be helpful for common use cases. This project is a private project written during leasure time, published in hopes that it will be useful for other projects too. Last but not least, feel free to extend the framework :)

Installation

Simply use this framework by referencing one of the following NuGet packages.

Documentation

This sections provides a short overview of the framework components and how to use them.

Components

ObserveableObject : INotifyPropertyChange

An observeable object is an object which provides change notifications for its properties. There’s also a helper Method SetPropety which allows a simple way for setting a property and notify a change if the value has changed in a single call.

public class TestObject : ObserveableObject
{
    private string _stringProperty;
    public string StringProperty	
    {
        get
        {
            return _stringProperty;
        }
        set
        {
            SetProperty(ref _stringProperty, Value);
        }
    }
}

ViewModel : ObserveableObject

The view model is an observeable object which provides an additional ClosureRequestedEvent used to handle a closure request from the models domain logic. The view might subscribe to this event and handle the closure independently. A call to OnClosureRequested will then inform the View properly.

public class TestModel : ViewModel
{
    public void SaveAndCloseDialog()	
    {
    	
        SaveChanges();
        OnClosureRequested();
    }
}

IActivateable

The IActivateable interface is used to identify activateable components. An activateable component implements

/// <summary>
/// Activates the component asynchronously.
/// </summary>
/// <param name="isInitialActivation"> Flag indicating wheather this activation is an initial activation or a reactivation. </param>
/// <returns> The operational Task. </returns>
Task ActivateAsync(bool isInitialActivation);

which might be called from the View after it was activated. The WPF module of this framework provides a behavior which automatically connects the ClosureEvent to the DataContext and triggers the activation if the window gets activated.

Commands

ICommandFactory

The ICommandFactory allows the creation of various ICommand, respectively INotifiableCommand implementations. There are methods for creating synchronous command operations as well as asnychronous command operations.

/// <summary>
/// Creates an asynchronous command which executes the given action.
/// </summary>
/// <param name="executeAsync"> Asynchronous action which will be executed by this command. </param>
/// <param name="canExecute"> Delegate which determines wheather the command can be executed. </param>
/// <returns> The command. </returns>
/// <exception cref="ArgumentNullException">If the execution action is not set. </exception>
INotifiableCommand CreateAyncCommand([NotNull] Func<Task> executeAsync, [CanBeNull] Func<bool> canExecute = null);

/// <summary>
/// Creates an asynchronous command which executes the given action.
/// </summary>
/// <param name="executeAsync"> Asynchronous action which will be executed by this command. </param>
/// <param name="canExecute"> Delegate which determines wheather the command can be executed. </param>
/// <typeparam name="T"> The type of the parameter passsed to the command. </typeparam>
/// <returns> The command. </returns>
/// <exception cref="ArgumentNullException">If the execution action is not set. </exception>
INotifiableCommand CreateAyncCommand<T>([NotNull] Func<T, Task> executeAsync, [CanBeNull] Func<T, bool> canExecute = null);

INotifiableCommand:ICommand

The INotifiableCommand command is an ICommand which triggers change notifications on demand. The developer has to decide when a command might get available and should call NotifyChange() to re-evaluate the execution restriciton. This design prevents unnecessary command updates like WPFs CommandManager would trigger.

CommandFactory:ICommandFactory

The command factory implements an ICommandFactory and handles the initialization of the hidden command implementations itself. Using the factory a command can be created by defining the corresponding execution action and delegate used to determine the enabled state of the command.

ICommandFactory commandFactory = new CommandFactory();
DeleteCommand = commandFactory.CreateCommand(DeleteItem, CanDeleteItem);

public object SelectedItem
{
	get
	{
		return _selectedItem;
	}
	set
	{
		SetProperty(ref _selectedItem, value);
		DeleteCommand.NotifyChange();
	}

}

private void DeleteItem()
{
	Items.Remove(SelectedItem);
}

private bool CanDeleteItem()
{
	return SelectedItem != null;
}

Collections

AttentiveCollection<T> : ObservableCollection<T>

The attentive collection extends an ObservableCollection<T> and provides notifications for changes of inner elements too. That means if any property of object T changes its value, an InnerElementChanged event will be fired, assumed that the property fires a change notification itself.

public class Product : ObserveableObject
{
	private decimal _price;
	public decimal Price
	{
		get
		{
			return _price;
		}
		set
		{
			SetProperty(ref _price, Value);
		}
	}
}

public class ProductManagement : ViewModel
{
	public AttentiveCollection<Product> Products;

	public ProductManagement()
	{
		Products = new AttentiveCollection<Product>(...);
		Products.InnerElementChanged += (s, e) => (Console.WriteLine($"Product {s} has changed its price"));
	}
}

Exceptions

The Exceptions namespace provides a helper called Throw. Using the Throw class, common parameter or argument validations can be inserted in your code without using a couple of if statements at the beginning of your method. Within the current release, the following Validations are included:

/// <summary>
/// Throws an exception if the provided argument is null.
/// </summary>
/// <param name="argument"> The argument which will be checked. </param>
/// <param name="argumentName"> The name of the argument, which will be checked. </param>
/// <exception cref="ArgumentNullException">If the provided argument is null. </exception>
public static void IfArgumentIsNull(object argument, [CanBeNull] string argumentName)`;

/// <summary>
/// Throws an exception if an argument is not valid.
/// </summary>
/// <param name="isArgumentValid"> Flag wheather the argument is valid. </param>
/// <param name="message"> The exception message, why the argument is not valid. </param>
/// <param name="argumentName"> The name of the argument, which was checked. </param>
/// <exception cref="ArgumentNullException">If the provided message is null. </exception>
/// /// <exception cref="ArgumentException">If the argument is not valid. </exception>
public static void IfArgumentIsNotValid(bool isArgumentValid, [NotNull] string message, [CanBeNull] string argumentName);

/// <summary>
/// Throws an exception if an operation is invalid.
/// </summary>
/// <param name="isOperationInvalid"> Flag wheather the operation is invalid. </param>
/// <param name="message"> The message, used to point out the invalid operation. </param>
/// <exception cref="ArgumentNullException">If the provided message is null. </exception>
/// /// <exception cref="InvalidOperationException">If the operation is not valid. </exception>
public static void IfOperationIsInvalid(bool isOperationInvalid, [NotNull] string message);

Example usage:

public void DoSomething(object importantParameter)
{
	Throw.IfArgumentIsNull(importantParameter, nameof(importantParameter));
}

public void DoSomethingWithPositiveNumbers(int number)
{
	Throw.IfArgumentIsNotValid(number < 0, "Number has to be positive", nameof(number));
}

Extensions

Currently the Extensions namespace provides a Task extension used to execute an action if the calling task faults. This extension is very useful if you want to prevent an application to fault up in an unkno wn state. There might be a point in your application when you have to use ‘async void’, because you can not return an operational Task. If your asynchronous method does not handle all possible exceptions, the OnUnobservedException task extension catches any unhandled exception and continues with the given action. It improves the readability of your code, since you don’t have to write your try/catch blocks over and over again.

public void LoadData()
{
	LoadDataAsync().OnUnobservedException(LogExceptionAndTerminateApplication);
}

Services

The service namespace includes a messenger service used to establish a bi-directional communication between models, views or view-models without violating the MVVM pattern. Using MVVM straight forwards it’s a challenge to fire up sub-dialogs, setting the focus, or simply showing a message box while separating the View from the ViewModel and keeping the code unit testable.

The IMessenger is an independent component which can be passed to any component using dependency injection. Afterwards a component can sent a message, which another component is able to handle to. Using this messenger pattern, a ViewModel may send send a message or an instance of new ViewModel which should open a sub-dialog. The View or a supervising component uses the IMessenger to register a handle for these specific message types and handle them properly.

public class Message
{
	public string Message { get; set; }
}

public class DialogViewModel : ViewModel
{
	public DialogViewModel(IMessenger messenger)
	{
		_messenger = messenger;
	}
	
	public void ShowMessage()
	{
		var message = new Message { Message = "Hi from the ViewModel" };
		_messenger.Send(messange);
	}
}

public class View : Window
{
	public View(IMessenger messenger)
	{
		InitializeComponents();
		
		_messenger = messenger;
		_messenger.Register<Message>(ShowMessage);
	}
	
	private void ShowMessage(Message message)
	{
		// View specific code
		MessageBox.Show(message.Message);
		
		// Or use custom dialogs
		var messageWindow = new MessageWindow();
		messageWindow.DataContext = message;
		messageWindow.Show();
	}
}

It’s up to you which operations will be delegated to other components while simply sending a message ;) Keep in mind that all components have to use the same instance of an IMessenger to establish a communication. The framework provides an implementaions of an IMessenger called MessageHub. You can use the default instance or use an IOC container to register a singleton.

// Provide the default instance
var view = new View(MessageHub.Default);
var viewModel = new DialogViewMode(MessageHub.Default);

// Using an IOC container, SimpleInjector for example
// https://simpleinjector.org/index.html
var container = new SimpleInjector.Container();
container.RegisterSingleton<IMessenger>(() => new MessageHub());
var view = _container.GetInstance<View>();
var viewMode = _container.GetInstance<DialogViewModel>();

WPF extension package

The WPF NuGet packages of this framework provides a couple of extensions and behaviours especially written for Microsoft’s WPF framework. Therefore, this packages isn’t platform independent.

Behaviors

ViewModelBehavior

The ViewModel behavior can be attached to any window. An attached window will handle the ViewModel of the window as it’s supposed to be used with this framework. This means, the Window triggers an IActivable component if the window gets activated. In addition the window will close automatically if the ViewModel requests a closure using the OnClosureRequested method. Nevertheless, the DataContext of the Window has to be set properly. Please have a look at the Samples to see the setup and registration of a MVVM application using this behavior.

AutoScrollBehavior

Implements an auto scrolling for a scroll viewer. If the layout changes, the view will scroll to the last element.

InputRestrictionBehavior

Restricts the input to a textbox by validating the entered text using a regular expression. The follwing example shows a restriction applied to a textbox so the user is only able to enter a valid hh:mm time representation.

<TextBox Width="45"
         Margin="5"
         VerticalContentAlignment="Center"
         Text="{Binding Time,
                        Converter={StaticResource TimeToStringConverter},
                        Mode=TwoWay,
                        UpdateSourceTrigger=Explicit}">
	<i:Interaction.Behaviors>
	    <behaviors:InputRestrictionBehavior InputExpression="^[0-2]?[0-9]?:?[0-5]?[0-9]?$" />
        </i:Interaction.Behaviors>
</TextBox>
MinimizeToTrayBehavior

The MinimizeToTrayBehavior behavior automatically sends the application to the notification area if the window, where this behavior is attached to minimizes.

Extensions

Application.InjectResourceDictionary(...)

Using the InjectResourceDictionary extension a resource dictionary can be loaded from code using a single line expression. This is very useful if you write your own application bootstrapper instead of using the empty App.xaml and the corresponding code behind file.

var app = new Application();
app.InjectResourceDictionary("MahApps.Metro", "Styles/Controls.xaml");
app.InjectResourceDictionary("MahApps.Metro", "Styles/Fonts.xaml");
app.InjectResourceDictionary("MahApps.Metro", "Styles/Colors.xaml")

// Setup windows and VMs...

app.Run();