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

WinForm VB.NET Hosting WPF Ribbon

5.00/5 (6 votes)
1 Jan 2022CPOL3 min read 8.5K   369  
Host WPF usercontrol with Ribbon within winforms VB.NET project
There are some Ribbon frameworks which allow using it on Winforms, but it seems not 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 C# WPF WYSIWYG HTML Editor - CodeProject article, which shows a beautiful Ribbon UI and gave me some ideas, code snippets and images.

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 and demo code.

Background

I did not want to „clone“ the HTML editor from the mentioned article and decided to use a richtextcontrol on the winform and make a WPF Ribbon with menuitems and buttons to control it.

The structure on the winform is easy.

There is a splitcontainer, panel1 (on top) with the ElementHost control.

Panel2 (on bottom) with the tabcontrol and Richtextboxes.

Image 2

Winform Concept and Code

As soon as the WPF Usercontrol is available, it appears on the toolbox.

Image 3

When you drag it from the toolbox to the Splitcontainer Panel1, a ElementHost control is placed there, which hosts the usercontrol.

Image 4

The code shows that we use a variable MyRibbonUserControl on the winform to get easy access to the WPF usercontrol.

VB.NET
Public Class AppForm
    Inherits Form
    Private WithEvents MyRibbonUserControl As New UserControlRibbonWPF
    Private blnIsMinimized As Boolean

    Private Sub AppForm_Load(sender As Object, e As EventArgs) Handles Me.Load
        Try
            ElementHost1.Dock = DockStyle.Fill
            MyRibbonUserControl = ElementHost1.Child

Some of the errors which may happen are written to a log file, which we use as data source for the RTBox.

In Module Mod_Public, we declare:

VB.NET
Public WithEvents activeRichTextBox As RichTextBox

It is used in the winform for:

VB.NET
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

    activeRichTextBox = CType(sender, RichTextBox)

End Sub

When the app is shown, we could do some Configuration | Settings for WPF Ribbon as well.

VB.NET
Private Sub AppForm_Shown(sender As Object, e As EventArgs) Handles Me.Shown

    Try

        activeRichTextBox = WinformRichTextBox
        activeRichTextBox.Text = ReadTextLines("Log.txt")

        ' Configuration | Settings for WPF Ribbon
        With MyRibbonUserControl

            blnIsMinimized = .RibbonSizeIsMinimized
            .TabHelp.IsEnabled = True
            .RibbonWPF.Visibility = Windows.Visibility.Visible

The remaining code on the winform is mostly related to ribbon settings which will be presented below.

WPF usercontrol Concept and Code

The Ribbon includes

  • Application Menu
  • QAT (QuickAccessToolbar)
  • Ribbon Tabs Start, Ribbon Settings and Help

Application Menu

Image 5

QAT (QuickAccessToolbar)

Image 6

You can remove buttons from the QAT (on right click, a context menu appears for that).

Image 7

And you can show QAT below the Ribbon:

Image 8

You can Restore QAT from Settings Tab as well.

And you can change backcolor of the ribbon.

Ribbon Tabs Start, Insert, Ribbon Settings and Help

Image 9

The Ribbon can be minimized via Context menu or doubleclick on a tab header.

Image 10

VB.NET Code in UserControlRibbonWPF.xaml.vb

Public Sub New() shows that commandBinding is used which I found here.

VB.NET
Public Sub New()

        Dim binding As CommandBinding

        binding = New CommandBinding(ApplicationCommands.[New])

        AddHandler binding.Executed, AddressOf New_Click
        Me.CommandBindings.Add(binding)

        binding = New CommandBinding(ApplicationCommands.Open)
        AddHandler binding.Executed, AddressOf Open_Click
        Me.CommandBindings.Add(binding)

        binding = New CommandBinding(ApplicationCommands.Close)
        AddHandler binding.Executed, AddressOf CloseCommand
        Me.CommandBindings.Add(binding)

        binding = New CommandBinding(ApplicationCommands.Print)
        AddHandler binding.Executed, AddressOf PrintCommand
        Me.CommandBindings.Add(binding)

        binding = New CommandBinding(ApplicationCommands.SaveAs)
        AddHandler binding.Executed, AddressOf SaveAs_Click
        Me.CommandBindings.Add(binding)

    End Sub

The code for the other button events on the WPF side is almost the same as it would be on the winform side.

For handling the RTBox content, the above noted variable activeRichTextBox can be used.

Example:

