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

RIATasks:一个简单的 Silverlight CRUD 示例(使用 View Model)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (71投票s)

2010 年 6 月 27 日

CPOL

12分钟阅读

viewsIcon

350477

downloadIcon

2607

一个实现使用 Web 服务的创建、读取、更新和删除的 Silverlight 应用程序示例

一个简单的 Silverlight CRUD 示例

实时示例: http://silverlight.adefwebserver.com/RIATasks/

另请参阅: Silverlight RIA Tasks 2:动态 View Model

img32.jpg

本教程的原因是,我注意到我的朋友们在学习 Silverlight 时遇到了困难。他们花费大量时间“学习 Silverlight”,但却很难真正开始。

我还想向他们展示如何使用 **View Model 风格**编程,因为我相信使用 **View Model**,您将编写 **更少** 的 **代码**(您可能没想到这一点!)。不信?让我向您展示...

Silverlight 的不同之处在于它使用 异步通信 与启动它的网站进行通信。学习如何以这种方式设计应用程序可能有点挑战。

因此,我创建了一个端到端示例,它实现了这些目标

  • **创**建、**读**取、**更**新和**删**除数据库中的记录
  • 实现基于表单的安全性
  • 实现“精细化安全”(“仅允许 **用户一** 查看、编辑和创建自己的 **任务**”)
  • 实现 **View Model 风格**

View Model 风格

**View Model 风格**允许程序员创建一个完全没有 UI(用户界面)的应用程序。程序员只需创建一个 **View Model** 和一个 **Model**。然后,一位完全没有编程能力的**设计**师就可以从一个空白页面开始,在 **Microsoft Expression Blend 4**(或更高版本)中完全创建 **View**(UI)。如果您不熟悉 **View Model 风格**,建议您阅读 Silverlight View Model 风格:一个(过于)简化的解释 以获得介绍。

应用程序

首先,让我们看看示例应用程序。

当您首次启动应用程序时,您处于“已注销”状态。您可以使用下拉列表登录为 **用户一** 或 **用户二**。

单击 **添加** 按钮以添加一个新 **任务**。

单击 **更新** 按钮将保存 **任务**。

  • 单击列表框中的 **任务** 将显示 **任务**。
  • 单击 **更新** 按钮将保存任何更改
  • 单击 **删除** 按钮将删除 **任务**。

创建应用程序

现在我们将创建应用程序。您需要

  • Visual Studio 2010(或更高版本)
  • Expression Blend 4(或更高版本)
  • SQL Server(2005 或更高版本)

设置数据库

创建一个名为 **RIATasks** 的新数据库

使用以下脚本创建一个名为 **Tasks** 的表

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[Tasks]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[Tasks](
    [TaskID] [int] IDENTITY(1,1) NOT NULL,
    [TaskName] [nvarchar](50) NOT NULL,
    [TaskDescription] [nvarchar](max) NOT NULL,
    [UserID] [int] NOT NULL,
 CONSTRAINT [PK_Tasks] PRIMARY KEY CLUSTERED 
(
    [TaskID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]
END
GO
IF NOT EXISTS (SELECT * FROM sys.indexes WHERE object_id = OBJECT_ID(N'[dbo].[Tasks]') AND name = N'IX_Tasks_UserID')
CREATE NONCLUSTERED INDEX [IX_Tasks_UserID] ON [dbo].[Tasks] 
(
    [UserID] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, 
DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

您还需要设置安全性,以便在下一步中能够连接到数据库。

(如果 SQL Server 对您来说设置太难,您可以参考以下类似主题: Silverlight View Model 风格文件管理器View Model 风格:Silverlight 视频播放器

创建 Web 应用程序项目

打开 **Visual Studio**,然后选择 **文件**,再选择 **新建项目...**

创建一个 **Silverlight 应用程序** 项目。

接受默认设置,然后单击 **确定**。

项目将创建。

启用表单身份验证

我们需要设置 Web 应用程序以使用 **表单身份验证**。当用户登录时,他们将在其 Web 浏览器中创建一个加密的 身份验证“令牌”,其中包含他们的 **UserID**。Silverlight 应用程序在发出 Web 服务调用时将使用此“令牌”。Web 服务方法(将在后续步骤中创建)将检查此“令牌”以强制执行安全性。

打开 **Web.config** 文件。

将 **<authentication mode="Forms"/>** 添加到文件中。然后保存并关闭它。

创建默认页面

在 **RIATasks.Web** 项目中创建一个名为 **Default.aspx** 的 **Web 窗体**页面。

打开 **RIATasksTestPage.aspx** 页面,然后切换到 **源** 视图。

复制从 **<!DOCTYPE** 到页面末尾的所有内容...

...然后将其粘贴到 **Default.aspx** 页面的源中,替换从 **<!DOCTYPE** 到页面末尾的所有内容。

我们需要将 Silverlight 控件所在的 **Div** 标签转换为 **Panel** 控件,以便在用户未登录时可以以编程方式隐藏它。

  • 替换: **<div id="silverlightControlHost">**
    • 替换为: **<asp:panel id="silverlightControlHost" runat="server">**
  • 替换: **</div>**
    • 替换为: **</asp:panel>**

现在,我们需要添加一个下拉列表以供用户登录(在实际应用程序中,您会使用普通的 **登录表单**)。

在 **Form** 标签下方将以下代码插入页面

<asp:DropDownList ID="ddlUser" runat="server" AutoPostBack="True" 
onselectedindexchanged="ddlUser_SelectedIndexChanged">
<asp:ListItem Selected="True" Value="0">Logged Out</asp:ListItem>
<asp:ListItem Value="1">User One</asp:ListItem>
<asp:ListItem Value="2">User Two</asp:ListItem>
</asp:DropDownList>

打开 **Default.aspx.cs** 文件,并用以下代码替换 **所有** 代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.Security;
 
namespace RIATasks.Web
{
    public partial class Default : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                LogOut();
            }
        }
 
        #region ddlUser_SelectedIndexChanged
        protected void ddlUser_SelectedIndexChanged(object sender, EventArgs e)
        {
            int intSelectedUser = Convert.ToInt32(ddlUser.SelectedValue);
            if (intSelectedUser > 0)
            {
                LogUserIntoSite(Convert.ToInt32(ddlUser.SelectedValue));
            }
            else
            {
                LogOut();
            }
        }
        #endregion
 
        #region LogUserIn
        private void LogUserIntoSite(int intUser)
        {
            // Log the user into the site
            FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1,
              intUser.ToString(),
              DateTime.Now,
              DateTime.Now.AddDays(30),
              false,
              "Role One",
              FormsAuthentication.FormsCookiePath);
 
            // Encrypt the ticket.
            string encTicket = FormsAuthentication.Encrypt(ticket);
 
            // Create the cookie.
            Response.Cookies.Add(new HttpCookie(FormsAuthentication.FormsCookieName, encTicket));
 
            // Show the Silverlight control
            silverlightControlHost.Visible = true;
        }
        #endregion
 
        #region LogOut
        protected void LogOut()
        {
            FormsAuthentication.SignOut();
 
            // Hide the Silverlight control
            silverlightControlHost.Visible = false;
        }
        #endregion
    }
}

右键单击 **Default.aspx** 页面,然后选择 **设置为起始页...**

按 **F5** 运行项目。

项目将 **运行** 并在 Web 浏览器中 **打开**。

您将能够更改 **下拉列表** 以便 **登录** 为用户。

关闭 **Web 浏览器**。

创建数据层

向 **RIATaks.Web** 站点添加一个名为 **RIATasksDB.dbml** 的 **Linq to SQL** 类。

注意:您可以使用 **Entity Framework** 代替 **Linq to SQL**(或任何其他数据访问技术)。我们使用 **Linq to SQL** 仅仅因为它更容易设置。

选择 **服务器资源管理器**。

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

**数据层** 已完成。

**保存** 并 **关闭** 文件。

创建 Web 服务

向 **RIATaks.Web** 站点添加一个名为 **Webservice.asmx** 的 **Web 服务**文件。

打开创建的 **WebService.asmx.cs** 文件,并用以下代码替换 **所有** 代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Services;
 
