1,将Page更改为一个泛型的Page
public class BasePage<VM> : Page where VM:BaseViewModel,new() { #region Private Member private VM mViewModel;//创建Page的ViewModel字段 #endregion #region public properties /// <summary> /// the Animation is play when the page is first loaded /// </summary> public PageAnimation PageLoadAnimation { get; set; } = PageAnimation.SlideAndFadeInFromRight; /// <summary> /// The Animation is play when the page is unloaded /// </summary> public PageAnimation PageUnloadAnimation { get; set; } = PageAnimation.SlideAndFadeOutToLeft; /// <summary> /// the time any slide animation takes to complete /// </summary> public float SlideSeconds { get; set; } = 0.8f; public VM ViewModel //设置和获取ViewModel { get { return mViewModel; } set { //if nothing has changed return; if (mViewModel == value) return; //Update the value mViewModel = value; //Set the datacontext for this page this.DataContext = mViewModel;//设置的时候,另Page的DataContext=特定的ViewModel } } #endregion #region Constructor /// <summary> /// Default Constructor /// </summary> public BasePage() { // If We are animate in ,hide to begin with if (this.PageLoadAnimation != PageAnimation.None) this.Visibility = Visibility.Collapsed; //Fire when Page loaded. this.Loaded += BasePage_LoadedAsync; //create a default view model this.ViewModel = new VM();//构造函数建立默认的ViewModel实例.?如何建立带参数的 } #endregion #region Animation Load / Unload /// <summary> /// /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void BasePage_LoadedAsync(object sender, RoutedEventArgs e) { await AnimateIn(); } #region AnimateIn public async Task AnimateIn() { //make sure we have something to do if (this.PageLoadAnimation == PageAnimation.None) return; //select the LoadSwitch switch (this.PageLoadAnimation) { //choose Slide And Fade In From Right case PageAnimation.SlideAndFadeInFromRight: await this.SlideAndFadeInFromRight(SlideSeconds); break; } } #endregion #region AnimateOut public async Task AnimateOut() { //make sure we have something to do if (this.PageUnloadAnimation == PageAnimation.None) return; //select the LoadSwitch switch (this.PageUnloadAnimation) { //choose Slide And Fade In From Right case PageAnimation.SlideAndFadeOutToLeft: await this.SlideAndFadeOutToLeft(SlideSeconds); break; } } #endregion #endregion }
- 创建View Model的私有字段
- ,并且属性化.
- 并且在运行时,默认创建一个ViewModel
- Page.DataContext=ViewModel
2,创建ViewMode类
/// <summary> /// ViewModel For Login Page /// </summary> public class LoginViewModel:BaseViewModel { #region Private Members #endregion #region Public Property /// <summary> /// the Email of the user /// </summary> public string Email { get; set; } public SecureString SecurePassword { get; set; } #endregion #region Commands public ICommand LoginCommand { get; set; } /// <summary> /// Indicate the login is runn /// </summary> public bool LoginIsRunning { get; set; } #endregion #region Constructor /// <summary> /// default construct /// </summary> public LoginViewModel() { //Create commands LoginCommand = new RelayParameterizedCommand(async (parameter) =>await Login(parameter) ) ; } #endregion /// <summary> /// Attempts to log the user in /// </summary> /// <param name="parameter">the<see cref="SecureString"/>passed in from the view for the users password</param> /// <returns></returns> public async Task Login(object parameter) { await RunCommand(() => this.LoginIsRunning, async () => { //body of a async work! await Task.Delay(5000); var email = this.Email; var password = this.SecurePassword.Unsecure(); }); } }
- 创建属性对应页面的内容
- public string Email { get; set; }//Email
- public SecureString SecurePassword { get; set; } //密码
- public ICommand LoginCommand { get; set; }//登录
- public bool LoginIsRunning { get; set; }//登录运行中
- 绑定LoginCommand
- 建立参数化的RelayCommand类
public class RelayParameterizedCommand : ICommand { /// <summary> /// for action /// </summary> private Action<object> mAction; /// <summary> /// constructor /// </summary> /// <param name="action"></param> public RelayParameterizedCommand(Action<object> action) { mAction = action; } //event when CanExecuteChanged,and doing Nothing public event EventHandler CanExecuteChanged = (send, e) => { }; /// <summary> /// Judge if CanExecute the Command. /// </summary> /// <param name="parameter"></param> /// <returns></returns> public bool CanExecute(object parameter) { return true; } /// <summary> /// do action of command /// </summary> /// <param name="parameter"></param> public void Execute(object parameter) { mAction(parameter); } } #endregion
- 创建一个BaseViewModel的一个等待函数
protected async Task RunCommand(Expression<Func<bool>> updatingFlag,Func<Task> action) { //Expression.complie.get value if (updatingFlag.GetPropertyValue()) return; //Indicate we're running. updatingFlag.SetPropertyValue(true); try { await action(); } finally { //set the property flag back to false now it's finished updatingFlag.SetPropertyValue(false); } }
- 用来避免异步任务多次执行.并且指示如果当前任务正在执行,则,updatingFlag=True,奖励了一个陷阱来防止任务多次执行.
- finally来保证在一次时,释放掉锁.
- 制作扩展函数利用反射来获取和设置属性的值.
public static class ExpressionHelpers { /// <summary> /// get a propertyValue use lambda /// </summary> /// <typeparam name="T"></typeparam> /// <param name="lambda"></param> /// <returns></returns> public static T GetPropertyValue<T>(this Expression<Func<T>> lambda) { return lambda.Compile().Invoke(); } public static void SetPropertyValue<T>(this Expression<Func<T>> lambda,T value) { //comvert a lambada()=>some.Property, to some.Property. var expression = (lambda as LambdaExpression).Body as MemberExpression; var propertyInfo = (PropertyInfo)expression.Member; // use this var target = Expression.Lambda(expression.Expression).Compile().DynamicInvoke(); propertyInfo.SetValue(target, value); } }
- 从lambda中提取出来表达式本体://()=>value(LoginPage).LoginIsRunning===>value(LoginPage).LoginIsRunning
- 作为MemberExpression,从中提取PropertyInfo
- expression.Expression--->将value(LoginPage).LoginIsRunning--->获取包含属性的类对象.
- 利用Expression.Lambda(expression.Expression)--->重构类对象指针.
- Compile().DynamicInvoke()—>获取到其对象.
3,获取Password的值的信息
- 建立扩展函数用于解析SecureString--->String
public static class SecureStringHelper { /// <summary> /// Unsecure a<see cref="SecureString"/> class /// </summary> /// <param name="securePassword">the Secure String</param> /// <returns></returns> public static string Unsecure(this SecureString secureString) { //makesure we has a secure string if (secureString == null) return string.Empty; var unmanagedString = IntPtr.Zero; //a way to get the secure string to a common string. try { //get a pointer of a secure string unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secureString); //copy it to the string . return Marshal.PtrToStringUni(unmanagedString); } finally { //Clean up any memory allocation Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString); } } }
- 新建一个字符串指针//var unmanagedString = IntPtr.Zero
- 将其指向安全字符串类//:unmanagedString = Marshal.SecureStringToGlobalAllocUnicode(secureString);
- 将指针转回字符串//Marshal.PtrToStringUni(unmanagedString);
- 注意Finally强制释放指针Marshal.ZeroFreeGlobalAllocUnicode(unmanagedString);
- 在LoginPage类中建立 映射,将密码框的SecurePassword转为SecurePassword.
- SecureString SecurePassword => PasswordText.SecurePassword;
- 建立一个IhaveSecure接口,从而避免将整个页面信息暴露.
- 然后再页面的CommandParameter中,将整个xaml类赋值过去
4,将Login按钮添加一个Spinner功能
- 新建一个等待的TextBlock Style
<Style TargetType="{x:Type TextBlock}" x:Key="SpinnerText" BasedOn="{StaticResource BaseTextBlockStyle}"> <Style.Resources> <Storyboard x:Key="sb" > <DoubleAnimation Storyboard.TargetProperty="(UIElement.RenderTransform).(RotateTransform.Angle)" From="0" To="360" Duration="0:0:2" RepeatBehavior="Forever"/> </Storyboard> </Style.Resources> <Setter Property="FontFamily" Value="{StaticResource FontAwesome}"/> <Setter Property="Text" Value=""/> <Setter Property="RenderTransform"> <Setter.Value> <RotateTransform/> </Setter.Value> </Setter> <Setter Property="RenderTransformOrigin" Value="0.5 0.5"/> <Style.Triggers> <DataTrigger Binding="{Binding RelativeSource={RelativeSource Self},Path=IsVisible}" Value="True"> <DataTrigger.EnterActions> <BeginStoryboard x:Name="beginSb" Storyboard="{StaticResource sb}"/> </DataTrigger.EnterActions> <DataTrigger.ExitActions> <RemoveStoryboard BeginStoryboardName="beginSb"/> </DataTrigger.ExitActions> </DataTrigger> </Style.Triggers> </Style>
- 在FontAweSome网站下载字体.
- 并且讲述了如何查看每个图标的字体代码---0类似这种,在图标里面查找spin,然后右击该图标选择查看类型.然后在跳出来的网页源代码里面,点开i开头的东西,然后点击before,
- 添加字体
- 添加资源
- 引用字体,及字体中的某个图标值
- 注意创建动画的效果.
<FontFamily x:Key="FontAwesome">pack://application;,,,/Fonts/#FontAwesome</FontFamily>
<Setter Property="FontFamily" Value="{StaticResource FontAwesome}"/>
<Setter Property="Text" Value=""/>//注意格式.
5,button按钮中制作LoginIsRunning连接
<ControlTemplate TargetType="{x:Type ButtonBase}"> <Border x:Name="border" CornerRadius="10" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True"> <Grid> <TextBlock Text="{TemplateBinding Content}" Focusable="False" FontFamily="{TemplateBinding FontFamily}" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Visibility="{TemplateBinding local:IsBusyProperty.Value, Converter={local:BooleanToVisibilityConverter}}" /> <TextBlock Style="{StaticResource SpinnerText}" Focusable="False" FontSize="{TemplateBinding FontSize}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Visibility="{TemplateBinding local:IsBusyProperty.Value, Converter={local:BooleanToVisibilityConverter},ConverterParameter=True}"/> </Grid> </Border> <ControlTemplate.Triggers> <EventTrigger RoutedEvent="MouseEnter"> <BeginStoryboard> <Storyboard> <ColorAnimation To="{StaticResource WordBlue}" Duration="0:0:0.3" Storyboard.TargetName="border" Storyboard.TargetProperty="Background.Color" /> </Storyboard> </BeginStoryboard> </EventTrigger> <EventTrigger RoutedEvent="MouseLeave"> <BeginStoryboard> <Storyboard> <ColorAnimation From="{StaticResource WordBlue}" Duration="0:0:0.3" Storyboard.TargetName="border" Storyboard.TargetProperty="Background.Color" /> </Storyboard> </BeginStoryboard> </EventTrigger> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Background" TargetName="border" Value="{StaticResource ForegroundDarkBrush}"/> </Trigger> </ControlTemplate.Triggers> </ControlTemplate>
- 建立依赖性属性 IsBusyProperty.Value
- 建立一个泛型的依赖项属性生成类
public abstract class BaseAttachedProperty <Parent,Property> where Parent : new() { #region Public event /// <summary> /// Fired when the value changes, /// </summary> public event Action<DependencyObject, DependencyPropertyChangedEventArgs> ValueChanged = (sender, e) => { }; /// <summary> /// Fired when the value changes, even when the value is the same /// </summary> public event Action<DependencyObject, object> ValueUpdated = (sender, value) => { }; #endregion #region Public Properties /// <summary> /// A singleton instance of our parent class; 使用这个办法是可以一种使用泛型模板制造静态实例的经典案列. /// </summary> public static Parent Instance { get; private set; } = new Parent(); #endregion #region Attaced Property Definitions public static readonly DependencyProperty Value = DependencyProperty.RegisterAttached("Value", typeof(Property), typeof(BaseAttachedProperty<Parent, Property>), new UIPropertyMetadata(default(Property),new PropertyChangedCallback(OnValuePropertyChanged), new CoerceValueCallback(OnValuePropertyUpdated) ) ); public static Property GetValue(DependencyObject d) => (Property)d.GetValue(Value); public static void SetValue(DependencyObject d, Property value) => d.SetValue(Value, value); #endregion #region FireFunctions /// <summary> /// when value changed ,trigger it /// </summary> /// <param name="d"></param> /// <param name="e"></param> private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { //Fire the class Changed (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueChanged(d, e); //Fire the Listerner Changed (Instance as BaseAttachedProperty<Parent, Property>)?.ValueChanged(d, e); } /// <summary> /// when value updated ,do this /// </summary> /// <param name="d"></param> /// <param name="baseValue"></param> /// <returns></returns> private static object OnValuePropertyUpdated(DependencyObject d, object value) { //Fire the class Changed (Instance as BaseAttachedProperty<Parent, Property>)?.OnValueUpdated(d, value); //Fire the Listerner Changed (Instance as BaseAttachedProperty<Parent, Property>)?.ValueUpdated(d, value); return value; } #endregion #region Local Fun /// <summary> /// Func Value Changed doing sth. /// </summary> /// <param name="d"></param> /// <param name="e"></param> public virtual void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { } /// <summary> /// ValueUpdated Fun doing sth. /// </summary> /// <param name="d"></param> /// <param name="value"></param> public virtual void OnValueUpdated(DependencyObject d, object value) { } #endregion }
- 定义ValueChanged和ValueUpdate事件
- 建立Singlton类的实例和依赖性属性Value
- 建立i两个虚拟函数,用于在事件触发时优先调用.
将button的Visibility进行绑定之后,使用Convert.
public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (parameter == null) return (bool)value == false ? Visibility.Visible : Visibility.Hidden; return (bool)value == true ? Visibility.Visible : Visibility.Hidden; }
结果:
在程序中查看密码和Email地址: