Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF Re-Size your Main Window from your UserControls

0.00/5 (No votes)
26 Jun 2014 1  
Size-to-content just doesn't give you the window sizes you want for each usercontrol.

"Hey, Main Window - Switch to me and I want you to be so high by so wide."

Introduction

When creating applications with WPF and using the MVVM pattern, it is common to have one "Main" window for your application. This window would contain a content-control that would display a usercontrol to layout your screen display. To switch "views", you change the content of the content-control.

If you set the main window xaml to SizeToContent = WidthAndHeight and set your content-control HorizontalAlignment and VerticalAlignment to Stretch, the main window will automatically resize to fit the content of your usercontrol.  Simple. No problem.

However, if your usercontrol contains list-views and/or items-controls and/or grids and wrap panels etc., the auto size of the main window might not be the size you want. You can set min-heights/widths and max-heights/widths and usercontrol height/width and still might not get the size you want.

This article will show a simple way to re-size and reposition your "Main" window for each usercontrol in your application, while still being able to let the user re-size the window and its contents. It will also show a method of viewmodel switching to display your views.

Overview

This application will bind the height and width of the main window to properties on the viewmodel that you can manipulate. So, you can set the height and width of the main window to a property on the main window viewmodel. Isn't that the basic premise of WPF/MVVM to bind properties? So why do I need to read any further? Good question.

What-if the main window is just a shell and has no knowledge of the viewmodels it is displaying?
What-if you want to re-size the main window for some views, but have other views size-to-content automatically?
What-if you want to size only the width of one view but size the height of another?
What-if you want to ignore re-sizing for certain viewmodels?
I thought if you set the width and height properties directly that it would not re-size properly!

I guess then its time to read on.

The application created for this article will show how to switch between several usercontrols using the viewmodel switching pattern. Each usercontrol viewmodel will send a message to the main window to tell it what usercontrol to switch to and what size it should be.                              

Laying the Groundwork

First - The Plumbing

If you are creating any kind of WPF application (an enterprise wide solution to a small standalone app) it makes sense to use the Model-View-ViewModel pattern. This is not an article to explain MVVM, there are plenty of them on Code Project that you can reference. When using MVVM it pays to use a framework for the program infrastructure. There are also numerous frameworks available to choose from.

I have created a simple MVVM framework module to provide the basic "plumbing" for the application. The framework contains the following items.

  • RelayCommand.vb
  • Messenger.vb
  • ServiceContainer.vb
  • IMessageBoxService.vb
  • MessageBoxService.vb
  • ServiceInjector.vb
  • IScreen.vb
  • BaseViewModel.vb
  • IShowDialogService.vb
  • ShowDialogService.vb

The relay-command, messenger, iscreen and service provider are from articles from Josh Smith and the baseviewmodel class complete with INotifyPropertyChanged logic is from Karl Shiffletts articles, all here on codeproject. All the code for this framework is included in the project source code download file.

Explaining the Code

The Interface

In order to implement our sizing logic, we first start with creating an interface for the necessary properties that we will be utilizing. Creating an interface allows us to easily apply this interface to each usercontrol  viewmodel that needs to re-size the main window.

By programming to the interface, it enables us to pass various different viewmodels into our code and be able to process them without recasting each one.

The interface contains three properties as shown below. One for the height, one for the width and the third one is a string which allows us to change the title of the main window for each different usercontrol. Since the main window is just a shell, it doesn't have a title of it's own. It's purpose is to display the other views.

The IViewModel Interface
Public Interface IViewModel

    Property DisplayName() As String

    Property UserControlHeight() As Double

    Property UserControlWidth() As Double

End Interface

The DisplayViewModel Implementation

To implement our IViewModel Interface, I created a helper viewmodel class that contains all the getter/setter logic for our three interface properites. This view model inherits from our baseviewmodel in our custom MVVM framework. As described above the baseviewmodel contains all of the INotifyPropertyChanged code for all viewmodels.

The height and width properties are both set to a default value of double.NaN when initialized. Setting the size property to double.NaN is the same as setting it to Auto. If the usercontrol does not set the size properties itself then the default "auto" size will be sent to the main window.

The DisplayViewModel Class
Imports System.IO
Imports GwsMvvmBase

Public MustInherit Class DisplayViewModel
    Inherits BaseViewModel
    Implements IViewModel