namespace RIATasks.Web
{
    [WebService(Namespace = "http://OpenLightGroup.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    [System.ComponentModel.ToolboxItem(false)]
    public class WebService : System.Web.Services.WebService
    {
        #region GetCurrentUserID
        private int GetCurrentUserID()
        {
            int intUserID = -1;
            if (HttpContext.Current.User.Identity.IsAuthenticated)
            {
                // Get the current user
                intUserID = Convert.ToInt32(HttpContext.Current.User.Identity.Name);
            }
            return intUserID;
        }
        #endregion
 
        // Web Methods
 
        #region GetTasks
        [WebMethod]
        public List<Task> GetTasks()
        {
            // Create a collection to hold the results
            List<Task> colResult = new List<Task>();
 
            RIATasksDBDataContext DB = new RIATasksDBDataContext();
 
            var colTasks = from Tasks in DB.Tasks
                           where Tasks.UserID == GetCurrentUserID()
                           select Tasks;
 
            // Loop thru the Tasks
            foreach (var item in colTasks)
            {
                // Create a Task
                Task tmpTask = new Task();
 
                // Set only the TaskID and the Name
                // We do this because Description could be 
                // a large amount of data that will slow down
                // the application and we don't need it now
                tmpTask.TaskID = item.TaskID;
                tmpTask.TaskName = item.TaskName;
 
                // Add to the final results
                colResult.Add(tmpTask);
            }
 
            return colResult;
        }
        #endregion
 
        #region GetTask
        [WebMethod]
        public Task GetTask(int TaskID)
        {
            RIATasksDBDataContext DB = new RIATasksDBDataContext();
 
            var result = (from Tasks in DB.Tasks
                          where Tasks.TaskID == TaskID
                          where Tasks.UserID == GetCurrentUserID()
                          select Tasks).FirstOrDefault();
 
            return result;
        }
        #endregion
 
        #region DeleteTask
        [WebMethod]
        public string DeleteTask(int TaskID)
        {
            string strError = "";
            RIATasksDBDataContext DB = new RIATasksDBDataContext();
 
            try
            {
                var result = (from Tasks in DB.Tasks
                              where Tasks.TaskID == TaskID
                              where Tasks.UserID == GetCurrentUserID()
                              select Tasks).FirstOrDefault();
 
                if (result != null)
                {
                    DB.Tasks.DeleteOnSubmit(result);
                    DB.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                strError = ex.Message;
            }
 
            return strError;
        }
        #endregion
 
        #region UpdateTask
        [WebMethod]
        public string UpdateTask(Task objTask)
        {
            string strError = "";
            RIATasksDBDataContext DB = new RIATasksDBDataContext();
 
            try
            {
                var result = (from Tasks in DB.Tasks
                              where Tasks.TaskID == objTask.TaskID
                              where Tasks.UserID == GetCurrentUserID()
                              select Tasks).FirstOrDefault();
 
                if (result != null)
                {
                    result.TaskDescription = objTask.TaskDescription;
                    result.TaskName = objTask.TaskName;
 
                    DB.SubmitChanges();
                }
            }
            catch (Exception ex)
            {
                strError = ex.Message;
            }
 
            return strError;
        }
        #endregion
 
        #region InsertTask
        [WebMethod]
        public Task InsertTask(Task objTask)
        {
            RIATasksDBDataContext DB = new RIATasksDBDataContext();
 
            try
            {
                Task InsertTask = new Task();
 
                InsertTask.TaskDescription = objTask.TaskDescription;
                InsertTask.TaskName = objTask.TaskName;
                InsertTask.UserID = GetCurrentUserID();
 
                DB.Tasks.InsertOnSubmit(InsertTask);
                DB.SubmitChanges();
 
                // Set the TaskID 
                objTask.TaskID = InsertTask.TaskID;
            }
            catch (Exception ex)
            {
                // Log the error
                objTask.TaskID = -1;
                objTask.TaskDescription = ex.Message;
            }
 
            return objTask;
        }
        #endregion
    }
}

请注意,Web 服务方法调用 **GetCurrentUserID()**,它使用 **Convert.ToInt32(HttpContext.Current.User.Identity.Name)** 来获取当前用户。

当前的 **UserID** 在用户登录并创建身份验证“**令牌**”时设置。用户的 Web 浏览器在所有请求中都传递此令牌,包括 Silverlight 应用程序将发出的 Web 服务请求。

要检查一切是否设置正确,您可以*右键单击* **WebService.asmx** 文件,然后选择 **在浏览器中查看**。

Web 方法将显示。

注意:您可以使用 WCF 代替。我们使用 .asmx Web 服务是因为它们更容易部署。

Silverlight 项目

现在我们将完成 **RIATasks** Silverlight 项目。首先,我们需要从 Silverlight 项目创建对刚才创建的 Web 服务的引用。

然后,我们需要创建一个 **Model** 来调用我们创建的 Web 服务,以及一个 **ICommand** 支持类,它将允许我们在 **View Model** 中轻松引发事件。

创建 Web 服务引用

在 **Silverlight 项目**中,*右键单击* **引用**,然后选择 **添加服务引用...**

  • 单击 **发现** 按钮
  • 为命名空间输入 **wsRIATasks**
  • 单击 **确定** 按钮

Silverlight 项目和网站之间的连接已完成。我们将在后续步骤中实现代码,该代码将使用此引用来调用我们创建的 Web 服务。

添加引用

添加对 Microsoft.VisualBasic 的引用

ICommand 支持类

添加一个新文件夹并命名为 **Classes**。然后*右键单击*它并选择 **新建项...**

创建一个名为 **DelegateCommand.cs** 的类。

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

using System.Windows.Input;
using System;
 
// From http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/
namespace RIATasks
{
    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
    }
}

此类允许我们轻松调用 **ICommands**。您可以在此处找到有关此类的更多信息:http://johnpapa.net/silverlight/5-simple-steps-to-commanding-in-silverlight/

模型

创建一个名为 **Models** 的文件夹,以及一个名为 **TasksModel.cs** 的类。

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

using Microsoft.VisualBasic;
using System.Linq;
using System;
using System.Collections.Generic;
using System.ServiceModel;
using RIATasks.wsRIATasks;

namespace RIATasks
{
    public class TasksModel
    {
        #region GetTask
        public static void GetTask(int TaskID, EventHandler<GetTaskCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();

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

            WS.GetTaskCompleted += eh;
            WS.GetTaskAsync(TaskID);
        }
        #endregion

        #region GetTasks
        public static void GetTasks(EventHandler<GetTasksCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();

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

            WS.GetTasksCompleted += eh;
            WS.GetTasksAsync();
        }
        #endregion

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

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

            WS.DeleteTaskCompleted += eh;
            WS.DeleteTaskAsync(TaskID);
        }
        #endregion

        #region UpdateTask
        public static void UpdateTask(Task objTask, EventHandler<UpdateTaskCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();

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

            WS.UpdateTaskCompleted += eh;
            WS.UpdateTaskAsync(objTask);
        }
        #endregion

        #region InsertTask
        public static void InsertTask(Task objTask, EventHandler<InsertTaskCompletedEventArgs> eh)
        {
            // Set up web service call
            WebServiceSoapClient WS = new WebServiceSoapClient();

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

            WS.InsertTaskCompleted += eh;
            WS.InsertTaskAsync(objTask);
        }
        #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
    }
}

注意: GetBaseAddress() 是一个方法,用于确定用户启动了包含 Silverlight 应用程序的 .xap 的位置,并使用该位置来确定 WebService.asmx 文件的位置。Web 服务方法使用该地址来调用 Web 服务。

我们现在已经在 **Model** 中创建了与前面创建的 **Web 服务**中的方法通信的方法。

创建一个名为 **ViewModels** 的文件夹,并添加一个名为 **MainPageModel.cs** 的类。

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

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows.Input;
using System.Windows;
using RIATasks.wsRIATasks;
 
namespace RIATasks
{
    public class MainPageModel : INotifyPropertyChanged
    {
        public MainPageModel()
        {
            // Set the command property
            GetTasksCommand = new DelegateCommand(GetTasks, CanGetTasks);
            GetTaskCommand = new DelegateCommand(GetTask, CanGetTask);
            DeleteTaskCommand = new DelegateCommand(DeleteTask, CanDeleteTask);
            UpdateTaskCommand = new DelegateCommand(UpdateTask, CanUpdateTask);
            AddNewTaskCommand = new DelegateCommand(AddNewTask, CanAddNewTask);
 
            // The following line prevents Expression Blend
            // from showing an error when in design mode
            if (!DesignerProperties.IsInDesignTool)
            {
                // Get the Tasks for the current user
                GetTasks();
 
                // Set Visibility
                HasCurrentTask = Visibility.Collapsed;
                AddVisibility = Visibility.Visible;
                UpdateVisibility = Visibility.Collapsed;
                DeleteVisibility = Visibility.Collapsed;
            }
        }
 
   
 
