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






4.98/5 (71投票s)
一个实现使用 Web 服务的创建、读取、更新和删除的 Silverlight 应用程序示例
一个简单的 Silverlight CRUD 示例
实时示例: http://silverlight.adefwebserver.com/RIATasks/
另请参阅: Silverlight RIA Tasks 2:动态 View Model
本教程的原因是,我注意到我的朋友们在学习 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**,然后单击 **确定**
添加样式
请参阅此链接上的文章,以获取样式的 **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