我没有大牛搞懂原理源代码的本事,我只有一颗帮助别人的心。
今天我们来通过修改代码模块来介绍一下Command绑定以及ViewModel的作用。先看一张图,调一下胃口,看看修改密码页面。
首先我们点击修改密码,弹出修改密码界面,那么我们肯定用的是双击,但是Silverlight无双击事件,只有左右键单击事件。怎么办呢,我们只能根据点击的时间间隔去做判断,如果小于200ms,则属于双击事件。看看代码
- DispatcherTimer doubleClickTimer;
- public MainPage()
- {
- InitializeComponent();
- doubleClickTimer = new DispatcherTimer();
- doubleClickTimer.Interval = new TimeSpan(0, 0, 0, 0, 200);
- doubleClickTimer.Tick += new EventHandler(DoubleClick_Timer);
- }
在主页面的构造函数中,我们定义一个DoubleClickTimer,设置时间间隔为200ms,开始计时调用DoubleClick_Timer方法,如下
- private void DoubleClick_Timer(object sender, EventArgs e)
- {
- doubleClickTimer.Stop();
- }
也就是200ms停止一次,如果你没有在这200ms内双击图标,则不会代开界面。看下面一段代码
- private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
- {
- if (doubleClickTimer.IsEnabled)
- {
- doubleClickTimer.Stop();
- ModifyPassWordView modifyPassWordWindow = new ModifyPassWordView(userNo);
- modifyPassWordWindow.Show();
- }
- else
- {
- doubleClickTimer.Start();
- }
- }
在点击图标的时候,判断如果doubleClickTimer目前可用,也就是还在200ms内的话,就打开页面,并且停止doubleClickTimer的tick事件。否则重新启动doubleClickTimer开始计时,这个功能其实就是对双击的一个模拟。好了,重点我们看看修改密码。
先看设计界面UI代码
- <controls:ChildWindow x:Class="MISInfoManage.ModifyPassWordView"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
- xmlns:LocalResource="clr-namespace:MISInfoManage.Resources"
- xmlns:ViewModel="clr-namespace:ViewModel;assembly=ViewModel"
- Width="400" Height="240"
- Title="修改密码">
- <controls:ChildWindow.Resources>
- <LocalResource:MainPageResource x:Key="LocalResource"></LocalResource:MainPageResource>
- </controls:ChildWindow.Resources>
- <Grid x:Name="LayoutRoot" HorizontalAlignment="Center" Margin="0,15,0,0">
- <Grid.ColumnDefinitions>
- <ColumnDefinition Width="Auto"/>
- <ColumnDefinition Width="*"/>
- </Grid.ColumnDefinitions>
- <Grid.RowDefinitions>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- <RowDefinition Height="Auto"/>
- </Grid.RowDefinitions>
- <TextBlock Text="{Binding Tb_UserNo,Source={StaticResource LocalResource}}" HorizontalAlignment="Right" Grid.Row="0" Grid.Column="0" Margin="5,10,0,10" Style="{StaticResource PwdPageTextStyle}"/>
- <TextBlock Text="{Binding UserID,Mode=OneTime}" Grid.Row="0" Grid.Column="1" HorizontalAlignment="Left" Margin="0,10,0,0"/>
- <TextBlock Text="{Binding Tb_OldPwd,Source={StaticResource LocalResource}}" Grid.Row="1" Grid.Column="0" HorizontalAlignment="Right" Style="{StaticResource PwdPageTextStyle}"/>
- <TextBox Text="{Binding OldPwd,Mode=TwoWay}" Grid.Row="1" Grid.Column="1" Width="200" HorizontalAlignment="Left" FontSize="12"/>
- <TextBlock Text="{Binding Tb_NewPwd,Source={StaticResource LocalResource}}" HorizontalAlignment="Right" Grid.Row="2" Grid.Column="0" Margin="5,10,0,10" Style="{StaticResource PwdPageTextStyle}"/>
- <TextBox Text="{Binding NewPwd,Mode=TwoWay}" Grid.Row="2" Grid.Column="2" Width="200" Margin="0,10,0,10" HorizontalAlignment="Left" FontSize="12"/>
- <TextBlock Text="{Binding Tb_ReNewPwd,Source={StaticResource LocalResource}}" HorizontalAlignment="Right" Grid.Row="3" Grid.Column="0" Style="{StaticResource PwdPageTextStyle}"/>
- <TextBox Text="{Binding ReNewPwd,Mode=TwoWay}" Width="200" Grid.Column="1" Grid.Row="3" HorizontalAlignment="Left" FontSize="12"/>
- <StackPanel Grid.Row="4" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,20,0,0">
- <Button Content="{Binding Tb_Modify,Source={StaticResource LocalResource}}" Command="{Binding UpdatePwdCommands}" CommandParameter="1" Width="80" Margin="0,0,10,0"></Button>
- <Button Content="{Binding Tb_Cancel,Source={StaticResource LocalResource}}" Command="{Binding CancelCommands}" Width="80"></Button>
- </StackPanel>
- </Grid>
- </controls:ChildWindow>
我们看到了,在界面上绑定了三个文本框,其Text属性均绑定ViewModel中的属性值。都是双向的(Two Way),UserID为一次性绑定(OneTime)。在这里我要说的是一次性绑定就是绑定一次,直到页面关闭其值也不会再有变化。单项绑定(OneWay)就是只要ViewModel的值变化,就会反映在UI上,而UI上值的变化不会反映到ViewModel。双向绑定则不管是UI上的变化还是ViewModel的变化,都会互相体现在对方的属性值上,相信我说的已经是通俗易懂。OK,那么现在我们看看修改密码界面的cs代码
- public partial class ModifyPassWordView:ChildWindow
- {
- private UserPwdInfo userPwdInfo;
- public ModifyPassWordView()
- {
- InitializeComponent();
- }
- public ModifyPassWordView(string userNo)
- : this()
- {
- userPwdInfo = new UserPwdInfo();
- userPwdInfo.UserID = userNo;
- this.LayoutRoot.DataContext = userPwdInfo;
- }
- }
在这里我们设置页面的数据源为userPwdInfo。那么页面的文本框什么的就可以绑定这个对象的属性。为什么只有这么点代码呢?不要忘了本节主要说的是ViewModel,大量的处理逻辑已经放在了ViewModel中,ViewModel其实是一个充血模型,界面所牵涉的所有业务逻辑都可以在ViewModel中完成,从而实现了UI与业务的分离。看上面的这段代码,我们声明了一个UserPwdInfo,其实这个是我定义的一个ViewModel类,代码如下
- namespace ViewModel
- {
- public class UserPwdInfo : INotifyPropertyChanged
- {
- public event PropertyChangedEventHandler PropertyChanged;
- private string oldPwd;
- public string OldPwd
- {
- get
- {
- return oldPwd;
- }
- set
- {
- oldPwd = value;
- NotifyPropertyChange("OldPwd");
- }
- }
- private string newPwd;
- public string NewPwd
- {
- get
- {
- return newPwd;
- }
- set
- {
- newPwd = value;
- NotifyPropertyChange("NewPwd");
- }
- }
- private string reNewPwd;
- public string ReNewPwd
- {
- get
- {
- return reNewPwd;
- }
- set
- {
- reNewPwd = value;
- NotifyPropertyChange("ReNewPwd");
- }
- }
- private void NotifyPropertyChange(string property)
- {
- if (PropertyChanged != null)
- {
- PropertyChanged(this, new PropertyChangedEventArgs(property));
- }
- }
- public string userID;
- public string UserID
- {
- get
- {
- return userID;
- }
- set
- {
- userID = value;
- NotifyPropertyChange("UserID");
- }
- }
- public ICommand UpdatePwdCommands
- {
- get
- {
- UpdatePwdCommand updatePwdCommand = new UpdatePwdCommand(delegate()
- {
- if (string.IsNullOrEmpty(OldPwd))
- {
- CommonMessage.ShowInfo("旧密码不能为空!");
- return;
- }
- if (string.IsNullOrEmpty(NewPwd))
- {
- CommonMessage.ShowInfo("请输入新密码!");
- return;
- }
- if (string.IsNullOrEmpty(ReNewPwd))
- {
- CommonMessage.ShowInfo("请重复输入新密码!");
- return;
- }
- if (!Validate.IsCorrectPwd(this.NewPwd))
- {
- CommonMessage.ShowInfo("新密码格式不正确!");
- return;
- }
- if (!NewPwd.Equals(ReNewPwd))
- {
- CommonMessage.ShowInfo("新密码和重复密码不一致!");
- return;
- }
- LoginDAL.Instance.GetUser(this.UserID, (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.Equals(OldPwd))
- {
- CommonMessage.ShowInfo("旧密码不正确!");
- return;
- }
- ModifyPwd();
- }
- }
- });
- }, delegate(string userId) { return !string.IsNullOrEmpty(userId); });
- return updatePwdCommand;
- }
- }
- public ICommand CancelCommands
- {
- get
- {
- CancelCommand cancelCommand = new CancelCommand(delegate()
- {
- this.OldPwd = string.Empty;
- this.NewPwd = string.Empty;
- this.ReNewPwd = string.Empty;
- });
- return cancelCommand;
- }
- }
- public static ManualResetEvent allDone = new ManualResetEvent(false);
- private void ModifyPwd()
- {
- WebRequest myWebRequest = WebRequest.Create("Http://localhost:37206/MISInfoService/ModifyPwd");
- RequestState myRequestState = new RequestState();
- myRequestState.request = myWebRequest;
- myWebRequest.ContentType = "application/json";
- myRequestState.request.Method = "POST";
- IAsyncResult r = (IAsyncResult)myWebRequest.BeginGetRequestStream(
- new AsyncCallback(ReadCallback), myRequestState);
- allDone.WaitOne();
- IAsyncResult myWebResponse = myWebRequest.BeginGetResponse(new AsyncCallback(ResponseCallBack), myRequestState);
- }
- private void ReadCallback(IAsyncResult asynchronousResult)
- {
- RequestState myRequestState = (RequestState)asynchronousResult.AsyncState;
- WebRequest myWebRequest = myRequestState.request;
- Stream requestStream = myWebRequest.EndGetRequestStream(asynchronousResult);
- string jsonString = SeriealizeHelper<ModifyPwdRequest>.JsonSerialize<ModifyPwdRequest>(new ModifyPwdRequest() { UserID = UserID, Pwd = NewPwd });
- byte[] buffer = System.Text.Encoding.Unicode.GetBytes(jsonString);
- requestStream.Write(buffer, 0, buffer.Length);
- requestStream.Close();
- allDone.Set();
- }
- private void ResponseCallBack(IAsyncResult asynchronousResult)
- {
- RequestState myRequestState = (RequestState)asynchronousResult.AsyncState;
- WebRequest myWebRequest = myRequestState.request;
- WebResponse webResponse = myWebRequest.EndGetResponse(asynchronousResult);
- using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
- {
- string result = reader.ReadToEnd();
- result = result.Replace("\"", "");
- JsonModel jsonModel = new JsonModel(result);
- string suc = jsonModel.GetValue("suc");
- if (suc == "1")
- {
- new Thread(() =>
- {
- UISynchronizationContext.Context.Post((state) =>
- {
- MessageBox.Show(jsonModel.GetValue("msg"));
- }, null);
- }).Start();
- //MessageBox.Show(jsonModel.GetValue("msg"));
- }
- }
- }
- }
- public class RequestState
- {
- public WebRequest request;
- public RequestState()
- {
- request = null;
- }
- }
- }
看到了吧,这里面包含了很多逻辑。首先我要说的是Command绑定,我们在ViewModel中定义了两个Command,一个针对修改按(UpdatePwdCommands ),一个针对取消按钮(CancelCommands)。UpdatePwd构造函数中的第一个参数delegate()是一个匿名委托,用来实现修改密码功能,第二个参数delegate(string userId)用来判断是否可以执行Command命令,我们看看UpdatePwdCommands 类的代码你就明白了
- public class UpdatePwdCommand : ICommand
- {
- public event EventHandler CanExecuteChanged;
- public delegate void UpdatePwdhandler();
- public delegate bool CanExecuteHandler(string UserID);
- public CanExecuteHandler canExecuteHandler;
- public event UpdatePwdhandler UpdatePwdChange;
- public UpdatePwdCommand(UpdatePwdhandler handler,CanExecuteHandler canExecuteHandler)
- {
- this.UpdatePwdChange = handler;
- this.canExecuteHandler = canExecuteHandler;
- }
- public bool CanExecute(object parameter)
- {
- if (canExecuteHandler != null)
- {
- return canExecuteHandler(parameter.ToString());
- }
- return true;
- }
- public void Execute(object parameter)
- {
- if (CanExecute(parameter))
- {
- UpdatePwdChange();
- }
- }
- }
我声明了一个委托public delegate bool CanExecuteHandler(string UserID);在CanExecute方法中判断如果委托指向了某个方法,那么久执行这个方法来判断Command是否可以执行。在ViewModel中我们通过指定UpdatePwdCommand构造函数的第二个参数,来确定Command是否可以执行:delegate(string userId) { return !string.IsNullOrEmpty(userId); }如果不能执行,则修改按钮为disable状态。同样的取消按钮也是这样的一个逻辑。我们看到在页面上我们给修改按钮和取消按钮绑定了Command,如下
- <Button Content="{Binding Tb_Modify,Source={StaticResource LocalResource}}" Command="{Binding UpdatePwdCommands}" CommandParameter="1" Width="80" Margin="0,0,10,0"></Button>
- <Button Content="{Binding Tb_Cancel,Source={StaticResource LocalResource}}" Command="{Binding CancelCommands}" Width="80"></Button>
这样当我们点击修改按钮的时候,就会调用UpdatePwdCommand类的Execute方法,在Execute方法触发UpdatePwdChange事件,而这个UpdatePwdChange的原型是我们UserPwdInfo中UpdatePWdCommand构造函数的第一个参数,如下
- public ICommand UpdatePwdCommands
- {
- get
- {
- UpdatePwdCommand updatePwdCommand = new UpdatePwdCommand(delegate()
- { ......}
- }
- }
ok,Command看完了,我们看看调用WCF的部分,WCF部分代码如下,在这里我的WCF服务代码具有如下特点,无SVC文件,无ABC配置。看看代码
- namespace WCFService
- {
- [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
- [ServiceContract]
- [ServiceBehavior(IncludeExceptionDetailInFaults = true)]
- public class UserService
- {
- MISInfoEntities misInfoEntities;
- public UserService()
- {
- misInfoEntities = new MISInfoEntities();
- }
- [OperationContract]
- [WebInvoke(UriTemplate = "ModifyPwd",
- Method = "POST",
- RequestFormat=WebMessageFormat.Json,
- ResponseFormat=WebMessageFormat.Json)]
- public string ModifyPwd(ModifyPwdRequest request)
- {
- Users user = misInfoEntities.users.Find(request.UserID);
- user.user_password = Cryptor.Encrypt(request.Pwd);
- misInfoEntities.SaveChanges();
- return "{suc:1,msg:'修改成功!'}";
- }
- }
- }
在这里需要注意的是 [AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]这个Attribute设置为Allowed,并且webConfig也要设置。
- <system.serviceModel>
- <serviceHostingEnvironment aspNetCompatibilityEnabled="true"></serviceHostingEnvironment>
- <standardEndpoints>
- <webHttpEndpoint>
- <standardEndpoint name="" helpEnabled="true" automaticFormatSelectionEnabled="true"/>
- </webHttpEndpoint>
- </standardEndpoints>
如果有什么不明白,请看我的文章WCF目录。大家看到了我的WCF是Rest风格的,那么参数ModifyPwdRequest 就无需标记DataContract。我们只需要告诉客户端它的序列化方式为RequestFormat=WebMessageFormat.Json,请求的时候使用json序列化方式,同理ResponseFormat=WebMessageFormat.Json,响应客户端的时候也是json格式。
所以在客户端调用的时候,我们采用如上面ViewModel中ModifyPwd方法的调用方式
myWebRequest.ContentType = "application/json"; myRequestState.request.Method = "POST";
设置请求发送数据的格式以及httpMethod。在ReadCallBack方法中,
- Stream requestStream = myWebRequest.EndGetRequestStream(asynchronousResult);
- string jsonString = SeriealizeHelper<ModifyPwdRequest>.JsonSerialize<ModifyPwdRequest>(new ModifyPwdRequest() { UserID = UserID, Pwd = NewPwd });
- byte[] buffer = System.Text.Encoding.Unicode.GetBytes(jsonString);
- requestStream.Write(buffer, 0, buffer.Length);
- requestStream.Close();
我们序列化一个和服务端一模一样的对象,将它写进请求流中,服务端接收到这个流以后,根据格式序列化为ModifyPwdRequest对象,调用修改密码方法。OK,当调用完以后进入ResponseCallBack方法,重点看这段代码
- using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
- {
- string result = reader.ReadToEnd();
- result = result.Replace("\"", "");
- JsonModel jsonModel = new JsonModel(result);
- string suc = jsonModel.GetValue("suc");
- if (suc == "1")
- {
- new Thread(() =>
- {
- UISynchronizationContext.Context.Post((state) =>
- {
- MessageBox.Show(jsonModel.GetValue("msg"));
- }, null);
- }).Start();
- //MessageBox.Show(jsonModel.GetValue("msg"));
- }
- }
获取到服务端响应流以后,我们读成字符串,然后通过json解析,读取json对象的值。最后显示修改成功,在显示修改成功的时候,因为ViewModel和界面UI不在一个线程上,如果直接调用MessageBox.Show将会抛出异常。所以我们采取如下的办法,首先在App启动类里面
- public App()
- {
- this.Startup += this.Application_Startup;
- this.UnhandledException += this.Application_UnhandledException;
- UISynchronizationContext.Context = SynchronizationContext.Current;
- InitializeComponent();
- }
我们获取当前线程的实例,UISynchronizationContext是我定义的一个类,如下
- namespace Client.Common
- {
- public static class UISynchronizationContext
- {
- public static SynchronizationContext Context { get; set; }
- }
- }
所以当修改密码成功以后,我们获取当前UI线程,并弹出提示信息。OK,今天就讲到这里,如果有什么不明白,可以找我要源代码。看看执行效果