        // Utility
 
        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
 
        private void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }
        #endregion
    }
}

此代码实现了 **INotifyPropertyChanged**,它将被 **Properties**(将在后续步骤中创建)用于在 **View** 更新时自动通知 **View**。

构造函数 **MainPageModel()** 设置了一些 **ICommands**(将在后续步骤中完全实现)。它还调用 **GetTasks()** 方法,该方法将调用 **Model**(然后调用 Web 服务),并检索已登录用户的任何 **Tasks**。

***注意**:您会看到代码中的大多数行下方有波浪形的红线,因为它们设置了尚未创建的属性和命令。

将以下代码添加到中:

// Properties
 
        #region CurrentTask
        private Task _CurrentTask = new Task();
        public Task CurrentTask
        {
            get { return _CurrentTask; }
            private set
            {
                if (CurrentTask == value)
                {
                    return;
                }
                _CurrentTask = value;
                this.NotifyPropertyChanged("CurrentTask");
            }
        }
        #endregion
 
        #region AddVisibility
        private Visibility _AddVisibility = Visibility.Visible;
        public Visibility AddVisibility
        {
            get { return _AddVisibility; }
            private set
            {
                if (AddVisibility == value)
                {
                    return;
                }
                _AddVisibility = value;
                this.NotifyPropertyChanged("AddVisibility");
            }
        }
        #endregion
 
        #region UpdateVisibility
        private Visibility _UpdateVisibility = Visibility.Visible;
        public Visibility UpdateVisibility
        {
            get { return _UpdateVisibility; }
            private set
            {
                if (UpdateVisibility == value)
                {
                    return;
                }
                _UpdateVisibility = value;
                this.NotifyPropertyChanged("UpdateVisibility");
            }
        }
        #endregion
 
        #region DeleteVisibility
        private Visibility _DeleteVisibility = Visibility.Visible;
        public Visibility DeleteVisibility
        {
            get { return _DeleteVisibility; }
            private set
            {
                if (DeleteVisibility == value)
                {
                    return;
                }
                _DeleteVisibility = value;
                this.NotifyPropertyChanged("DeleteVisibility");
            }
        }
        #endregion
 
        #region HasTasks
        private Visibility _HasTasks = Visibility.Collapsed;
        public Visibility HasTasks
        {
            get { return _HasTasks; }
            private set
            {
                if (HasTasks == value)
                {
                    return;
                }
                _HasTasks = value;
                this.NotifyPropertyChanged("HasTasks");
            }
        }
        #endregion
 
        #region HasCurrentTask
        private Visibility _HasCurrentTask = Visibility.Collapsed;
        public Visibility HasCurrentTask
        {
            get { return _HasCurrentTask; }
            private set
            {
                if (HasCurrentTask == value)
                {
                    return;
                }
                _HasCurrentTask = value;
                this.NotifyPropertyChanged("HasCurrentTask");
            }
        }
        #endregion
 
        #region Message
        private string _Message;
        public string Message
        {
            get { return _Message; }
            private set
            {
                if (Message == value)
                {
                    return;
                }
                _Message = value;
                this.NotifyPropertyChanged("Message");
            }
        }
        #endregion
这看起来代码量很大,但它只是 **Properties**,它们将保存 **View** 将设置和使用的值。当它们更改时,所有这些都会引发 **NotifyPropertyChanged**。

将以下代码添加到中:
        // Collections
 
        #region colTasks
        private ObservableCollection<Task> _colTasks
            = new ObservableCollection<Task>();
        public ObservableCollection<Task> colTasks
        {
            get { return _colTasks; }
            private set
            {
                if (colTasks == value)
                {
                    return;
                }
                _colTasks = value;
                this.NotifyPropertyChanged("colTasks");
            }
        }
        #endregion

这类似于一个 **Property**,但它包含一个 **Tasks** 的 **Collection**,而不是单个值。

将以下代码添加到中:
        
        // Operations

        #region GetTasks
        private void GetTasks()
        {
            // Clear the current Tasks
            colTasks.Clear();
            // Call the Model to get the collection of Tasks
            TasksModel.GetTasks((Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // loop thru each item
                    foreach (var Task in EventArgs.Result)
                    {
                        // Add to the colTasks collection
                        colTasks.Add(Task);
                    }

                    // Count the records returned
                    if (colTasks.Count == 0)
                    {
                        // If there are no records, indicate that
                        Message = "No Records Found";

                        // Set HasCurrentTask
                        HasCurrentTask = Visibility.Collapsed;

                        // We have no Tasks so set HasTasks
                        HasTasks = Visibility.Collapsed;
                    }
                    else
                    {
                        // We have Tasks so set HasTasks
                        HasTasks = Visibility.Visible;
                    }
                }
            });
        }
        #endregion

        #region GetTask
        private void GetTask(int intTaskID)
        {
            // Call the Model to get the Task
            TasksModel.GetTask(intTaskID, (Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Set the CurrentTask Property
                    CurrentTask = EventArgs.Result;

                    // Set Visibility
                    HasCurrentTask = Visibility.Visible;
                    AddVisibility = Visibility.Visible;
                    UpdateVisibility = Visibility.Visible;
                    DeleteVisibility = Visibility.Visible;
                }
            });
        }
        #endregion

        #region DeleteTask
        private void DeleteTask(Task objTask)
        {
            // Call the Model to delete the Task
            TasksModel.DeleteTask(objTask.TaskID, (Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Set the Error Property
                    Message = EventArgs.Result;

                    // Set current Task to null
                    CurrentTask = null;

                    // Update the Tasks list
                    GetTasks();

                    // Set Visibility
                    HasCurrentTask = Visibility.Collapsed;
                    AddVisibility = Visibility.Visible;
                    UpdateVisibility = Visibility.Collapsed;
                    DeleteVisibility = Visibility.Collapsed;
                }
            });
        }
        #endregion

        #region UpdateTask
        private void UpdateTask(Task objTask)
        {
            // Call the Model to UpdateTask the Task
            TasksModel.UpdateTask(objTask, (Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Set the Error Property
                    Message = EventArgs.Result;

                    // Update the Tasks list
                    GetTasks();

                    // Set Visibility
                    HasCurrentTask = Visibility.Visible;
                    AddVisibility = Visibility.Visible;
                    UpdateVisibility = Visibility.Visible;
                    DeleteVisibility = Visibility.Visible;
                }
            });
        }
        #endregion

        #region InsertTask
        private void InsertTask(Task objTask)
        {
            // Call the Model to Insert the Task
            TasksModel.InsertTask(objTask, (Param, EventArgs) =>
            {
                if (EventArgs.Error == null)
                {
                    // Set the CurrentTask Property
                    CurrentTask = EventArgs.Result;

                    // Update the Tasks list
                    GetTasks();

                    // Set Visibility
                    HasCurrentTask = Visibility.Visible;
                    AddVisibility = Visibility.Visible;
                    UpdateVisibility = Visibility.Visible;
                    DeleteVisibility = Visibility.Visible;
                }
            });
        }
        #endregion

        #region SetToNewTask
        private void SetToNewTask()
        {
            // Create a empty Task
            // so form will be blank
            Task objTask = new Task();

            // Set TaskID = -1 so we know it's 
            // a new Task
            objTask.TaskID = -1;

            // Set the CurrentTask Property
            CurrentTask = objTask;

            // Set Visibility
            HasCurrentTask = Visibility.Visible;
            AddVisibility = Visibility.Collapsed;
            UpdateVisibility = Visibility.Visible;
            DeleteVisibility = Visibility.Collapsed;
        }
        #endregion