VB.NET
Private Sub LeftAlignRibbonButton_Click(sender As Object, e As Windows.RoutedEventArgs) _
        Handles LeftAlignRibbonButton.Click

    If activeRichTextBox IsNot Nothing Then If activeRichTextBox.SelectedText.Length = 0 _
       Then Exit Sub
    If activeRichTextBox IsNot Nothing Then activeRichTextBox.SelectionAlignment = _
                                            System.Windows.Forms.HorizontalAlignment.Left

End Sub

XAML File UserControlRibbonWPF.xaml

The part with the application menu:

XAML
<Ribbon.ApplicationMenu>

            <RibbonApplicationMenu CanAddToQuickAccessToolBarDirectly="True">

                <RibbonApplicationMenuItem x:Name="AppCmdNew" Header="New" 
                 Command="ApplicationCommands.New" IsCheckable="False" 
                 ImageSource="Images/newdocument32.png" 
                 QuickAccessToolBarImageSource="Images/newdocument32.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />

                <RibbonApplicationMenuItem x:Name="AppCmdOpen" Header="Open" 
                 Command="ApplicationCommands.Open" IsCheckable="False" 
                 ImageSource="Images/open16.png" 
                 QuickAccessToolBarImageSource="Images/open16.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />

                <RibbonApplicationMenuItem x:Name="AppCmdSaveAs" 
                 Header="Save As" Command="ApplicationCommands.SaveAs" 
                 IsCheckable="False" ImageSource="Images/save16.png" 
                 QuickAccessToolBarImageSource="Images/save16.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />

                <RibbonApplicationMenuItem x:Name="AppCmdPrint" Header="Print" 
                 Command="ApplicationCommands.Print" IsCheckable="False" 
                 ImageSource="Images/print.png" 
                 QuickAccessToolBarImageSource="Images/print.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />

                <RibbonApplicationMenuItem x:Name="AppCmdClose" 
                 Header="Close App" Command="ApplicationCommands.Close" 
                 ImageSource="Images/close.png" 
                 QuickAccessToolBarImageSource="Images/close.png" 
                 CanAddToQuickAccessToolBarDirectly="False" />

            </RibbonApplicationMenu>

        </Ribbon.ApplicationMenu>

The part with the QAT:

XAML
<Ribbon.QuickAccessToolBar>

            <RibbonQuickAccessToolBar HorizontalAlignment="Right" Width="120">

                <RibbonButton x:Name="AppCmdNewQAT" Command="ApplicationCommands.New" 
                 SmallImageSource="Images\newdocument32.png" KeyTip="N" />

                <RibbonButton x:Name="AppCmdOpenQAT" Command="ApplicationCommands.Open" 
                 SmallImageSource="Images\open16.png" KeyTip="O"/>

                <RibbonButton x:Name="AppCmdSaveAsQAT" Command="ApplicationCommands.SaveAs" 
                 SmallImageSource="Images\save16.png" KeyTip="S"/>

                <RibbonButton x:Name="AppCmdCloseQAT" Command="ApplicationCommands.Close" 
                 SmallImageSource="Images\close.png"/>

            </RibbonQuickAccessToolBar>

        </Ribbon.QuickAccessToolBar>

And the first part of the EditorTab" ("Start"):

XAML
<RibbonTab x:Name="EditorTab" Header="Start" Height="97" Margin="0,0,-2,-9" 
 RenderTransformOrigin="0.5,0.682" KeyTip="E">

            <RibbonGroup Header="File" Height="92" Margin="0" 
             VerticalAlignment="Top" Width="199" FontFamily="Arial" 
             CanAddToQuickAccessToolBarDirectly="False">

                <RibbonButton x:Name="New" Content="RibbonButton" 
                 HorizontalAlignment="Left" Height="53" Margin="10,2,-86,-53" 
                 VerticalAlignment="Top" Width="80" Label="New" 
                 LargeImageSource="Images/newdocument32.png" ToolTipTitle="" 
                 ToolTip="New" KeyTip="N" CanAddToQuickAccessToolBarDirectly="False" />

                <RibbonButton x:Name="Open" Content="RibbonButton" 
                 HorizontalAlignment="Left" Height="22" Margin="95,0,-176,-26" 
                 VerticalAlignment="Top" Width="85" Label=" Open" 
                 SmallImageSource="Images/open16.png" ToolTipTitle="" 
                 ToolTip="Open" KeyTip="O" CanAddToQuickAccessToolBarDirectly="False" />

                <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"/>

                <RibbonButton x:Name="Save" Content="RibbonButton" 
                 HorizontalAlignment="Left" Height="Auto" Margin="94,46,-162,-70" 
                 VerticalAlignment="Top" Width="80" Label=" Save"  KeyTip="S" 
                 AutomationProperties.AccessKey="S" AutomationProperties.AcceleratorKey="S" 
                 SmallImageSource="Images/save16.png" Visibility="Hidden" 
                 CanAddToQuickAccessToolBarDirectly="False"/>

            </RibbonGroup>

            <RibbonGroup Header="Clipboard" Margin="0" Width="100" FontFamily="Arial" 
             VerticalAlignment="Top" Height="92" CanAddToQuickAccessToolBarDirectly="False">

                <RibbonButton x:Name="ClipboardCmdCut" Label=" Cut" 
                 SmallImageSource="Images/cut16.png" Margin="0,2,0,0" 
                 CanAddToQuickAccessToolBarDirectly="False"/>

                <RibbonButton x:Name="ClipboardCmdCopy" Label=" Copy" 
                 SmallImageSource="Images/copy16.png" KeyTip="C" Margin="0,2,-1.708,0" 
                 Height="Auto" CanAddToQuickAccessToolBarDirectly="False"/>

                <RibbonButton x:Name="ClipboardCmdPaste" Label=" Paste" 
                 SmallImageSource="Images/paste16.png" Margin="0,2,0,0" 
                 CanAddToQuickAccessToolBarDirectly="False"/>

            </RibbonGroup>

