You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WPF MVVM架构下如何实现视图间的切换?

实现WPF MVVM架构下的登录视图切换

我开发了一个采用MVVM架构的WPF在线测验应用,刚接触C# WPF,对MVVM概念不太熟悉。目前想实现点击登录按钮,验证输入内容通过后,从登录视图切换到科目选择视图。

界面说明

  • 登录视图:包含用户ID、用户名输入框和登录按钮,输入为空时显示错误提示
  • 目标视图(科目选择):展示可选的测验科目列表
  • 文件结构:项目包含Core、MVVM(下分View、ViewModel)等文件夹,MainWindow作为主视图容器

现有代码片段

MainWindow.xaml

<Window x:Class="QuizAppV2.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:QuizAppV2"
        xmlns:viewModel="clr-namespace:QuizAppV2.MVVM.ViewModel"
        mc:Ignorable="d"
        Height="600" Width="920"
        WindowStartupLocation="CenterScreen"
        WindowStyle="None"
        ResizeMode="NoResize"
        Background="Transparent"
        AllowsTransparency="True">
    <Window.DataContext>
        <viewModel:MainViewModel/>
    </Window.DataContext>
    <Border Background="#272537"
            CornerRadius="20">
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="75"/>
                <RowDefinition/>
                <RowDefinition Height="25"/>
            </Grid.RowDefinitions>

            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                    <ColumnDefinition/>
                </Grid.ColumnDefinitions>

                <TextBlock Text="Online Quiz"
                            Grid.Column="1"
                            FontSize="20"
                            Foreground="White"
                            HorizontalAlignment="Center"
                            VerticalAlignment="Center"/>
                <StackPanel Grid.Column="2"
                            Margin="30,20"
                            Orientation="Horizontal"
                            HorizontalAlignment="Right"
                            VerticalAlignment="Top">

                    <Button Content="–"
                            Background="#00CA4E"
                            Style="{StaticResource UserControls}"
                            Click="Minimise"/>
                    <Button Content="▢"
                            Background="#FFBD44"
                            Style="{StaticResource UserControls}"
                            Click="Restore"/>
                    <Button Content="X"
                            Background="#FF605C"
                            Style="{StaticResource UserControls}"
                            Click="Exit"/>
                </StackPanel>
            </Grid>
            <ContentControl Grid.Column="1"
                            Grid.Row="1"
                            Margin="20,10,20,50"
                            Content="{Binding CurrentView}"/>
        </Grid>
    </Border>
</Window>

MainViewModel.cs

using QuizAppV2.Core;
using System;

namespace QuizAppV2.MVVM.ViewModel
{
    class MainViewModel : ObservableObject
    {
        public RelayCommand LoginViewCommand { get; set; }
        public RelayCommand SubjectSelectionViewCommand { get; set; }
        public RelayCommand QuizViewCommand { get; set; }
        public RelayCommand ResultViewCommand { get; set; }

        public LoginViewModel LoginVM { get; set; }
        public SubjectSelectionViewModel SubjectSelectVM { get; set; }
        public QuizViewModel QuizVM { get; set; }
        public ResultViewModel ResultVM { get; set; }

        private object _currentView;
        public object CurrentView
        {
            get { return _currentView; }
            set
            {
                _currentView = value;
                onPropertyChanged();
            }
        }

        public MainViewModel()
        {
            LoginVM = new LoginViewModel();
            SubjectSelectVM = new SubjectSelectionViewModel();
            QuizVM = new QuizViewModel();
            ResultVM = new ResultViewModel();
            CurrentView = SubjectSelectVM;

            LoginViewCommand = new RelayCommand(o =>
            {
                CurrentView = LoginVM;
            });
            SubjectSelectionViewCommand = new RelayCommand(o =>
            {
                CurrentView = SubjectSelectVM;
            });
        }
    }
}

LoginView.xaml.cs

using QuizAppV2.MVVM.ViewModel;
using System.Windows;
using System.Windows.Controls;

namespace QuizAppV2.MVVM.View
{
    public partial class LoginView : UserControl
    {
        public LoginView()
        {
            InitializeComponent();
        }

        private void Button_Click(object sender, RoutedEventArgs e)
        {
            if (UsrId.Text == "" || UsrName.Text == "")
            {
                UsrIDErrMsg.Visibility = Visibility.Visible;
                UsrNameErrMsg.Visibility = Visibility.Visible;
            }
            else
            {
                UsrIDErrMsg.Visibility = Visibility.Hidden;
                UsrNameErrMsg.Visibility = Visibility.Hidden;
                MainWindow.currentUser = new Student(UsrId.Text, UsrName.Text);
            }
        }
    }
}

解决方案:符合MVVM的视图切换实现

1. 重构LoginViewModel,添加验证逻辑与视图切换事件

将登录验证逻辑从视图代码隐藏移到ViewModel,通过事件通知主视图切换:

using QuizAppV2.Core;
using System.Windows.Input;
using System.Windows;

namespace QuizAppV2.MVVM.ViewModel
{
    public class LoginViewModel : ObservableObject
    {
        // 绑定输入框的属性
        private string _usrId;
        public string UsrId
        {
            get => _usrId;
            set
            {
                _usrId = value;
                OnPropertyChanged();
            }
        }

        private string _usrName;
        public string UsrName
        {
            get => _usrName;
            set
            {
                _usrName = value;
                OnPropertyChanged();
            }
        }