这些是 **View Model** 将执行的操作。这些是执行实际工作的方法。大多数情况下,这些方法只是调用 **Model** 中的方法。

将以下代码添加到中:
     // Commands
 
        #region GetTasksCommand
        public ICommand GetTasksCommand { get; set; }
        public void GetTasks(object param)
        {
            GetTasks();
        }
 
        private bool CanGetTasks(object param)
        {
            return true;
        }
        #endregion
 
        #region GetTaskCommand
        public ICommand GetTaskCommand { get; set; }
        public void GetTask(object param)
        {
            // Get the Task that was passed as a parameter
            Task objTask = (Task)param;
 
            // Call GetTask to get and set
            // the CurrentTask property
            GetTask(objTask.TaskID);
        }
 
        private bool CanGetTask(object param)
        {
            // Only allow this ICommand to fire 
            // if a Task was passed as a parameter
            return ((param as Task) != null);
        }
        #endregion
 
        #region DeleteTaskCommand
        public ICommand DeleteTaskCommand { get; set; }
        public void DeleteTask(object param)
        {
            if (CurrentTask.TaskID == -1)
            {
                // This is a new Task
                SetToNewTask();
            }
            else
            {
                // This is an Existing Task
                DeleteTask(CurrentTask);
            }
        }
 
        private bool CanDeleteTask(object param)
        {
            // Do not allow if there is no Current Task
            return (CurrentTask != null);
        }
        #endregion
 
        #region UpdateTaskCommand
        public ICommand UpdateTaskCommand { get; set; }
        public void UpdateTask(object param)
        {
            if (CurrentTask.TaskID == -1)
            {
                // This is a new Task
                InsertTask(CurrentTask);
            }
            else
            {
                // This is an Update
                UpdateTask(CurrentTask);
            }
        }
 
        private bool CanUpdateTask(object param)
        {
            // Do not allow if there is no Current Task
            return (CurrentTask != null);
        }
        #endregion
 
        #region AddNewTaskCommand
        public ICommand AddNewTaskCommand { get; set; }
        public void AddNewTask(object param)
        {
            SetToNewTask();
        }
 
        private bool CanAddNewTask(object param)
        {
            return true;
        }
        #endregion

