我没有大牛的本事,只有一颗帮助别人的心,我喜欢写博客。
今天我们通过一个登录得例子来看看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端的登陆界面。
- <navigation:Page x:Class="MISInfoManage.Login"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- mc:Ignorable="d"
- xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
- xmlns:source="clr-namespace:MISInfoManage.Resources"
- Title="Login Page" Loaded="Page_Loaded">
- <navigation:Page.Resources>
- <source:LoginResource x:Key="LoginResource"></source:LoginResource>
- </navigation:Page.Resources>
- <Grid x:Name="LayoutRoot">
- <Grid.Background>
- <ImageBrush ImageSource="/MISInfoManage;component/Images/LoginBack.jpg" />
- </Grid.Background>
- <Grid.RowDefinitions>
- <RowDefinition Height="300"></RowDefinition>
- <RowDefinition Height="Auto"></RowDefinition>
- <RowDefinition Height="Auto"></RowDefinition>
- <RowDefinition Height="Auto"></RowDefinition>
- </Grid.RowDefinitions>
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="530" ></ColumnDefinition>
- <ColumnDefinition Width="*"></ColumnDefinition>
- </Grid.ColumnDefinitions>
- <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>
- <TextBlock FontSize="16" HorizontalAlignment="Right" Text="{Binding Tb_UserName, Source={StaticResource LoginResource}}" Grid.Row="1" Grid.Column="0"></TextBlock>
- <TextBox Text="{Binding UserNo,Mode=TwoWay}" Width="300" Grid.Row="1" Grid.Column="1" Margin="0,0,0,15" HorizontalAlignment="Left" Height="30"></TextBox>
- <TextBlock Text="{Binding Tb_UserPwd,Source={StaticResource LoginResource}}" Grid.Row="2" Grid.Column="0" HorizontalAlignment="Right" FontSize="16"></TextBlock>
- <TextBox Text="{Binding UserPwd,Mode=TwoWay}" Width="300" Grid.Row="2" Grid.Column="1" HorizontalAlignment="Left" Height="30"></TextBox>
- <StackPanel Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" Orientation="Horizontal">
- <Button x:Name="BtnLogin" Content="{Binding BtnLogin,Source={StaticResource LoginResource}}" Margin="80,20,20,0" Style="{StaticResource BtnLoginStyle}" Click="BtnLogin_Click"></Button>
- <Button x:Name="BtnCancel" Content="{Binding BtnCancel,Source={StaticResource LoginResource}}" Margin="10,20,0,0" Width="90" Style="{StaticResource BtnLoginStyle}" Click="BtnCancel_Click"></Button>
- </StackPanel>
- </Grid>
- </navigation:Page>
在这里需要说明的是几个Binding,其中
- <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页面看完了,我们看看后台。
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Net;
- using System.Windows;
- using System.Windows.Controls;
- using System.Windows.Documents;
- using System.Windows.Input;
- using System.Windows.Media;
- using System.Windows.Media.Animation;
- using System.Windows.Shapes;
- using System.Windows.Navigation;
- using System.IO;
- namespace MISInfoManage
- {
- using ViewModel;
- using DataAccess.Login;
- using Client.Common;
- using Client.Entity;
- using MISInfoManage.Resources;
- public partial class Login : Page
- {
- LoginUser user;
- public Login()
- {
- InitializeComponent();
- }
- private void Page_Loaded(object sender, RoutedEventArgs e)
- {
- user = new LoginUser();
- this.LayoutRoot.DataContext = user;
- }
- private void BtnLogin_Click(object sender, RoutedEventArgs e)
- {
- if (string.IsNullOrWhiteSpace(user.UserNo))
- {
- CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNoIsEmpty);
- return;
- }
- if (string.IsNullOrWhiteSpace(user.userPwd))
- {
- CommonMessage.ShowInfo(MessageResource.Login_Msg_UserPwdIsEmpty);
- return;
- }
- LoginDAL.Instance.GetUser(user.UserNo, (obj, args) =>
- {
- if (args.Error == null)
- {
- Stream stream = args.Result;
- User loginUser = SeriealizeHelper<User>.JsonDeserialize<User>(stream);
- if (loginUser != null)
- {
- string passWordEncrypt = loginUser.user_password;
- Cryptor cryptor = new Cryptor();
- string passWord = cryptor.Decrypt(passWordEncrypt.ToCharArray());
- if (!(passWord.ToLower() == user.UserPwd.ToLower()))
- {
- CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);
- return;
- }
- else
- {
- this.Content = new MainPage(loginUser.user_name);
- }
- }
- else
- {
- CommonMessage.ShowInfo(MessageResource.Login_Msg_UserNotCorrect);
- return;
- }
- }
- else
- {
- CommonMessage.ShowInfo(args.Error.Message);
- }
- });
- }
- private void BtnCancel_Click(object sender, RoutedEventArgs e)
- {
- this.user.UserPwd = string.Empty;
- this.user.UserNo = string.Empty;
- }
- }
- }
需要注意的是Page_Load的时候, this.LayoutRoot.DataContext = user;这就将一个ViewModel绑定到了页面,我们注意到页面上的用户名和密码都是采用双向绑定,Mode=TwoWay,如果Mode=TwoWay,那么只要文本框值更改了,对应的ViewModel的值也会改变,如果ViewModel值变了,文本框也会体现出来变化。但是ViewModel中的属性都需要进行跟踪。
- public class LoginUser : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- public string userNo;
- public string UserNo
- {
- get
- {
- return userNo;
- }
- set
- {
- userNo = value;
- NotifyPropertyChange("UserNo");
- }
- }
- public string userPwd;
- public string UserPwd
- {
- get
- {
- return userPwd;
- }
- set
- {
- userPwd = value;
- NotifyPropertyChange("UserPwd");
- }
- }
- private void NotifyPropertyChange(string property)
- {
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs(property));
- }
- }
- }
这就是为什么我在点击按钮取消的时候,没有直接操作文本框,而是 this.user.UserPwd = string.Empty;this.user.UserNo = string.Empty;因为双向绑定。我们重点看GetUser这个方法,LoginDAL.Instance.GetUser(user.UserNo, (obj, args) => {})这里第二个参数我是用来回调的一个匿名委托。我们看看LoginDAL中的这个方法。
- public class LoginDAL : BaseDAL
- {
- public static readonly LoginDAL Instance = new LoginDAL();
- private LoginDAL() { }
- public void GetUser(string userNo, OpenReadCompletedEventHandler handler)
- {
- WebClient webClient = new System.Net.WebClient();
- Uri uri = this.GetUri("Login/GetUser/" + userNo);
- webClient.OpenReadAsync(uri);
- webClient.OpenReadCompleted += handler;
- }
- }
第二个参数是一个委托: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界面。好了今天就到这里,下期继续。