65.9K
CodeProject 正在变化。 阅读更多。
Home

Silverlight MVVM 应用程序的数据和命令绑定

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.90/5 (10投票s)

2011年2月15日

CPOL

8分钟阅读

viewsIcon

92646

downloadIcon

2873

本文总结了在 Silverlight MVVM 应用程序中用于数据和命令绑定的两个实用类,并通过一个运行中的示例演示了如何使用这两个类。

引言

本文总结了在 Silverlight MVVM 应用程序中用于数据和命令绑定的两个实用类,并通过一个运行中的示例演示了如何使用这两个类。

背景

MVVM 设计模式现在已成为构建 Silverlight 应用程序的默认设计模式。数据绑定命令绑定是在 Silverlight 中开发 MVVM 应用程序的基本构建块。本文将总结两个非常常用的实用类,以帮助实现 Silverlight MVVM 应用程序中的绑定,并提供一个运行示例来使用这两个类。将介绍的两个实用类如下:

  • ViewModelBase”类用于帮助实现数据绑定。
  • RelayCommand”类用于帮助实现命令绑定。

如果您对 Silverlight 中 MVVM 的更普遍讨论感兴趣,您可以轻松找到参考资料,例如此篇此篇此篇

SolutionExplorer.JPG

随附的 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 ContractStudent”和一个 Service ContractStudentService”。Operation ContractGenerateStudents”接受一个整数,表示 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”设置为起始页,我们就可以启动应用程序了。如果将下拉框保留为“* 请选择 *”并单击“获取学生”按钮,我们将看到一个模态消息窗口显示要求我们进行选择。

NoSelectResult.JPG

单击“确定”按钮关闭模态窗口,选择“学生数量”并单击“获取学生”按钮,将调用 WCF 服务并检索学生列表。以下显示了检索 10 名学生数据的结果。

SelectResult.JPG

关注点

  • 本文介绍了两个非常常用的实用类,用于在 Silverlight MVVM 应用程序中实现数据和命令绑定。随附的 Visual Studio 项目是一个完全符合 MVVM 模式的 Silverlight 应用程序,演示了如何使用这两个类。
  • 在为 Silverlight MVVM 应用程序开发视图模型时,您不必使用“ViewModelBase”和“RelayCommand”类。但它们确实使您的代码更整洁,通过避免直接处理 INotifyPropertyChangedICommand 接口,节省了很多代码。
  • MVVM 是开发 Silverlight 应用程序的一个好模式。它使单元测试更加容易。但是,您可能并不总是获得实现 MVVM 所需的所有支持。当您的应用程序变得复杂并且您正在使用第三方控件时,情况尤其如此。在这种情况下,实现 MVVM 可能会变得困难,您应该能够将这些情况视为“例外”。Silverlight 仍在发展中。根据 Scott Guthrie 的说法,我们应该期望在 Silverlight 5 中看到更好的 MVVM 支持。
  • 您可以在 Silverlight MVVM 应用程序中选择双向、单向和一次性绑定模式。绑定模式越强大,占用的资源越多。您应始终选择满足您需求的、最不强大的绑定模式。
  • 我希望您喜欢我的文章,希望本文能以某种方式帮助您。

历史

这是本文的第一个修订版。

© . All rights reserved.