在WPF(Windows Presentation Foundation)开发中,MVVM(Model-View-ViewModel)是一种非常流行的设计模式,它旨在将应用程序的UI逻辑与业务逻辑和数据模型分离,从而提高代码的可维护性、可测试性和可扩展性。MVVM模式由三个核心部分组成:Model(模型)、View(视图)和ViewModel(视图模型)。
1. Model(模型)
Model代表应用程序的数据和业务逻辑。它通常包含数据字段、属性以及操作这些数据的方法。Model是独立于View和ViewModel的,意味着它不知道也不关心数据是如何被展示或修改的。Model层主要负责数据的存储、检索和验证等业务逻辑。
2. View(视图)
View是用户界面的表示层,负责展示数据。在WPF中,View通常是由XAML(可扩展应用程序标记语言)和后台代码(C#或VB.NET)组成的。XAML用于定义UI的布局和样式,而后台代码则用于处理用户交互。View通过数据绑定与ViewModel进行交互,但View本身不直接处理业务逻辑或数据访问。
3. ViewModel(视图模型)
ViewModel是Model和View之间的桥梁,它封装了与View交互的Model数据。ViewModel为View提供数据,并处理用户交互逻辑。ViewModel通常包含与View绑定的属性(这些属性通常与Model中的属性相对应),以及用于更新这些属性的命令(如按钮点击事件)。ViewModel还负责将Model的数据转换为View可以理解和展示的格式,并处理用户输入,将其转换为Model可以理解的格式。
MVVM的优势
- 高内聚低耦合:Model、View和ViewModel之间的职责明确,相互之间的依赖关系降到最低,提高了代码的可维护性和可扩展性。
- 易于测试:由于ViewModel不依赖于View,因此可以独立于UI进行单元测试。
- 提高开发效率:设计师和开发人员可以并行工作,设计师可以专注于View的设计,而开发人员则专注于ViewModel和Model的实现。
- 更好的用户体验:ViewModel可以处理复杂的用户交互逻辑,使得View层更加简洁,从而提供更好的用户体验。
实现MVVM
在WPF中实现MVVM通常需要使用一些辅助库,如Prism、Caliburn.Micro或MVVM Light等,这些库提供了实现MVVM模式所需的基础结构和工具。然而,即使没有这些库,你也可以通过WPF的数据绑定、命令和INotifyPropertyChanged接口等特性来手动实现MVVM模式。
阶段一:
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 5 0 0">
<TextBlock Text="第一个数:" Margin="0 0 10 0"/>
<TextBox Name="textNumber1" Width="150"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="第二个数:" Margin="0 0 10 0"/>
<TextBox Name="textNumber2" Width="150"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="结 果:" Margin="0 0 10 0"/>
<TextBox Name="textResult" Width="150"/>
</StackPanel>
<Button x:Name="btnResult" Content="计算结果" Width="150" HorizontalAlignment="Left" Margin="10 15 0 0" Click="btnResult_Click"/>
</StackPanel>
</Grid>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void btnResult_Click(object sender, RoutedEventArgs e)
{
this.textResult.Text = int.Parse(textNumber1.Text) + int.Parse(textNumber2.Text) + "";
}
}
阶段二:
创建ViewModel文件
1.更改启动的路径
App.xaml文件 : StartupUri="Views/MainWindow.xaml"
2.更改MainWindow.xaml文件
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 5 0 0">
<TextBlock Text="第一个数:" Margin="0 0 10 0"/>
<TextBox Width="150" Text="{Binding Number1}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="第二个数:" Margin="0 0 10 0"/>
<TextBox Width="150" Text="{Binding Number2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="结 果:" Margin="0 0 10 0"/>
<TextBox Width="150" Text="{Binding Result}"/>
</StackPanel>
<Button x:Name="btnResult" Content="计算结果" Width="150" HorizontalAlignment="Left" Margin="10 15 0 0" Command="{Binding CalcCommand}"/>
</StackPanel>
</Grid>
3.更改MainWindow.xaml.cs文件把视图模型对象给到界面绑定
public MainWindow()
{
InitializeComponent();
//把视图模型对象给到界面绑定
this.DataContext = new MainWindowViewModel();
}
4.MainWindowViewModel编写
public class MainWindowViewModel:INotifyPropertyChanged
{
private int _number1=10;
public int Number1 {
get { return _number1; }
set{ _number1 = value; }
}
private int _number2=16;
public int Number2
{
get { return _number2; }
set { _number2 = value; }
}
private int _result;
public int Result
{
get { return _result; }
set { _result = value; OnPropertyChanged(nameof(Result)); }
}
//数值更改进行界面的同时
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public CommandBase CalcCommand
{
get => new CommandBase
{
DoExecte = new Action<object>(CalcResult)
};
}
//真正需要执行的计算逻辑
private void CalcResult(object obj)
{
this.Result = _number1 + _number2;
Debug.Write("AAA");
}
}
5.CommandBase通用类的编写,按钮控件按下触发
public class CommandBase : ICommand
{
public event EventHandler? CanExecuteChanged;
public Func<object, bool>? CanExecution { get; set; }
public Action<object>? DoExecte { get; set; }
//IsEnabled="False",就不会发起操作
public bool CanExecute(object? parameter)
{
if(CanExecution != null)
{
CanExecute(parameter);
}
return true;
}
//正在执行命令的方法
public void Execute(object? parameter)
{
DoExecte!.Invoke(parameter!);
}
}
阶段三
将数据放入Model中
1.CommandBase
public class CommandBase : ICommand
{
public event EventHandler? CanExecuteChanged;
public Func<object, bool>? CanExecution { get; set; }
public Action<object>? DoExecte { get; set; }
//IsEnabled="False",就不会发起操作
public bool CanExecute(object? parameter)
{
if(CanExecution != null)
{
CanExecute(parameter);
}
return true;
}
//正在执行命令的方法
public void Execute(object? parameter)
{
DoExecte!.Invoke(parameter!);
}
}
2.NotifyBase
public class NotifyBase:INotifyPropertyChanged
{
//数值更改进行界面的同时
public event PropertyChangedEventHandler? PropertyChanged;
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
3.MainWindowModel
public class MainWindowModel : NotifyBase
{
private int _number1 = 10;
public int Number1
{
get { return _number1; }
set { _number1 = value; }
}
private int _number2 = 16;
public int Number2
{
get { return _number2; }
set { _number2 = value; }
}
private int _result;
public int Result
{
get { return _result; }
set { _result = value; OnPropertyChanged(nameof(Result)); }
}
private string _time = "8888-88-88 88:88:88";
public string Time
{
get { return _time; }
set { _time = value; OnPropertyChanged(nameof(Time)); }
}
}
4.MainWindowViewModel
public class MainWindowViewModel
{
public MainWindowModel MainWindowModel {get; set;}=new MainWindowModel();
public MainWindowViewModel()
{
Task.Run(async () =>
{
while (true)
{
await Task.Delay(500);
MainWindowModel.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
}
});
}
public CommandBase CalcCommand
{
get => new CommandBase
{
DoExecte = new Action<object>(CalcResult)
};
}
//真正需要执行的计算逻辑
private void CalcResult(object obj)
{
MainWindowModel.Result = MainWindowModel.Number1 + MainWindowModel.Number2;
Debug.Write("AAA");
}
}
5.MainWindow.xaml
<Grid>
<StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 5 0 0">
<TextBlock Text="第一个数:" Margin="0 0 10 0"/>
<TextBox Width="150" Text="{Binding MainWindowModel.Number1}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="第二个数:" Margin="0 0 10 0"/>
<TextBox Width="150" Text="{Binding MainWindowModel.Number2}"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="0 10 0 0">
<TextBlock Text="结 果:" Margin="0 0 10 0"/>
<TextBox Name="Tresult" Width="150" Text="{Binding MainWindowModel.Result}"/>
<!--同步文本框中的文本-->
<TextBlock Text="{Binding ElementName=Tresult ,Path=Text}" Margin="10 0 0 0"/>
</StackPanel>
<Button x:Name="btnResult" Content="计算结果" Width="150" HorizontalAlignment="Left" Margin="10 15 0 0" Command="{Binding CalcCommand}"/>
<TextBlock Text="{Binding MainWindowModel.Time}" Width="150" Height="20" Margin="10"/>
</StackPanel>
</Grid>
6.MainWindow.xaml.cs
public MainWindow()
{
InitializeComponent();
//把视图模型对象给到界面绑定
this.DataContext = new MainWindowViewModel();
}