繁体   English   中英

MVVM 在视图之间切换

[英]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 上无法提供足够的上下文。 整体架构将取决于有关您的程序的太多小细节。 但是,我认为一种很好的解决方法是:

  • 创建管理应用程序整体行为的“主”视图 model
  • 创建与 UI 的不同状态相关的单个视图模型
  • 让主视图 model 配置各个视图模型以根据用户输入(例如单击按钮)切换当前视图 model

把它翻译成代码,看起来像这样……

首先,视图模型:

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM