Click here to Skip to main content
15,881,938 members
Articles / Desktop Programming / WPF

Prism for WPF: A Basic Primer

Rate me:
Please Sign up or sign in to vote.
5.00/5 (5 votes)
1 Jul 2020CPOL4 min read 29.4K   14   7
An overview of the Prism framework
This article will provide a basic platform from which you can delve deeper into other aspects of Prism.

Introduction

Prism is a framework that enables development of loosely coupled applications that are flexible, maintainable and easy to test. Prism applications are made up of modules – loosely coupled functional units that encapsulate portions of an application's overall functionality. In a team setting, modules can be individually developed, tested and deployed, thus minimizing cross-team dependencies and enabling teams, or individuals in a team, to focus on specific aspects of an application. Prism can be used to develop either desktop or mobile applications, that follow the MVVM design pattern, as it supports creation of WPF, Xamarin and UnoPlatform projects.

Background

This article provides a basic overview of Prism for WPF using a sample application that displays profiles of imaginary staff members. The sample project can be cloned or downloaded from GitHub.

Image 1
The sample application.

The sample application contains four projects: a WPF application project; a class library with shared code; and two Prism modules.

Image 2

TIP: The easiest way to set up a Prism project is by first installing the VS Prism Template Pack extension and making use of the Prism project templates. The main project in the sample application was created using the Prism Blank App (WPF) template while the modules were added using the Prism Module (WPF) template.

Image 3
Some Prism project templates.

Prism

Shell, Regions & Views

Prism applications are made up of a shell which hosts all the visual components of an application. As is the norm in Prism, and by default, the Prism app project template sets MainWindow as the application's shell.

C#
public partial class App
{
    protected override Window CreateShell()
    {
        return Container.Resolve<MainWindow>();
    }

    ...
}		

A shell contains one or more regions where modules can inject views, and a view can in turn contain regions where other views can be placed. The following diagram, from the Prism documentation, highlights this setup,

Image 4

To place a view into a region, modules make use of the RegionManager which keeps track of all the regions in an application. The sample application has only one region named ContentRegion,

XML
<mah:MetroWindow x:Class="StaffStuff.Views.MainWindow"
                 ...
                 xmlns:prism="http://prismlibrary.com/"
                 xmlns:common="clr-namespace:StaffStuff.Common;assembly=StaffStuff.Common"
                 xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
                 ...
                 prism:ViewModelLocator.AutoWireViewModel="True"
                 ...>
    ...

    <Grid>
        <ContentControl prism:RegionManager.RegionName=
                        "{x:Static common:RegionNames.ContentRegion}" />
    </Grid>
</mah:MetroWindow>	

The sample application has two views: A user control that displays cards containing some employee details and another user control that displays more details of a specific employee. The latter user control also contains a button for navigating back to the first view.

Image 5

The two views are in a module named UIModule and as you can probably tell, this module is UI specific. (The modules in the sample application are organized in 'horizontal layers'. If the application was organized around vertical slices each of the views would be in different modules and those modules would contain functionality specific to a view.)

UIModule implements Prism's IModule interface. This interface has two methods; one that is called when the module is initialized and another that's used for type registration (I set Unity as the IoC container when creating the project. The Prism project wizard offers you the option of using either Unity or DryIoc).

C#
using StaffStuff.UI.Views;
using Prism.Ioc;
using Prism.Modularity;
using Prism.Regions;
using StaffStuff.Common;

namespace StaffStuff.UI
{
    public class UIModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            var regionManager = containerProvider.Resolve<IRegionManager>();
            regionManager.RequestNavigate(RegionNames.ContentRegion, nameof(StaffView));
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<StaffView>();
            containerRegistry.RegisterForNavigation<StaffDetailsView>();
        }
    }
}		

In OnInitialized(), I'm requesting the RegionManager to place StaffView in ContentRegion. As a result, StaffView will be the default view that is displayed when the application is launched. In RegisterTypes(), I'm specifying that both views should be available for navigation. This enables moving back and forth between the two views using Prism's navigation capabilities.

View Models & Navigation

Each of the views has its data context set to a view model that is wired to the view using Prism's ViewModelLocator. Prism associates a view model with a view using a default convention which assumes that a view model is in the same assembly as the view; is in a .ViewModels child namespace; has a name that corresponds with that of a view; and that the corresponding view is in a .Views child namespace. (If you prefer using a different convention, you can specify a custom one.) The UI module has two view models, each associated with a specific view.

Image 6

The view models inherit a base class that inherits Prism's BindableBase and implements its INavigationAware interface.

C#
using Prism.Mvvm;
using Prism.Regions;

namespace StaffStuff.UI.ViewModels
{
    public class ViewModelBase : BindableBase, INavigationAware
    {
        protected IRegionManager RegionManager { get; }