#Region "Initialization"
    Protected Sub New()
    End Sub
#End Region

#Region "IViewModel Implementation"
    ' Returns the user-friendly name of this object.
    Private _DisplayName As String = String.Empty
    Public Overrides Property DisplayName() As String Implements IViewModel.DisplayName
        Get
            Return _DisplayName
        End Get
        Protected Set(ByVal value As String)
            _DisplayName = value
        End Set
    End Property

    'Allows each usercontrol to resize the main form.
    Private m_UserControlHeight As Double = Double.NaN
    Public Property UserControlHeight() As Double Implements IViewModel.UserControlHeight
        Get
            Return m_UserControlHeight
        End Get
        Set(ByVal value As Double)
            SetPropertyValue("UserControlHeight", m_UserControlHeight, value)
        End Set
    End Property

    Private m_UserControlWidth As Double = Double.NaN
    Public Property UserControlWidth() As Double Implements IViewModel.UserControlWidth
        Get
            Return m_UserControlWidth
        End Get
        Set(ByVal value As Double)
            SetPropertyValue("UserControlWidth", m_UserControlWidth, value)
        End Set
    End Property

#End Region

End Class

The UserControl ViewModel Initialization

The first viewmodel our application displays is the MainMenuViewModel to display a main menu view on the screen. Each usercontrol viewmodel will inherit from the DisplayViewModel above. We are now able to set the properties from our IViewModel Interface.

There is one important item to note here. The size you set for the height and width properties are the size you want the Main Window to be that includes this userconrol. Not the size of the usercontrol. More on this a bit later.

As shown below we set the displayname to "Main Menu". We set the UserControlHeight to 450 and the UserConrtrolWidth to 350. This means that when we display this usercontrol (MainMenuViewModel) we want the main window to re-size to a height of 450 and  a width of 350.

MainMenuViewModel Initialization Code
Public Class MainMenuViewModel
    Inherits DisplayViewModel

...

#Region "Initialization"
    Public Sub New()
        MyBase.DisplayName = "Main Menu"
        MyBase.UserControlHeight = 450
        MyBase.UserControlWidth = 350
...

End Class

The Main Window View and ViewModel

The main window will bind to the properties that we defined in our IViewModel Interface. There are several items to point out in this code.

First the main window contains a ContentControl. The ContentControl's content is bound to a property on our MainWindowViewModel called CurrentPageViewModel. The CurrentPageViewModel is the viewmodel for the usercontrol that we want to display. We switch out this viewmodel to display different usercontrols. This viewmodel is of type IViewModel so we can switch in any viewmodel that implements our IViewModel interface with no need of casting types. The main window knows how to display each viewmodel using a datatemplate. This is explained further below in the viewmodel switching section.

Main Window Content Control
 <ContentControl Content="{Binding Path=CurrentPageViewModel}"
                        Grid.Row="1"
                        x:Name="MyView"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch" />

The ContentControl's content is bound to a property on our MainWindowViewModel called CurrentPageViewModel.

"Current Page ViewModel" property in the main window viewmodel
Private m_currentPageViewModel As IViewModel
Public Property CurrentPageViewModel() As IViewModel
        Get
            Return m_currentPageViewModel
        End Get
        Set(ByVal value As IViewModel)
            MyBase.SetPropertyValue("CurrentPageViewModel", m_currentPageViewModel, value)
        End Set
    End Property

The horizontal and vertical alignment of the ContentControl is set to "Stretch" to utilize the space given to it by the main window. This is why our usercontrol viewmodel above set the main window size and not the usercontrol size. The usercontrol tells the main window how big the main window should be and sizes itself along with it. This allows the window to be manually re-sized by the user and the usercontrol will go along for the "ride".

"Hey, Main Window - Giddy-Up."

                                                          

Back to the Beginning

So now we are back at our original premise.

If you set the main window xaml to SizeToContent = WidthAndHeight and set your content-control HorizontalAlignment and VerticalAlignment to Stretch, the main window will automatically resize to fit the content of your usercontrol.  Simple. No problem.

Here is where we change things up. SizeToContent = WidthAndHeight will try to grab as much screen as the usercontrols need. This could be the entire screen if the usercontrol contains any itemscontrols that have a lot of data.

Instead of setting the SizeToContent of our main window to SizeToContent = WidthAndHeight, we bind the SizeToContent to a property on our main window viewmodel named WindowSize. Then we can change the SizeToContent to any value we need.

