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

使用 Silverlight DataGrid 和 View Model / MVVM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (48投票s)

2010 年 7 月 29 日

Ms-PL

17分钟阅读

viewsIcon

257842

downloadIcon

6136

使用 View Model,实现 Silverlight DataGrid 的内联编辑、分页、排序和按钮事件。

[点击此处查看在线示例]

另请参阅

视图模型与 Silverlight DataGrid

Silverlight DataGrid 是一个强大的控件,支持内联编辑分页排序以及拖放列重新排序。使用传统的代码隐藏技术理解它可能具有挑战性,而使用视图模型/MVVM 风格编程时,有时则令人费解。

在接下来的示例中,我们不会直接在视图模型中引用 DataGrid。我们将创建的代码可以被任何集合控件(如 ListBox)使用。

img32.jpg

注意:如果您是视图模型风格编程的新手,建议您阅读:Silverlight 视图模型风格:一个(过于)简化的解释

您要操作的不是 DataGrid - 而是绑定到 DataGrid 的集合

理解 Silverlight DataGrid 的一个主要方面是,它始终使用实现了 ICollectionView 的“数据视图”。根据 http://msdn.microsoft.com/en-us/library/system.componentmodel.icollectionview(VS.95).aspx

DataGrid 控件使用此接口来访问分配给其 ItemsSource 属性的数据源中的指示功能。如果 ItemsSource 实现 IList 但未实现 ICollectionView,则 DataGrid 会将 ItemsSource 包装在一个内部 ICollectionView 实现中。”

在最初的示例中,我们将仅将集合绑定到 DataGrid,并允许创建此自动 CollectionView。在排序示例中,我们将实现自己的 CollectionViewSource,以便能够挂钩并检测事件。

我们将发现,当我们能够控制视图模型中 DataGrid 所绑定的集合时,它将为我们提供实现所需功能所需的所有控制。

