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
相关产品推荐
相关产品推荐

