Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / WPF

C# MVVM Toolkit Demo

Rate me:
Please Sign up or sign in to vote.
5.00/5 (19 votes)
27 Feb 2023CPOL5 min read 35.1K   3K   55   6
This article and the demo are about getting started using the MVVM Toolkit and some self-created interfaces / services for MessageBox and some dialogs.
There are many CodeProject articles about other MVVM frameworks, but almost nothing with WPF and the MVVM Toolkit. So I started to create this document.

Image 1

Introduction

This article and the demo are about getting started using the MVVM Toolkit and some self-created interfaces / services for MessageBox and some dialogs.

Background

There are many CodeProject articles about other MVVM frameworks, but almost nothing with WPF and the MVVM Toolkit. So I started to create this document.

The Model, View and ViewModel (the MVVM pattern) is a good way to organize or structure your code and helps you to simplify, develop and test (e.g. unit testing) your application.

The Model holds the data and has nothing to do with the application logic.

The ViewModel acts as the connection between Model and View.

The View is the User Interface.

I will not describe and explain every detail of the complete demo project. The focus is how to test some of the features.

Using the Code

MVVM Structure / Features

The MVVM Toolkit is from Microsoft and also some of the other used features are not my own: Sources as listed in the Credits / Reference section.

Quick Overview of the Content

  • MVVM Toolkit and .NET 4.7.2
    • RelayCommand
    • OnPropertyChanged
    • ObservableRecipient (Messenger and ViewModelBase)
    • DependencyInjection (to run MsgBox and Dialogs as a service)
    • ObservableCollection (for Credits Listbox)
  • Ribbon Menu
  • Services / dialogs
  • Using ICommand to bind buttons to the ViewModel

Installation of the MVVM Toolkit

With the installation of the NuGet package of the MVVM Toolkit, it installs 6 or 7 other packages.

For DependencyInjection, we need to install another NuGet package:

Image 2

And I made interfaces / services for MsgBox and some dialogs.

DependencyInjection manages to start the following dialogs independent from the viewmodels:

  • FontDlgVM
  • MsgBoxService
  • DialogVM
  • OpenFileDlgVM
  • RibbonStatusService
  • SaveAsFileDlgVM
  • SearchDlgVM

This allows the usage of custom Messageboxes / dialogs as well as Unit Testing.

MainWindow Concept and Code

MainWindow shows the Ribbon.

Below that there is a Tabcontrol, with tabs for MVVM Toolkit Testing and RichText.

The code behind Class MainWindow is that:

C#
{
    /// <summary>
    /// Interaktionslogik für MainWindow.xaml
    /// </summary>

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new TestingViewModel();
        }
    }
}

Application

Registering the services / viewmodels is in the App code behind.

C#
public partial class App : Application
    {
        private bool blnReady;
        public App()
        {
            InitializeComponent();
            Exit += (_, __) => OnClosing();
            Startup += Application_Startup;
            try
            {
                Mod_Public.sAppPath = Directory.GetCurrentDirectory();
                Ioc.Default.ConfigureServices(
                    new ServiceCollection()
                    .AddSingleton<IMsgBoxService, MsgBoxService>()
                    .AddSingleton((IDialog)new DialogVM())
                    .AddSingleton((IOpenFileDlgVM)new OpenFileDlgVM())
                    .AddSingleton((ISaveAsFileDlgVM)new SaveAsFileDlgVM())
                    .AddSingleton((IRichTextDlgVM)new RichTextDlgVM())
                    .BuildServiceProvider());
            }
            catch (Exception ex)
            {
                File.AppendAllText(Mod_Public.sAppPath + @"\Log.txt", 
                string.Format("{0}{1}", Environment.NewLine, 
                DateAndTime.Now.ToString() + "; " + ex.ToString()));
                var msgBoxService = Ioc.Default.GetService<IMsgBoxService>();
                msgBoxService.Show("Unexpected error:" + Constants.vbNewLine + 
                Constants.vbNewLine + ex.ToString(), img: MessageBoxImage.Error);
            }
        }
        private void OnClosing()
        {
        }
        private void Application_Startup(object sender, EventArgs e)
        {
            blnReady = true;
        }
   }

Mod_Public

Mod_Public includes:

