• 【转】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界面。好了今天就到这里,下期继续。

  • 相关阅读:
    28 对10个数进行排序
    27 求100之内的素数
    26 请输入星期几的第一个字母,判断一下是星期几
    25 一个五位数,判断它是不是回文数
    24 给一个不多于5位的正整数
    23 请问第5个人多大?
    22 1+2!+3!+...+5!的和
    快捷生成getter和setter方法
    Error creating bean with name 'as' defined in class path resource
    CentOS7-1810 系统DNS服务器BIND软件配置说明
  • 原文地址:https://www.cnblogs.com/h20064528/p/2666944.html
Copyright © 2020-2023  润新知