MainWindow XAML
<Window x:Class="MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        WindowStartupLocation="CenterScreen"

        SizeToContent="{Binding Path=WindowSize}"
...

>
"Size to Content" property in the main window viewmodel
  Private m_WindowSize As SizeToContent
    Public Property WindowSize() As SizeToContent
        Get
            Return m_WindowSize
        End Get
        Set(ByVal value As SizeToContent)
            MyBase.SetPropertyValue("WindowSize", m_WindowSize, value)
        End Set
    End Property

This property is of type "SizeToContent". SizeToContent is a .Net Enum that allows for the following values:

  • SizeToContent.Manual  -   (Allows you to set the height and width)
  • SizeToContent.Width  -   (Allows you to set the height but the width is set automatically)
  • SizeToContent.Height  -   (Allows you to set the width but the height is set automatically)
  • SizeToContent.WidthAndHeight  -   (Sets the height and width automatically)

IViewModel Main Window Binding

Now we can implement the binding to our IViewModel interface. We bind the Height, Width and Title of our main window to our IViewModel Properties (UserControlHeight, UserControlWidth, DisplayName).

"IViewModel" properties bound to the main window view
 <Window x:Class="MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        ...

        Title="{Binding Path=CurrentPageViewModel.DisplayName}"
        Height="{Binding Path=UserControlHeight, Mode=TwoWay}"
        Width="{Binding Path=UserControlWidth, Mode=TwoWay}"
        SizeToContent="{Binding Path=WindowSize}"
          
        ...
>

Here I would like to point out that the Title is bound to a property on our CurrentPageViewModel, but the Height, Width and SizeToContent is bound to our MainWindowViewModel properties. This means the height and width are bound to the default values of Double.NaN (Auto) and not to the properties that we just set in our usercontrol viewmodel above (height of 450, width of 350).

If we bind the height and width directly to our CurrentPageViewModel (our usercontrol) it will change automatically when we switch viewmodels (Viewmodel Switching is explained below). By binding to our main window viewmodel (which also implements the IViewmodel interface) we can examine the properties provided by the CurrentPageViewModel when it is switched and accept or reject the property values as needed.

The height and width binding mode must be set to "TwoWay" so the main window can be re-sized and the bindings kept.

Here is the complete XAML for our main window shell.
<Window x:Class="MainWindowView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        MinHeight="100"
        MinWidth="100"
        WindowStartupLocation="CenterScreen"

        Title="{Binding Path=CurrentPageViewModel.DisplayName}"
        Height="{Binding Path=UserControlHeight, Mode=TwoWay}"
        Width="{Binding Path=UserControlWidth, Mode=TwoWay}"
        SizeToContent="{Binding Path=WindowSize}"

        Icon="/Images/basket.ico"
        ShowInTaskbar="True">

    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/MainResources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

    <Grid Background="Orange">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"
                           MinHeight="21" />
            <RowDefinition Height="*" />
            <RowDefinition Height="Auto"
                           MinHeight="0" />
        </Grid.RowDefinitions>

        <ContentControl Content="{Binding Path=CurrentPageViewModel}"
                        Grid.Row="1"
                        x:Name="MyView"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch" />

        <DockPanel Grid.Row="0"
                   DataContext="{Binding CurrentPageViewModel}">
            <Menu DockPanel.Dock="Top"
                  Background="Orange"
                  Name="MainMenu">
                <MenuItem Header="_File">
                    <MenuItem Header="_Save"
                              Command="{Binding Path=SaveCommand}">
                        <MenuItem.Icon>
                            <Image Source="/Images/saveHS.png"
                                   Width="15"
                                   Height="15" />
                        </MenuItem.Icon>
                    </MenuItem>
                    <Separator></Separator>
                    <MenuItem Header="E_xit"
                              Command="{Binding Path=CloseCommand}">
                        <MenuItem.Icon>
                            <Image Source="/Images/HomeHS.png"
                                   Width="15"
                                   Height="15" />
                        </MenuItem.Icon>
                    </MenuItem>
                </MenuItem>
            </Menu>
        </DockPanel>

        <Button Name="GWSButton"
                Style="{DynamicResource GWSButton}"
                Grid.Row="2"
                Command="{Binding Path=GoToMenuCommand}"
                Visibility="{Binding Path=ShowHideMe}" />
    </Grid>