C#
public static void ErrHandler(string sErr)

and:

C#
public static string ReadTextLines(string FileName)

MVVM Pattern - Details

From the WPF Ribbon’s point of view, the data structure / model is simple:

The Ribbon has menu items / ribbon buttons which work together with the ActiveRichTextBox or the ActiveTextBox.

That is what we see in model class TextData.

Model class called TextData:

C#
{
    public class TextData : ObservableRecipient, INotifyPropertyChanged
    {
        private string _text;
        private string _richText;
        private RibbonTextBox _NotifyTest;
        private string _readText;
        private TextBox _ActiveTextBox;
        private RichTextBox __ActiveRichTextBox;
        private RichTextBox _ActiveRichTextBox
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            get
            {
                return __ActiveRichTextBox;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            set
            {
                __ActiveRichTextBox = value;
            }
        }
        private Ribbon _MyRibbonWPF;
        private MainWindow _MyMainWindow;
        public TextData()
        {
            //
        }
            public MainWindow MyMainWindow
        {
            get
            {
                return _MyMainWindow;
            }
            set
            {
                _MyMainWindow = (MainWindow)value;
            }
        }
        public Ribbon MyRibbonWPF
        {
            get
            {
                return _MyRibbonWPF;
            }
            set
            {
                _MyRibbonWPF = value;
            }
        }
        public RibbonTextBox NotifyTestbox
        {
            get
            {
                return _NotifyTest;
            }
            set
            {
                _NotifyTest = value;
            }
        }
        public string RichText
        {
            get
            {
                return _richText;
            }
            set
            {
                _richText = value;
                OnPropertyChanged("RichText");
            }
        }
        public string GetText
        {
            get
            {
                return _text;
            }
            set
            {
                _text = value;
                OnPropertyChanged("GetText");
            }
        }
        public string ReadText
        {
            get
            {
                return _readText;
            }
            set
            {
                _readText = value;
                GetText = _readText;
                OnPropertyChanged("ReadText");
            }
        }
        public TextBox ActiveTextBox
        {
            get
            {
                return _ActiveTextBox;
            }
            set
            {
                _ActiveTextBox = value;
                OnPropertyChanged("ActiveTextBox");
            }
        }
        public RichTextBox ActiveRichTextBox
        {
            get
            {
                return _ActiveRichTextBox;
            }
            set
            {
                _ActiveRichTextBox = value;
            }
        }
    }
}

ViewModel class called TestingViewModel

The class called TestingViewModel contains properties, ICommands and methods for the testing of some MVVM features. It contains also code for the ObservableCollection(Of Credits), which is used for the Listview with the References / Credits for this article.

Putting Things Together - WPF Concept and Code

QAT (QuickAccessToolbar)

You can remove buttons from the QAT (on right click, a context menu appears for that). And you can show QAT below the Ribbon. You can Restore QAT from Settings Tab as well. And you can change backcolor of the Ribbon.

DependencyInjection or ServiceInjection

As already mentioned, there is some code for this in code behind of the App.

Save File Dialog Example with ISaveAsFileDlgVM

It uses interface ISaveAsFileDlgVM and service / viewmodel SaveAsFileDlgVM.

public class TestingViewModel : ObservableRecipient, INotifyPropertyChanged

C#
...    
       public ICommand SaveAsFileDlgCommand { get; set; }
...  
       RelayCommand cmdSAFD = new RelayCommand(SaveAsFileDialog);
       SaveAsFileDlgCommand = cmdSAFD;
...
        private void SaveAsFileDialog()
        {
            var dialog = Ioc.Default.GetService<ISaveAsFileDlgVM>();
            if (ActiveRichTextBox is object)
            {
                dialog.SaveAsFileDlg(_textData.RichText, ActiveRichTextBox);
            }
            if (ActiveTextBox is object)
            {
                dialog.SaveAsFileDlg(_textData.GetText, ActiveTextBox);
            }
        }
...

And, very important, Command="{Binding SaveAsFileDlgCommand}"/> in the XAML file.

