"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"
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
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()
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)
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)
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.
- UserControl #1 has a grid with 100 boxes. It's height and width are set.
- 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.
- 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.