</Window>

Determine Main Window Height, Width and SizeToContent

Our MainWindowViewModel inherits from the DisplayViewModel which implements our IViewModel interface. Here we create a variable to hold our "HomePageViewModel" which is the MainMenuViewModel of our application. We then set our CurrentPageViewModel to our HomePageViewModel.

Main Window ViewModel Initialzation
Public Class MainWindowViewModel
    Inherits DisplayViewModel

    Private HomePageViewModel As IViewModel


    Public Sub New()
        ' Set starting page
        HomePageViewModel = New MainMenuViewModel
        CurrentPageViewModel = HomePageViewModel
        ...
    End Sub

...

End Class

This will trigger the setter property of the CurrentPageViewModel where we can inspect the incoming viewmodel.

Main Window ViewModel CurrentPageViewModel Property Setter
 Private m_currentPageViewModel As IViewModel
    Public Property CurrentPageViewModel() As IViewModel
        Get
            Return m_currentPageViewModel
        End Get
        Set(ByVal value As IViewModel)
            MyBase.SetPropertyValue("CurrentPageViewModel", m_currentPageViewModel, value)
            If value IsNot Nothing Then
                WindowSize = WindowSizeMode(m_currentPageViewModel)
                MainWindowSize(WindowSize)
            End If
        End Set
    End Property

Determine SizeToContent 

Our CurrentPageViewModel has been set and we can now inspect the IViewModel properties to adjust our Main Window. We need to determine the SizeToContent mode for our main window first.  If the SizeToContent is set to WidthAndHeight then the main window Xaml will disregard the height and width properties. If it is set to Height, then the main widow Xaml will disregard the height property. Same goes for the Width. If it is set to Manual then we can set both the height and width.
 
To determine the SizeToContent we want for each usercontrol viewmodel, we pass the CurrentPageViewModel to the WindowSizeFunction to calculate the SizeToContent Enum for our main window.
Main Window ViewModel WindowSizeMode Function
    Private Function WindowSizeMode(ByVal vm As IViewModel) As SizeToContent

        Dim Menu2Vm As MainMenuViewModel2 = TryCast(vm, MainMenuViewModel2)
        If Menu2Vm IsNot Nothing Then
            Return SizeToContent.WidthAndHeight
        End If

        Dim MainWindowSizeMode As New SizeToContent

        Dim GoodHeight As Boolean = vm.UserControlHeight > 0
        Dim GoodWidth As Boolean = vm.UserControlWidth > 0

        If GoodHeight And GoodWidth Then
            MainWindowSizeMode = SizeToContent.Manual
        ElseIf GoodHeight And Not GoodWidth Then
            MainWindowSizeMode = SizeToContent.Width
        ElseIf Not GoodHeight And GoodWidth Then
            MainWindowSizeMode = SizeToContent.Height
        Else
            MainWindowSizeMode = SizeToContent.WidthAndHeight
        End If

        Return MainWindowSizeMode

    End Function

In order to demonstrate, we will first see if the incoming IViewModel is MainMenuViewModel2  (a dummy menu to display). If it is, we will set the SizetoContent to WidthAndHeight which will cause the main window to auto size this usercontrol automatically as if we did not set any dimensions. (Our original premise once again).

Next you could insert any logic that you need to analyze the height and width. For this demonstration I compare the height and width properties to 0. If less than 0 then the value is incorrect. Based on this I return a sizetocontent enum accordingly. Note: Double.NaN returns a negative value.

  • Good height and good width = sizetocontent.manual
  • Good height and incorrect width = sizetocontent.width
  • Incorrect height and good width = sizetocontent.height
  • Incorrect height and incorrect width = sizetocontent.widthandheight

Determine Main Window Height and Width

Now based on the window size mode we just returned, we can set the height and/or width of the main window to the height and/or width from our CurrentPageViewModel usercontrol.

Main Window ViewModel CurrentPageViewModel Property Setter
  Set(ByVal value As IViewModel)
            MyBase.SetPropertyValue("CurrentPageViewModel", m_currentPageViewModel, value)
            If value IsNot Nothing Then
                WindowSize = WindowSizeMode(m_currentPageViewModel)
                MainWindowSize(WindowSize)
            End If
        End Set

