MVVM 视图之间切换

我是 WPF 新手,请耐心等待。我有一个 WinForms 应用程序,我正在尝试在 WPF 中重做。在我当前的 WinForms 应用程序中,我将所有控件粘贴到一个窗体中,并根据点击的按钮隐藏/显示它们,以及使用第二个窗体。

我的目标:创建不同的视图,以便根据按下的按钮在不同的视图之间平滑切换,而不是隐藏控件或制作单独的表单,然后隐藏它们。

我当前有一个 MainWindow 视图(我的初始启动窗口),通过一个按钮,我可以切换到 CreateAccount 视图。我遇到的问题是,如何使 CreateAccount 中的按钮“返回”主窗口?

我的最终目标是能够根据按钮点击在 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();

        }

BIG阳
浏览 101回答 1
1回答

九州编程

在我看来,你目前的尝试是在正确的轨道上。您发布的代码的主要问题是CreateAccountView.Button_Click()处理程序无权访问DataContext它应该设置的属性:private&nbsp;void&nbsp;Button_Click(object&nbsp;sender,&nbsp;RoutedEventArgs&nbsp;e)&nbsp;{ &nbsp;&nbsp;&nbsp;&nbsp;DataContext&nbsp;=&nbsp;new&nbsp;MainWindowViewModel(); }该DataContext属性属于CreateAccountView用户控件。但是,这不是所显示内容的控制上下文。因此更改该属性的值DataContext不会产生任何有用的效果。(事实上,用户控件DataContext根本不应该设置自己的属性,因为这样做会丢弃使用该用户控件的客户端代码设置的任何上下文。)没有足够的上下文来确切地知道执行此操作的最佳方法是什么。我认为在 Stack Overflow 上不可能提供足够的上下文。整体架构将取决于程序的太多小细节。但是,我认为解决这个问题的一种很好的方法是:创建一个“主”视图模型来管理应用程序的整体行为创建与 UI 的不同状态相关的单独视图模型让主视图模型配置各个视图模型,根据用户输入(例如单击按钮)适当切换当前视图模型将其翻译成代码,看起来像这样......首先,视图模型:class MainViewModel : NotifyPropertyChangedBase{&nbsp; &nbsp; private object _currentViewModel;&nbsp; &nbsp; public object CurrentViewModel&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => _currentViewModel;&nbsp; &nbsp; &nbsp; &nbsp; set => _UpdateField(ref _currentViewModel, value);&nbsp; &nbsp; }&nbsp; &nbsp; private readonly HomeViewModel _homeViewModel;&nbsp; &nbsp; private readonly Sub1ViewModel _sub1ViewModel;&nbsp; &nbsp; private readonly Sub2ViewModel _sub2ViewModel;&nbsp; &nbsp; public MainViewModel()&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; _sub1ViewModel = new Sub1ViewModel&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; _sub2ViewModel = new Sub2ViewModel&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; BackCommand = new DelegateCommand(() => CurrentViewModel = _homeViewModel)&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; _homeViewModel = new HomeViewModel&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ShowSub1Command = new DelegateCommand(() => CurrentViewModel = _sub1ViewModel),&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ShowSub2Command = new DelegateCommand(() => CurrentViewModel = _sub2ViewModel)&nbsp; &nbsp; &nbsp; &nbsp; };&nbsp; &nbsp; &nbsp; &nbsp; CurrentViewModel = _homeViewModel;&nbsp; &nbsp; }}class HomeViewModel : NotifyPropertyChangedBase{&nbsp; &nbsp; private ICommand _showSub1Command;&nbsp; &nbsp; public ICommand ShowSub1Command&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => _showSub1Command;&nbsp; &nbsp; &nbsp; &nbsp; set => _UpdateField(ref _showSub1Command, value);&nbsp; &nbsp; }&nbsp; &nbsp; private ICommand _showSub2Command;&nbsp; &nbsp; public ICommand ShowSub2Command&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => _showSub2Command;&nbsp; &nbsp; &nbsp; &nbsp; set => _UpdateField(ref _showSub2Command, value);&nbsp; &nbsp; }}class Sub1ViewModel : NotifyPropertyChangedBase{&nbsp; &nbsp; private ICommand _backCommand;&nbsp; &nbsp; public ICommand BackCommand&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => _backCommand;&nbsp; &nbsp; &nbsp; &nbsp; set => _UpdateField(ref _backCommand, value);&nbsp; &nbsp; }}class Sub2ViewModel : NotifyPropertyChangedBase{&nbsp; &nbsp; private ICommand _backCommand;&nbsp; &nbsp; public ICommand BackCommand&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; get => _backCommand;&nbsp; &nbsp; &nbsp; &nbsp; set => _UpdateField(ref _backCommand, value);&nbsp; &nbsp; }}当然,这些视图模型只包含处理 UI 切换所需的实现细节。在您的程序中,每个视图状态还包含您需要的特定于每个视图状态的内容。在我的小示例中,“主页”视图包含几个按钮,用于选择可用的各个子视图:<UserControl x:Class="WpfApp1.HomeView"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:d="http://schemas.microsoft.com/expression/blend/2008"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mc:Ignorable="d"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;d:DesignHeight="450" d:DesignWidth="800">&nbsp; <StackPanel Orientation="Horizontal">&nbsp; &nbsp; <TextBlock Text="Home: "/>&nbsp; &nbsp; <Button Content="Sub1" Command="{Binding ShowSub1Command}"/>&nbsp; &nbsp; <Button Content="Sub2" Command="{Binding ShowSub2Command}"/>&nbsp; </StackPanel></UserControl>子视图仅包含返回主视图所需的按钮:<UserControl x:Class="WpfApp1.Sub1View"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:d="http://schemas.microsoft.com/expression/blend/2008"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mc:Ignorable="d"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;d:DesignHeight="450" d:DesignWidth="800">&nbsp; <StackPanel Orientation="Horizontal">&nbsp; &nbsp; <TextBlock Text="Sub1 View: "/>&nbsp; &nbsp; <Button Content="Back" Command="{Binding BackCommand}"/>&nbsp; </StackPanel></UserControl><UserControl x:Class="WpfApp1.Sub2View"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;xmlns:d="http://schemas.microsoft.com/expression/blend/2008"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;mc:Ignorable="d"&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;d:DesignHeight="450" d:DesignWidth="800">&nbsp; <StackPanel Orientation="Horizontal">&nbsp; &nbsp; <TextBlock Text="Sub2 View: "/>&nbsp; &nbsp; <Button Content="Back" Command="{Binding BackCommand}"/>&nbsp; </StackPanel></UserControl>最后,主窗口设置主视图模型,并声明用于每个特定子视图的模板:<Window x:Class="WpfApp1.MainWindow"&nbsp; &nbsp; &nbsp; &nbsp; xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"&nbsp; &nbsp; &nbsp; &nbsp; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&nbsp; &nbsp; &nbsp; &nbsp; xmlns:d="http://schemas.microsoft.com/expression/blend/2008"&nbsp; &nbsp; &nbsp; &nbsp; xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"&nbsp; &nbsp; &nbsp; &nbsp; xmlns:l="clr-namespace:WpfApp1"&nbsp; &nbsp; &nbsp; &nbsp; mc:Ignorable="d"&nbsp; &nbsp; &nbsp; &nbsp; Title="MainWindow" Height="450" Width="800">&nbsp; <Window.DataContext>&nbsp; &nbsp; <l:MainViewModel/>&nbsp; </Window.DataContext>&nbsp; <Window.Resources>&nbsp; &nbsp; <DataTemplate DataType="{x:Type l:HomeViewModel}">&nbsp; &nbsp; &nbsp; <l:HomeView/>&nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; <DataTemplate DataType="{x:Type l:Sub1ViewModel}">&nbsp; &nbsp; &nbsp; <l:Sub1View/>&nbsp; &nbsp; </DataTemplate>&nbsp; &nbsp; <DataTemplate DataType="{x:Type l:Sub2ViewModel}">&nbsp; &nbsp; &nbsp; <l:Sub2View/>&nbsp; &nbsp; </DataTemplate>&nbsp; </Window.Resources>&nbsp; <StackPanel>&nbsp; &nbsp; <ContentControl Content="{Binding CurrentViewModel}"/>&nbsp; </StackPanel></Window>重要的是,您将看到所有视图对象都不包含任何隐藏代码。当您以这种方式处理问题时,没有必要这样做,至少不是为了控制代码中的基本行为。(您可能仍然会遇到视图对象的代码隐藏,但这通常只是为了实现该视图对象特有的特定用户界面行为,而不是为了处理视图模型状态。)使用这种方法,您可以让 WPF 完成尽可能多的繁重工作。它还将所有视图模型对象相互解耦。有一个清晰的层次结构:只有顶级“主”视图模型才知道其他视图模型。这允许子视图模型(“home”、“sub1”和“sub2”)根据需要在其他场景中重用,而无需在其中进行任何修改或特殊情况处理。这是我上面使用的辅助类:class NotifyPropertyChangedBase : INotifyPropertyChanged{&nbsp; &nbsp; public event PropertyChangedEventHandler PropertyChanged;&nbsp; &nbsp; protected void _UpdateField<T>(ref T field, T newValue,&nbsp; &nbsp; &nbsp; &nbsp; Action<T> onChangedCallback = null,&nbsp; &nbsp; &nbsp; &nbsp; [CallerMemberName] string propertyName = null)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; if (EqualityComparer<T>.Default.Equals(field, newValue))&nbsp; &nbsp; &nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return;&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; T oldValue = field;&nbsp; &nbsp; &nbsp; &nbsp; field = newValue;&nbsp; &nbsp; &nbsp; &nbsp; onChangedCallback?.Invoke(oldValue);&nbsp; &nbsp; &nbsp; &nbsp; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));&nbsp; &nbsp; }}class DelegateCommand : ICommand{&nbsp; &nbsp; private readonly Action _execute;&nbsp; &nbsp; public DelegateCommand(Action execute)&nbsp; &nbsp; {&nbsp; &nbsp; &nbsp; &nbsp; _execute = execute;&nbsp; &nbsp; }#pragma warning disable 67&nbsp; &nbsp; public event EventHandler CanExecuteChanged;#pragma warning restore&nbsp; &nbsp; public bool CanExecute(object parameter) => true;&nbsp; &nbsp; public void Execute(object parameter) => _execute();}
打开App,查看更多内容
随时随地看视频慕课网APP