Saving QAT State when Closing the App

VB.NET code in AppForm.vb:

VB.NET
Private Sub AppForm_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing

    Try

        If MyRibbonUserControl.AppCmdNewQAT.IsLoaded = False Then
            My.Settings.AppCmdNewQAT_Visible = False
        ElseIf MyRibbonUserControl.AppCmdNewQAT.IsLoaded = True Then
            My.Settings.AppCmdNewQAT_Visible = True
        End If

        If MyRibbonUserControl.AppCmdOpenQAT.IsLoaded = False Then
            My.Settings.AppCmdOpenQAT_Visible = False
        ElseIf MyRibbonUserControl.AppCmdopenQAT.IsLoaded = True Then
            My.Settings.AppCmdOpenQAT_Visible = True
        End If

        If MyRibbonUserControl.AppCmdSaveAsQAT.IsLoaded = False Then
            My.Settings.AppCmdSaveAsQAT_Visible = False
        ElseIf MyRibbonUserControl.AppCmdSaveAsQAT.IsLoaded = True Then
            My.Settings.AppCmdSaveAsQAT_Visible = True
        End If

        If MyRibbonUserControl.AppCmdCloseQAT.IsLoaded = False Then
            My.Settings.AppCmdCloseQAT_Visible = False
        ElseIf MyRibbonUserControl.AppCmdcloseQAT.IsLoaded = True Then
            My.Settings.AppCmdCloseQAT_Visible = True
        End If

Project Settings

Image 11

Restore QAT State when Winform Loads

VB.NET
Private Sub AppForm_Load(sender As Object, e As EventArgs) Handles Me.Load 

If My.Settings.AppCmdNewQAT_Visible = False Then 
MyRibbonUserControl.RibbonWPF.QuickAccessToolBar.Items.Remove(MyRibbonUserControl.AppCmdNewQAT) 
End If 

If My.Settings.AppCmdOpenQAT_Visible = False Then 
MyRibbonUserControl.RibbonWPF.QuickAccessToolBar.Items.Remove(MyRibbonUserControl.AppCmdOpenQAT) 
End If
...

Restore QAT During User Session (from Tab ribbon Settings):

VB.NET
VB.NET code in UserControlRibbonWPF.xaml.vb

Private Sub RestoreQAT_Click(sender As Object, e As RoutedEventArgs) _
        Handles RestoreQATRibbonButton.Click

    If AppCmdNewQAT.IsLoaded = False Then
        RibbonWPF.QuickAccessToolBar.Items.Add(AppCmdNewQAT)
    End If

    If AppCmdOpenQAT.IsLoaded = False Then
        RibbonWPF.QuickAccessToolBar.Items.Add(AppCmdOpenQAT)
    End If

    If AppCmdSaveAsQAT.IsLoaded = False Then
        RibbonWPF.QuickAccessToolBar.Items.Add(AppCmdSaveAsQAT)
    End If

    If AppCmdCloseQAT.IsLoaded = False Then
        RibbonWPF.QuickAccessToolBar.Items.Add(AppCmdCloseQAT)
    End If

End Sub

Known Bugs

After minimizing the ribbon, it may not appear complete when you use the context menu to maximize it.

As a workaround, doubleclick on a tab header can be used to minimize/maximize the ribbon.

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. I also think that with Command Bindings, a helpful Command interface should be possible.

History

  • 1st January, 2022 - Initial submission
  • 1st January, 2022 - Added download link

License

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