We pass in our SizeToContent value to our MainWindowSize method. Based on this value the Height and/or Width of the main window is set the the value from the CurrentPageViewModel. Any values not needed are set to Auto (Double.NaN).

Main Window ViewModel MainWindowSize Method
 Private Sub MainWindowSize(ByVal value As SizeToContent)
        Select Case value
            Case SizeToContent.Height
                MyBase.UserControlHeight = Double.NaN
                MyBase.UserControlWidth = m_currentPageViewModel.UserControlWidth
            Case SizeToContent.Width
                MyBase.UserControlWidth = Double.NaN
                MyBase.UserControlHeight = m_currentPageViewModel.UserControlHeight
            Case SizeToContent.Manual
                MyBase.UserControlHeight = m_currentPageViewModel.UserControlHeight
                MyBase.UserControlWidth = m_currentPageViewModel.UserControlWidth
            Case SizeToContent.WidthAndHeight
                MyBase.UserControlHeight = Double.NaN
                MyBase.UserControlWidth = Double.NaN
        End Select

    End Sub

This property change will cause our bindings to relay the changes to our main window view and change the size of the main window. The usercontrol will re-size automatically with the window.

"Voila! The Main Window is Re-Sized to your UserControl."

                                                         

 


"Whoa! Wait a minute.

The Main Window is out of whack."

When changing the height and width of the main window, the window will grow or shrink to the new size, but it will still be based on the upper left corner of the window. This coordinate will not change when the window changes. If the window is made bigger, then it will grow down and to the right but not center itself on the screen.

In order to re-center the window on the screen after it is re-sized we need to add the following code to our main window. The OnRenderSizeChange will be fired when our window size changes. Here we can inspect the changes and re-center the window

Main Window View Re-Center Window on Screen
Partial Public Class MainWindowView

    Public Sub New()
        InitializeComponent()
    End Sub

#Region "Window Resize"
    Protected Overrides Sub OnRenderSizeChanged(ByVal sizeInfo As SizeChangedInfo)
        'This will reposition the main window to the center of the screen
        'after it is resized by a usercontrol/viewmodel.
        MyBase.OnRenderSizeChanged(sizeInfo)

        If sizeInfo.HeightChanged = True Then
            Me.Top = (SystemParameters.WorkArea.Height - Me.ActualHeight) / 2 + SystemParameters.WorkArea.Top
        End If

        If sizeInfo.WidthChanged = True Then
            Me.Left = (SystemParameters.WorkArea.Width - Me.ActualWidth) / 2 + SystemParameters.WorkArea.Left
        End If
    End Sub
#End Region

End Class

There you have it.

When the viewmodel is changed, the IViewModel properties of the new viewmodel are analyzed and the main window is sized and repositioned accordingly.


ViewModel Switching

Now that our main window will be sized to our viewmodels, let's take a look of how to switch the viewmodel on the main window. To do this we will be using the messenger system from our custom MVVM framework.

"Hey, Anybody Listening?  I am ViewModel 'XYZ' !"

                                                      

The messenger system allows the application to "float" a message when an event occurs. Then other classes can register to receive this message and act on it - if it so desires. We will send a message from our main menu viewmodels and have the main window viewmodel receive it.

The message can be sent with or without an object attached. We will show one message sent with an IViewModel object and one sent with a string attached. (To see a message with no object attached -view the attached source code for the "I want to close" message used to close a view.)

Set up the Message

To set up the messenger system, we must declare the messenger and the message in the application class. I used a constant to declare the message variable. The Messenger property will return an instance of the messenger from our custom MVVM framework.

Application class declaring the Messenger
Imports GwsMvvmFramework
Imports GwsMvvmBase
Imports System.ComponentModel

Class Application
    Shared ReadOnly _messenger As New GwsMvvmFramework.Messenger

    Friend Const MSG_I_WANT_TO_CLOSE As String = "I want to close"
    Friend Const MSG_CHANGE_VIEW_MODEL As String = "Change ViewModel"
    Friend Const MSG_CHANGE_VIEW_MODEL2 As String = "Change ViewModel2"

    Friend Shared ReadOnly Property Messenger() As Messenger
        Get
            Return _messenger
        End Get
    End Property
... 

Create the IViewModel Collection

The Main Menu viewmodel contains a collection of our IViewModels. There is one viewmodel for each usercontrol to display. The collection is named m_PageViewModels and a corresponding property named PageViewModels is created that is bound to the buttons on the main menu view. The view models are created when the class is initialized.

