Silverlight MVVM 应用程序的数据和命令绑定
本文总结了在 Silverlight MVVM 应用程序中用于数据和命令绑定的两个实用类,并通过一个运行中的示例演示了如何使用这两个类。
引言
本文总结了在 Silverlight MVVM 应用程序中用于数据和命令绑定的两个实用类,并通过一个运行中的示例演示了如何使用这两个类。
背景
MVVM 设计模式现在已成为构建 Silverlight 应用程序的默认设计模式。数据绑定和命令绑定是在 Silverlight 中开发 MVVM 应用程序的基本构建块。本文将总结两个非常常用的实用类,以帮助实现 Silverlight MVVM 应用程序中的绑定,并提供一个运行示例来使用这两个类。将介绍的两个实用类如下:
- “
ViewModelBase
”类用于帮助实现数据绑定。 - “
RelayCommand
”类用于帮助实现命令绑定。
如果您对 Silverlight 中 MVVM 的更普遍讨论感兴趣,您可以轻松找到参考资料,例如此篇、此篇和此篇。
随附的 Visual Studio 解决方案包含两个项目:
- “
MVVMUtility
”项目是 Silverlight 应用程序。 - “
MVVMUtilityWeb
”项目是一个 ASP.NET Web 应用程序,用于托管 Silverlight 应用程序。它还公开了一个 WCF 服务供 Silverlight 应用程序消耗,以构建应用程序的数据模型。
随附的 Visual Studio 解决方案是使用 Visual Studio 2010 和 Silverlight 4 构建的。我将首先介绍这两个实用类,然后介绍 MVVM 应用程序以演示如何使用它们。我建议您在阅读本文之前下载随附的 Visual Studio 解决方案并运行 Silverlight 应用程序。如果您了解演示应用程序的作用,将有助于您更轻松地阅读本文。
数据和命令绑定实用类
这两个实用类在“MVVMUtility
”项目中的“BindingUtilities”文件夹中实现。以下是“ViewModelBase
”类:
using System;
using System.Windows;
using System.ComponentModel;
namespace MVVMUtility.BindingUtilities
{
public abstract class ViewModelBase
: DependencyObject, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public bool IsDesignTime
{
get { return DesignerProperties.IsInDesignTool; }
}
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
这是一个实用类,用于帮助实现数据绑定。它是 INotifyPropertyChanged 接口的简单包装类。INotifyPropertyChanged
接口是数据绑定的关键。如果视图模型中的数据更改需要通过绑定反映到 XAML 组件中,那么 Silverlight MVVM 应用程序中的所有视图模型都需要实现此接口。有了这个实用类,视图模型就可以简单地继承它并调用“NotifyPropertyChanged
”方法,而无需处理 INotifyPropertyChanged
接口的细节。
在构建 Silverlight MVVM 应用程序时,另一个重要部分是命令绑定。为了支持命令绑定的实现,创建了“RelayCommand
”类。
using System;
using System.Windows.Input;
namespace MVVMUtility.BindingUtilities
{
public class RelayCommand : ICommand
{
private readonly Action handler;
private bool isEnabled;
public RelayCommand(Action handler)
{
this.handler = handler;
}
public bool IsEnabled
{
get { return isEnabled; }
set
{
if (value != isEnabled)
{
isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
handler();
}
}
}
ICommand 接口允许 Silverlight 应用程序在视图模型中处理用户命令,例如 Button 点击。“RelayCommand
”类包装了 ICommand
接口的基本实现,因此视图模型不必直接处理 ICommand
接口。您很快就会在示例 Silverlight 应用程序中看到“ViewModelBase
”和“RelayCommand
”类是如何用于构建视图模型的。但在进入视图模型之前,我们先来看看应用程序的数据模型。
数据模型
此 MVVM 应用程序的数据模型在两个地方实现:宿主 Web 应用程序中的 WCF 服务和 Silverlight 应用程序中的 StudentModel
类。WCF 服务在“StudentService.svc.cs”文件中实现。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace MVVMUtilityWeb
{
[DataContract]
public class Student
{
[DataMember(Order = 0)]
public string ID { get; set; }
[DataMember(Order = 1)]
public string Name { get; set; }
[DataMember(Order = 2)]
public DateTime EnrollmentDate { get; set; }
[DataMember(Order = 3)]
public int Score { get; set; }
[DataMember(Order = 4)]
public string Gender { get; set; }
}
[ServiceContract]
public class StudentService
{
[OperationContract]
public List<Student> GenerateStudents(int NoOfStudents)
{
List<Student> students = new List<Student>();
Random rd = new Random();
for (int i = 1; i <= NoOfStudents; i++)
{
int score = Convert.ToInt16(60 + rd.NextDouble() * 40);
students.Add(new Student()
{
ID = "ID No." + i.ToString(),
Name = "Student Name " + i.ToString(),
EnrollmentDate = DateTime.Now,
Score = score,
Gender = (score >= 80) ? "Famle" : "Male"
});
}
return students;
}
}
}
这是一个简单的 WCF 服务。它有一个 Data Contract“Student
”和一个 Service Contract“StudentService
”。Operation Contract“GenerateStudents
”接受一个整数,表示 WCF 客户端想要检索的学生数量,并创建一个随机生成的“Student
”对象列表发送回客户端。在将此 WCF 服务添加为 Silverlight 应用程序中的服务引用后,“StudentModel
”类在“Models”文件夹中实现。
using System;
using System.Collections.Generic;
using MVVMUtility.StudentService;
namespace MVVMUtility.Models
{
public class StudentModel
{
public void GetStudents(int NumberOfStudent,
EventHandler<GenerateStudentCompletedEventArgs> targetFunction)
{
StudentServiceClient client = new StudentServiceClient();
client.GenerateStudentCompleted
+= new EventHandler<GenerateStudentCompletedEventArgs>(targetFunction);
client.GenerateStudentAsync(NumberOfStudent);
}
}
}
由于所有学生生成功能都已在 WCF 服务中实现,“StudentModel
”只有一个方法,该方法调用 WCF 服务来检索“Student
”对象的列表。“GetStudents
”方法的输入参数之一是对 Callback 函数的引用。当 WCF 服务返回结果时,将异步调用此回调函数。此回调函数将在应用程序的视图模型中实现。
ViewModel
Silverlight 应用程序的视图模型在“ViewModels”文件夹中的“MainPageViewModel
”类中实现。
using System;
using MVVMUtility.BindingUtilities;
using System.Collections.Generic;
using MVVMUtility.Models;
using System.Collections.ObjectModel;
using System.Windows;
using MVVMUtility.StudentService;
namespace MVVMUtility.ViewModels
{
public class NoOfStudentsDropdownItem
{
public int NoOfStudents { get; set; }
public string DisplayText { get; set; }
}
public class MainPageViewModel : ViewModelBase
{
// Properties
public List<NoOfStudentsDropdownItem> ListNoOfStudents { get; private set; }
private NoOfStudentsDropdownItem selectedNoOfStudents;
public NoOfStudentsDropdownItem SelectedNoOfStudents
{
get { return selectedNoOfStudents; }
set
{
if (selectedNoOfStudents != value)
{
selectedNoOfStudents = value;
NotifyPropertyChanged("SelectedNoOfStudents");
ListOfStudents = null;
}
}
}
private List<Student> listOfStudents;
public List<Student> ListOfStudents
{
get { return listOfStudents; }
private set
{
if (listOfStudents != value)
{
listOfStudents = value;
NotifyPropertyChanged("ListOfStudents");
}
}
}
private string message;
public string Message
{
get { return message; }
private set
{
if (message != value)
{
message = value;
NotifyPropertyChanged("Message");
}
}
}
private Visibility messageVisibility;
public Visibility MessageVisibility
{
get { return messageVisibility; }
set
{
if (messageVisibility != value)
{
messageVisibility = value;
NotifyPropertyChanged("MessageVisibility");
}
}
}
// Commands
private void WireCommands()
{
GetStudentsCommand = new RelayCommand(GetStudents);
GetStudentsCommand.IsEnabled = true;
HideMessageCommand = new RelayCommand(HideMessage);
HideMessageCommand.IsEnabled = true;
}
private void ShowMessage(string message)
{
Message = message;
MessageVisibility = Visibility.Visible;
}
public RelayCommand GetStudentsCommand { get; private set; }
private void GetStudents()
{
if (SelectedNoOfStudents.NoOfStudents == -1)
{
ShowMessage("Please select the number of students to retrieve.");
MessageVisibility = Visibility.Visible;
return;
}
StudentModel model = new StudentModel();
model.GetStudents(SelectedNoOfStudents.NoOfStudents, GetStudentsCompleted);
}
private void GetStudentsCompleted(object sender,
GenerateStudentsCompletedEventArgs e)
{
try { ListOfStudents = (List<Student>)e.Result; }
catch
{
string message = "Unable to obtain the students from the WCF service"
+ " - you may have lost internet access or the server is down.";
ShowMessage(message);
ListOfStudents = null;
}
}
public RelayCommand HideMessageCommand { get; private set; }
private void HideMessage()
{
MessageVisibility = Visibility.Collapsed;
}
// Constructor
public MainPageViewModel()
{
InitiateViewModel();
}
private void InitiateViewModel()
{
ListNoOfStudents = new List<NoOfStudentsDropdownItem>();
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = -1, DisplayText = "* Please Select *" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 5, DisplayText = "5" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 10, DisplayText = "10" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 20, DisplayText = "20" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 50, DisplayText = "50" });
ListNoOfStudents.Add(new NoOfStudentsDropdownItem()
{ NoOfStudents = 100, DisplayText = "100" });
SelectedNoOfStudents = ListNoOfStudents[0];
MessageVisibility = Visibility.Collapsed;
WireCommands();
}
}
}
通过继承实用类“ViewModelBase
”并使用实用类“RelayCommand
”,可以轻松实现视图模型中的属性和命令。此类实现了 5 个 public
属性和 2 个 public
“RelayCommand
”对象。public
属性如下:
- “
ListNoOfStudents
”属性将由 XAML 视图用于显示一个下拉框,供用户选择要从 WCF 服务检索的“学生数量”。 - “
SelectedNoOfStudents
”属性用于 XAML 视图将用户选择的“学生数量”通知视图模型。 - “
ListOfStudents
”属性是从 WCF 服务检索到的“Student
”对象列表。 - “
Message
”是一个 String 类型属性。通过绑定,XAML 视图可以显示设置到此属性的文本。 - “
MessageVisibility
”属性控制显示“Message
”信息的 XAML 组件的 Visibility。
public
“RelayCommand
”对象如下:
- “
GetStudentsCommand
”将由视图用于“命令”视图模型发出 WCF 调用以检索学生。 - “
HideMessageCommand
”将由视图用于隐藏显示给用户的“Message
”文本。
您应该注意“ViewModelBase
”和“RelayCommand
”类的使用方式以及它们如何简化视图模型的开发。现在我们可以看一下应用程序的视图以及视图模型如何绑定到 XAML 视图。
View
Silverlight 应用程序的视图是“MainPage.xaml”文件。
<UserControl
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk"
x:Class="MVVMUtility.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"
mc:Ignorable="d" Style="{StaticResource WindowStyle}"
d:DesignHeight="500" d:DesignWidth="800">
<UserControl.DataContext>
<Binding Source="{StaticResource MainPageViewModel}" />
</UserControl.DataContext>
<Grid x:Name="LayoutRoot">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" HorizontalAlignment="Right"
Orientation="Horizontal">
<TextBlock Text="Please select the number of the students"
VerticalAlignment="Center" />
<ComboBox Width="150" Margin="10, 0, 5, 0"
ItemsSource="{Binding Path=ListNoOfStudents, Mode=OneTime}"
DisplayMemberPath="DisplayText"
SelectedItem="{Binding Path=SelectedNoOfStudents,
Mode=TwoWay}" />
<Button Content="Get students" Width="130"
Command="{Binding Path=GetStudentsCommand}" />
</StackPanel>
<sdk:DataGrid Grid.Row="1" Margin="0, 5, 0, 5"
IsReadOnly="True" ColumnWidth="120"
ItemsSource="{Binding Path=ListOfStudents, Mode=OneWay}">
</sdk:DataGrid>
</Grid>
<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>
此 XAML 视图包含以下重要组件:
- 一个 Combobox 绑定到“
ListNoOfStudents
”和“SelectedNoOfStudents
”属性。它从“ListNoOfStudents
”属性获取下拉项列表,并通过“SelectedNoOfStudents
”属性将用户选择发送到视图模型。 - 一个 Button 绑定到“
GetStudentsCommand
”命令。用户将使用它来发出命令以执行 WCF 调用。 - 一个 Datagrid 绑定到“
ListOfStudents
”属性。它用于显示从 WCF 服务获取的学生。
此 XML 文件还包含一个 Grid 以显示消息给用户。此网格的可见性绑定到“MessageVisibility
”属性。Textblock 在此 Grid 中显示“Message
”属性中的文本信息,并且此 Grid 中的按钮发出“HideMessageCommand
”命令以隐藏此 Grid。此 Silverlight 应用程序完全符合 MVVM 模式。您可以查看“MainPage.xaml”的代码隐藏文件,该文件已最小化,仅包含构造函数。
using System.Windows.Controls;
using System.Windows;
namespace MVVMUtility
{
public partial class MainPage : UserControl
{
public MainPage() { InitializeComponent(); }
}
}
视图模型在“App.xaml”文件中定义为静态资源。它在“MainPage.xaml”文件的开头绑定到 XAML 视图。以下是“App.xaml”文件:
<Application xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VModel="clr-namespace:MVVMUtility.ViewModels"
x:Class="MVVMUtility.App">
<Application.Resources>
<Style TargetType="UserControl" x:Key="WindowStyle">
<Setter Property="FontFamily" Value="Verdana" />
<Setter Property="FontSize" Value="12" />
</Style>
<VModel:MainPageViewModel x:Key="MainPageViewModel" />
</Application.Resources>
</Application>
宿主 Web 应用程序“MVVMUtilityWeb”
我们现在完成了 Silverlight 应用程序的开发。在测试运行应用程序之前,我想简要介绍一下“MVVMUtilityWeb
”项目中的宿主 Web 页“Default.aspx”。
<%@ Page Language="C#" AutoEventWireup="true" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>MVVM Utility</title>
<link type="text/css" rel="Stylesheet" href="Styles/SilverlightStyle.css" />
<script type="text/javascript" src="Scripts/Silverlight.js"></script>
</head>
<body>
<div id="silverlightControlHost">
<object data="data:application/x-silverlight-2," id="SilverlightObject"
type="application/x-silverlight-2" width="100%" height="100%">
<param name="source" value="ClientBin/MVVMUtility.xap"/>
<param name="onError" value="onSilverlightError" />
<param name="background" value="white" />
<param name="minRuntimeVersion" value="4.0.50826.0" />
<param name="autoUpgrade" value="true" />
<a href="http://go.microsoft.com/fwlink/?LinkID=149156&v=4.0.50826.0"
style="text-decoration:none">
<img src="http://go.microsoft.com/fwlink/?LinkId=161376"
alt="Get Microsoft Silverlight" style="border-style:none"/></a>
</object><iframe id="_sl_historyFrame"
style="visibility:hidden;height:0px;width:0px;border:0px"></iframe></div>
</body>
</html>
当您创建 Silverlight 应用程序时,Visual Studio 会自动为其创建一个宿主 Web 页。本文中的“default.aspx”文件不使用 Visual Studio 创建的 Web 页,而是将 Silverlight 相关的 CSS 样式和 JavaScript 整合到“Styles\SilverlightStyle.css”和“Scripts\Silverlight.js”文件中,其中“Styles\SilverlightStyle.css”文件如下所示:
html, body {height: 100%; overflow: auto;}
body {padding: 0; margin: 0;}
#silverlightControlHost {height: 100%; text-align:center;}
#SilverlightObject, #silverlightControlHost {min-width:600px; min-height:400px;}
运行应用程序
我们现在完成了示例 Silverlight 应用程序的开发,然后可以进行测试运行。将“MVVMUtilityWeb
”项目设置为启动项目,“Default.aspx”设置为起始页,我们就可以启动应用程序了。如果将下拉框保留为“* 请选择 *
”并单击“获取学生”按钮,我们将看到一个模态消息窗口显示要求我们进行选择。
单击“确定”按钮关闭模态窗口,选择“学生数量”并单击“获取学生”按钮,将调用 WCF 服务并检索学生列表。以下显示了检索 10 名学生数据的结果。
关注点
- 本文介绍了两个非常常用的实用类,用于在 Silverlight MVVM 应用程序中实现数据和命令绑定。随附的 Visual Studio 项目是一个完全符合 MVVM 模式的 Silverlight 应用程序,演示了如何使用这两个类。
- 在为 Silverlight MVVM 应用程序开发视图模型时,您不必使用“
ViewModelBase
”和“RelayCommand
”类。但它们确实使您的代码更整洁,通过避免直接处理 INotifyPropertyChanged 和 ICommand 接口,节省了很多代码。 - MVVM 是开发 Silverlight 应用程序的一个好模式。它使单元测试更加容易。但是,您可能并不总是获得实现 MVVM 所需的所有支持。当您的应用程序变得复杂并且您正在使用第三方控件时,情况尤其如此。在这种情况下,实现 MVVM 可能会变得困难,您应该能够将这些情况视为“例外”。Silverlight 仍在发展中。根据 Scott Guthrie 的说法,我们应该期望在 Silverlight 5 中看到更好的 MVVM 支持。
- 您可以在 Silverlight MVVM 应用程序中选择双向、单向和一次性绑定模式。绑定模式越强大,占用的资源越多。您应始终选择满足您需求的、最不强大的绑定模式。
- 我希望您喜欢我的文章,希望本文能以某种方式帮助您。
历史
这是本文的第一个修订版。