MVVM简介
在WPF应用程序开发中,MVVM(Model-View-ViewModel)是一种非常流行的架构模式。它为应用程序的设计提供了良好的分层结构和可扩展性。
结构分为下列三部分
-
Model:定义了应用程序的数据模型 就是系统中的对象,可包含属性和行为(是一个class实体,是对现实中事物的抽象,开发过程中涉及到的事物都可以抽象为Model,例如用户的账号、密码、电话等),负责从数据源中获取数据并将其提供给ViewModel。
-
ViewModel:封装了应用程序的业务逻辑,通过View类的DataContext属性绑定到View,负责将数据从Model传递到View,并将用户交互事件传递回Model。显示数据对应ViewMode中的Property,执行命令对应ViewModel中的Command。
-
View:用xaml实现的界面,接收用户输入,把数据展现给用户,并与ViewModel交互以便进行数据绑定和命令绑定。
在MVVM模式中,ViewModel的主要职责是将数据从Model传递到View,并响应View的用户交互事件。ViewModel通过命令绑定和数据绑定与View进行交互。
MVVM优点
- 低耦合:视图(View)可以独立于Model变化和修改,一个ViewModel可以绑定到不同的"View"上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
- 灵活扩展:可以把一些视图逻辑放在一个ViewModel里面,让很多view重用这段视图逻辑;
- 易测试:可以针对ViewModel来写测试用例
- 独立开发:开发人员可以专注于业务逻辑和数据的开发(ViewModel),设计人员可以专注于xaml页面设计。
MVVM示例
以下是一个简单的WPF MVVM登录示例:
底层通用实体
ViewModelBase
添加一个viewmodelbase方便后续数据双向绑定更新
cs
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace YourProjectName.Comm
{
public class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void PC(string propertyName)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public void PCEH([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
CommandBase
添加一个继承ICommand实体,方便后续绑定command
cs
using System;
using System.Windows.Input;
namespace YourProjectName.Comm
{
public class CommandBase : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public CommandBase(Action<object> execute)
: this(execute, null)
{
}
public CommandBase(Action<object> execute, Func<object, bool> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute ?? ((Func<object, bool>)((object x) => true));
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void Refresh()
{
CommandManager.InvalidateRequerySuggested();
}
}
}
ViewModel及Model
在ViewModel文件夹下,创建一个名为LoginViewModel的类。 因为只有两个字段测试所以未新建Model,实际开发中注意新建
cs
using System.Windows.Input;
using YourProjectName.Comm;
namespace YourProjectName.ViewModel
{
public class LoginViewModel : ViewModelBase
{
private string _username;
private string _password;
private bool _rememberMe;
public string Username
{
get { return _username; }
set { _username= value; PCEH();}
}
public string Password
{
get { return _password; }
set { _password= value; PCEH();}
}
public bool RememberMe
{
get { return _rememberMe; }
set { _rememberMe= value; PCEH();}
}
public ICommand LoginCommand;
private void Login()
{
LoginCommand= new CommandBase(async l =>
{
// TODO: Add login logic.
});
}
}
}
View
在View文件夹下,创建一个名为LoginView的XAML和CS文件。
cs
<Window x:Class="YourProjectName.View.LoginView"
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:YourProjectName.View"
xmlns:vm="clr-namespace:YourProjectName.ViewModel"
mc:Ignorable="d"
Title="LoginView" Height="300" Width="400">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Label Grid.Row="0" Grid.Column="0" Content="Username:" />
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding Username}" />
<Label Grid.Row="1" Grid.Column="0" Content="Password:" />
<PasswordBox Grid.Row="1" Grid.Column="1" Password="{Binding Password, UpdateSourceTrigger=PropertyChanged}" />
<CheckBox Grid.Row="2" Grid.Column="1" Content="Remember me" IsChecked="{Binding RememberMe}" />
<Button Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" Content="Login" Command="{Binding LoginCommand}" />
<TextBlock Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Text="Don't have an account? Sign up." />
</Grid>
</Window>
初始化DataContext ,也可在页面中初始化
cs
using System.Windows;
namespace YourProjectName.View
{
public partial class LoginView : Window
{
private readonly LoginViewModel model;
public LoginView()
{
InitializeComponent();
DataContext = model = new();
}
}
}
启动应用程序并测试登录页面。在"TODO"注释的位置添加实际的登录代码。
全局异常处理
在WPF应用程序中,全局异常处理非常重要。全局异常处理可以帮助我们捕获应用程序中的所有未处理异常,防止程序异常崩溃并提供更好的用户体验。在WPF中,可以通过在应用程序的App.xaml.cs文件中添加以下代码来实现全局异常处理:
异常处理示例
cs
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
DispatcherUnhandledException += App_DispatcherUnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
//处理UI线程上的未处理异常
e.Handled = true;
MessageBox.Show("发生错误:" + e.Exception.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
//处理非UI线程上的未处理异常
Exception ex = (Exception)e.ExceptionObject;
MessageBox.Show("发生错误:" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
/// <summary>
/// Task线程内未捕获异常处理事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs ex)
{
MessageBox.Show("Task线程异常:" + ex.Message, "错误", MessageBoxButton.OK, MessageBoxImage.Error);
//设置该异常已察觉(这样处理后就不会引起程序崩溃)
e.SetObserved();
}
}
以上代码会在应用程序启动时为当前域的未处理异常事件和UI线程上的未处理异常事件及task线程异常注册处理程序。在发生异常时,会弹出一个消息框来通知用户并不会造成程序崩溃。
总结:
MVVM模式是WPF应用程序开发中非常重要的主题。通过理解和实战练习,我们可以更好地开发出强大和稳定的应用程序。
全局异常处理是程序中必不可少的一步,能保障程序在异常发生过程中正常运行。