【转】Silverlight MVVM 贴近实战(一)

【转】http://leelei.blog.51cto.com/856755/871759

今天我们通过一个登录得例子来看看Silverlight的用法以及MVVM模式。这系列的博客我会把以前做的一个WinForm的小程序改装成SL。首先先上一张图。

好了功能就这么点,今天我们先把登陆界面做出来。要说这个小程序,我先把程序架构贴出来,如下所示。

首先,SilverLight的宿主是MVC3项目,整个框架采用SilverLight调用Controller/Action的模式。在Server端我们用到了领域驱动设计一小部分,为了降低耦合,采用了Unity注入。在Server端,主要包括Repository泛型设计,EntityFrameWork 4.1 edmx,Domain中的一个小工厂模式,以及应用层,业务层。他们之间的调用关系依次为Application调用Repository,Business调用Application,Controller调用Business,而Model贯穿于它们。具体的大家看看代码就知道了。在Client端,主要包括Common,DataAccess,Entity,ViewModel,Common主要是一些加密解密,序列化、反序列化等。DataAccess主要是负责调用Controller/Action,从Server端获取数据。Entity包括一些反序列化对象,以及向Server端传递的对象(如果调用的是MVC的Controller,都必须序列化成Json或者XML,如果调用的是WCF或者WebService,定义成DTO就可以了)。ViewModel定义了与页面和Model交互的一些对象,一般是用来双向绑定。好了基本上就是这么一个情况。我们看看代码,首先看Client端的登陆界面。

  1. <navigation:Pagex:Class="MISInfoManage.Login"
  2. xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3. xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4. xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
  5. xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
  6. mc:Ignorable="d"
  7. xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
  8. xmlns:source="clr-namespace:MISInfoManage.Resources"
  9. Title="Login Page"Loaded="Page_Loaded">
  10. <navigation:Page.Resources>
  11. <source:LoginResourcex:Key="LoginResource"></source:LoginResource>
  12. </navigation:Page.Resources>
  13. <Gridx:Name="LayoutRoot">
  14. <Grid.Background>
  15. <ImageBrushImageSource="/MISInfoManage;component/Images/LoginBack.jpg"/>
  16. </Grid.Background>
  17. <Grid.RowDefinitions>
  18. <RowDefinitionHeight="300"></RowDefinition>
  19. <RowDefinitionHeight="Auto"></RowDefinition>
  20. <RowDefinitionHeight="Auto"></RowDefinition>
  21. <RowDefinitionHeight="Auto"></RowDefinition>
  22. </Grid.RowDefinitions>
  23. <Grid.ColumnDefinitions>
  24. <ColumnDefinitionWidth="530"></ColumnDefinition>
  25. <ColumnDefinitionWidth="*"></ColumnDefinition>
  26. </Grid.ColumnDefinitions>
  27. <TextBlockText="{Binding Tb_Title,Source={StaticResource LoginResource}}"FontSize="48"FontFamily="Arial"Foreground="White"Grid.Row="0"Grid.Column="0"Grid.ColumnSpan="2"HorizontalAlignment="Center"VerticalAlignment="Center"></TextBlock>
  28. <TextBlockFontSize="16"HorizontalAlignment="Right"Text="{Binding Tb_UserName, Source={StaticResource LoginResource}}"Grid.Row="1"Grid.Column="0"></TextBlock>
  29. <TextBoxText="{Binding UserNo,Mode=TwoWay}"Width="300"Grid.Row="1"Grid.Column="1"Margin="0,0,0,15"HorizontalAlignment="Left"Height="30"></TextBox>
  30. <TextBlockText="{Binding Tb_UserPwd,Source={StaticResource LoginResource}}"Grid.Row="2"Grid.Column="0"HorizontalAlignment="Right"FontSize="16"></TextBlock>
  31. <TextBoxText="{Binding UserPwd,Mode=TwoWay}"Width="300"Grid.Row="2"Grid.Column="1"HorizontalAlignment="Left"Height="30"></TextBox>
  32. <StackPanelGrid.Row="3"Grid.Column="0"Grid.ColumnSpan="2"HorizontalAlignment="Center"Orientation="Horizontal">
  33. <Buttonx:Name="BtnLogin"Content="{Binding BtnLogin,Source={StaticResource LoginResource}}"Margin="80,20,20,0"Style="{StaticResource BtnLoginStyle}"Click="BtnLogin_Click"></Button>
  34. <Buttonx:Name="BtnCancel"Content="{Binding BtnCancel,Source={StaticResource LoginResource}}"Margin="10,20,0,0"Width="90"Style="{StaticResource BtnLoginStyle}"Click="BtnCancel_Click"></Button>
  35. </StackPanel>
  36. </Grid>
  37. </navigation:Page>

