[英]MVVM Switching Between Views
我是 WPF 的新手,所以请耐心等待。 我有一个 WinForms 应用程序,我试图在 WPF 中重做。 在我当前的 WinForms 应用程序中,我将所有控件粘贴到一个表单中,并根据点击的按钮隐藏/显示它们,以及使用第二个表单。
我的目标:创建不同的视图以根据按下的按钮平滑切换,而不是隐藏控件或制作单独的 Forms 然后隐藏它们。
我目前有一个 MainWindow 视图(我的初始启动窗口),通过一个按钮,我切换到我的 CreateAccount 视图。 我遇到的问题是,如何将 CreateAccount go 中的按钮“返回”到主窗口?
我的最终目标是能够根据按钮点击在 4 个视图之间切换。
这是我的 MainWindow.xaml
<Window x:Class="MusicPlayer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MusicPlayer"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<DataTemplate x:Name="CreateAccountTemplate" DataType="{x:Type ViewModels:CreateAccountViewModel}">
<Views:CreateAccountView DataContext="{Binding}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<Button x:Name="TestButton" Content="Button" HorizontalAlignment="Left" Margin="164,182,0,0" VerticalAlignment="Top" Height="61" Width="68" Click="CreateAccountView_Clicked"/>
<PasswordBox HorizontalAlignment="Left" Margin="164,284,0,0" VerticalAlignment="Top" Width="120"/>
<ContentPresenter Content="{Binding}"/>
</Grid>
</Window>
我的 MainWindow.xaml.cs
using System;
using System.Windows;
using MusicPlayer.ViewModels;
namespace MusicPlayer {
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
protected override void OnClosed(EventArgs e) {
base.OnClosed(e);
Application.Current.Shutdown();
} //end of onClosed
private void CreateAccountView_Clicked(object sender, RoutedEventArgs e) {
DataContext = new CreateAccountViewModel();
} //end of CreateAccountView_Clicked
}
}
这是我的 CreateAccount.xaml
<UserControl x:Class="MusicPlayer.Views.CreateAccountView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:Views="clr-namespace:MusicPlayer.Views"
xmlns:ViewModels="clr-namespace:MusicPlayer.ViewModels"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
</UserControl.Resources>
<Grid Background="White">
<Button Content="Button" HorizontalAlignment="Left" Margin="276,279,0,0" VerticalAlignment="Top" Height="60" Width="59" Click="Button_Click"/>
</Grid>
</UserControl>
还有我的 CreateAccountView.xaml.cs
using System.Windows;
using System.Windows.Controls;
using MusicPlayer.ViewModels;
namespace MusicPlayer.Views {
public partial class CreateAccountView : UserControl {
//public static readonly DependencyProperty TestMeDependency = DependencyProperty.Register("MyProperty", typeof(string), typeof(CreateAccountView));
public CreateAccountView() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
}
}
在我看来,你目前的尝试是在正确的轨道上。 您发布的代码的主要问题是CreateAccountView.Button_Click()
处理程序无权访问它应该设置的DataContext
属性:
private void Button_Click(object sender, RoutedEventArgs e) {
DataContext = new MainWindowViewModel();
}
此DataContext
属性属于CreateAccountView
用户控件。 但是,这不是显示内容的控制上下文。 因此,更改该DataContext
属性的值没有任何有用的效果。 (实际上,用户控件根本不应该设置它自己的DataContext
属性,因为这样做会丢弃使用该用户控件的客户端代码设置的任何上下文。)
没有足够的上下文来确切地知道您执行此操作的最佳方法是什么。 我认为在 Stack Overflow 上无法提供足够的上下文。 整体架构将取决于有关您的程序的太多小细节。 但是,我认为一种很好的解决方法是:
把它翻译成代码,看起来像这样……
首先,视图模型:
class MainViewModel : NotifyPropertyChangedBase
{
private object _currentViewModel;
public object CurrentViewModel
{
get => _currentViewModel;
set => _UpdateField(ref _currentViewModel, value);
}
private readonly HomeViewModel _homeViewModel;
private readonly Sub1ViewModel _sub1ViewModel;
private readonly Sub2ViewModel _sub2ViewModel;
public MainViewModel()
{
_sub1ViewModel = new Sub1ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_sub2ViewModel = new Sub2ViewModel
{
BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)
};
_homeViewModel = new HomeViewModel
{
ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),
ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)
};
CurrentViewModel = _homeViewModel;
}
}
class HomeViewModel : NotifyPropertyChangedBase
{
private ICommand _showSub1Command;
public ICommand ShowSub1Command
{
get => _showSub1Command;
set => _UpdateField(ref _showSub1Command, value);
}
private ICommand _showSub2Command;
public ICommand ShowSub2Command
{
get => _showSub2Command;
set => _UpdateField(ref _showSub2Command, value);
}
}
class Sub1ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
class Sub2ViewModel : NotifyPropertyChangedBase
{
private ICommand _backCommand;
public ICommand BackCommand
{
get => _backCommand;
set => _UpdateField(ref _backCommand, value);
}
}
当然,这些视图模型只包含处理 UI 切换所需的实现细节。 在您的程序中,每个还包括特定于您需要的每个视图 state 的内容。
在我的小示例中,“主页”视图包含几个按钮,用于 select 可用的各个子视图:
<UserControl x:Class="WpfApp1.HomeView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Home: "/>
<Button Content="Sub1" Command="{Binding ShowSub1Command}"/>
<Button Content="Sub2" Command="{Binding ShowSub2Command}"/>
</StackPanel>
</UserControl>
子视图只包含 go 返回主视图所需的按钮:
<UserControl x:Class="WpfApp1.Sub1View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub1 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
<UserControl x:Class="WpfApp1.Sub2View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Sub2 View: "/>
<Button Content="Back" Command="{Binding BackCommand}"/>
</StackPanel>
</UserControl>
最后,主 window 设置主视图 model,并声明要用于每个特定子视图的模板:
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:l="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.DataContext>
<l:MainViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType="{x:Type l:HomeViewModel}">
<l:HomeView/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub1ViewModel}">
<l:Sub1View/>
</DataTemplate>
<DataTemplate DataType="{x:Type l:Sub2ViewModel}">
<l:Sub2View/>
</DataTemplate>
</Window.Resources>
<StackPanel>
<ContentControl Content="{Binding CurrentViewModel}"/>
</StackPanel>
</Window>
重要的是,您会看到没有任何视图对象包含任何代码隐藏。 当您以这种方式处理问题时,没有必要这样做,至少不是为了控制代码中的基本行为。 (您可能仍会使用视图对象的代码隐藏,但这通常仅用于实现该视图 object 独有的特定用户界面行为,而不是用于处理视图 model Z9ED39E2EA9314286B6A985A673E)
使用这种方法,您可以让 WPF 尽可能多地完成繁重的工作。 它还将所有视图 model 对象彼此分离。 有一个清晰的层次结构:只有顶级“主”视图 model 甚至知道其他视图模型。 这允许在其他场景中根据需要重用子视图模型(“home”、“sub1”和“sub2”),而无需在其中进行任何修改或特殊情况处理。
以下是我在上面使用的辅助类:
class NotifyPropertyChangedBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void _UpdateField<T>(ref T field, T newValue,
Action<T> onChangedCallback = null,
[CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, newValue))
{
return;
}
T oldValue = field;
field = newValue;
onChangedCallback?.Invoke(oldValue);
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
class DelegateCommand : ICommand
{
private readonly Action _execute;
public DelegateCommand(Action execute)
{
_execute = execute;
}
#pragma warning disable 67
public event EventHandler CanExecuteChanged;
#pragma warning restore
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _execute();
}
usually... you would have one main window and sub windows or user controls... let's suppose your MainWindow.xaml is the main window and you have a UserControl1 and UserControl2... and you want to switch between them... here您 go:您可以将 ContentPresenter 上的“Content”属性绑定到 MainWindow.xaml.cs 中的属性,并且该属性将在单击处理程序中通过您的按钮(或列表...)进行修改假设您将该属性命名为“SelectedUserControl”这是您的 MainWindow.xaml.cs:
public partial class MainWindow : Window,INotifyPropertyChanged
{
private UserControl1 myUserControl1= new UserControl1(){DataContext= new VM1()};
private UserControl2 myUserControl2= new UserControl2(){DataContext= new VM2()};
private UserControl _selectedUserControl;
public UserControl SelectedUserControl
{
get => _selectedUserControl;
set
{
_selectedUserControl = value;
OnPropertyChanged();
}
}
private void Button1_OnClick(object sender, RoutedEventArgs e)
{
SelectedUserControl = myUserControl1;
}
private void Button2_OnClick(object sender, RoutedEventArgs e)
{
SelectedUserControl = myUserControl2;
}
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
并且为您 MainWindow.xaml:您可以将其修改为:
<ContentPresenter Content="{Binding SelectedUserControl,RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=Window}}"></ContentPresenter>
<Button Click="Button1_OnClick" >change to usercontrol 1</Button>
<Button Click="Button2_OnClick" >change to usercontrol 2</Button>
选择更改时,您不能更新您的视图模型......您的视图模型必须保留那里的值,您只需切换 userControls
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.