为了账号安全,请及时绑定邮箱和手机立即绑定

MVVM 视图之间切换

MVVM 视图之间切换

PHP
BIG阳 2024-01-20 15:55:21
我是 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.csusing System;using System.Windows;using MusicPlayer.ViewModels;namespace MusicPlayer {    public partial class MainWindow : Window {        public MainWindow() {            InitializeComponent();        }
查看完整描述

1 回答

?
九州编程

TA贡献1785条经验 获得超4个赞

在我看来,你目前的尝试是在正确的轨道上。您发布的代码的主要问题是CreateAccountView.Button_Click()处理程序无权访问DataContext它应该设置的属性:

private void Button_Click(object sender, RoutedEventArgs e) {
    DataContext = new MainWindowViewModel();
}

DataContext属性属于CreateAccountView用户控件。但是,这不是所显示内容的控制上下文。因此更改该属性的值DataContext不会产生任何有用的效果。(事实上,用户控件DataContext根本不应该设置自己的属性,因为这样做会丢弃使用该用户控件的客户端代码设置的任何上下文。)

没有足够的上下文来确切地知道执行此操作的最佳方法是什么。我认为在 Stack Overflow 上不可能提供足够的上下文。整体架构将取决于程序的太多小细节。但是,我认为解决这个问题的一种很好的方法是:

  • 创建一个“主”视图模型来管理应用程序的整体行为

  • 创建与 UI 的不同状态相关的单独视图模型

  • 让主视图模型配置各个视图模型,根据用户输入(例如单击按钮)适当切换当前视图模型

将其翻译成代码,看起来像这样......

首先,视图模型:

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 切换所需的实现细节。在您的程序中,每个视图状态还包含您需要的特定于每个视图状态的内容。


在我的小示例中,“主页”视图包含几个按钮,用于选择可用的各个子视图:


<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>

子视图仅包含返回主视图所需的按钮:


<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 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>

重要的是,您将看到所有视图对象都不包含任何隐藏代码。当您以这种方式处理问题时,没有必要这样做,至少不是为了控制代码中的基本行为。(您可能仍然会遇到视图对象的代码隐藏,但这通常只是为了实现该视图对象特有的特定用户界面行为,而不是为了处理视图模型状态。)


使用这种方法,您可以让 WPF 完成尽可能多的繁重工作。它还将所有视图模型对象相互解耦。有一个清晰的层次结构:只有顶级“主”视图模型才知道其他视图模型。这允许子视图模型(“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();

}


查看完整回答
反对 回复 2024-01-20
  • 1 回答
  • 0 关注
  • 100 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信