好久没写文章了,今天我们就来看一下WPF,WPF的概念什么的,我就不说了,我不知道,我也不想知道。有人面试爱问WPF和Winform的区别,我管你什么区别呢,只要能为老子挣钱,哪怕是VB6老子都可以拿起来给你整。有的人技术搞得好,号称大牛,但是手里却拿不出像样的框架或产品。这种牛是伪大牛,当然了大多数大牛还是有两下子的。
废话不多说,看下登录界面。
大家知道WPF做图形界面比较牛逼,动画都不在话下,WPF做的游戏我都见过。这里我们登录界面的顶部是个动画,类似于QQ的。
这个不是重点,重点是我们的页面如何实现登录,先看一下登录页面的代码。
<Base:WindowBase x:Class="HealthyInfomation.Login" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Base="clr-namespace:HealthyInformation.FrameWork;assembly=HealthyInformation.FrameWork" xmlns:helper="clr-namespace:HealthyInformation.FrameWork.ClientHelper;assembly=HealthyInformation.FrameWork" xmlns:local="clr-namespace:HealthyInfomation.Resource" Title="用户登录" Height="320" Width="420" ResizeMode="NoResize" FontSize="14" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" Icon="Images/login.ico"> <Base:WindowBase.Background> <LinearGradientBrush EndPoint="0.952,0.241" StartPoint="0.079,0.956"> <GradientStop Color="{DynamicResource WarningColor4}" Offset="0"/> <GradientStop Color="White" Offset="1"/> </LinearGradientBrush> </Base:WindowBase.Background> <Grid x:Name="Main"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid Grid.Row="0" Name="layout" Height="80"> <Grid.Clip> <RectangleGeometry Rect="0,0,500,100"/> </Grid.Clip> </Grid> <Grid Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <!--<Image Source="Images/login.jpg" Height="51" Grid.ColumnSpan="2" Width="420" Grid.Row="0" Grid.Column="0"/>--> <Grid Margin="20,20,20,0" Grid.Row="1"> <Grid.RowDefinitions> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> <RowDefinition Height="Auto"></RowDefinition> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto"></ColumnDefinition> <ColumnDefinition Width="*"></ColumnDefinition> </Grid.ColumnDefinitions> <TextBlock Text="{x:Static local:LoginResource.Lab_UserName}" Grid.Row="0" Grid.Column="0" Foreground="{DynamicResource Foreground-Default}" FontWeight="SemiBold"></TextBlock> <TextBox x:Name="Txt_UserName" Text="{Binding UserName,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" MaxLength="50" Grid.Row="0" Grid.Column="1" Margin="5,20,0,10" TabIndex="0"></TextBox> <TextBlock Text="{x:Static local:LoginResource.Lab_PassWord}" Grid.Row="1" Grid.Column="0" Foreground="{DynamicResource Foreground-Default}" FontWeight="SemiBold" /> <PasswordBox x:Name="Txt_PassWord" PasswordChar="*" helper:PasswordBoxHelper.Password="{Binding PassWord,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" MaxLength="200" Grid.Row="1" Grid.Column="1" Margin="5,5,0,0"></PasswordBox> <StackPanel Orientation="Horizontal" Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" HorizontalAlignment="Center" Margin="0,20,0,0"> <Button Command="{Binding LoginCommand}" Content="{x:Static local:LoginResource.Btn_Login}" Style="{DynamicResource btn-primary}" Margin="0,10,0,0"></Button> <Button Command="{Binding CancelCommand}" Content="{x:Static local:LoginResource.Btn_Cancel}" Style="{DynamicResource btn-danger}" Margin="10,10,0,0"></Button> </StackPanel> </Grid> </Grid> </Grid> </Base:WindowBase>
采用Grid布局,做过Silverlight的都知道这是怎么回事。其实就是定义行和列,然后在行和列里面放置元素。
对于所有的标签TextBlock,都是采用资源绑定的方式。大家注意这里的Local,其实这就是声明,其实上面的编程方式也叫声明式编程。
其实我们系统中的资源都是一些resx文件,如下
他们的内容就是我们页面标签要绑定的文字。
相信这下你肯定有很好的理解,再看一下用户名和密码的绑定。传统的方式cs代码中我们直接从控件取值,或者用MVP模式。而在WPF和Silverlight中用的最多的还是MVVM,因此我们这里password和userName都是绑定的ViewModel的属性,双向绑定。TwoWay,意思是控件值发生变化会通知ViewModel,ViewModel值变化也会通知UI更新。OneWay是当ViewModel的值发生改变,会通知UI更新。OneTime是一次性的,绑定后即使再变UI都不会再发生改变。
同时需要注意这里的UpdateSourceTrigger,在这里我们设置的是PropertyChanged,即只要是文本框中发生任何改变都会立即更新ViewModel值。默认的UpdateSourceTrigger是Explicit,意思是当该控件失去焦点后才会刷新ViewModel值。结合这种情况,因为用户要求是在文本框中输入回车键就要登录。因此只能设置为PropertyChanged,当然细想一下,这种频繁通知比较耗费资源,不在不得已的情况下尽量少用。
OK,我们看一下登录后的界面。
其实就是一个民航的飞行员健康登记,美工自己搞。
OK,欣赏过UI之后,其实UI不是我要说的重点,重点是登录cs代码。
public partial class Login : WindowBase { UserFacade userFacade; public Login() { InitializeComponent(); this.userFacade = new UserFacade(this); this.DataContext = this; this.Loaded += (obj, args) => { Txt_UserName.Focus(); this.InitTriggeraction(); }; } #region ViewModel private string userName; public string UserName { get { return userName; } set { userName = value; RaisePropertyChanged("UserName"); } } private string passWord; public string PassWord { get { return passWord; } set { passWord = value; RaisePropertyChanged("PassWord"); } } #endregion #region Command public ICommand LoginCommand { get { return CommandFactory.CreateCommand((obj) => { this.DoLogin(); }); } } public ICommand CancelCommand { get { return CommandFactory.CreateCommand((obj) => { this.Close(); }); } } #endregion #region method private void InitTriggeraction() { this.Txt_PassWord.AttchEventTriggerAction(new BaseTrigger<DependencyObject, Login>((obj) => { DoAction(obj); }), EventEnums.KeyDown.ToString()); this.Txt_UserName.AttchEventTriggerAction(new BaseTrigger<DependencyObject, Login>((obj) => { DoAction(obj); }), EventEnums.KeyDown.ToString()); } private void DoAction(object obj) { KeyEventArgs eventArgs = obj as KeyEventArgs; if (eventArgs != null && eventArgs.Key == Key.Enter) { this.DoLogin(); } } private async void DoLogin() { if (string.IsNullOrWhiteSpace(this.UserName)) { this.ShowMessage(LoginResource.Msg_UserNameEmpty); return; } if (string.IsNullOrWhiteSpace(this.PassWord)) { this.ShowMessage(LoginResource.Msg_UserPassWordEmpty); return; } var user = await this.userFacade.GetUser(this.UserName.Trim()); if (user == null) { this.ShowMessage(LoginResource.Msg_UserNotExist); return; } var decryptPwd = user.PassWord.UnAes("X86123321qwerasdf", "9915098765362112"); if (this.PassWord.Trim() != decryptPwd) { this.ShowMessage(LoginResource.Msg_UserNotExist); Txt_PassWord.Focus(); return; } CPApplication.CurrentUser = new SystemUser { UserName = this.UserName.Trim(), PassWord = this.PassWord.Trim() }; this.Hide(); new MainWindow().Show(); } }
注意这里的ViewModel的两个字段,就是我们页面双向绑定的字段。这里的RaisePropertyChanged,其实就是事件通知。当ViewModel的值发生改变以后,就会触发事件将改变通知出去,然后UI做改变。
public void RaisePropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } public event PropertyChangedEventHandler PropertyChanged;
刚才提到用户希望回车提交,回车这个那是必须要走事件的,要不怎么判断按键。所以在页面加载完成以后,我们就初始化了triggerAction。WPF和Silverlight中提供了TriggerAction和Behavior类就是解决UI控件事件和后端ViewModel耦合的。
所以在这里我给两个文本框分别附加了回车按键的triggerAction,当按键的时候,就会调用DoAction方法。
看一下这个BaseTrigger类,我自己写的,慷慨奉献给大家了。
public class BaseTrigger<TElement, TViewModel> : Interactivity.TriggerAction<TElement> where TElement : DependencyObject where TViewModel : WindowBase { public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(TViewModel), typeof(BaseTrigger<TElement, TViewModel>), new PropertyMetadata(null)); Action<object> invokeAction; public BaseTrigger(Action<object> invokeAction) { this.invokeAction = invokeAction; } public BaseTrigger() { } public TViewModel ViewModel { get { return (TViewModel)GetValue(ViewModelProperty); } set { SetValue(ViewModelProperty, value); } } protected override void OnAttached() { base.OnAttached(); } protected override void OnDetaching() { base.OnDetaching(); } protected override void Invoke(object parameter) { if (invokeAction != null) { invokeAction(parameter); } } }
在构造函数中,我们可以传入事件响应后需要调用的方法,即Invoke中要执行的方法。这里的ViewModel其实是用来在页面绑定事件用的,但是这样写的class有点多,每个页面ViewModel都不一样,每个页面都要写,所以还是cs代码attach比较好。
接下来我们看一下LoginCommand,传统的我们都是直接用Click方法,但是在这里,我们用ICommand。在登录按钮上我们绑定Command来代替Click事件。其实自己实现一个CommandFactory就是如此简单。
public class CommandFactory { public static ICommand CreateCommand(Action<object> action) { return new CommonCommand(action); } public static ICommand CreateCommand(Action<object> action, Func<object, bool> func) { return new CommonCommand(action, func); } class CommonCommand : ICommand { Action<object> ExecuteMethod; Func<object, bool> CanExecuteMethod; public event EventHandler CanExecuteChanged; public CommonCommand(Action<object> action, Func<object, bool> func) { this.ExecuteMethod = action; this.CanExecuteMethod = func; } public CommonCommand(Action<object> action) { this.ExecuteMethod = action; } public bool CanExecute(object parameter) { if (CanExecuteMethod != null) { return CanExecuteMethod(parameter); } return true; } public void Execute(object parameter) { if (ExecuteMethod != null) { ExecuteMethod(parameter); } } } }
CanExecute就是指能不能执行,所以如果你要控制按钮点击后能不能响应就传入构造函数第二个参数。但是大多数我们的按钮是要响应的,所以就传入一个执行函数就行了。所以我们就传入一个Dologin方法调用登录。
OK,在这里我们登录调用的是WCF Restful Service。注意这里的facade,其实在新蛋上班的同学比较熟悉,我就是按照新蛋的框架思维自己实现了一下。
public class UserFacade : RestClientWrapper { public UserFacade(WindowBase windowBase) : base("UsersService", "SystemManage", windowBase) { } public async Task<UserEntity> GetUser(string userName) { return await this.GetAsync<UserEntity>(string.Format("get?userName={0}", Uri.EscapeDataString(userName))); } }
在这里为啥传这几个参数到构造函数,我先不说。好了 ,今天就到这里。