Silverlight MVVM概念的完全简单介绍






4.77/5 (21投票s)
在本文中,我将 Silverlight 中的 MVVM 模式的使用减少到最少的代码行,以便理解基础知识。
注意:您可能会收到一条错误消息,提示 Windows 已禁用从 Internet 下载的 DLL。如果发生这种情况,请通过资源管理器导航到这些文件,然后在“属性”显示中,您会看到一个名为“取消阻止”的按钮。单击该按钮,您将能够编译。
为什么选择 MVVM?
根据我编程的经验,我一直以“复杂性是敌人”作为指导原则。作为程序员,我们在构建程序/系统到一定程度时做得相当好。超过这一点,生产力就会开始显著下降。我们都有过这样的经历。您知道代码在哪里,但项目已达到一定规模,查找内容开始耗费时间。然后,您就无法再以“思维的速度”工作,而是以检索的速度工作。
模式是解决系统中“复杂性”问题的一种有趣方法。我一直认为模式是一种隔离或疫苗,可以应对系统复杂性。在某种程度上,您在编码过程的开始就增加了复杂性,希望以后能限制复杂性。所谓的 MVVM 模式,我认为就是一个很好的例子。不使用“代码隐藏”的想法是违反直觉的。没有什么比做一个简单的基于事件的应用程序更快了,其中 UI 在 XAML 中,编码在代码隐藏中。但是,随着系统的增长,您很快就会达到生产力平台期。我们都有过这样的经历。我知道我有。
通过 MVVM,我们只需最大限度地利用 Silverlight 中的“绑定”概念。我们都熟悉数据绑定,但通过“命令”绑定的潜力,我们可以将 UI(视图)与处理(ViewModel
)完全分离。此模式中的抽象流程是 Model<->ViewModel<->View。这带来了“关注点分离”这一备受吹捧的副产品。这种概念是,在开发中,将 UI 的开发与处理(连接)完全分开是一个巨大的优势,这样 UI 部分的过程就可以被替换而不会在代码中导致异常。从可测试性的角度来看,MVVM 也被认为是一个很好的策略。
从编码人员的角度来看,问题在于这是一个依赖于不明显语法和约定的新范例。无论是样式还是诸如将数据加载到页面、响应事件和验证等基本功能,都存在学习曲线。
当面对大量的“新”技术时,我的策略是尝试将这些技术分解为最简单的实现。我花了很长时间寻找一个好的例子,虽然有很多关于 MVVM 的好文章,但我希望一次性涵盖 MVVM 的所有基础知识。
- 关注点分离
- 数据绑定
- 命令绑定
- 验证
- 单元测试
- 链接到框架(Prism)
应用程序的 UI 非常简单。

