WPF 中的 MVVM 模式:面向绝对初学者的简单教程
WPF 中的 MVVM 模式:面向绝对初学者的简单教程。
引言
作为学习 MVVM 模式的一部分,我尝试搜索了许多网站和博客,发现大多数都以复杂的方式解释了该模式。经过一些研究,我破解了 MVVM 模式中最基本的步骤,在这里我试图为绝对初学者编写一个 MVVM 教程。
我认为不需要花费更多的时间或文字来解释 MVVM 的各个部分以及 MVVM 和 WPF 之间的关系。如果你深入了解 WPF,你会意识到 MVVM 是最适合 WPF 的模式(你可能不理解这两者之间的区别)。
作为一种正式的程序,我给出了一个简单的 MVVM 图表和定义
我从两个例子开始本教程:WpfSimple.csproj 和 WpfMvvmTest.csproj。
为了简单起见,在第一个项目 (WpfSimple.csproj) 中,我们避免使用 Model 对象(稍后将提供一个带 Model 的示例)。
在示例 WpfSimple
中,View 仅包含一个 Button
并且没有代码隐藏,但按钮点击事件与 ViewModel
松散绑定。View
和 ViewModel
之间的绑定很容易构建,因为 ViewModel
对象被设置为 View
的 DataContext
。如果 ViewModel
中的属性值发生变化,这些新值会自动通过数据绑定传播到 View
。当用户在 View
中单击按钮时,ViewModel
上的一个命令会执行以执行请求的操作。
View
以下代码片段来自 WpfSimple
应用程序(随教程提供)
<Window x:Class="WpfSimple.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfSimple"
Title="MainWindow" Height="150" Width="370">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Button Content="Click"
Height="23"
HorizontalAlignment="Left"
Margin="77,45,0,0"
Name="btnClick"
VerticalAlignment="Top"
Width="203"
Command="{Binding ButtonCommand}"
CommandParameter="Hai" />
</Grid>
</Window>
这里使用的 ViewModel
类是 MainWindowViewModel
,该对象被设置为 View 的 DataContext
。
ViewModel
这里使用的 ViewModel
类是 MainWindowViewModel
。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
using System.Windows;
namespace WpfSimple
{
class MainWindowViewModel
{
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
public MainWindowViewModel()
{
ButtonCommand=new RelayCommand(new Action<object>(ShowMessage));
}
public void ShowMessage(object obj)
{
MessageBox.Show(obj.ToString());
}
}
}
你可以在这里看到一个空的code-behind文件。如果你点击按钮,它将提示一个消息框,尽管 Views 中缺少事件处理方法。当用户点击按钮时,应用程序会做出反应并满足用户的请求。这之所以有效,是因为在 UI 中显示的 Button
的 Command
属性上建立了绑定。命令对象充当适配器,可以轻松地从 XAML 中声明的 View 中使用 ViewModel 的功能。
RelayCommand
RelayCommand
是在 ICommand
接口中实现的自定义类。你可以使用任何名称代替 RelayCommand
。
它的用法如下
private ICommand m_ButtonCommand;
public ICommand ButtonCommand
{
get
{
return m_ButtonCommand;
}
set
{
m_ButtonCommand = value;
}
}
ButtonCommand=new RelayCommand(new Action<object>(ShowMessage));
public void ShowMessage(object obj)
{
// do Something
}
在 View 中使用 ButtonCommand
如下所示
<Button Content="Click"
Command="{Binding ButtonCommand}"
CommandParameter="Hai" />
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Input;
namespace WpfSimple
{
class RelayCommand : ICommand
{
private Action<object> _action;
public RelayCommand(Action<object> action)
{
_action = action;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
if (parameter != null)
{
_action(parameter);
}
else
{
_action("Hello World");
}
}
#endregion
}
}
我认为这是一个简单且标准的方法。另一种常见的方法是使用嵌套的内部类。
接下来,我将通过一个带有 Model
对象的示例来讨论 MVVM。我还将简要介绍一下 INotifyPropertyChanged
接口。
INotifyPropertyChanged
INotifyPropertyChanged
接口用于在属性值发生更改时通知客户端(通常是绑定客户端)。INotifyPropertyChanged
接口包含一个名为 PropertyChanged
的事件。每当 ViewModel
/ Model
对象上的属性具有新值时,它都可以引发 PropertyChanged
事件以通知 WPF 绑定系统新值。在收到该通知后,绑定系统查询该属性,并且 UI 元素上的绑定属性将接收新值。
从示例 WpfMvvmTest
项目中,我将演示以下代码
public class Product:INotifyPropertyChanged
{
private int m_ID;
private string m_Name;
private double m_Price;
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#endregion
public int ID
{
get
{
return m_ID;
}
set
{
m_ID = value;
OnPropertyChanged("ID");
}
}
public string Name
{
get
{
return m_Name;
}
set
{
m_Name = value;
OnPropertyChanged("Name");
}
}
public double Price
{
get
{
return m_Price;
}
set
{
m_Price = value;
OnPropertyChanged("Price");
}
}
}
这里展示了另一种将 ViewModel
对象绑定为 View
的 DataContext
的方法
public partial class App: Application
{
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
WpfMvvmTest.MainWindow window = new MainWindow();
ProductViewModel VM = new ProductViewModel();
window.DataContext = VM;
window.Show();
}
}
在以下代码片段中,我们使用内部类方法来实现 ICommand
接口;即,在 ViewModel
类中创建一个 private
嵌套类,以便命令可以访问其包含的 ViewModel
的 private
成员,并且不会污染命名空间。
这种方法的缺点是,对于 ViewModel
公开的每个命令,都需要创建一个实现 ICommand
的嵌套类,这将增加 ViewModel
类的大小。
class ProductViewModel
{
private IList<Product> m_Products;
public ProductViewModel()
{
m_Products = new List<Product>
{
new Product {ID=1, Name ="Pro1", Price=10},
new Product{ID=2, Name="BAse2", Price=12}
};
}
public IList<Product> Products
{
get
{
return m_Products;
}
set
{
m_Products = value;
}
}
private ICommand mUpdater;
public ICommand UpdateCommand
{
get
{
if (mUpdater == null)
mUpdater = new Updater();
return mUpdater;
}
set
{
mUpdater = value;
}
}
private class Updater : ICommand
{
#region ICommand Members
public bool CanExecute(object parameter)
{
return true;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
}
#endregion
}
}
从以下 View
XAML,你可以了解如何将对象集合绑定到列表控件。
<Window x:Class="WpfMvvmTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid Height="314">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<ListView Name="ListViewEmployeeDetails" Grid.Row="1"
Margin="4,109,12,23" ItemsSource="{Binding Products}" >
<ListView.View>
<GridView x:Name="grdTest">
<GridViewColumn Header="ID"
DisplayMemberBinding="{Binding ID}" Width="100"/>
<GridViewColumn Header="Name"
DisplayMemberBinding="{Binding Name}" Width="100" />
<GridViewColumn Header="Price"
DisplayMemberBinding="{Binding Price}" Width="100" />
</GridView>
</ListView.View>
</ListView>
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left"
Margin="80,7,0,0" Name="txtID" VerticalAlignment="Top" Width="178"
Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.ID}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left"
Margin="80,35,0,0" Name="txtName" VerticalAlignment="Top" Width="178"
Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.Name}" />
<TextBox Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="80,61,0,0"
Name="txtPrice" VerticalAlignment="Top" Width="178"
Text="{Binding ElementName=ListViewEmployeeDetails,Path=SelectedItem.Price}" />
<Label Content="ID" Grid.Row="1" HorizontalAlignment="Left"
Margin="12,12,0,274" Name="label1" />
<Label Content="Price" Grid.Row="1" Height="28"
HorizontalAlignment="Left" Margin="12,59,0,0"
Name="label2" VerticalAlignment="Top" />
<Label Content="Name" Grid.Row="1" Height="28"
HorizontalAlignment="Left" Margin="12,35,0,0"
Name="label3" VerticalAlignment="Top" />
<Button Content="Update" Grid.Row="1" Height="23"
HorizontalAlignment="Left" Margin="310,40,0,0" Name="btnUpdate"
VerticalAlignment="Top" Width="141"
Command="{Binding Path=UpdateCommand}"
/>
</Grid>
</Window>
我希望你能够从这篇文章中对 MVVM 有一些了解。