Silverlight MVVM 贴近实战(一)

我没有大牛的本事,只有一颗帮助别人的心,我喜欢写博客。

今天我们通过一个登录得例子来看看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:Page x: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:LoginResource x:Key="LoginResource"></source:LoginResource> 
  12.     </navigation:Page.Resources> 
  13.     <Grid x:Name="LayoutRoot"> 
  14.         <Grid.Background> 
  15.             <ImageBrush ImageSource="/MISInfoManage;component/Images/LoginBack.jpg" /> 
  16.         </Grid.Background> 
  17.         <Grid.RowDefinitions> 
  18.             <RowDefinition Height="300"></RowDefinition> 
  19.             <RowDefinition Height="Auto"></RowDefinition> 
  20.             <RowDefinition Height="Auto"></RowDefinition> 
  21.             <RowDefinition Height="Auto"></RowDefinition> 
  22.         </Grid.RowDefinitions> 
  23.         <Grid.ColumnDefinitions> 
  24.             <ColumnDefinition Width="530" ></ColumnDefinition> 
  25.             <ColumnDefinition Width="*"></ColumnDefinition> 
  26.         </Grid.ColumnDefinitions> 
  27.         <TextBlock Text="{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.         <TextBlock FontSize="16" HorizontalAlignment="Right" Text="{Binding Tb_UserName, Source={StaticResource LoginResource}}" Grid.Row="1" Grid.Column="0"></TextBlock> 
  29.         <TextBox Text="{Binding UserNo,Mode=TwoWay}" Width="300" Grid.Row="1" Grid.Column="1" Margin="0,0,0,15" HorizontalAlignment="Left" Height="30"></TextBox> 
  30.         <TextBlock Text="{Binding Tb_UserPwd,Source={StaticResource LoginResource}}" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" FontSize="16"></TextBlock> 
  31.         <TextBox Text="{Binding UserPwd,Mode=TwoWay}" Width="300" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Height="30"></TextBox> 
  32.         <StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" Orientation="Horizontal"> 
  33.             <Button x:Name="BtnLogin" Content="{Binding BtnLogin,Source={StaticResource LoginResource}}" Margin="80,20,20,0" Style="{StaticResource BtnLoginStyle}" Click="BtnLogin_Click"></Button> 
  34.             <Button x: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. <TextBlock Text="{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.  
  15. namespace MISInfoManage  
  16. {  
  17.     using ViewModel;  
  18.     using DataAccess.Login;  
  19.     using Client.Common;  
  20.     using Client.Entity;  
  21.     using MISInfoManage.Resources;  
  22.     public partial class Login : Page  
  23.     {  
  24.         LoginUser user;  
  25.         public Login()  
  26.         {  
  27.             InitializeComponent();  
  28.         }  
  29.  
  30.         private void Page_Loaded(object sender, RoutedEventArgs e)  
  31.         {  
  32.             user = new LoginUser();  
  33.             this.LayoutRoot.DataContext = user;  
  34.         }  
  35.  
  36.         private void BtnLogin_Click(object sender, RoutedEventArgs e)  
  37.         {  
  38.             if (string.IsNullOrWhiteSpace(user.UserNo))  
  39.             {  
  40.                 CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNoIsEmpty);  
  41.                 return;  
  42.             }  
  43.             if (string.IsNullOrWhiteSpace(user.userPwd))  
  44.             {  
  45.                 CommonMessage.ShowInfo(MessageResource.Login_Msg_UserPwdIsEmpty);  
  46.                 return;  
  47.             }  
  48.             LoginDAL.Instance.GetUser(user.UserNo, (obj, args) =>   
  49.             {  
  50.                 if (args.Error == null)  
  51.                 {  
  52.                     Stream stream = args.Result;  
  53.                     User loginUser = SeriealizeHelper<User>.JsonDeserialize<User>(stream);  
  54.                     if (loginUser != null)  
  55.                     {  
  56.                         string passWordEncrypt = loginUser.user_password;  
  57.                         Cryptor cryptor = new Cryptor();  
  58.                         string passWord = cryptor.Decrypt(passWordEncrypt.ToCharArray());  
  59.                         if (!(passWord.ToLower() == user.UserPwd.ToLower()))  
  60.                         {  
  61.                             CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);  
  62.                             return;  
  63.                         }  
  64.                         else 
  65.                         {  
  66.                             this.Content = new MainPage(loginUser.user_name);  
  67.                         }  
  68.                     }  
  69.                     else 
  70.                     {  
  71.                         CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);  
  72.                         return;  
  73.                     }  
  74.                 }  
  75.                 else 
  76.                 {  
  77.                     CommonMessage.ShowInfo(args.Error.Message);  
  78.                 }  
  79.             });  
  80.         }  
  81.  
  82.         private void BtnCancel_Click(object sender, RoutedEventArgs e)  
  83.         {  
  84.             this.user.UserPwd = string.Empty;  
  85.             this.user.UserNo = string.Empty;  
  86.         }  
  87.  
  88.     }  
  89. }  

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

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

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

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

第二个参数是一个委托: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界面。好了今天就到这里,下期继续。

你可能感兴趣的:(silverlight,MVVM)