WPF 从 UserControls 调整主窗口大小





5.00/5 (4投票s)
Size-to-content 无法为您提供每个 usercontrol 想要的窗口大小。
“嘿,主窗口——切换到我,我希望你变得那么高、那么宽。”

引言
在创建 WPF 应用程序并使用 MVVM 模式时,通常会为您的应用程序设置一个“主”窗口。此窗口将包含一个内容控件,该控件将显示一个 usercontrol 来布局您的屏幕显示。要切换“视图”,您可以更改内容控件的内容。
如果您将主窗口的 xaml 设置为 SizeToContent = WidthAndHeight,并将您的内容控件的 HorizontalAlignment 和 VerticalAlignment 设置为 Stretch,那么主窗口将自动调整大小以适应您的 usercontrol 的内容。简单。没问题。
但是,如果您的 usercontrol 包含列表视图、项控件、网格和包装面板等,那么主窗口的自动大小可能不是您想要的大小。您可以设置最小高度/宽度和最大高度/宽度以及 usercontrol 的高度/宽度,但仍然可能得不到想要的大小。
本文将介绍一种简单的方法,可以在应用程序的每个 usercontrol 中重新调整和重新定位您的“主”窗口的大小,同时仍然允许用户重新调整窗口及其内容的大小。它还将展示一种视图模型切换方法来显示您的视图。
概述
此应用程序会将主窗口的高度和宽度绑定到视图模型中的属性,您可以对其进行操作。因此,您可以将主窗口的高度和宽度设置为主窗口视图模型上的属性。这不就是 WPF/MVVM 的基本原理吗?为什么要继续阅读?好问题。
如果主窗口只是一个外壳,并且不知道它正在显示的视图模型怎么办? 如果您想为某些视图调整主窗口的大小,但让其他视图自动调整为内容大小怎么办? 如果您只想调整一个视图的宽度,而要调整另一个视图的高度怎么办? 如果您想忽略某些视图模型的重新调整大小怎么办? 我以为如果直接设置宽度和高度属性,它就不会正确地重新调整大小!我想是时候继续阅读了。
为本文创建的应用程序将展示如何使用视图模型切换模式在多个 usercontrol 之间进行切换。每个 usercontrol 视图模型都会向主窗口发送一条消息,告诉它切换到哪个 usercontrol 以及它应该是什么大小。
打下基础
首先 - 管道
如果您正在创建任何类型的 WPF 应用程序(从企业级解决方案到小型独立应用程序),使用 Model-View-ViewModel 模式是有意义的。这不是一篇解释 MVVM 的文章,Code Project 上有很多可以参考的文章。在使用 MVVM 时,使用框架来处理程序基础结构是值得的。也有很多框架可供选择。
我创建了一个简单的 MVVM 框架模块,为应用程序提供基本“管道”。该框架包含以下项目。
- RelayCommand.vb
- Messenger.vb
- ServiceContainer.vb
- IMessageBoxService.vb
- MessageBoxService.vb
- ServiceInjector.vb
- IScreen.vb
- BaseViewModel.vb
- IShowDialogService.vb
- ShowDialogService.vb
中继命令、信使、屏幕和服务提供程序来自Josh Smith的文章,而包含 INotifyPropertyChanged 逻辑的 baseviewmodel 类则来自Karl Shiffletts的文章,这些都在 codeproject 上。此框架的所有代码都包含在项目源代码下载文件中。
代码解释
接口
为了实现我们的尺寸逻辑,我们首先创建一个接口,用于我们即将使用的必要属性。创建接口允许我们将此接口轻松应用于每个需要调整主窗口大小的 usercontrol 视图模型。
通过编程接口,我们可以将各种不同的视图模型传递到我们的代码中,并能够处理它们而无需重新转换它们。
接口包含三个属性,如下所示。一个用于高度,一个用于宽度,第三个是一个字符串,允许我们为每个不同的 usercontrol 更改主窗口的标题。由于主窗口只是一个外壳,它没有自己的标题。它的目的是显示其他视图。
IViewModel 接口
Public Interface IViewModel
Property DisplayName() As String
Property UserControlHeight() As Double
Property UserControlWidth() As Double
End Interface
DisplayViewModel 实现
为了实现我们的 IViewModel 接口,我创建了一个辅助视图模型类,其中包含我们三个接口属性的所有 getter/setter 逻辑。这个视图模型继承自我们自定义 MVVM 框架中的 baseviewmodel。如上所述,baseviewmodel 包含所有视图模型的所有 INotifyPropertyChanged 代码。
高度和宽度属性在初始化时都设置为 double.NaN 的默认值。将大小属性设置为 double.NaN 等同于将其设置为 Auto。如果 usercontrol 本身不设置大小属性,那么默认的“自动”大小将发送到主窗口。
DisplayViewModel 类
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
UserControl ViewModel 初始化
我们的应用程序显示的第一个视图模型是 MainMenuViewModel,用于在屏幕上显示主菜单视图。每个 usercontrol 视图模型都将继承自上面的 DisplayViewModel。我们现在可以设置 IViewModel 接口中的属性了。
这里有一个重要的项目需要注意。您为高度和宽度属性设置的大小是主窗口期望的大小,包括此 usercontrol。而不是 usercontrol 的大小。稍后会详细介绍。
如下所示,我们将 displayname 设置为“Main Menu”。我们将 UserControlHeight 设置为 450,将 UserConrtrolWidth 设置为 350。这意味着当我们显示此 usercontrol (MainMenuViewModel) 时,我们希望主窗口调整到高度 450,宽度 350。
MainMenuViewModel 初始化代码
Public Class MainMenuViewModel
Inherits DisplayViewModel
...
#Region "Initialization"
Public Sub New()
MyBase.DisplayName = "Main Menu"
MyBase.UserControlHeight = 450
MyBase.UserControlWidth = 350
...
End Class
主窗口视图和视图模型
主窗口将绑定到我们在 IViewModel 接口中定义的属性。此代码中有几点需要指出。
首先,主窗口包含一个 ContentControl。ContentControl 的内容绑定到我们 MainWindowViewModel 上的 CurrentPageViewModel 属性。CurrentPageViewModel 是我们想要显示的 usercontrol 的视图模型。我们切换此视图模型来显示不同的 usercontrol。此视图模型类型为 IViewModel,因此我们可以切换实现我们 IViewModel 接口的任何视图模型,而无需转换类型。主窗口知道如何使用数据模板显示每个视图模型。这将在下面的视图模型切换部分进行更详细的解释。
主窗口内容控件
<ContentControl Content="{Binding Path=CurrentPageViewModel}" Grid.Row="1" x:Name="MyView" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
ContentControl 的内容绑定到我们 MainWindowViewModel 上的 CurrentPageViewModel 属性。
主窗口视图模型中的“当前页面视图模型”属性
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
ContentControl 的水平和垂直对齐方式设置为“Stretch”,以利用主窗口提供的空间。这就是为什么我们上面的 usercontrol 视图模型设置主窗口大小而不是 usercontrol 大小的原因。usercontrol 告诉主窗口主窗口应该有多大,并随之调整自身大小。这允许用户手动调整窗口大小,usercontrol 也会随之“随行”。
“嘿,主窗口——驾!”
回到起点
所以我们回到了最初的设想。
如果您将主窗口的 xaml 设置为 SizeToContent = WidthAndHeight,并将您的内容控件的 HorizontalAlignment 和 VerticalAlignment 设置为 Stretch,那么主窗口将自动调整大小以适应您的 usercontrol 的内容。简单。没问题。
这是我们改变事情的地方。SizeToContent = WidthAndHeight 将尝试获取 usercontrol 所需的屏幕空间。如果 usercontrol 包含任何具有大量数据的 itemscontrol,这可能是整个屏幕。
我们不将主窗口的 SizeToContent 设置为 SizeToContent = WidthAndHeight,而是将 SizeToContent 绑定到我们主窗口视图模型上的一个名为 WindowSize 的属性。然后,我们可以将 SizeToContent 更改为任何我们需要的值。
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”属性
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
此属性的类型为“SizeToContent”。SizeToContent 是一个 .Net 枚举,允许使用以下值:
- SizeToContent.Manual - (允许您设置高度和宽度)
- SizeToContent.Width - (允许您设置高度,但宽度会自动设置)
- SizeToContent.Height - (允许您设置宽度,但高度会自动设置)
- SizeToContent.WidthAndHeight - (自动设置高度和宽度)
IViewModel 主窗口绑定
现在我们可以实现到 IViewModel 接口的绑定。我们将主窗口的高度、宽度和标题绑定到我们的 IViewModel 属性(UserControlHeight、UserControlWidth、DisplayName)。
“IViewModel”属性绑定到主窗口视图
<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}" ... >
我想指出的是,Title 绑定到我们 CurrentPageViewModel 上的属性,而 Height、Width 和 SizeToContent 绑定到我们的 MainWindowViewModel 属性。这意味着高度和宽度绑定到 Double.NaN(自动)的默认值,而不是我们上面在 usercontrol 视图模型中刚刚设置的属性(高度 450,宽度 350)。
如果我们将高度和宽度直接绑定到我们的 CurrentPageViewModel(我们的 usercontrol),当我们在切换视图模型时(视图模型切换将在下面解释),它将自动更改。通过绑定到我们的主窗口视图模型(它也实现了 IViewmodel 接口),我们可以检查当前页面视图模型切换时提供的属性,并根据需要接受或拒绝属性值。
绑定模式的高度和宽度必须设置为“TwoWay”,这样主窗口才能重新调整大小并保持绑定。
这是我们主窗口外壳的完整 XAML。
<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>
确定主窗口高度、宽度和 SizeToContent
我们的 MainWindowViewModel 继承自实现我们 IViewModel 接口的 DisplayViewModel。在这里,我们创建一个变量来保存我们的“HomePageViewModel”,它是我们应用程序的 MainMenuViewModel。然后我们将 CurrentPageViewModel 设置为我们的 HomePageViewModel。
主窗口视图模型初始化
Public Class MainWindowViewModel
Inherits DisplayViewModel
Private HomePageViewModel As IViewModel
Public Sub New()
' Set starting page
HomePageViewModel = New MainMenuViewModel
CurrentPageViewModel = HomePageViewModel
...
End Sub
...
End Class
这将触发 CurrentPageViewModel 的 setter 属性,我们可以在其中检查传入的视图模型。
主窗口视图模型 CurrentPageViewModel 属性 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
确定 SizeToContent
主窗口视图模型 WindowSizeMode 函数
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
为了演示,我们将首先检查传入的 IViewModel 是否为 MainMenuViewModel2(一个用于显示的虚拟菜单)。如果是,我们将 SizetoContent 设置为 WidthAndHeight,这将导致主窗口自动调整此 usercontrol 的大小,就像我们没有设置任何尺寸一样。(再次回到我们最初的设想)。
接下来,您可以插入任何需要的逻辑来分析高度和宽度。在本演示中,我将高度和宽度属性与 0 进行比较。如果小于 0,则该值不正确。基于此,我返回一个相应的 sizetocontent 枚举。注意:Double.NaN 返回负值。
- 良好的高度和良好的宽度 = sizetocontent.manual
- 良好的高度和不正确的宽度 = sizetocontent.width
- 不正确的高度和良好的宽度 = sizetocontent.height
- 不正确的高度和不正确的宽度 = sizetocontent.widthandheight
确定主窗口高度和宽度
现在,基于我们刚刚返回的窗口大小模式,我们可以将主窗口的高度和/或宽度设置为 CurrentPageViewModel usercontrol 的高度和/或宽度。
主窗口视图模型 CurrentPageViewModel 属性 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
我们将 SizeToContent 值传递给我们的 MainWindowSize 方法。基于此值,主窗口的高度和/或宽度将设置为 CurrentPageViewModel 的值。任何不需要的值都设置为 Auto (Double.NaN)。
主窗口视图模型 MainWindowSize 方法
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
此属性更改将导致我们的绑定将更改中继到我们的主窗口视图并更改主窗口的大小。usercontrol 将随窗口自动重新调整大小。
“瞧!主窗口已调整为 UserControl 的大小。”
“哇!等等。”
“主窗口出了问题。”
更改主窗口的高度和宽度时,窗口会增长或缩小到新大小,但仍基于窗口的左上角。此坐标在窗口更改时不会改变。如果窗口变大,它会向下和向右增长,但不会居中显示。
为了在调整窗口大小后将其重新居中显示,我们需要将以下代码添加到我们的主窗口中。当我们的窗口大小改变时,将触发 OnRenderSizeChange。在这里,我们可以检查更改并重新居中窗口。
主窗口视图重新居中窗口
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
搞定。
当视图模型更改时,会分析新视图模型的 IViewModel 属性,并相应地调整主窗口的大小和位置。
视图模型切换
既然我们的主窗口将调整为视图模型的大小,让我们来看看如何在主窗口上切换视图模型。为此,我们将使用我们自定义 MVVM 框架中的信使系统。
“嘿,有人在听吗? 我是视图模型‘XYZ’!”
信使系统允许应用程序在事件发生时“广播”一条消息。然后其他类可以注册接收此消息并根据需要采取行动。我们将从我们的主菜单视图模型发送一条消息,并让主窗口视图模型接收它。
消息可以带或不带对象发送。我们将展示一条带有 IViewModel 对象发送的消息和一条带有字符串附加的消息。(要查看没有附加对象的消息,请查看用于关闭视图的“我想关闭”消息的附加源代码。)
设置消息
为了设置信使系统,我们必须在应用程序类中声明信使和消息。我使用了一个常量来声明消息变量。Messenger 属性将返回我们自定义 MVVM 框架中的信使实例。
应用程序类声明信使
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 ...
创建 IViewModel 集合
主菜单视图模型包含我们的 IViewModel 集合。每个要显示的 usercontrol 都有一个视图模型。该集合名为 m_PageViewModels,并创建了一个对应的名为 PageViewModels 的属性,该属性绑定到主菜单视图上的按钮。视图模型在类初始化时创建。
主菜单视图模型中的 IViewModel 集合
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
MainMenu2,而不是创建 IViewModel 对象,而是创建一个视图模型名称的字符串列表。这样,当我们可能不访问视图模型时,我们就不会创建视图模型及其开销。
MainMenu2 视图模型中的 IViewModel 字符串名称集合
Private m_pageViewModels As Collection(Of String) Private Sub CreateRefreshViewModelData() ' Add available pages PageViewModels.Add("UserControl1ViewModel") PageViewModels.Add("UserControl2ViewModel") PageViewModels.Add("UserControl3ViewModel") End Sub
为我们的 IViewModel 设置 ICommand
当菜单上的按钮被点击时,ChangeCurrentViewModelCommand 将被触发。CommandParameter="{Binding}" 将向命令传递主菜单上的 IViewModel 对象或主菜单 2 上的视图模型名称字符串。
主菜单视图 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>
该命令将发出 Messenger.NotifyColleagues,它将发送我们的消息。消息将附加 IViewModel 或字符串。这是一个常规命令结构,也可以用于禁用命令,如下所示。
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 将指向消息接收时要执行的方法。如果它包含 IViewModel 对象,它将 CurrentPageViewModel 更改为它。这将导致我们上面解释的窗口重新调整大小。
MainWindow ViewModel 注册消息
' 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
如果消息是从 mainMenu2 发送的,它将包含要切换的视图模型名称字符串。我们需要使用一些反射来从名称创建视图模型对象的实例,然后再将其分配给 CurrentPageViewModel。
创建 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
创建资源字典
要为每个 usercontrol 视图模型显示视图,我们必须创建一个数据模板,该模板将正确的视图与每个视图模型关联起来。对于每个 usercontrol 视图模型,您创建一个数据模板,将视图模型名称作为 DataType,将视图名称作为其内容。我将所有这些数据模板放在一个名为 MainResources.XAML 的中央 ResourceDictionary 模块中。
MainResources Xaml 文件
<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>
将资源字典添加到我们的主窗口
在 Main Window Xaml 中,我们引用了我们的资源字典,以便在我们切换视图模型时,它可以找到要显示的关联视图。
主窗口资源和 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" />
现在,当我们从主菜单视图中选择一个视图模型显示时,会发送一条消息,主窗口会拦截该消息并更改绑定到内容控件的 currentpageviewmodel。视图模型被更改,窗口被调整大小和重新定位。
运行应用程序
“嘿,代码,让我们去‘运行’一下。”
主窗口只是一个用于包含构成应用程序视图的各种 usercontrol 内容的外壳。主窗口是一个具有三行的网格。一行用于顶部的菜单项,一行用于底部的“返回主菜单”按钮(在菜单视图中隐藏),中间是 contentcontrol。MainMenu usercontrol 显示 4 个按钮(篮球),单击它们将更改视图模型。
为了演示各种尺寸选项,示例应用程序具有以下 UserControls 视图。Usercontrol 1 和 2 包含 100 个项目,如果设置为自动,视图将占据整个屏幕。
- UserControl #1 有一个包含 100 个框的网格。其高度和宽度已设置。
- UserControl #2 有一个包含 100 个项目的列表框和一个包含 100 个项目的水平堆栈面板。其高度已设置,宽度将自动调整。
- UserControl #3 有一个包含 5 个按钮的项控件。它不设置高度或宽度 - 两者都将自动调整。
主菜单
主菜单视图具有以下 IViewModel 属性设置:
- UserControlHeight = 450
- UserControlWidth = 350
- DisplayName = "主菜单"
带有主菜单视图的主窗口
UserControl #1
UserControl #1 视图具有以下 IViewModel 属性设置:
- UserControlHeight = 725
- UserControlWidth = 1000
- DisplayName = "User Control #1"
带有 UserControl #1 视图的主窗口
UserControl #2
UserControl #2 视图具有以下 IViewModel 属性设置:
- UserControlHeight = 650
- UserControlWidth = 未设置 - 将自动调整
- DisplayName = "User Control #2"
带有 UserControl #2 视图的主窗口
UserControl #3
UserControl #3 视图具有以下 IViewModel 属性设置:
- UserControlHeight = 未设置 - 将自动调整
- UserControlWidth = 未设置 - 将自动调整
- DisplayName = "User Control #3"
此视图已从主菜单禁用,通过 ICommand CanChangeCurrentViewModel,并且可以从 Main Menu 2 访问。
带有 UserControl #3 视图的主窗口
注意: 添加了旋转篮球按钮作为额外奖励。
*卡通图片来自 Xamalot.com 免费剪贴画。