XML
<RibbonButton x:Name="SaveAs" Content="RibbonButton" 
 HorizontalAlignment="Left" Height="Auto"
 Margin="94,24,-162,-70" VerticalAlignment="Top" Width="80" Label=" Save As"  KeyTip="S"
 AutomationProperties.AccessKey="S" AutomationProperties.AcceleratorKey="S"
 SmallImageSource="Images/save16.png" CanAddToQuickAccessToolBarDirectly="False"
 ="Save As" Command="{Binding SaveAsFileDlgCommand}"/>

From the Ribbon, you can start and test other dialogs or the messagebox with:

  • Open Dialog
  • Search (Source as listed in Credits/References)
  • OpenFileDialog
  • Tab Help > Info
  • FontDialog

Messenger Test

It is important to add Inherits ObservableRecipient, this and other details are described in ObservableObject - Windows Community Toolkit | Microsoft Docs.

"View specific messages should be registered In the Loaded Event Of a view And deregistered In the Unloaded Event To prevent memory leaks And problems multiple callback registrations."

We can send a Msg from Class TestingViewModel:

C#
Imports Microsoft.Toolkit.Mvvm.Messaging

    public class TestingViewModel : ObservableRecipient, INotifyPropertyChanged

private string msg;
…
_cmdMsg = new Command(SendMsgRibbonButton_Click);
…
        public ICommand SendMsg
        {
            get
            {
                return _cmdMsg;
            }
        }
…       

        private void SendMsgRibbonButton_Click()
        {
            try
            {
                // DataExchange / Messenger
                string msg = "Test Msg...";
                SetStatus("TestingViewModel", msg);
            }
            catch (Exception ex)
            {
                SetStatus("TestingViewModel", ex.ToString());
                Mod_Public.ErrHandler(ex.ToString());
            }
        }
...
        public void SetStatus(string r, string m)
        {
            try
            {
                Messenger.Send(new DialogMessage(m));
            }
            catch (Exception ex)
            {
                SetStatus("TestingViewModel", ex.ToString());
                Mod_Public.ErrHandler(ex.ToString());
            }
        }
...
public class StatusMessage
    {
        public StatusMessage(string status)
        {
            NewStatus = status;
        }
        public string NewStatus { get; set; }
    }

Send Msg is only possible if the message is registered:

C#
using Microsoft.Toolkit.Mvvm.Messaging;
...
Messenger.Register<DialogMessage>(this, (r, m) => DialogMessage = m.NewStatus);
Messenger.Register<StatusMessage>(this, (r, m) => StatusBarMessage = m.NewStatus);
...
Messenger.Unregister<StatusMessage>(this);
Messenger.Unregister<DialogMessage>(this);

On closing the viewmodel, we have to unregister the message.

The message appears on StatusBar and the Ribbon.

PropertyChanged Test

XAML
<RibbonTextBox x:Name="ribbonTextBox" 
 Text="{Binding OnPropertyChangedTest, UpdateSourceTrigger=PropertyChanged}" 
       HorizontalAlignment="Right" Margin="0,0,-90,-30" 
       TextWrapping="Wrap" VerticalAlignment="Bottom" 
       Width="120" UndoLimit="10" FontSize="12"/>
<RibbonTextBox x:Name="NotifyTextBox" Text="{Binding OnPropertyChangedTest, 
 UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Right" Margin="0,0,-90,-53" 
       TextWrapping="Wrap" VerticalAlignment="Bottom" Width="120" 
       UndoLimit="10" FontSize="12"/>

Both textboxes normally show only if the activeTextbox is related to "RichText" or "PlainText". But if you edit the upper one manually, you can see that the lower one's content is changed immediately.

This is caused by UpdateSourceTrigger=PropertyChanged in the XAML file.

EventTrigger

Requirements: Microsoft.Xaml.Behaviors.Wpf (NuGet package)

XAML
xmlns:b="http://schemas.microsoft.com/xaml/behaviors"
...
    <b:Interaction.Triggers>
         <b:EventTrigger EventName= "MouseWheel">
             <b:InvokeCommandAction Command="{Binding ParamCommandTBx}"
                 CommandParameter="{Binding ElementName=myTextBox, Mode=OneWay}"/>
         </b:EventTrigger>
         <b:EventTrigger EventName= "MouseDoubleClick">
             <b:InvokeCommandAction Command="{Binding ParamCommandTBx}"
                 CommandParameter="{Binding ElementName=myTextBox, Mode=OneWay}"/>
         </b:EventTrigger>
         <b:EventTrigger EventName= "TextChanged">
             <b:InvokeCommandAction Command="{Binding ParamCommandTBx}"
                 CommandParameter="{Binding ElementName=myTextBox, Mode=OneWay}"/>
         </b:EventTrigger>
         <b:EventTrigger EventName= "MouseEnter">
             <b:InvokeCommandAction Command="{Binding ParamCommandTBx}"
                 CommandParameter="{Binding ElementName=myTextBox, Mode=OneWay}"/>
         </b:EventTrigger>
     </b:Interaction.Triggers>