在这里需要说明的是几个Binding,其中

  1. <TextBlockText="{Binding Tb_Title,Source={StaticResource LoginResource}}"FontSize="48"FontFamily="Arial"Foreground="White"Grid.Row="0"Grid.Column="0"Grid.ColumnSpan="2"HorizontalAlignment="Center"VerticalAlignment="Center"></TextBlock>

这段代码绑定的是一段文字“人事档案管理系统”。在哪里呢,在我们定义的资源文件里。

就是LoginResource.resx文件,我们打开看看

看到了吧,在这里需要注意的是每次更改为资源文件后,必须把访问修饰符改成Public,并且把资源文件对应的cs文件中的构造函数的修饰符改成public。这个资源文件在页面我们需要引用一下。就是页面顶端这段代码:xmlns:source="clr-namespace:MISInfoManage.Resources",以及<navigation:Page.Resources>         <source:LoginResource x:Key="LoginResource"></source:LoginResource>     </navigation:Page.Resources>这两段。OK页面看完了,我们看看后台。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Net;
  5. using System.Windows;
  6. using System.Windows.Controls;
  7. using System.Windows.Documents;
  8. using System.Windows.Input;
  9. using System.Windows.Media;
  10. using System.Windows.Media.Animation;
  11. using System.Windows.Shapes;
  12. using System.Windows.Navigation;
  13. using System.IO;
  14. namespace MISInfoManage
  15. {
  16. using ViewModel;
  17. using DataAccess.Login;
  18. using Client.Common;
  19. using Client.Entity;
  20. using MISInfoManage.Resources;
  21. public partial class Login : Page
  22. {
  23. LoginUser user;
  24. public Login()
  25. {
  26. InitializeComponent();
  27. }
  28. privatevoid Page_Loaded(object sender, RoutedEventArgs e)
  29. {
  30. user = new LoginUser();
  31. this.LayoutRoot.DataContext = user;
  32. }
  33. privatevoid BtnLogin_Click(object sender, RoutedEventArgs e)
  34. {
  35. if (string.IsNullOrWhiteSpace(user.UserNo))
  36. {
  37. CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNoIsEmpty);
  38. return;
  39. }
  40. if (string.IsNullOrWhiteSpace(user.userPwd))
  41. {
  42. CommonMessage.ShowInfo(MessageResource.Login_Msg_UserPwdIsEmpty);
  43. return;
  44. }
  45. LoginDAL.Instance.GetUser(user.UserNo, (obj, args) =>
  46. {
  47. if (args.Error == null)
  48. {
  49. Stream stream = args.Result;
  50. User loginUser = SeriealizeHelper<User>.JsonDeserialize<User>(stream);
  51. if (loginUser != null)
  52. {
  53. string passWordEncrypt = loginUser.user_password;
  54. Cryptor cryptor = new Cryptor();
  55. string passWord = cryptor.Decrypt(passWordEncrypt.ToCharArray());
  56. if (!(passWord.ToLower() == user.UserPwd.ToLower()))
  57. {
  58. CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);
  59. return;
  60. }
  61. else
  62. {
  63. this.Content = new MainPage(loginUser.user_name);
  64. }
  65. }
  66. else
  67. {
  68. CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);
  69. return;
  70. }
  71. }
  72. else
  73. {
  74. CommonMessage.ShowInfo(args.Error.Message);
  75. }
  76. });
  77. }
  78. privatevoid BtnCancel_Click(object sender, RoutedEventArgs e)
  79. {
  80. this.user.UserPwd = string.Empty;
  81. this.user.UserNo = string.Empty;
  82. }
  83. }
  84. }