这些是 **ICommands**。它们将从 **View** 调用。

在 **Visual Studio** 中,选择 **生成**,然后选择 **生成 RIATasks**。

项目应该能够成功生成。

在 **Expression Blend 4(或更高版本)**中打开 **MainPage.xaml** 文件。

项目将在 **Expression Blend** 中打开。

在 **对象和时间线**窗口中单击 **LayoutRoot**,然后在 **属性**窗口中,在 **搜索**框中键入 *DataContext*。

接下来,单击 **DataContext** 旁边的 **新建** 按钮。

选择 **MainPageModel**,然后单击 **确定**。

单击 **数据** 选项卡,然后展开 **MainPageModel**(在 **数据上下文** 部分下)。

您将看到 **MainPageModel** 中的所有公共 **Properties**、**Collections** 和 **ICommands**。

创建示例数据

如果也能看到示例数据,设计表单会更容易。

在 **数据**窗口中,单击 **创建示例数据** 图标。

选择 **从类创建示例数据...**

选择 **MainPageModel**,然后单击 **确定**

您现在将看到 **MainPageModelSampleData** 类。

生成 View

在 **示例数据**部分单击 **colTasks**...

...并将其拖到页面上。

它将创建一个带有示例数据的 **ListBox**。注意:*运行时将显示真实数据,而不是示例数据。*

在 **ListBox** 的 **属性**中,将 **HorizontalAlignment** 设置为 *Left*,将 **VerticalAlignment** 设置为 *Top*

*右键单击* **ListBox**,然后选择

编辑附加模板 > **编辑生成项** > **编辑当前**

在 **对象和时间线**窗口中单击第三个 **TextBlock**

在 **TextBlock** 的 **属性**中,选择 **Text** 旁边的 **高级选项**

选择 **数据绑定**

我们看到它绑定到 **TaskName**。这是我们希望在 **ListBox** 中显示的唯一 **TextBlock**

在 **对象和时间线**窗口中,删除其他三个 TextBlocks

单击 **返回范围** 图标

现在 **ListBox** 已格式化

现在,选择 **Grid** 控件

在 **ListBox** 旁边绘制一个 **Grid**

将鼠标悬停在 **Grid** 的边缘附近,然后单击鼠标左键,以创建单元格

从 **工具栏**中,拖一个 **TextBox**

在 **Grid** 上绘制一个 **TextBox**

从 **数据**窗口中,拖 **TaskName**

将 **TaskName** 拖到 **TextBox** 上

在 **TextBox** 的 **属性**中

  • 将 **Width** 和 **Height** 设置为 **Auto**
  • 设置 **Horizontal** 和 **Vertical** **Alignment**
  • 将所有 **Margins** 设置为 **0**

在 **Grid** 上绘制另一个 **TextBox**

在 **TextBox** 的 **属性**中

  • 将 **Width** 和 **Height** 设置为 **Auto**
  • 设置 **Horizontal** 和 **Vertical** **Alignment**
  • 将所有 **Margins** 设置为 **0**