Collection of IViewModels in Main Menu View Model
  Private m_pageViewModels As Collection(Of IViewModel)

  Private Sub CreateRefreshViewModelData()
        ' Add available pages
        PageViewModels.Add(New UserControl1ViewModel)
        PageViewModels.Add(New UserControl2ViewModel)
        PageViewModels.Add(New UserControl3ViewModel)
        PageViewModels.Add(New MainMenuViewModel2)
  End Sub

Main Menu 2, instead of creating IViewModel objects, creates a listing of strings of the viewmodel names. This way we are not creating viewmodels and their overhead when they might not be accessed.

Collection of IViewModel String Names in Main Menu 2 ViewModel
   Private m_pageViewModels As Collection(Of String)

   Private Sub CreateRefreshViewModelData()
        ' Add available pages
        PageViewModels.Add("UserControl1ViewModel")
        PageViewModels.Add("UserControl2ViewModel")
        PageViewModels.Add("UserControl3ViewModel")
   End Sub

Set up the ICommand for our IViewModels

When a button is clicked on the menu the ChangeCurrentViewModelCommand is fired. The CommandParameter="{Binding}" will pass to the command either the IViewModel object on the main menu or the string name of the viewmodel on the main menu 2.

Main Menu View XAML
 <ItemsControl ItemsSource="{Binding Path=PageViewModels}">
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <Grid>
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="*" />
                                </Grid.ColumnDefinitions>

                                <Button Grid.Column="0"
                                        Style="{DynamicResource GWSButtonLarge}"
                                        Command="{Binding DataContext.ChangeCurrentViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
                                        CommandParameter="{Binding}"
                                        ToolTip="Must enter all data first!"
                                        ToolTipService.ShowOnDisabled="true"
                                        Margin="50,5,5,5" />
                                <TextBlock Grid.Column="1"
                                           Text="{Binding DisplayName}"
                                           FontSize="18"
                                           FontWeight="Bold"
                                           TextAlignment="Left"
                                           Margin="10,25,0,0"
                                           Foreground="White" />
                            </Grid>
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>

The command will issue the Messenger.NotifyColleagues which will send our message. The message will have either the IViewModel or string attached. This is a normal command structure that can also be used to also disable commands as shown below.

The MainMenu ViewModel Change Current Viewmodel Command
 Public ReadOnly Property ChangeCurrentViewModelCommand() As ICommand
        Get
            Return New RelayCommand(Of IViewModel)(AddressOf Me.ChangeCurrentViewModel, AddressOf Me.CanChangeCurrentViewModel)
        End Get
    End Property
    Public Function CanChangeCurrentViewModel(ByVal viewModel As IViewModel) As Boolean
        If TypeOf (viewModel) Is UserControl3ViewModel Then
            Dim vm As UserControl3ViewModel = DirectCast(viewModel, UserControl3ViewModel)
            Return vm.AllDataEntered
        Else
            Return True
        End If

        Return True
    End Function
    Private Sub ChangeCurrentViewModel(ByVal viewModel As IViewModel)
        Application.Messenger.NotifyColleagues(Application.MSG_CHANGE_VIEW_MODEL, viewModel)
    End Sub

Register the Message

Our main window will register to receive this message when it is sent. Register will point to a method to execute when the message is received. If it contains an IViewModel object it will change the CurrentPageViewModel to it. This will then cause our window re-sizing explained above.

The MainWindow ViewModel register message
        ' Register to receive messages
        Application.Messenger.Register(Application.MSG_CHANGE_VIEW_MODEL, _
           New Action(Of IViewModel)(AddressOf ChangeViewModel))

    Private Sub ChangeViewModel(ByVal viewModel As IViewModel)
        If viewModel IsNot Nothing Then
            ShowHideMe = Visibility.Visible
            CurrentPageViewModel = Nothing
            CurrentPageViewModel = viewModel
        End If
    End Sub

If the message was sent from the main menu 2 it will contain the string name of the viewmodel to switch to. We need to use a little reflection to create an instance of the viewmodel object from the name before we assign it to the CurrentPageViewModel.

