最近在学WPF的MVVM,有两种方式实现,一种是自己实现,一种是借助MVVM框架,接下来通过一个医院自助打印报告机键盘输入界面来演示自己实现、框架CommunityToolkit和Prism的区别。
项目源码:https://gitee.com/cplmlm/SelfServiceReportPrinter
推荐学习博主:B站UP十月的寒流
一、自己实现
1、首先我们创建一个BaseNotifyPropertyChanged类,继承INotifyPropertyChanged,这个方法的作用是属性值变化时自动更新UI界面。
public class BaseNotifyPropertyChanged : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;
public void RaisePropertyChanged(string propertyName)
{
if (!string.IsNullOrEmpty(propertyName))
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
2、创建一个KeyPressViewModel类,继承BaseNotifyPropertyChanged。
public class KeyPressViewModel : BaseNotifyPropertyChanged
{
private string cardNumber;
private int selectionStart=0;
/// <summary>
/// 输入文本框的值
/// </summary>
public string CardNumber
{
get { return cardNumber; }
set
{
cardNumber = value;
RaisePropertyChanged(nameof(CardNumber));
}
}
/// <summary>
/// 输入框光标位置
/// </summary>
public int SelectionStart
{
get { return selectionStart; }
set { RaisePropertyChanged(nameof(SelectionStart)); }
}
/// <summary>
/// 数字按钮绑定事件
/// </summary>
public ICommand NumberCommand
{
get { return new RelayCommand<string>(Number); }
}
/// <summary>
/// 清空按钮绑定事件
/// </summary>
public ICommand ClearCommand
{
get { return new RelayCommand(Clear); }
}
/// <summary>
/// 删除按钮绑定事件
/// </summary>
public ICommand DeleteCommand
{
get { return new RelayCommand(Delete); }
}
/// <summary>
/// 数字点击事件
/// </summary>
/// <param name="key"></param>
private void Number(string? key)
{
CardNumber += key;
}
/// <summary>
/// 清空点击事件
/// </summary>
private void Clear()
{
CardNumber = string.Empty;
}
/// <summary>
/// 删除点击事件
/// </summary>
private void Delete()
{
// 光标在输入框时,删除光标前一个字符
if (!string.IsNullOrEmpty(CardNumber) && SelectionStart > 0)
{
CardNumber = CardNumber.Remove(SelectionStart - 1, 1);
}
//光标没有在输入框时,删除最后一个字符
if (SelectionStart == 0)
{
CardNumber = CardNumber.Remove(CardNumber.Length - 1, 1);
}
}
}
3、创建RelayCommand类,这个类的作用是绑定事件的操作,一个泛型版本,一个是非泛型。
public class RelayCommand : ICommand
{
private Action _execute;
private Func<bool> _canExecute;
public RelayCommand(Action execute) : this(execute, null)
{
}
public RelayCommand(Action execute, Func<bool> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged
{
add
{
if (_canExecute != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (_canExecute != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute();
}
public void Execute(object parameter)
{
_execute();
}
}
public class RelayCommand<T> : ICommand
{
private readonly Predicate<T> _canExecute;
private readonly Action<T> _execute;
public RelayCommand(Action<T> execute) : this(execute, null)
{
}
public RelayCommand(Action<T> execute, Predicate<T> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler? CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute((T)parameter);
}
public void Execute(object parameter)
{
_execute((T)parameter);
}
}
4、通过binding绑定输入框的值和事件的操作,代替传统直接在后台cs文件写事件。
<TextBox Text="{Binding CardNumber}" x:Name="CardNumberTextBox" local:TextBoxSelectionHelper.SelectionStart="{Binding SelectionStart, Mode=TwoWay}" Height="40" Width="460" />
<Button Content="1" Style="{StaticResource NumberButtonStyle}" Command="{Binding NumberCommand}" CommandParameter="1"/>
<Button Content="删除" Style="{StaticResource RedButtonStyle}" Command="{Binding DeleteCommand}" />
<Button Content="清空" Style="{StaticResource RedButtonStyle}" Command="{Binding ClearCommand}" />
5、将MainWindow的DataContext赋值给ViewModel,我这里用了依赖注入的方法,所以直接是在app.cs里面赋值的,也可以在MainWindow.cs。
public partial class App : Application
{
public App()
{
Services = ConfigureServices();
this.InitializeComponent();
}
/// <summary>
/// Gets the current <see cref="App"/> instance in use
/// </summary>
public new static App Current => (App)Application.Current;
/// <summary>
/// Gets the <see cref="IServiceProvider"/> instance to resolve application services.
/// </summary>
public IServiceProvider Services { get; }
/// <summary>
/// Configures the services for the application.
/// </summary>
private static IServiceProvider ConfigureServices()
{
var services = new ServiceCollection();
services.AddTransient<KeyPressViewModelCommunityToolkit>();
services.AddTransient<KeyPressViewModelPrism>();
services.AddTransient<KeyPressViewModel>();
services.AddTransient(sp=>new MainWindow() { DataContext=sp.GetRequiredService<KeyPressViewModelCommunityToolkit>()});
return services.BuildServiceProvider();
}
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
MainWindow= Services.GetRequiredService<MainWindow>();
MainWindow.Show();
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext =App.Current.Services.GetService<KeyPressViewModel>();
}
}
6、如果不用依赖注入的方式,可以在MainWindow.xaml或者MainWindow.cs将MainWindow的DataContext赋值给ViewModel。
<Window x:Class="SelfServiceReportPrinter.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:selfservicereportprinter="clr-namespace:SelfServiceReportPrinter"
xmlns:local="clr-namespace:SelfServiceReportPrinter"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
AllowsTransparency="True"
WindowStyle="None"
Title="MainWindow"
Height="1080"
Width="1920">
<Window.DataContext>
<local:KeyPressViewModel />
</Window.DataContext>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext =new KeyPressViewModel();
}
}
以上就是自己去实现mvvm的方式,会比较繁琐,如果不是复杂的项目大多数还是用社区的CommunityToolkit.MVVM,复杂的项目可以使用Prism
二、使用微软社区的CommunityToolkit.MVVM
1、首先安装CommunityToolkit.MVVM的包。
2、创建一个新的类KeyPressViewModelCommunityToolkit类,继承ObservableObject,CommunityToolkit代码就简洁很多了,直接在方法或者属性上面加特性就可以。
public partial class KeyPressViewModelCommunityToolkit : ObservableObject
{
[ObservableProperty]
private string cardNumber = string.Empty;
[ObservableProperty]
private int selectionStart;
/// <summary>
/// 数字点击事件
/// </summary>
/// <param name="key"></param>
[RelayCommand]
private void Number(string? key)
{
CardNumber += key;
}
/// <summary>
/// 清空输入框
/// </summary>
[RelayCommand]
private void Clear()
{
CardNumber = string.Empty;
}
/// <summary>
/// 删除点击事件
/// </summary>
[RelayCommand]
private void Delete()
{
// 光标在输入框时,删除光标前一个字符
if (!string.IsNullOrEmpty(CardNumber) && SelectionStart > 0)
{
CardNumber = CardNumber.Remove(SelectionStart - 1, 1);
}
//光标没有在输入框时,删除最后一个字符
if (SelectionStart == 0)
{
CardNumber = CardNumber.Remove(CardNumber.Length - 1, 1);
}
}
}
三、Prism
1、安装Prism.Core和Prism.Wpf,其他的包根据后续实际使用在安装。
2、创建一个KeyPressViewModelPrism类继承BindableBase。
public partial class KeyPressViewModelPrism : BindableBase
{
private string cardNumber;
private int selectionStart;
public string CardNumber
{
get { return cardNumber; }
set { SetProperty(ref cardNumber, value); }
}
public int SelectionStart
{
get { return selectionStart; }
set { SetProperty(ref selectionStart, value); }
}
public DelegateCommand<string> NumberCommand { get; }
public DelegateCommand ClearCommand { get; }
public DelegateCommand DeleteCommand { get; }
public KeyPressViewModelPrism()
{
NumberCommand = new DelegateCommand<string>(Number);
ClearCommand = new DelegateCommand(Clear);
DeleteCommand = new DelegateCommand(Delete);
}
/// <summary>
/// 数字点击事件
/// </summary>
/// <param name="key"></param>
private void Number(string? key)
{
CardNumber += key;
}
/// <summary>
/// 清空点击事件
/// </summary>
private void Clear()
{
CardNumber = string.Empty;
}
/// <summary>
/// 删除点击事件
/// </summary>
private void Delete()
{
// 光标在输入框时,删除光标前一个字符
if (!string.IsNullOrEmpty(CardNumber) && SelectionStart > 0)
{
CardNumber = CardNumber.Remove(SelectionStart - 1, 1);
}
//光标没有在输入框时,删除最后一个字符
if (SelectionStart == 0)
{
CardNumber = CardNumber.Remove(CardNumber.Length - 1, 1);
}
}
}
四、总结
以上就是三种不同ViewModel的实现方式,个人比较推荐使用社区的CommunityToolkit,但是还是要根据自己的项目情况来决定。