Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

WinForm VB.NET Hosting WPF Ribbon - Part 2 - Using ICommand with MVVM Pattern

5.00/5 (3 votes)
11 Jan 2022CPOL2 min read 6.5K   147  
My idea was to host a WPF user control with Ribbon within a WinForm VB.NET project and try to use MVVM pattern.
There are Ribbon frameworks which allow using it on Winforms, but it does not seem so easy to customize them. So my idea was to host a WPF usercontrol with Ribbon within a Winforms VB.NET project. This is no richtext editor but only a demo with example menu items.

Image 1

Introduction

This article and the demo were inspired by the CodeProject article, Using ICommand with MVVM Pattern, which creates and uses ICommands according to MVVM pattern.

I used code from there as a template for my own classes / interface.

Background

My first article, WinForm VB.NET Hosting WPF Ribbon was focused on some basic work to get the demo running.

But I wasn‘t too happy about the interface / communication between the WinForm and the WPF usercontrol.

Here, we will only discuss things that were missing in the first article and/or are new or have been changed.

Winform Concept and Code

Needed Project References to Host a WPF Usercontrol

  • PresentationCore
  • PresentationFramework
  • WindowsBase
  • WindowsFormsIntegration

The code to detect ActiveTextBox and ActiveRichTextBox is as given below:

VB.NET
Private Sub TextBox_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles PlainTextBox.MouseClick,
    PlainTextBox.Enter, PlainTextBox.MouseEnter, PlainTextBox.TextChanged

    _textData.ActiveTextBox = CType(sender, TextBox)
    _textData.ActiveRichTextBox = Nothing

    MyRibbonUserControl.StackpanelRT1.Visibility = Windows.Visibility.Hidden
    MyRibbonUserControl.StackpanelRT2.Visibility = Windows.Visibility.Hidden
    MyRibbonUserControl.ribbonComboBoxColor.Visibility = Windows.Visibility.Hidden
    MyRibbonUserControl.FontsDlgRibbonButton.Visibility = Windows.Visibility.Hidden
    MyRibbonUserControl.GraphicAlignRibbonButton.Visibility = Windows.Visibility.Hidden

End Sub

Private Sub RichTextBox_Enter(ByVal sender As System.Object, ByVal e As System.EventArgs) _
  Handles WinformRichTextBox.MouseClick,
    WinformRichTextBox.Enter, WinformRichTextBox.MouseEnter, WinformRichTextBox.TextChanged,
    LogRichTextBox.MouseClick, LogRichTextBox.Enter, _
    LogRichTextBox.MouseEnter, LogRichTextBox.TextChanged

    _textData.ActiveRichTextBox = CType(sender, RichTextBox)
    _textData.ActiveTextBox = Nothing

    MyRibbonUserControl.StackpanelRT1.Visibility = Windows.Visibility.Visible
    MyRibbonUserControl.StackpanelRT2.Visibility = Windows.Visibility.Visible
    MyRibbonUserControl.ribbonComboBoxColor.Visibility = Windows.Visibility.Visible
    MyRibbonUserControl.FontsDlgRibbonButton.Visibility = Windows.Visibility.Visible
    MyRibbonUserControl.GraphicAlignRibbonButton.Visibility = Windows.Visibility.Visible

End Sub

ICommand with MVVM Pattern

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

The Usercontrol has menu items / ribbon buttons which work together with the ActiveRichTextBox or the ActiveTextBox. That is what we see in model class TextData.

Some menu items / commands have been moved to the TextDataViewModel, except all of the Richtext formatting Ribbon Buttons.

There are two new classes, TextDataViewModel and TextData.

VB.NET
Imports System.Windows.Controls.Ribbon

Public Class TextData

    Private _ActiveTextBox As TextBox
    Private WithEvents _ActiveRichTextBox As RichTextBox
    Private _AppPath As String
    Dim _MyRibbonWPF As Ribbon
    Dim _MyRibbonUserCtl As UserControlRibbonWPF

    Public Sub New()
        '
    End Sub

    Public Property MyRibbonUserCtl() As UserControlRibbonWPF
        Get
            Return _MyRibbonUserCtl
        End Get
        Set(value As UserControlRibbonWPF)
            _MyRibbonUserCtl = value
        End Set
    End Property

    Public Property MyRibbonWPF() As Ribbon
        Get
            Return _MyRibbonWPF
        End Get
        Set(value As Ribbon)
            _MyRibbonWPF = value
        End Set
    End Property

    Public Property AppPath() As String
        Get
            Return _AppPath
        End Get
        Set(value As String)
            _AppPath = value
        End Set
    End Property

    Public Property ActiveTextBox() As TextBox
        Get
            Return _ActiveTextBox
        End Get
        Set(value As TextBox)
            _ActiveTextBox = value
        End Set
    End Property

    Public Property ActiveRichTextBox() As RichTextBox
        Get
            Return _ActiveRichTextBox
        End Get
        Set(value As RichTextBox)
            _ActiveRichTextBox = value
        End Set
    End Property