Create Instance of IVewModel
    Application.Messenger.Register(Application.MSG_CHANGE_VIEW_MODEL2, _
          New Action(Of String)(AddressOf ChangeViewModel2))


  Private Sub ChangeViewModel2(ByVal viewModelString As String)
        'Since viewmodel is passed as a string we must create an instance.
        Dim type As Type = Me.GetType
        Dim [assembly] As System.Reflection.Assembly = type.Assembly
        Dim viewModel As IViewModel = CType([assembly].CreateInstance(type.Namespace & "." & viewModelString), IViewModel)

        If viewModel IsNot Nothing Then
            ShowHideMe = Visibility.Visible
            CurrentPageViewModel = Nothing
            CurrentPageViewModel = viewModel
        End If

    End Sub

Create a Resource Dictionary

To display a view  for each usercontrol viewmodel, we must create a data template that will associate the proper view with each viewmodel. For each usercontrol viewmodel you set up a data template with the viewmodel name as the DataType and the view name as its content. I put all these datatemplates in a central ResourceDictionary module named MainResources.XAML.

MainResources Xaml File
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfUserControl">
  <!--
  The templates apply a View to an instance
  of the ViewModel class shown in the main window.
  -->
    <DataTemplate DataType="{x:Type local:MainMenuViewModel}">
        <local:MainMenuView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:UserControl1ViewModel}">
        <local:UserControl1View />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:UserControl2ViewModel}">
        <local:UserControl2View />
    </DataTemplate>
    
    <DataTemplate DataType="{x:Type local:UserControl3ViewModel}">
        <local:UserControl3View />
    </DataTemplate>

    <DataTemplate DataType="{x:Type local:MainMenuViewModel2}">
        <local:MainMenuView2 />
    </DataTemplate>

</ResourceDictionary>

Add the Resource Dictionary to our Main Window

In the Main Window Xaml we add reference to our resource dictionary so when we switch viewmodels it can locate the associated view to display.

Main Window Resources and ContentControl
  <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="/MainResources.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

        <ContentControl Content="{Binding Path=CurrentPageViewModel}"
                        Grid.Row="1"
                        x:Name="MyView"
                        HorizontalAlignment="Stretch"
                        VerticalAlignment="Stretch" />

Now when we select a viewmodel to display from the main menu view, a message is sent that the main window intercepts and changes the currentpageviewmodel that is bound to the content control. The viewmodel is changed and the window is resized and repositioned.


Running the Application

"Hey code, let's go for a little test 'run'."

                                                             

The main window is only a shell to contain the content from the various usercontrols that make up the application's views. The main window is a grid with three rows. One for the menu items along the top, one for the "return to main menu" button on the bottom (hidden on the menu view), and in the middle is the contentcontrol. The Main Menu usercontrol displays 4 buttons (basketballs) that when clicked will change the viewmodel.

To demonstrate the various sizing options, the sample application has the following UserControls Views. Usercontrol 1 and 2 have 100 items in them which would cause the view to take up the entire screen if set to auto.

  1. UserControl #1 has a grid with 100 boxes. It's height and width are set.
  2. UserControl #2 has a listbox with 100 items and a horizontal stackpanel with 100 items. It's height is set and it's width will be automatic.
  3. UserControl #3 has an itemscontrol with 5 buttons. It does not set the height or the width - both will be automatic.

The Main Menu

The Main Menu view has the following IViewModel properties set:

  • UserControlHeight = 450
  • UserControlWidth = 350
  • DisplayName = "Main Menu"
     Main Window with Main Menu View

                                      

UserControl #1

The UserControl #1  view has the following IViewModel properties set:

  • UserControlHeight = 725
  • UserControlWidth = 1000
  • DisplayName = "User Control #1"
    Main Window with UserControl #1 View

             

UserControl #2

The UserControl #2  view has the following IViewModel properties set:

  • UserControlHeight = 650
  • UserControlWidth = Not Set - will be auto
  • DisplayName = "User Control #2"
Main Window with UserControl #2 View

              

UserControl #3

The UserControl #3  view has the following IViewModel properties set:

  • UserControlHeight = Not Set - will be auto
  • UserControlWidth = Not Set - will be auto
  • DisplayName = "User Control #3"

This view is disabled from the Main Menu through the ICommand CanChangeCurrentViewModel and can be accessed from the Main Menu 2.

Main Window with UserControl #3 View

                                  

 

Note: Spinning basketball buttons added as an extra bonus.

*Cartoon images from Xamalot.com free clipart.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here