(有关 DataGrid 可用方法和属性的更多信息,请参阅: http://msdn.microsoft.com/en-us/library/system.windows.controls.datagrid_methods(v=VS.95).aspx)

本教程使用Visual Studio 2010(或更高版本)和Expression Blend 4(或更高版本)。

应用程序

该应用程序允许通过单击列标题进行排序,并通过 DataGrid 底部的按钮进行分页。

您可以拖动列标题并重新排序它们。

除非您正在编辑评论,否则评论字段仅显示前 25 个字符。

双击评论字段即可编辑评论。单击其他地方(应用程序中非编辑框的任何位置)即可保存评论。

单击清除按钮会将该行的评论字段变为****

在 DataGrid 上显示项目

让我们先在 DataGrid 上简单地显示项目。

首先,我们创建一个带有网站的 Silverlight 项目。

然后,我们创建一个名为 RIATasks 的数据库,并添加一个名为 RIAComments 的表。

USE [RIATasks]
GO
CREATE TABLE [dbo].[RIAComments](
    [CommentID] [int] IDENTITY(1,1) NOT NULL,
    [Comment] [nvarchar](max) NOT NULL,
    [CommentUpdateDate] [datetime] NOT NULL,
 CONSTRAINT [PK_RIAComments] PRIMARY KEY CLUSTERED 
(
    [CommentID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, 
       IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, 
       ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
GO

注意:下载中包含一个文件 ReadMe_DatabaseSetup.txt,其中包含表的脚本,以及创建示例数据的脚本。

RIADataGrid.Web 网站添加一个 LINQ to SQL 类,名为 RIATasksDB.dbml

选择服务器资源管理器

建立到 RIATasks 数据库的连接,并将 Tasks 表拖到对象关系设计器表面。

保存关闭文件。

现在我们将创建一个 Web Service 方法,该方法将使用以下代码返回表中的项目。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
using Microsoft.VisualBasic;

namespace RIADataGrid.Web
{
    [WebService(Namespace = "http://OpenLightGroup.net/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class WebService : System.Web.Services.WebService
    {
        // Web Methods

        #region GetRIAComments
        [WebMethod]
        public List<RIAComment> GetRIAComments()
        {
            RIATasksDBDataContext DB = new RIATasksDBDataContext();

            var colRIAComments = (from RIAComments in DB.RIAComments
                                  select RIAComments).ToList();

            return colRIAComments;
        }
        #endregion
    }
}

在 Silverlight 应用程序项目中,我们添加一个服务引用。我们将服务引用命名为 wsRIARIAComments

同时,向 Silverlight 应用程序项目添加对 Microsoft.VisualBasic 的程序集引用。

现在,我们将在 Silverlight 应用程序项目中创建一个模型,以调用 Web Service 中的 GetRIAComments 方法。

创建一个名为 Models 的文件夹,以及一个名为 RIACommentsModel.cs 的类,其代码如下:

using Microsoft.VisualBasic;
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel;
using RIADataGrid.wsRIARIAComments;
using RIADataGrid;

namespace RIADataGrid
{
    public class RIACommentsModel
    {
        #region GetRIAComments
        public static void GetRIAComments(
           EventHandler<GetRIACommentsCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();

            // Set the EndpointAddress
            WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

            WS.GetRIACommentsCompleted += eh;
            WS.GetRIACommentsAsync();
        }
        #endregion

        // Utility

        #region GetBaseAddress
        private static Uri GetBaseAddress()
        {
            // Get the web address of the .xap that launched this application     
            string strBaseWebAddress = App.Current.Host.Source.AbsoluteUri;
            // Find the position of the ClientBin directory        
            int PositionOfClientBin =
                App.Current.Host.Source.AbsoluteUri.ToLower().IndexOf(@"/clientbin");
            // Strip off everything after the ClientBin directory         
            strBaseWebAddress = Strings.Left(strBaseWebAddress, PositionOfClientBin);
            // Create a URI
            Uri UriWebService = new Uri(String.Format(@"{0}/WebService.asmx", 
                                        strBaseWebAddress));
            // Return the base address          
            return UriWebService;
        }
        #endregion
    }
}

这将使我们能够从视图模型调用 GetRIAComments Web Service 方法。

接下来,我们创建视图模型;这是我们的编程逻辑将包含的位置。

创建一个名为 ViewModels 的文件夹,以及一个名为 MainPageModel.cs 的类,其代码如下:

using System;
using System.Linq;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows;
using RIADataGrid.wsRIARIAComments;
using Microsoft.VisualBasic;
using System.Windows.Controls;
using System.Windows.Data;
using System.Collections.Specialized;

namespace RIADataGrid
{
    public class MainPageModel : INotifyPropertyChanged
    {
        public MainPageModel()
        {   
            // The following line prevents Expression Blend
            // from showing an error when in design mode
            if (!DesignerProperties.IsInDesignTool)
            {
                // Get the RIAComments
                GetRIAComments();
            }
        }

        // Operations

        #region GetRIAComments
        private void GetRIAComments()
        {
            // Call the Model to get the collection of RIAComments
            RIACommentsModel.GetRIAComments((Sender, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Clear the current RIAComments
                    colRIAComments.Clear();

                    // loop thru each item
                    foreach (var RIAComment in EventArgs.Result)
                    {
                        // Add to the colRIAComments collection
                        colRIAComments.Add(RIAComment);
                    }                                       
                }
            });
        }
        #endregion

        // Collections

        #region colRIAComments
        private ObservableCollection<RIAComment> _colRIAComments
            = new ObservableCollection<RIAComment>();
        public ObservableCollection<RIAComment> colRIAComments
        {
            get { return _colRIAComments; }
            private set
            {
                if (colRIAComments == value)
                {
                    return;
                }
                _colRIAComments = value;
                this.NotifyPropertyChanged("colRIAComments");
            }
        }
        #endregion

        // Utility

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;

        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

我们将在后面的部分扩展此类和其他类;目前,它仅用 Web Service 的结果填充一个名为 colRIAComments 的集合。

最后一部分是创建视图。

右键单击 MainPage.xaml,在 Expression Blend 中打开它。

对象和时间线窗口中,单击LayoutRoot

在其属性中,选择DataContext

将其设置为 MainPageModel

选择数据选项卡,然后选择从类创建示例数据...

选择 MainPageModel

单击资源按钮,然后选择DataGrid

在主页面上绘制一个DataGrid

colRIAComments 从示例数据拖动...

...并将其放入 DataGrid。

示例数据将显示在 DataGrid 上。

注意:如果您在对象和时间线窗口的 DataGrid 下方看不到 Columns,请关闭并重新打开 Expression Blend。当您在 Expression Blend 中重新打开项目时,您将看到 Columns 部分。

格式化列

对象和时间线窗口中,选择Comment列。

将其属性设置为与上图匹配。

注意:重要的是将布局设置为 Width 1Star。没有这些设置,Clear 按钮(在本教程后面使用)将无法工作。

接下来,在对象和时间线窗口中,选择CommentID列。将其属性设置为与上图匹配。

最后,在对象和时间线窗口中,选择CommentUpdateDate列。将其属性设置为与上图匹配。

ID 列移到 Comment 列的上方。

右键单击项目窗口中的 Web 项目,并将其设置为启动项目

右键单击.aspx页面(在您的项目中,它可能名为“*...TestPage.aspx*”),并将其设置为启动

F5 来构建并运行项目。项目将运行并在您的 Web 浏览器中打开。

Inline Editing

现在我们将修改项目以启用评论字段的内联编辑。实际上,由于我们在列定义中取消了IsReadOnly复选框,因此它已经启用了。我们现在将启用更新后的评论保存到数据库。

此外,我们将修改 GetRIAComments Web Service,使其仅显示评论字段的前 25 个字符。这将允许应用程序运行得更快,因为它需要显示的数据更少。当用户编辑评论时,它将调用一个 Web Service 方法,该方法将返回完整的评论并显示给用户进行编辑。

我们还将实现一个方法,以确保我们只更新自上次检索以来未更改的记录。我们通过仅在上次更新时间匹配时才更新记录来实现这一点。

添加 Errors 属性

我们从教程《中心 Silverlight 业务规则验证》中学到,我们可以向 LINQ to SQL 表的部分类添加一个 Errors 属性。

我们创建一个名为 Classes 的文件夹和一个名为 DataBaseParticalClass.cs 的文件,并使用以下代码:

using System.Collections.Generic;

namespace RIADataGrid.Web
{
    #region public partial class RIAComment
    public partial class RIAComment
    {
        public List<string> Errors = new List<string>();
    }
    #endregion
}

修改 Web Service

现在我们将修改现有的 Web Service 方法并添加另外两个。

#region GetRIAComments
[WebMethod]
public List<RIAComment> GetRIAComments()
{
    // Create a collection to hold the results
    List<RIAComment> colResult = new List<RIAComment>();

    RIATasksDBDataContext DB = new RIATasksDBDataContext();

    var colRIAComments = from RIAComments in DB.RIAComments
                         select RIAComments;

    // Loop thru the Tasks
    foreach (var item in colRIAComments)
    {
        // Create a Task
        RIAComment OutputRIAComment = new RIAComment();

        // Get only the first 25 charaters of the comment
        OutputRIAComment.CommentID = item.CommentID;
        OutputRIAComment.Comment = Strings.Left(item.Comment, 25) + " ...";
        OutputRIAComment.CommentUpdateDate = item.CommentUpdateDate;

        // Add to the final results
        colResult.Add(OutputRIAComment);
    }

    return colResult;
}
#endregion

#region GetRIAComment
[WebMethod]
public RIAComment GetRIAComment(int RIACommentID)
{
    RIATasksDBDataContext DB = new RIATasksDBDataContext();

    var result = (from RIAComments in DB.RIAComments
                  where RIAComments.CommentID == RIACommentID
                  select RIAComments).FirstOrDefault();

    return result;
}
#endregion

#region UpdateRIAComment
[WebMethod]
public RIAComment UpdateRIAComment(RIAComment objRIAComment)
{
    DateTime dtCurrentDate = DateTime.Now;
    RIATasksDBDataContext DB = new RIATasksDBDataContext();

    var result = (from RIAComments in DB.RIAComments
                  where RIAComments.CommentID == objRIAComment.CommentID
                  // This will only perform the update if the CommentUpdateDate matches
                  // the existing CommentUpdateDate in the database
                  where RIAComments.CommentUpdateDate == objRIAComment.CommentUpdateDate
                  select RIAComments).FirstOrDefault();

    if (result != null)
    {               
        result.Comment = Strings.Left(objRIAComment.Comment, 10000);
        result.CommentUpdateDate = dtCurrentDate;
        
        DB.SubmitChanges();

        // Update the CommentUpdateDate on the object that will be returned
        objRIAComment.CommentUpdateDate = dtCurrentDate;
    }
    else
    {
        // The record could not be found because the CommentUpdateDate did not match
        // Or the record was deleted
        objRIAComment.Errors.Add("The record was not updated");
    }

    // Update comments to only show 25 characters
    objRIAComment.Comment = Strings.Left(objRIAComment.Comment, 25) + " ...";

    return objRIAComment;
}
#endregion

以下是 Web 方法的概述:

  • GetRIAComments - 修改为仅返回前 25 个字符,并在 Comment 末尾添加“...” 。
  • GetRIAComment - 检索单个 Comment。这用于在用户使用内联编辑时获取完整的 Comment。
  • UpdateRIAComment - 仅当 CommentUpdateDate 字段匹配时才更新 Comment。如果更新成功,它还会返回更新后的 Comment。如果不成功,Errors 属性将填充错误消息。

更新 Web 引用

右键单击 wsRIARIAComments Web 引用,然后选择更新服务引用

更新模型

将以下方法添加到现有的模型中:

#region GetRIAComment
public static void GetRIAComment(int RIACommentID, 
       EventHandler<GetRIACommentCompletedEventArgs> eh)
{
    // Set up web service call
    WebServiceSoapClient WS = new WebServiceSoapClient();

    // Set the EndpointAddress
    WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

    WS.GetRIACommentCompleted += eh;
    WS.GetRIACommentAsync(RIACommentID);
}
#endregion

#region UpdateRIAComment
public static void UpdateRIAComment(RIAComment objRIAComment, 
       EventHandler<UpdateRIACommentCompletedEventArgs> eh)
{
    // Set up web service call
    WebServiceSoapClient WS = new WebServiceSoapClient();

    // Set the EndpointAddress
    WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

    WS.UpdateRIACommentCompleted += eh;
    WS.UpdateRIACommentAsync(objRIAComment);
}
#endregion

这些方法只是调用了添加的 Web Service 方法。

注意:由于 GetRIAComments Web Service 方法的参数未更改,因此我们无需更改模型中的相应方法。

ViewModel

现在我们将更新视图模型。首先,我们将添加一个辅助类,它将帮助我们实现 ICommandICommand 用于从视图视图模型引发事件。

DelegateCommand 辅助类

创建一个名为 Classes 的文件夹和一个名为 DelegateCommand.cs 的文件。

用以下代码替换所有代码:

using System.Windows.Input;
using System;
 
// From http://johnpapa.net/silverlight/
//          5-simple-steps-to-commanding-in-silverlight/
namespace RIADataGrid
{
    public class DelegateCommand : ICommand
    {
        Func<object, bool> canExecute;
        Action<object> executeAction;
        bool canExecuteCache;
 
        public DelegateCommand(Action<object> executeAction, 
                               Func<object, bool> canExecute)
        {
            this.executeAction = executeAction;
            this.canExecute = canExecute;
        }
 
        #region ICommand Members
 
        public bool CanExecute(object parameter)
        {
            bool temp = canExecute(parameter);
 
            if (canExecuteCache != temp)
            {
                canExecuteCache = temp;
                if (CanExecuteChanged != null)
                {
                    CanExecuteChanged(this, new EventArgs());
                }
            }
 
            return canExecuteCache;
        }
 
        public event EventHandler CanExecuteChanged;
 
        public void Execute(object parameter)
        {
            executeAction(parameter);
        }
 
        #endregion
    }
}

此类使我们能够轻松调用 ICommand。您可以在此处获取有关此类的更多信息:http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/

ViewModel

将以下属性添加到视图模型:

#region MessageVisibility
private Visibility _MessageVisibility
    = Visibility.Collapsed;
public Visibility MessageVisibility
{
    get { return _MessageVisibility; }
    private set
    {
        if (_MessageVisibility == value)
        {
            return;
        }
        _MessageVisibility = value;
        this.NotifyPropertyChanged("MessageVisibility");
    }
}
#endregion

#region Errors
private ObservableCollection<string> _Errors
    = new ObservableCollection<string>();
public ObservableCollection<string> Errors
{
    get { return _Errors; }
    private set
    {
        if (_Errors == value)
        {
            return;
        }
        _Errors = value;
        this.NotifyPropertyChanged("Errors");
    }
}
#endregion

这会添加一个用于保存任何返回错误属性,以及一个允许错误列表显示(或不显示)的属性。

将以下方法添加到类中:

#region GetRIAComment
private void GetRIAComment(RIAComment Comment)
{
    // Call the Model to get the full RIAComment
    RIACommentsModel.GetRIAComment(Comment.CommentID, (Sender, EventArgs) =>
    {
        if (EventArgs.Error == null)
        {
            // Find the comment in the colRIAComments collection
            var CommentInCollection = (from comment in colRIAComments
                                       where comment.CommentID == EventArgs.Result.CommentID
                                       select comment).FirstOrDefault();

            if (CommentInCollection != null)
            {
                CommentInCollection.Comment = EventArgs.Result.Comment;
            }
        }
    });
}
#endregion

#region UpdateRIAComment
private void UpdateRIAComment(RIAComment objRIAComment)
{
    // Call the Model to UpdateRIAComment the RIAComment
    RIACommentsModel.UpdateRIAComment(objRIAComment, (Sender, EventArgs) =>
    {
        if (EventArgs.Error == null)
        {
            // Find the comment 
            var CommentInCollection = (from comment in colRIAComments
                                       where comment.CommentID == EventArgs.Result.CommentID
                                       select comment).FirstOrDefault();

            if (CommentInCollection != null)
            {
                // Update the Comment
                CommentInCollection.Comment = EventArgs.Result.Comment;
                CommentInCollection.CommentUpdateDate = EventArgs.Result.CommentUpdateDate;

            }

            // Show any errors
            Errors = EventArgs.Result.Errors;
            // Set the visibility of the Message ListBox
            MessageVisibility = (Errors.Count > 0) ? 
                                   Visibility.Visible : Visibility.Collapsed;

        }
    });
}
#endregion

这些方法提取 Comment 字段的完整内容,并通过调用模型中的相应方法来更新 Comment。

将以下代码添加到中:

#region GetRIACommentsCommand
public ICommand GetRIACommentsCommand { get; set; }
public void GetRIAComments(object param)
{
    GetRIAComments();
}

private bool CanGetRIAComments(object param)
{
    return true;
}
#endregion

#region GetRIACommentCommand
public ICommand GetRIACommentCommand { get; set; }
public void GetRIAComment(object param)
{
    GetRIAComment((RIAComment)param);
}

private bool CanGetRIAComment(object param)
{
    return true;
}
#endregion

#region UpdateRIACommentCommand
public ICommand UpdateRIACommentCommand { get; set; }
public void UpdateRIAComment(object param)
{
    // This is an Update
    UpdateRIAComment((RIAComment)param);
}

private bool CanUpdateRIAComment(object param)
{
    // Do not allow if there is no Current RIAComment
    return (param as RIAComment != null);
}
#endregion

此代码实现了 ICommand。我们将从视图使用 Behaviors 来引发这些 ICommand

将以下代码添加到视图模型的构造函数中:

GetRIACommentsCommand = new DelegateCommand(GetRIAComments, CanGetRIAComments);
GetRIACommentCommand = new DelegateCommand(GetRIAComment, CanGetRIAComment);
UpdateRIACommentCommand = new DelegateCommand(UpdateRIAComment, CanUpdateRIAComment);

此代码使用 DelegateCommand 辅助类来设置 ICommand

View

现在我们将完成视图。您需要构建项目,然后关闭并重新打开 MainPage.xaml 文件,以使 Expression Blend 能够识别新属性。

数据上下文窗口中,单击Errors集合...

...并将其拖到设计表面。将自动创建一个 ListBox 并将其绑定到该集合。

创建 ListBox 后,调整 ListBox 的大小,使其位于右下角。

数据上下文窗口中,单击 MessageVisibility 属性,然后将其拖放到Errors listbox 上。

将出现一个创建数据绑定框。为Property of [ListBox]选择Visibility,然后单击OK

添加 Behaviors

资源中,选择InvokeCommand Action Behavior。

将其拖放到对象和时间线窗口中的 DataGrid 上。

在 Behavior 的属性中,将Event Name选择为PreparingCellForEdit

单击 Command 旁边的数据绑定图标。

将其绑定到 GetRIAComment 命令。

选择 CommandParameter 旁边的高级选项,然后从弹出菜单中选择数据绑定...

将其绑定到 DataGrid 的 SelectedItem

使用另一个 InvokeCommand Action Behavior 重复此过程

  • RowEditEnded 选择为Event Name
  • UpdateRIACommentsCommand 选择为Command
  • DataGridSelectedItem 选择为CommandParameter

F5 构建并运行应用程序。您将看到评论只显示前 25 个字符。

当您双击一行时,您将看到完整的评论,并且可以对其进行修改。

如果您在数据库中修改了记录的更新时间,而您已开始编辑它,则它将不会保存,并且会显示错误。

DataGrid 分页

目前,所有记录都显示在 DataGrid 中。如果我们有很多记录,这将是一个问题(应用程序会运行得更慢)。现在我们将实现记录的分页。

在 Web Service 中,用以下代码替换 GetRIAComments Web 方法的代码:

#region GetRIAComments
[WebMethod]
public List<RIAComment> GetRIAComments(int intPage)
{
    // Create a collection to hold the results
    List<RIAComment> colResult = new List<RIAComment>();

    RIATasksDBDataContext DB = new RIATasksDBDataContext();

    var colRIAComments = from RIAComments in DB.RIAComments
                         select RIAComments;

    // Compute the CurrentPage
    int CurrentPage = ((intPage * 5) - 5);
    // Implement paging
    colRIAComments = colRIAComments.Skip(CurrentPage).Take(5);

    // Loop thru the Tasks
    foreach (var item in colRIAComments)
    {
        // Create a Task
        RIAComment OutputRIAComment = new RIAComment();

        // Get only the first 25 charaters of the comment
        OutputRIAComment.CommentID = item.CommentID;
        OutputRIAComment.Comment = Strings.Left(item.Comment, 25) + " ...";
        OutputRIAComment.CommentUpdateDate = item.CommentUpdateDate;

        // Add to the final results
        colResult.Add(OutputRIAComment);
    }

    return colResult;
}
#endregion

右键单击 wsRIARIAComments Web 引用,然后选择更新服务引用

将模型中的 GetRIAComments 方法修改为以下内容:

#region GetRIAComments
public static void GetRIAComments(int intPage, 
       EventHandler<GetRIACommentsCompletedEventArgs> eh)
{
    // Set up web service call
    WebServiceSoapClient WS = new WebServiceSoapClient();

    // Set the EndpointAddress
    WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

    WS.GetRIACommentsCompleted += eh;
    WS.GetRIACommentsAsync(intPage);
}
#endregion

在视图模型中,添加以下属性:

#region CurrentPage
private int _CurrentPage = 1;
public int CurrentPage
{
    get { return _CurrentPage; }
    private set
    {
        if (CurrentPage == value)
        {
            return;
        }
        _CurrentPage = value;
        this.NotifyPropertyChanged("CurrentPage");
    }
}
#endregion

GetRIAComments 方法修改为以下内容:

#region GetRIAComments
private void GetRIAComments()
{
    // Call the Model to get the collection of RIAComments
    RIACommentsModel.GetRIAComments(CurrentPage, (Sender, EventArgs) =>
    {
        if (EventArgs.Error == null)
        {
            // Clear the current RIAComments
            colRIAComments.Clear();

            // loop thru each item
            foreach (var RIAComment in EventArgs.Result)
            {
                // Add to the colRIAComments collection
                colRIAComments.Add(RIAComment);
            }
        }
    });
}
#endregion

添加以下 ICommand

#region PreviousPageCommand
public ICommand PreviousPageCommand { get; set; }
public void PreviousPage(object param)
{
    CurrentPage--;
    GetRIAComments();
}

private bool CanPreviousPage(object param)
{
    // Must not already be on the first page
    return (CurrentPage > 1);
}
#endregion

#region NextPageCommand
public ICommand NextPageCommand { get; set; }
public void NextPage(object param)
{
    CurrentPage++;
    GetRIAComments();
}

private bool CanNextPage(object param)
{
    // There must be records to move to the next page
    return (colRIAComments.Count > 0);
}
#endregion

最后,将以下代码添加到构造函数中:

PreviousPageCommand = new DelegateCommand(PreviousPage, CanPreviousPage);
NextPageCommand = new DelegateCommand(NextPage, CanNextPage);

View

在页面上绘制两个按钮并标记它们。

InvokeCommandAction Behavior 拖到每个按钮上。

在每个Behavior属性中,将EventName选择为Click,然后单击Command旁边的高级选项框。

选择数据绑定...

并绑定到相应的命令(PreviousPageCommandNextPageCommand)。

对另一个按钮重复此过程。

分页现在可以工作了。

从 DataGrid 内的按钮单击调用 ICommand

接下来,我们想在 DataGrid 的每一行上放置一个按钮,当单击该按钮时,将当前评论设置为****

首先,将以下代码添加到视图模型:

#region ClearCommand
public ICommand ClearCommand { get; set; }
public void Clear(object param)
{
    // Clear the Comment
    RIAComment objRIAComment = (RIAComment)param;
    objRIAComment.Comment = "****";
}

private bool CanClear(object param)
{
    // Do not allow if there is no Current RIAComment
    return (param as RIAComment != null);
}
#endregion

在视图模型的构造函数中添加以下行:

ClearCommand = new DelegateCommand(Clear, CanClear);

右键单击 DataGrid,然后在对象和时间线窗口中,创建一个 DataGridTextColumn

添加列后,将其拖动到第一列。

注意,如果您收到值不在预期的范围内。错误...

您有一个重复或编号错误的 DisplayIndex。您可以通过在 XAML 视图中正确编号列(从第一列的 0 开始)来修复此问题。

重要的是 DataGrid 中的每一行都已绑定到数据;否则,在尝试运行项目时会收到值不能为空的错误。

在(刚刚添加的)新行的属性中,选择Binding旁边的高级选项

将其绑定到 colRIAComments

设置绑定后,将其余属性设置为与上图匹配。

再次右键单击该列,然后复制编辑Edit CellStyle

创建样式资源框中,选择新建...

点击**确定**。

返回创建样式资源框,单击OK

现在您正在编辑样式;右键单击样式并选择编辑当前

单击ContentPresenter。这将移动主设计页面,以便您编辑内容模板

在设计页面上绘制一个 Button。

Button属性中:

  • Margins设置为5
  • Content设置为Clear
  • 单击Button,然后设置DataContext...

...到主页面使用的ViewModelMainPageModel)。

您这样做是为了让您将在下一步添加的Behavior能够看到主视图模型上的ICommand

否则,Button(及其上附加的任何 Behaviors)的作用域将仅限于 DataGrid 中行的元素。

但是,请注意,我们正在实例化 MainPageModel 的新实例。我们将引发的 ICommand 不会在连接到 UI 所在的 .xaml 页面的 MainPageModel 实例上,而是在另一个副本上。

另请注意,将为每一行创建一个新的 MainPageModel 实例,并且在此代码示例中,类构造函数中有一个调用来获取 RIAComments,该调用将每次被调用。因此,当此应用程序加载时,它将额外调用 6 次 GetRIAComments Web Service。它只会这样做(在第一页上),但您可能根本不喜欢此行为。

解决方案是创建一个单独的视图模型,如果它被实例化多次,则不会产生任何不必要的副作用。我在此教程中涵盖了如何实现这一点:使用行内按钮删除 Silverlight DataGrid 行

InvokeCommandAction Behavior 拖到Button上。

Behavior属性中,将EventName设置为Click,并将Command绑定到ClearCommand

但是,您现在的问题是,Button 使用“主页面的作用域”,因此您不再能够访问当前所在的行,因此您无法将其作为参数传递。

没问题,contentPresenter(Button 的父级)仍然绑定到“当前行的上下文”,因此您可以将其 DataContext 作为参数传递。

Behavior属性中,单击CommandParameter旁边的高级选项

将参数绑定到 ContentPresenterDataContext

F5 构建并运行项目。

当您单击一行的Clear按钮时,该行的Comment将变为****

您可以轻松地将 Button 引发的 ICommand 更改为更新删除 Comment。

排序

我们将排序留到最后,因为它实际上需要最多的代码。但是,在这种情况下,我们根本不会修改视图(除了更改 DataGrid 的绑定)。我们需要做的是创建一个自己的集合,该集合实现了 ICollectionView

我们需要这样做的原因是为了能够挂钩到我们集合中的事件,并检测用户何时正在对 DataGrid 进行排序。目前,DataGrid 会进行排序。但是,它只对当前页进行排序。我们不直接允许 DataGrid 自动处理排序,而是调用 Web Service 并在此处对记录进行排序,然后提取已应用排序的当前页面。

这将提供正确的排序体验。例如,如果您在第二页并且按 ID 号排序记录,您将看到整个排序集合的第二页。

修改 GetRIAComments Web Service 方法,以允许传递排序参数并将其应用于查询。

#region GetRIAComments
[WebMethod]
public List<RIAComment> GetRIAComments(int intPage, 
                   string SortProperty, string SortDirection)
{
    // Create a collection to hold the results
    List<RIAComment> colResult = new List<RIAComment>();

    RIATasksDBDataContext DB = new RIATasksDBDataContext();

    var colRIAComments = from RIAComments in DB.RIAComments
                         select RIAComments;

    if (SortDirection == "Descending")
    {
        switch (SortProperty)
        {
            case "CommentID":
                colRIAComments = colRIAComments.OrderByDescending(x => x.CommentID);
                break;
            case "Comment":
                colRIAComments = colRIAComments.OrderByDescending(x => x.Comment);
                break;
            case "CommentUpdateDate":
                colRIAComments = 
                  colRIAComments.OrderByDescending(x => x.CommentUpdateDate);
                break;
            default:
                colRIAComments = colRIAComments.OrderByDescending(x => x.CommentID);
                break;
        }
    }
    else
    {
        switch (SortProperty)
        {
            case "CommentID":
                colRIAComments = colRIAComments.OrderBy(x => x.CommentID);
                break;
            case "Comment":
                colRIAComments = colRIAComments.OrderBy(x => x.Comment);
                break;
            case "CommentUpdateDate":
                colRIAComments = colRIAComments.OrderBy(x => x.CommentUpdateDate);
                break;
            default:
                colRIAComments = colRIAComments.OrderBy(x => x.CommentID);
                break;
        }
    }

    // Compute the CurrentPage
    int CurrentPage = ((intPage * 5) - 5);
    // Implement paging
    colRIAComments = colRIAComments.Skip(CurrentPage).Take(5);

    // Loop thru the Tasks
    foreach (var item in colRIAComments)
    {
        // Create a Task
        RIAComment OutputRIAComment = new RIAComment();

        // Get only the first 25 charaters of the comment
        OutputRIAComment.CommentID = item.CommentID;
        OutputRIAComment.Comment = Strings.Left(item.Comment, 25) + " ...";
        OutputRIAComment.CommentUpdateDate = item.CommentUpdateDate;

        // Add to the final results
        colResult.Add(OutputRIAComment);
    }

    return colResult;
}
#endregion

右键单击 wsRIARIAComments Web 引用,然后选择更新服务引用

将模型中的 GetRIAComments 方法修改为以下内容:

#region GetRIAComments
public static void GetRIAComments(int intPage, string SortProperty, 
       string SortDirection, EventHandler<GetRIACommentsCompletedEventArgs> eh)
{
    // Set up web service call
    WebServiceSoapClient WS = new WebServiceSoapClient();

    // Set the EndpointAddress
    WS.Endpoint.Address = new EndpointAddress(GetBaseAddress());

    WS.GetRIACommentsCompleted += eh;
    WS.GetRIACommentsAsync(intPage, SortProperty, SortDirection);
}
#endregion

这允许将排序参数传递给 GetRIAComments Web Service 方法。

将以下属性添加到视图模型:

#region ViewSource
private CollectionViewSource _ViewSource = new CollectionViewSource();
public CollectionViewSource ViewSource
{
    get { return _ViewSource; }
    private set
    {
        _ViewSource = value;
        this.NotifyPropertyChanged("ViewSource");
    }
}
#endregion

这提供了一个 CollectionViewSource,我们可以将 DataGrid 绑定到它。当我们这样做时,DataGrid 将不再使用其内部的 View Source。然后,我们将能够挂钩到我们否则无法访问的排序事件。

将以下属性添加到视图模型:

#region SortProperty
private string _SortProperty;
public string SortProperty
{
    get { return _SortProperty; }
    private set
    {
        if (SortProperty == value)
        {
            return;
        }
        _SortProperty = value;
        this.NotifyPropertyChanged("SortProperty");
    }
}
#endregion

#region SortDirection
private string _SortDirection;
public string SortDirection
{
    get { return _SortDirection; }
    private set
    {
        if (SortDirection == value)
        {
            return;
        }
        _SortDirection = value;
        this.NotifyPropertyChanged("SortDirection");
    }
}
#endregion

这允许我们存储当前正在排序的字段以及排序的方向。

将以下代码添加到视图模型的构造函数中:

// Connect the ViewSource to the Comments collection
// The DataGrid will be bound to the ViewSource so that it
// can implement sorting that we can wire-up an event handler to
ViewSource.Source = colRIAComments;

// Wire-up an event handler to the SortDescriptions collection on the ViewSource
INotifyCollectionChanged sortchangeNotifier = 
          ViewSource.View.SortDescriptions as INotifyCollectionChanged;

// Call View_CollectionChanged when the SortDescriptions collection is changed
sortchangeNotifier.CollectionChanged += 
          new NotifyCollectionChangedEventHandler(View_CollectionChanged);

此代码将 colRIAComments 分配给 CollectionViewSourceViewSource)。

然后,它会挂钩一个处理程序到 SortDescriptions 发生变化的任何更改。当将绑定到集合的 DataGrid 更改其排序时(当用户单击 DataGrid 的标题时),CollectionViewSource 将自动更改 SortDescriptions 集合。

添加以下代码以响应事件:

#region View_CollectionChanged
void View_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
    // Clear the underlying collection to prevent the ViewSource
    // from sorting the collection currently displayed in the ViewSource
    // and then sorting it again after the web service call 
    colRIAComments.Clear();

    // When the sort is in Add mode, it is the second (the last step)
    // of defining a new Sort
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        // re-query the datasource
        GetRIAComments();
    }
}
#endregion

此方法会多次调用(对于放置在 SortDescriptions 集合中的每个操作)。

NotifyCollectionChangedAction.Add操作最后放置在集合中(在排序更改后),这时我们希望重新查询 Web Service 并提取记录,并将当前排序传递给它。

将以下方法添加到类中:

#region SetSortProperties
private void SetSortProperties()
{
    // Set the Sort PropertyName and Direction 
    // If there is anything in the SortDescriptions collection
    // Items are placed here when the Control attached to this ViewSource
    // is sorted (for example, by clicking on the Header in the DataGrid)
    if (ViewSource.View != null)
    {
        if (ViewSource.View.SortDescriptions.Count > 0)
        {
            SortProperty = ViewSource.View.SortDescriptions[0].PropertyName;
            SortDirection = ViewSource.View.SortDescriptions[0].Direction.ToString();
        }
    }
}
#endregion

这将在 SortDescriptions 集合中查找正在排序的属性(字段名)和方向。它将这些值放入 SortPropertySortDirection 属性(之前创建的)。这些将传递给 Web Service。

此方法将由以下更改后的方法调用:

#region GetRIAComments
private void GetRIAComments()
{
    // Set Sort Properties if there are any
    SetSortProperties();

    // Call the Model to get the collection of RIAComments
    RIACommentsModel.GetRIAComments(CurrentPage, SortProperty, 
                     SortDirection, (Sender, EventArgs) =>
    {
        if (EventArgs.Error == null)
        {
            // Clear the current RIAComments
            colRIAComments.Clear();

            // loop thru each item
            foreach (var RIAComment in EventArgs.Result)
            {
                // Add to the colRIAComments collection
                colRIAComments.Add(RIAComment);
            }
        }
    });
}
#endregion

此方法调用 Web Service 并提取记录页面。

最后一步是构建项目,然后单击 DataGrid,在属性中,在 ItemsSource 旁边,选择高级选项

选择数据绑定...

将其绑定到 ViewSource > View

现在,如果您转到最后一页并进行排序,它将对整个集合进行排序,而不仅仅是出现在最后一页上的记录。

样式

您可以轻松地为整个应用程序设置主题。首先,安装Silverlight Toolkit

定位主题。

将其拖到[UserControl]上。

应用程序将被主题化。

视图模型 - 代码更少,真的!

希望您能看到,视图模型一点也不难。一旦您看到它是如何完成的,它就真的不复杂。Expression Blend 是为了在“视图模型风格”下工作而设计的,因此当您使用这种简单的模式时,您应该会更容易使用 Expression Blend。

虽然使用代码隐藏实现 DataGrid 可能看起来更容易,但您通常会发现您需要创建大量代码来查找和修改 DataGrid 中的值和属性。

DataGrid 等控件的设计目的是绑定到集合。视图模型旨在实现绑定。是绑定为您节省了代码。一旦创建了绑定,它将自动执行功能。您不必为每个功能显式编写代码。最重要的是,您将直接绑定到您最终关心的 DataGrid 元素并从中收集参数,而不是挂钩到事件然后寻找您想要的值。

此外,您会意识到许多编程功能最好实现在底层数据源上,而不是 DataGrid 本身。

另请注意,此示例使用标准的 Web Services;您可以轻松使用 WCFWCF RIA Services。视图和视图模型将保持完全相同。

© . All rights reserved.