您可以从文本框添加数据,它会显示在数据网格中。如果您尝试添加过长的数据,您将收到字段的错误消息和弹出窗口。虽然此验证看起来有点过头,但我只是试图展示 MVVM 策略的各种“活动部件”。
XAML
<UserControl x:Class="MVVMtest1.MainPage"
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:app="clr-namespace:MVVMtest1"
mc:Ignorable="d"
d:DesignHeight="324" d:DesignWidth="459"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk" >
<UserControl.DataContext>
<app:HelloWorldModel />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot" Background="White" Height="326" Width="469">
<sdk:DataGrid AutoGenerateColumns="true" Height="182"
ItemsSource="{Binding Path=myData}"
HorizontalAlignment="Left" Margin="159,107,0,0"
Name="dataGrid1" VerticalAlignment="Top" Width="108" >
</sdk:DataGrid>
<Button Content="Add Data" Height="23" HorizontalAlignment="Left"
Command="{Binding SaveCommand}"
CommandParameter="{Binding Text,ElementName=txtText,Mode=TwoWay,
ValidatesOnDataErrors=True}"
Margin="96,45,0,0" Name="btnAdd" VerticalAlignment="Top" Width="75" />
<TextBox TabIndex="0" Height="23" Text="{Binding inputValue, Mode=TwoWay,
ValidatesOnDataErrors=True}" HorizontalAlignment="Right"
Margin="0,44,163,0"
Name="txtText" VerticalAlignment="Top" Width="120" />
<TextBlock Height="23" HorizontalAlignment="Left" Margin="150,12,0,0"
Name="textBlock1"
Text="MVVM Hello World Example" VerticalAlignment="Top"
FontWeight="Bold" />
<TextBlock Height="23" HorizontalAlignment="Left"
Margin="96,78,0,0" Name="textBlock2"
Text="Please limit your data to 10 characters"
VerticalAlignment="Top" Width="225" />
<Grid Visibility="{Binding Path=MessageVisibility}">
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Rectangle Grid.RowSpan="2" Fill="Black" Opacity="0.08" />
<Border Grid.Row="0" BorderBrush="blue" BorderThickness="1" CornerRadius="10"
Background="White"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<TextBlock Text="{Binding Path=Message}"
MinWidth="150"
MaxWidth="300"
MinHeight="30"
TextWrapping="Wrap" Grid.Row="0" Margin="10, 5, 10, 5" />
<Button Content="OK" Grid.Row="1"
Margin="5" Width="100"
Command="{Binding Path=HideMessageCommand}"/>
</Grid>
</Border>
</Grid>
</Grid>
</UserControl>
虽然 MVVM 中有很多“魔法”之处,但第一点是数据绑定。下面的代码片段使用以下代码将整个用户控件的数据绑定到 ViewModel
:
<UserControl.DataContext>
<app:HelloWorldModel />
</UserControl.DataContext>
通过这种数据绑定,用户控件中的所有控件都可以绑定到 ViewModel
的属性。同样,“命令”和“事件”可以绑定到 ViewModel
中的“命令属性”。首先,我们来看看简单的绑定。DataGrid
的 ItemSource
绑定到“myData
”属性。由于此属性定义为 ObservableCollection
,因此此数据绑定隐式为双向,并且此集合中的任何更改都会“自动”显示在屏幕上。
ItemsSource="{Binding Path=myData}"
接下来,我们将绑定到按钮的命令事件。
Command="{Binding SaveCommand}"
CommandParameter="{Binding Text,ElementName=txtText,Mode=TwoWay,
ValidatesOnDataErrors=True}"
此绑定的要点是:
- 我们绑定到
Command
属性,而不是 click 事件。这是 Silverlight 4.0 内置的基本命令功能。绑定到事件超出了本文的范围。
- 我们可以为该命令定义一个参数,该参数是另一个控件的属性。
- 在此参数中,我们可以通过添加
ValidatesOnDataErrors=True
语法来添加验证。
ViewModel
现在 XAML 已设置好,让我们看看 ViewModel
代码,了解一切是如何工作的。
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System.Windows;
using Microsoft.Practices.Prism.Commands;
namespace MVVMtest1
{
public class HelloWorldModel : INotifyPropertyChanged, IDataErrorInfo
{
private string _inputValue;
private string _message;
private bool isError = false;
public HelloWorldModel()
{
LoadData();
DefineCommands();
MessageVisibility = Visibility.Collapsed;
}
public string Message
{
get { return _message; }
set
{
_message = value;
NotifyPropertyChanged("Message");
}
}
public string inputValue
{
get { return _inputValue; }
set
{
_inputValue = value;
OnPropertyChanged("inputValue");
}
}
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public event PropertyChangedEventHandler PropertyChanged;
private ObservableCollection<dataitemclass> _myData;
public ObservableCollection<dataitemclass> myData
{
get
{
return _myData;
}
}
private void LoadData()
{
_myData = new ObservableCollection<dataitemclass>();
_myData.CollectionChanged +=
new System.Collections.Specialized.NotifyCollectionChangedEventHandler
(_myData_CollectionChanged);
_myData.Add(new DataItemClass { Id= _myDataId, Name= "first line" });
_myData.Add(new DataItemClass { Id = _myDataId, Name = "second line" });
_myData.Add(new DataItemClass { Id = _myDataId, Name = "third line" });
}
int _myDataId = 1;
void _myData_CollectionChanged(object sender,
System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
_myDataId++;
}
private Visibility messageVisibility;
public Visibility MessageVisibility
{
get { return messageVisibility; }
set
{
if (messageVisibility != value)
{
messageVisibility = value;
NotifyPropertyChanged("MessageVisibility");
}
}
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get
{
return _SaveCommand;
}
}
private ICommand _HideMessageCommand;
public ICommand HideMessageCommand
{
get
{
return _HideMessageCommand;
}
}
private void DefineCommands()
{
// this uses the prism framework but could also use a simple class
//(ICommandImplementation.cs)
_SaveCommand = new DelegateCommand<string> (OnSaveCommand);
_HideMessageCommand = new DelegateCommand(OnHideMessageCommand);
}
private void OnHideMessageCommand()
{
MessageVisibility = Visibility.Collapsed;
}
private void OnSaveCommand(string s)
{
if (!isError)
{
_myData.Add(new DataItemClass { Id = _myDataId, Name = s });
this.inputValue = "";
}
}
public string Error
{
get { return null; }
}
public string this[string propertyName]
{
get
{
if (propertyName == "inputValue")
{
if (this.inputValue != null)
{
if (this.inputValue.Length > 10)
{
isError = true;
MessageVisibility = Visibility.Visible;
_message = "Why are you typing such long words";
Message = _message;
return _message;
}
else
{
isError = false;
}
}
}
return null;
}
}
}
public class DataItemClass
{
public int Id { get; set; }
public string Name { get; set; }
}
}
此页面上有很多魔法。请注意,此类继承自 INotifyPropertyChanged
和 IDataErrorInfo
。这些接口提供了我们“免费”获得的此类与 XAML 之间的交互。因此,当我们将项添加到 myData
属性背后的集合中时,UI 上的 datagrid
就会“自动”更新。同样,当发生属性“get
”时,事件会触发“public string this[string propertyName]
”属性上的“get
”,而这个 get
是 IDataErrorInfo
实现的基础。所有这些功能都由对构造函数的调用启用,该构造函数是 XAML 中数据绑定结果的结果。
此类处理 XAML 命令的能力是实现 MVVM 的关键功能。命令绑定到 ViewModel
上的属性,这些属性会调用事件。为了使此策略奏效,必须有一些实现 ICommand
的代码。您可以使用各种“框架”,从简单的单个类(我在代码示例中包含了一个)到 Microsoft Patterns and Practices 团队发布的开源 Prism。我使用了 Prism(对于这个项目来说有点过度),这涉及到引用 DLL 并添加一个 using
。
using Microsoft.Practices.Prism.Commands;
命令结构所需的三个代码“部分”如下所示:
// the property referenced in the XAML
private ICommand _SaveCommand;
public ICommand SaveCommand
{
get
{
return _SaveCommand;
}
}
// called by the constructor
private void DefineCommands()
{
// this uses the prism framework but could also use a simple class
// (ICommandImplementation.cs)
_SaveCommand = new DelegateCommand<string> (OnSaveCommand);
_HideMessageCommand = new DelegateCommand(OnHideMessageCommand);
}
// this is the actual 'work'
private void OnSaveCommand(string s)
{
if (!isError)
{
_myData.Add(new DataItemClass { Id = _myDataId, Name = s });
this.inputValue = "";
}
}
然后,项目可以实现 ICommand
。这是基本绑定和命令结构完全简单的实现。
过去所有试图“管理”基本显示和 CRUD 任务的尝试都存在 5% 原则问题。 “框架”在 95% 的所需功能方面运作良好,但您将花费 95% 的项目开发时间来完成最后 5% 的功能。希望 MVVM 不会像许多其他框架一样被淘汰。MVVM 模式的实现还有一些更关键的部分,它们体现了该技术对于企业开发的适用性,例如验证和可测试性。
验证
我在代码中包含了一个简单的验证示例。我们实现 IDataErrorInfo
,其基本实现代码如下:
public class ErrorClass : IDataErrorInfo
{
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get { throw new NotImplementedException(); }
}
}
您可以在我上面的类代码中查看如何将其连接到您的 ViewModel
类。在调试时设置断点以跟踪此功能的处理流程很有帮助,因为它相当复杂。

消息框
在没有代码隐藏的情况下,通过模态消息框向用户发送消息会更加困难。我已包含相关代码。在此示例中,如果用户尝试输入超过 10 个字符的数据,则 TextBox
会显示错误,并显示模态消息框。

测试 MVVM
最后,我包含了一些测试。在使用 MVVM 测试 Silverlight 时首先要记住的是,请勿使用标准测试框架,该框架可以随典型的 Visual Studio 项目一起安装。请使用 Silverlight Unit Test Application 模板。示例中的第一个测试显示了如何测试“命令”,第二个测试显示了如何测试验证。

测试模板有一个有用的屏幕显示,允许运行所有测试或选定的测试组。
结果如下所示:

我试图将 MVVM 模式的基本实现的示例做到最简单。当然,构建 Silverlight 应用程序还有很多其他方面,例如转换器和行为,但希望这能帮助您克服可怕的初始学习曲线。