需要注意的是Page_Load的时候, this.LayoutRoot.DataContext = user;这就将一个ViewModel绑定到了页面,我们注意到页面上的用户名和密码都是采用双向绑定,Mode=TwoWay,如果Mode=TwoWay,那么只要文本框值更改了,对应的ViewModel的值也会改变,如果ViewModel值变了,文本框也会体现出来变化。但是ViewModel中的属性都需要进行跟踪。

  1. publicclass LoginUser : INotifyPropertyChanged
  2. {
  3. publicevent PropertyChangedEventHandler PropertyChanged;
  4. publicstring userNo;
  5. publicstring UserNo
  6. {
  7. get
  8. {
  9. return userNo;
  10. }
  11. set
  12. {
  13. userNo = value;
  14. NotifyPropertyChange("UserNo");
  15. }
  16. }
  17. publicstring userPwd;
  18. publicstring UserPwd
  19. {
  20. get
  21. {
  22. return userPwd;
  23. }
  24. set
  25. {
  26. userPwd = value;
  27. NotifyPropertyChange("UserPwd");
  28. }
  29. }
  30. privatevoid NotifyPropertyChange(string property)
  31. {
  32. if (PropertyChanged != null)
  33. {
  34. PropertyChanged(this, new PropertyChangedEventArgs(property));
  35. }
  36. }
  37. }

这就是为什么我在点击按钮取消的时候,没有直接操作文本框,而是 this.user.UserPwd = string.Empty;this.user.UserNo = string.Empty;因为双向绑定。我们重点看GetUser这个方法,LoginDAL.Instance.GetUser(user.UserNo, (obj, args) => {})这里第二个参数我是用来回调的一个匿名委托。我们看看LoginDAL中的这个方法。

  1. publicclass LoginDAL : BaseDAL
  2. {
  3. publicstaticreadonly LoginDAL Instance = new LoginDAL();
  4. private LoginDAL() { }
  5. publicvoid GetUser(string userNo, OpenReadCompletedEventHandler handler)
  6. {
  7. WebClient webClient = new System.Net.WebClient();
  8. Uri uri = this.GetUri("Login/GetUser/" + userNo);
  9. webClient.OpenReadAsync(uri);
  10. webClient.OpenReadCompleted += handler;
  11. }
  12. }

第二个参数是一个委托:public delegate void OpenReadCompletedEventHandler(object sender, OpenReadCompletedEventArgs e);相信大家这下理解了吧。我们往下看,

Stream stream = args.Result; User loginUser = SeriealizeHelper<User>.JsonDeserialize<User>(stream);

这段是从Server端取到Stream以后,进行反序列化,反序列化成客户端对象,然后我们根据反序列化生成的对象进行相关判断。在本例子中我需要说明的是,本来按钮的事件不应该出现在页面代码中,而是要在ViewModel中做处理,在页面按钮上绑定Command,但是介于这只是个登陆界面,所以.......。另外关于Server端的详细情况由于篇幅有限就不说了。我们看看运行效果

登陆以后进入主页面,这个主页面我现在还没做好,但是登陆成功的跳转是绝对没问题的,不忽悠大家。主页面导航我准备采用类似于苹果桌面或者Windows 7界面。好了今天就到这里,下期继续。

原文地址:https://www.cnblogs.com/h20064528/p/2666944.html