        // 错误消息可见性绑定
        private Visibility _usrIdErrMsgVisibility = Visibility.Hidden;
        public Visibility UsrIdErrMsgVisibility
        {
            get => _usrIdErrMsgVisibility;
            set
            {
                _usrIdErrMsgVisibility = value;
                OnPropertyChanged();
            }
        }

        private Visibility _usrNameErrMsgVisibility = Visibility.Hidden;
        public Visibility UsrNameErrMsgVisibility
        {
            get => _usrNameErrMsgVisibility;
            set
            {
                _usrNameErrMsgVisibility = value;
                OnPropertyChanged();
            }
        }

        // 登录命令
        public ICommand LoginCommand { get; }

        // 登录成功事件,供MainViewModel订阅
        public event Action LoginSuccess;

        public LoginViewModel()
        {
            LoginCommand = new RelayCommand(ExecuteLogin);
        }

        private void ExecuteLogin(object parameter)
        {
            bool isValid = true;

            // 输入验证
            if (string.IsNullOrEmpty(UsrId))
            {
                UsrIdErrMsgVisibility = Visibility.Visible;
                isValid = false;
            }
            else
            {
                UsrIdErrMsgVisibility = Visibility.Hidden;
            }

            if (string.IsNullOrEmpty(UsrName))
            {
                UsrNameErrMsgVisibility = Visibility.Visible;
                isValid = false;
            }
            else
            {
                UsrNameErrMsgVisibility = Visibility.Hidden;
            }

            if (isValid)
            {
                MainWindow.currentUser = new Student(UsrId, UsrName);
                // 触发登录成功事件
                LoginSuccess?.Invoke();
            }
        }
    }
}

2. 修改MainViewModel,订阅登录成功事件

在MainViewModel中订阅LoginViewModel的事件,触发视图切换:

using QuizAppV2.Core;

namespace QuizAppV2.MVVM.ViewModel
{
    public class MainViewModel : ObservableObject
    {
        public RelayCommand LoginViewCommand { get; set; }
        public RelayCommand SubjectSelectionViewCommand { get; set; }
        public RelayCommand QuizViewCommand { get; set; }
        public RelayCommand ResultViewCommand { get; set; }

        public LoginViewModel LoginVM { get; set; }
        public SubjectSelectionViewModel SubjectSelectVM { get; set; }
        public QuizViewModel QuizVM { get; set; }
        public ResultViewModel ResultVM { get; set; }

        private object _currentView;
        public object CurrentView
        {
            get => _currentView;
            set
            {
                _currentView = value;
                OnPropertyChanged();
            }
        }

        public MainViewModel()
        {
            LoginVM = new LoginViewModel();
            SubjectSelectVM = new SubjectSelectionViewModel();
            QuizVM = new QuizViewModel();
            ResultVM = new ResultViewModel();

            // 初始视图设为登录视图(根据需求调整)
            CurrentView = LoginVM;

            // 订阅登录成功事件,触发视图切换
            LoginVM.LoginSuccess += OnLoginSuccess;

            LoginViewCommand = new RelayCommand(o => CurrentView = LoginVM);
            SubjectSelectionViewCommand = new RelayCommand(o => CurrentView = SubjectSelectVM);
        }

        private void OnLoginSuccess()
        {
            // 登录成功后切换到科目选择视图
            CurrentView = SubjectSelectVM;
        }
    }
}

3. 调整LoginView的XAML,实现数据绑定

将输入框、错误消息和按钮绑定到LoginViewModel的属性和命令:

<UserControl x:Class="QuizAppV2.MVVM.View.LoginView"
             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:viewModel="clr-namespace:QuizAppV2.MVVM.ViewModel"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.DataContext>
        <viewModel:LoginViewModel/>
    </UserControl.DataContext>
    <Grid>
        <!-- 用户ID输入区域 -->
        <TextBox Text="{Binding UsrId}" 
                 PlaceholderText="输入用户ID"/>
        <TextBlock Text="请输入用户ID" 
                   Visibility="{Binding UsrIdErrMsgVisibility}" 
                   Foreground="Red"/>

        <!-- 用户名输入区域 -->
        <TextBox Text="{Binding UsrName}" 
                 PlaceholderText="输入用户名"/>
        <TextBlock Text="请输入用户名" 
                   Visibility="{Binding UsrNameErrMsgVisibility}" 
                   Foreground="Red"/>

        <!-- 登录按钮 -->
        <Button Content="登录" 
                Command="{Binding LoginCommand}"/>
    </Grid>
</UserControl>

4. 简化LoginView的代码隐藏

删除原有的Button_Click事件,让视图只负责展示:

using System.Windows.Controls;

namespace QuizAppV2.MVVM.View
{
    public partial class LoginView : UserControl
    {
        public LoginView()
        {
            InitializeComponent();
        }
    }
}

核心要点说明

  • 遵循MVVM原则:视图仅负责UI展示,业务逻辑完全放在ViewModel中,避免代码隐藏的耦合
  • 命令绑定:通过RelayCommand实现按钮点击与ViewModel方法的关联
  • 视图间通信:用事件实现ViewModel之间的解耦通信,避免直接引用导致的紧耦合
  • 数据绑定:输入框、错误消息与ViewModel属性双向绑定,实现UI自动更新

内容的提问来源于stack exchange,提问作者DEXOW

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:41:07