        public ViewModelBase(IRegionManager regionManager)
        {
            RegionManager = regionManager;
        }

        public virtual bool IsNavigationTarget(NavigationContext navigationContext)
        {
            return true;
        }

        public virtual void OnNavigatedFrom(NavigationContext navigationContext)
        {
            
        }

        public virtual void OnNavigatedTo(NavigationContext navigationContext)
        {
            
        }
    }
}		

BindableBase is simply Prism's implementation of INotifyPropertyChanged while INavigationAware enables a view model to participate in the navigation process.

StaffViewModel contains a DelegateCommand whose execute method instructs the RegionManager to navigate to the staff details view – DelegateCommand is Prism's implementation of the ICommand interface.

C#
using System.Collections.Generic;
using Prism.Commands;
using Prism.Regions;
using StaffStuff.Common;
using StaffStuff.Common.Interfaces;
using StaffStuff.Common.Models;

namespace StaffStuff.UI.ViewModels
{
    public class StaffViewModel : ViewModelBase
    {
        private List<Employee> _employees;
        public List<Employee> Employees
        {
            get => _employees;
            set => SetProperty(ref _employees, value);
        }

        public DelegateCommand<Employee> EmployeeDetailsCommand { get; }

        public StaffViewModel(IStaffData staffData, IRegionManager regionManager) : 
                              base(regionManager)
        {
            Employees = staffData.GetEmployees();
            EmployeeDetailsCommand = new DelegateCommand<Employee>(StaffDetails);
        }

        private void StaffDetails(Employee employee)
        {
            var parameters = new NavigationParameters();
            parameters.Add(nameof(Employee), employee);

            RegionManager.RequestNavigate
                   (RegionNames.ContentRegion, "StaffDetailsView", parameters);
        }
    }
}		

A parameter is passed in the navigation request. This parameter, an Employee object, is added to the NavigationParameters' collection with a key that uniquely identifies it. The key can be used by the view model of the view you're navigating to retrieve a particular object.

C#
using Prism.Commands;
using Prism.Regions;
using StaffStuff.Common.Models;

namespace StaffStuff.UI.ViewModels
{
    public class StaffDetailsViewModel : ViewModelBase
    {
        private Employee _employee;
        public Employee Employee
        {
            get => _employee; 
            set => SetProperty(ref _employee, value); 
        }

        private IRegionNavigationJournal _journal;

        public DelegateCommand GoBackCommand { get; }
        
        public StaffDetailsViewModel(IRegionManager regionManager) : base(regionManager)
        {
            GoBackCommand = new DelegateCommand(GoBack);
        }

        private void GoBack() => _journal.GoBack();

        public override void OnNavigatedTo(NavigationContext navigationContext)
        {
            Employee = navigationContext.Parameters[nameof(Employee)] as Employee;
            _journal = navigationContext.NavigationService.Journal;
        }
    }
}		

Navigation back to the previous view is done using the navigation journal. The navigation journal also has a GoForward() method which can be used for forward navigation.

Module Registration

The modules in the sample application have to be registered with Prism's ModuleCatalog so they can be loaded by the app. This is done in App.xaml.cs.

C#
public partial class App
{
    ...
    protected override void ConfigureModuleCatalog(IModuleCatalog moduleCatalog)
    {
        moduleCatalog.AddModule<ServicesModule>();
        moduleCatalog.AddModule<UIModule>();
    }
}		

Conclusion

I've covered some aspects of Prism for WPF but there are a couple of areas I didn't highlight; like the different navigation strategies, Prism's event aggragator, and composite commands. This article has hopefully provided a basic platform from which you can delve deeper into other aspects of Prism. I recommend you take a look at the various Prism sample projects on GitHub and go through the documentation to get a wider view.

History

  • 1st 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

 
GeneralMy vote of 5 Pin
george joy14-Jan-23 15:05
george joy14-Jan-23 15:05 
QuestionSource Code Pin
BlankDataCoder15-Feb-21 23:12
BlankDataCoder15-Feb-21 23:12 
AnswerRe: Source Code Pin
Meshack Musundi15-Feb-21 23:54
professionalMeshack Musundi15-Feb-21 23:54 
QuestionMVVM Light vs Prism Pin
Patrick CGDM7-Jul-20 23:57
Patrick CGDM7-Jul-20 23:57 
AnswerRe: MVVM Light vs Prism Pin
Meshack Musundi8-Jul-20 8:42
professionalMeshack Musundi8-Jul-20 8:42 
QuestionI like it, but for such small app using Prism is an overengineering Pin
Издислав Издиславов2-Jul-20 5:21
Издислав Издиславов2-Jul-20 5:21 
GeneralRe: I like it, but for such small app using Prism is an overengineering Pin
Meshack Musundi2-Jul-20 5:37
professionalMeshack Musundi2-Jul-20 5:37 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.