End Class

First part of class TextDataViewModel, which contains properties, ICommands and methods.

VB.NET
Imports System.ComponentModel
Imports System.Windows.Controls.Ribbon
Imports System.Windows.Input
Imports System.Windows.Media

Public Class TextDataViewModel

    Implements INotifyPropertyChanged

#Region " fields"
     Private WithEvents _ActiveTextBox As TextBox
    Private WithEvents _ActiveRichTextBox As RichTextBox
    Dim _MyRibbonWPF As Ribbon
    Dim _MyRibbonUserCtl As UserControlRibbonWPF
    Private _AppPath As String = Application.StartupPath

#End Region

#Region " constructors"
     Public Sub New()
        '
    End Sub

#End Region

#Region " properties"
     Public Property MyRibbonUserControl() As UserControlRibbonWPF
        Get
            Return _textData.MyRibbonUserCtl
        End Get
        Set(value As UserControlRibbonWPF)
            _textData.MyRibbonUserCtl = value
        End Set
    End Property

    Public Property MyRibbonWPF() As Ribbon
        Get
            Return _textData.MyRibbonWPF
        End Get
        Set(value As Ribbon)
            _textData.MyRibbonWPF = value
        End Set
    End Property

    Public Property ActiveRichTextBox() As RichTextBox
        Get
            Return _textData.ActiveRichTextBox
        End Get
        Set(value As RichTextBox)
            _textData.ActiveRichTextBox = value
            NotifyPropertyChanged()
        End Set
    End Property

    Public Property ActiveTextBox() As TextBox
        Get
            Return _textData.ActiveTextBox
        End Get
        Set(value As TextBox)
            _textData.ActiveTextBox = value
            NotifyPropertyChanged()
        End Set
    End Property

    Public Property AppPath() As String
        Get
            Return _textData.AppPath
        End Get
        Set(value As String)
            _textData.AppPath = value
            NotifyPropertyChanged()
        End Set
    End Property

    Public Event PropertyChanged As PropertyChangedEventHandler _
           Implements INotifyPropertyChanged.PropertyChanged

    Private Sub NotifyPropertyChanged(Optional propertyName As String = "")
        RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
    End Sub

    Dim _cmdSA As New Command(AddressOf SaveAsFileDlg)
    Dim _cmdO As New Command(AddressOf OpenFileDlg)
    Dim _cmdN As New Command(AddressOf New_Click)
    Dim _cmdE As New Command(AddressOf Exit_Click)
    Dim _cmdD As New Command(AddressOf Print_Click)

    Dim _cmdI As New Command(AddressOf Info_Click)

    Dim _cmdCut As New Command(AddressOf Cut_Click)
    Dim _cmdC As New Command(AddressOf Copy_Click)
    Dim _cmdP As New Command(AddressOf Paste_Click)

    Dim _cmdBG As New Command(AddressOf BackgroundGreenRibbonButton_Click)
    Dim _cmdBW As New Command(AddressOf BackgroundWhiteRibbonButton_Click)
    Dim _cmdQAT As New Command(AddressOf RestoreQAT_Click)

Example - For one iCommand, we need:

VB.NET
Public ReadOnly Property ExitApp As ICommand
    Get
        Return _cmdE
    End Get
End Property

Dim _cmdE As New Command(AddressOf Exit_Click) 

Private Sub Exit_Click()
    AppForm.Close()
End Sub

Another new class called RichDataViewModel contains properties, ICommands and methods for the Richtext menu commands / Ribbon Buttons.

WPF Usercontrol Concept and Code

For UserCtlRibbonWPF, we need the following to get ICommand available:

XAML
<UserControl.DataContext>
        <local:TextDataViewModel/>
</UserControl.DataContext>

When we add a new property to the viewmodel, we have to create a new build (F5 debug is not enough) before we see it in the designer / the interface.

In the designer, we can now connect menu commands and interface.

Image 2

The code behind the usercontrol:

VB.NET
Imports System.Windows

Public Class UserControlRibbonWPF

    ' Property variables
    Private _RibbonSizeIsMinimized As Boolean

    ' Internal variables
    Private sAppPath As String

#Region " constructors"
     Public Sub New()

        InitializeComponent()
        sAppPath = _textData.AppPath

    End Sub

#End Region

#Region " ApplicationCommands"
     ' now using iCommand

#End Region

#Region " RibbonControls"
     ' now using viewmodel and iCommand

#End Region

#Region " Properties"
     Public Property RibbonSizeIsMinimized() As Boolean
        Get
            Return _RibbonSizeIsMinimized
        End Get
        Set(ByVal value As Boolean)
            _RibbonSizeIsMinimized = value
        End Set
    End Property

#End Region

#Region " RibbonEvents"
     Private Sub RibbonWPF_MouseDoubleClick_
    (sender As Object, e As Windows.RoutedEventArgs) Handles RibbonWPF.MouseDoubleClick

        If RibbonWPF.IsMinimized = True Then
            _RibbonSizeIsMinimized = True
            AppForm.UserControlWPFisMinimized = True
        Else
            _RibbonSizeIsMinimized = False
            AppForm.UserControlWPFisMinimized = False
        End If

    End Sub

    Private Sub UserControlRibbonWPF_ContextMenuClosing_
            (sender As Object, e As EventArgs) Handles Me.ContextMenuClosing

        AppForm.UserControlWPFisMinimized = RibbonWPF.IsMinimized
        RibbonSizeIsMinimized = RibbonWPF.IsMinimized

    End Sub

#End Region

#Region " Form Events"
     Private Sub UserControlRibbonWPF_Loaded(sender As Object, e As RoutedEventArgs) _
            Handles Me.Loaded

        Try

            _textData.MyRibbonUserCtl = Me
            _textData.MyRibbonWPF = RibbonWPF

        Catch ex As Exception
            IO.File.AppendAllText(sAppPath & "\Log.txt", _
            String.Format("{0}{1}", Environment.NewLine, _
            Now.ToString & "; " & ex.ToString()))
        End Try

    End Sub

#End Region

#Region " RibbonSettings"
     ' moved to viewmodel

#End Region

End Class

Module Mod_Public:

VB.NET
Public _textData As New TextData
Public cTextDataVM As New TextDataViewModel

The Modified XAML Code for the ApplicationMenu

XAML
<Ribbon.ApplicationMenu>
            <RibbonApplicationMenu CanAddToQuickAccessToolBarDirectly="True">
                <RibbonApplicationMenuItem x:Name="AppCmdNew" Header="New" 
                 Command="{Binding NewFile, Mode=OneWay}" IsCheckable="False" 
                 ImageSource="Images/newdocument32.png" 
                 QuickAccessToolBarImageSource="Images/newdocument32.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />
                <RibbonApplicationMenuItem x:Name="AppCmdOpen" Header="Open" 
                 Command="{Binding OpenFile, Mode=OneWay}" IsCheckable="False" 
                 ImageSource="Images/open16.png" 
                 QuickAccessToolBarImageSource="Images/open16.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />
                <RibbonApplicationMenuItem x:Name="AppCmdSaveAs" 
                 Header="Save As" Command="{Binding SaveFileAs, Mode=OneWay}" 
                 IsCheckable="False" ImageSource="Images/save16.png" 
                 QuickAccessToolBarImageSource="Images/save16.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />
                <RibbonApplicationMenuItem x:Name="AppCmdPrint" Header="Print" 
                 Command="{Binding Print, Mode=OneWay}" IsCheckable="False" 
                 ImageSource="Images/print.png" 
                 QuickAccessToolBarImageSource="Images/print.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />
                <RibbonApplicationMenuItem x:Name="AppCmdClose" 
                 Header="Close App" Command="{Binding ExitApp, Mode=OneWay}" 
                 ImageSource="Images/close.png" 
                 QuickAccessToolBarImageSource="Images/close.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />
            </RibbonApplicationMenu>
        </Ribbon.ApplicationMenu>

Conclusion

I hope this demo shows that it would be possible to upgrade a Winforms app with a WPF Ribbon without too many changes regarding the data structure on the WinForm.

And I think with Command Binding and the above discussed Command interface, this should work really well.

Final Note

I am interested in feedback of any kind - problems, suggestions and other.

History

  • 11th January, 2022 - Initial submission

License

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