Caliburn.Micro support for PasswordBox?

前端 未结 3 461
遇见更好的自我
遇见更好的自我 2020-12-25 13:03

The Caliburn.Micro home page at http://caliburnmicro.com makes the below claim but I am unable to make CM work with a PasswordBox control using any variation I can think of

相关标签:
3条回答
  • 2020-12-25 13:13

    I've only been able to get it to work with dependency properties, effectively bypassing the convention binding goodness that Caliburn.Micro supplies. I recognize that's not your ideal, but pragmatically this is the solution I regularly use. I believe when I hit this snag historically, I found this post on StackOverflow that led me in this direction. For your consideration:

    public class BoundPasswordBox
        {
            private static bool _updating = false;
    
            /// <summary>
            /// BoundPassword Attached Dependency Property
            /// </summary>
            public static readonly DependencyProperty BoundPasswordProperty =
                DependencyProperty.RegisterAttached("BoundPassword",
                    typeof(string),
                    typeof(BoundPasswordBox),
                    new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
    
            /// <summary>
            /// Gets the BoundPassword property.
            /// </summary>
            public static string GetBoundPassword(DependencyObject d)
            {
                return (string)d.GetValue(BoundPasswordProperty);
            }
    
            /// <summary>
            /// Sets the BoundPassword property.
            /// </summary>
            public static void SetBoundPassword(DependencyObject d, string value)
            {
                d.SetValue(BoundPasswordProperty, value);
            }
    
            /// <summary>
            /// Handles changes to the BoundPassword property.
            /// </summary>
            private static void OnBoundPasswordChanged(
                DependencyObject d,
                DependencyPropertyChangedEventArgs e)
            {
                PasswordBox password = d as PasswordBox;
                if (password != null)
                {
                    // Disconnect the handler while we're updating.
                    password.PasswordChanged -= PasswordChanged;
                }
    
                if (e.NewValue != null)
                {
                    if (!_updating)
                    {
                        password.Password = e.NewValue.ToString();
                    }
                }
                else 
                {
                    password.Password = string.Empty;
                }
                // Now, reconnect the handler.
                password.PasswordChanged += PasswordChanged;
            }
    
            /// <summary>
            /// Handles the password change event.
            /// </summary>
            static void PasswordChanged(object sender, RoutedEventArgs e)
            {
                PasswordBox password = sender as PasswordBox;
                _updating = true;
                SetBoundPassword(password, password.Password);
                _updating = false;
            }
        }
    

    Then, in your XAML:

    <PasswordBox pwbx:BoundPasswordBox.BoundPassword="{Binding UserPassword, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged,NotifyOnValidationError=True,ValidatesOnDataErrors=True}" />
    

    and pwbx is found as a namespace on the Window tag:

    <Window x:Class="MyProject.Views.LoginView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 mc:Ignorable="d" 
                 xmlns:pwbx="clr-namespace:MyProject.Client.Controls">
    

    The ViewModel:

    using Caliburn.Micro;
    using MyProject.Core;
    using MyProject.Repositories;
    using MyProject.Types;
    using MyProject.ViewModels.Interfaces;
    
    namespace MyProject.ViewModels
    {
        public class LoginViewModel : Screen, ILoginViewModel
        {
            private readonly IWindowManager _windowManager;
            private readonly IUnitRepository _unitRepository;
            public bool IsLoginValid { get; set; }
            public Unit LoggedInUnit { get; set; }
    
            private string _password;
            public string UserPassword
            {
                get { return _password; }
                set
                {
                    _password = value;
                    NotifyOfPropertyChange(() => UserPassword);
                    NotifyOfPropertyChange(() => CanLogin);
                }
            }
    
            private string _name;
            public string Username
            {
                get { return _name; }
                set
                {
                    _name = value;
                    NotifyOfPropertyChange(() => Username);
                    NotifyOfPropertyChange(() => CanLogin);
                }
            }
            public LoginViewModel(IWindowManager windowManager,IUnitRepository unitRepository)
            {
                _windowManager = windowManager;
                _unitRepository = unitRepository;
                DisplayName = "MyProject - Login";
                Version = ApplicationVersionRepository.GetVersion();
            }
    
            public string Version { get; private set; }
    
            public void Login()
            {
                // Login logic
                var credentials = new UserCredentials { Username = Username, Password=UserPassword };
    
                var resp = _unitRepository.AuthenticateUnit(credentials);
                if (resp == null) return;
                if (resp.IsValid)
                {
                    IsLoginValid = true;
                    LoggedInUnit = resp.Unit;
                    TryClose();
                }
                else
                {
                    var dialog = new MessageBoxViewModel(DialogType.Warning, DialogButton.Ok, "Login Failed", "Login Error: " + resp.InvalidReason);
                    _windowManager.ShowDialog(dialog);
                }
            }
    
            public bool CanLogin
            {
                get
                {
                    return !string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(UserPassword);
                }
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-25 13:25

    Here's a much more simplified example, including a binding convention so that PasswordBox binding in Caliburn.Micro Just Works™:

    public static class PasswordBoxHelper
    {
        public static readonly DependencyProperty BoundPasswordProperty =
            DependencyProperty.RegisterAttached("BoundPassword",
                typeof(string),
                typeof(PasswordBoxHelper),
                new FrameworkPropertyMetadata(string.Empty, OnBoundPasswordChanged));
    
        public static string GetBoundPassword(DependencyObject d)
        {
            var box = d as PasswordBox;
            if (box != null)
            {
                // this funny little dance here ensures that we've hooked the
                // PasswordChanged event once, and only once.
                box.PasswordChanged -= PasswordChanged;
                box.PasswordChanged += PasswordChanged;
            }
    
            return (string)d.GetValue(BoundPasswordProperty);
        }
    
        public static void SetBoundPassword(DependencyObject d, string value)
        {
            if (string.Equals(value, GetBoundPassword(d)))
                return; // and this is how we prevent infinite recursion
    
            d.SetValue(BoundPasswordProperty, value);
        }
    
        private static void OnBoundPasswordChanged(
            DependencyObject d,
            DependencyPropertyChangedEventArgs e)
        {
            var box = d as PasswordBox;
    
            if (box == null)
                return;
    
            box.Password = GetBoundPassword(d);
        }
    
        private static void PasswordChanged(object sender, RoutedEventArgs e)
        {
            PasswordBox password = sender as PasswordBox;
    
            SetBoundPassword(password, password.Password);
    
            // set cursor past the last character in the password box
            password.GetType().GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(password, new object[] { password.Password.Length, 0 }); 
        }
    
    }
    

    Then, in your bootstrapper:

    public sealed class Bootstrapper : BootstrapperBase
    {
        public Bootstrapper()
        {
            Initialize();
    
            ConventionManager.AddElementConvention<PasswordBox>(
                PasswordBoxHelper.BoundPasswordProperty,
                "Password",
                "PasswordChanged");
        }
    
        // other bootstrapper stuff here
    }
    
    0 讨论(0)
  • 2020-12-25 13:27

    The solutions provided here seem to be needlessly complicated.

    We can very easily use Caliburn.Micro actions to send our password to the ViewModel.

    XAML:

    <PasswordBox cal:Message.Attach="[Event PasswordChanged] = [Action OnPasswordChanged($source)]" />
    

    ViewModel:

    public void OnPasswordChanged(PasswordBox source)
    {
        password = source.Password;
    }
    

    Then remember to clear the password fields so they don't remain in memory.

    NOTE: Obviously this solution doesn't allow you to easily change the password from the ViewModel, if that is necessary, then it's probably best to go with the attached property approach.

    0 讨论(0)
提交回复
热议问题