从 **数据**窗口中,将 **TaskDescription** 拖到 **TextBox** 上

将 **TextBlock** 拖到 **Grid** 上以作为 **Name** 和 **Description** 标签,并设置它们的 **Text** **Properties**。

在 **工具栏**中单击 **Button**

在 **ListBox** 上方绘制一个 **Button**

将按钮的 **Content** 设置为 **Add**

添加一个 **Update** 和一个 **Delete** 按钮

(我们将在后续步骤中设置按钮的 Click 事件)

设置可见性

现在我们已经将所有控件都放在了页面上,接下来我们将连接各个控件的 **可见性**。

在 **对象和时间线**窗口中单击 **Grid**

在 **Grid** 的 **属性**中,为 **Visibility** 选择 **高级选项**

选择数据绑定...

绑定到 **HasCurrentTask**,然后单击 **确定**

根据上图,连接剩余的 **Visibility** **Properties**。

注意:如果绑定可见性导致某些元素在 **设计**模式下消失,您可以编辑示例数据文件...

...并将它们设置为 **Visible**

使用行为

现在我们将使用 **InvokeCommandAction** 行为,以允许按钮在 **View Model** 中引发 **ICommands**。

添加按钮

获取一个 **InvokeCommandAction** 行为。

将其拖到 **对象和时间线**窗口中的 **Add** 按钮上

在 **Properties** 窗口中,对于行为

  • 为 **EventName** 选择 **Click**
  • 在 **Common Properties** 下,单击 **Command** 旁边的 **Data bind** 图标

选择 **Data Context** 选项卡,然后选择 **AddNewTaskCommand**,最后单击 **确定**。

根据上图,向 **Update** 和 **Delete** 按钮添加 **InvokeCommandAction** 行为。

从 ListBox 中选择一个 Task

  • 将一个 **InvokeCommandAction** 行为拖到 **ListBox** 上。
  • 将 **EventName** 设置为 **SelectionChanged**
  • 在 **Command** 旁边单击 **Data bind**

选择 **GetTaskCommand**,然后单击 **确定**

单击 **CommandParameter** 旁边的 **Advanced options**

从 **ListBox** 中选择 **SelectedItem**,然后单击 **确定**

添加样式

image

请参阅此链接上的文章,以获取样式的 **ResourceDictionary** 以及应用该样式的说明。

构建并运行项目

在 **Projects** 窗口中,*右键单击* **RIATasks.Web** 项目,然后选择 **Startup Project**

*右键单击* **Default.aspx** 页面,然后选择 **Startup**

按 **F5** **构建**并 **运行**项目

项目已完成。

当您想编写更少的代码时,请使用 View Model

绑定结合 **INotifyPropertyChanged**,可以为您节省大量代码。使用 **View Model** 可以让您充分利用 **Microsoft Expressions Blend** 中的许多有用功能。**View Model** 也是一个简单的模式,因此您可以快速轻松地实现 **Properties**、**Collections** 和 **ICommands**。

实际的“自定义代码”包含在 **View Model** 的“**Operations**”部分。您会发现代码量不多,而且大部分代码只是调用 **Model** 并设置 **View Model** 中的一个 **Property**。另外请注意,当像“**Task**”这样的对象被设置为当前选定的 **Task** 时,我们无需拆分 **Task** 对象中的每个字段并单独处理,我们只需将实际的“**Task**”对象在 **Model**、**View Model** 和 **View** 之间传递。

我们可以将 **Properties**(如自定义对象 **Task** 和 **Visibility**)原样暴露给 **View**,并让设计**师**负责实际实现它们,这非常有帮助。即使我们也是设计**师**,我们也可以使用 **Expression Blend** 的拖放功能来快速、准确地组装 UI。

程序员可以简单地专注于通过 **Model** 将数据传入和传出 **View Model**,并执行业务规则。这使得程序员可以专注于清晰、易于测试的任务。UI 问题被抽象化了。

我相信,在这里程序员会发现,与不使用 **View Model** 相比,他们将使用 **更少的代码**。至少,我合作过的许多程序员发现这种体验更加愉快,因为他们可以专注于过程逻辑。

更新于 2010 年 7 月 14 日: 删除了 Rx Extensions

© . All rights reserved.