...

This is used when the Ribbon is minimized via ContextMenu and for other stuff.

ObservableCollection

It is part of viewmodel TestingViewModel and used for the listbox with Credits / References.

C#
public class TestingViewModel : ObservableRecipient, INotifyPropertyChanged

#Region " fields"
...
private ObservableCollection<Credits> _credit = new ObservableCollection<Credits>();
...
#End Region
...
_credit = new ObservableCollection<Credits>()
                {
                    new Credits()
                    {
                        Item = "MVVM Toolkit",
                        Note = "Microsoft",
                        Link = "https://docs.microsoft.com/en-us/windows/
                                communitytoolkit/mvvm/introduction"
                    },
                    new Credits()
                    {
                        Item = "MVVMLight",
                        Note = "GalaSoft",
                        Link = "https://www.codeproject.com/Articles/768427/
                                The-big-MVVM-Template"
                    },
                    new Credits()
                    {
                        Item = "ICommand with MVVM pattern",
                        Note = "CPOL",
                        Link = "https://www.codeproject.com/Articles/863671/
                                Using-ICommand-with-MVVM-pattern"
                    },
                    new Credits()
                    {
                        Item = "C# WPF WYSIWYG HTML Editor - CodeProject",
                        Note = "CPOL",
                        Link = "https://www.codeproject.com/Tips/870549/
                                Csharp-WPF-WYSIWYG-HTML-Editor"
                    },
                    new Credits()
                    {
                        Item = "SearchDialog",
                        Note = "Forum Msg",
                        Link = "https://social.msdn.microsoft.com/forums/vstudio/en-US/
                        fc46affc-9dc9-4a8f-b845-89a024b263bc/
                        how-to-find-and-replace-words-in-wpf-richtextbox"
                    }
               };
...
    public class Credits
    {
        public string Item { get; set; }
        public string Note { get; set; }
        public string Link { get; set; }
}

Test with the ObservableCollection

Click on Clear Listbox the delete the credits.

Read XML to Listbox restores the references.

The advantage of the ObservableCollection is that we need no UpdateTrigger for the Listbox.

RichText

From tab 'RichText', you can select some text within the RichTextBox and use the RibbonButtons to format it. Many of these are EditingCommands and appear only in the UserCtlRibbonWPF.xaml file.

Conclusion

This is only a demo – it is not production ready.

But I think the MVVM Toolkit will allow you a variety of extensions.

Final note: I am very interested in feedback of any kind - problems, suggestions and other.

Credits / Reference

History

  • 23rd Feb, 2023 - Version 1.1 - Because Microsoft.ToolKit.Mvvm has been deprecated, we now must use this alternate package: CommunityToolkit.Mvvm
  • 8th June, 2022 - Added the Model, View and ViewModel (the MVVM pattern) explanation
  • 19th May, 2022 - Initial submission

License

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


Written By
Engineer
Germany Germany
Retired

Comments and Discussions

 
Questionupdate Pin
Robert Stefanz22-Feb-23 23:02
Robert Stefanz22-Feb-23 23:02 
SuggestionCommunityToolkit Pin
Member 155580291-Sep-22 3:29
Member 155580291-Sep-22 3:29 
GeneralHyperbole Indeed Pin
Massey Ferguson20-May-22 11:37
Massey Ferguson20-May-22 11:37 
GeneralRe: Hyperbole Indeed Pin
Jo_vb.net21-May-22 3:35
mvaJo_vb.net21-May-22 3:35 
GeneralMy vote of 5 Pin
Ștefan-Mihai MOGA19-May-22 4:58
professionalȘtefan-Mihai MOGA19-May-22 4:58 
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.

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.