Search code examples
c#caliburn.micropasswordbox

Caliburn.Micro support for PasswordBox?


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 from that example. Don't see how this would work anyway since the names are not the same case. Does anyone have a CM example that does allow me to get the value of the PasswordBox? Is there a particular version of CM required? I'm running version 1.5.2 of CM. Ideally w/o using Attached Properties but if can work with CM and the only way then fine. Please no lectures on security issues as that is not an issue in my case.


Apply methods between your view and view model automatically with parameters and guard methods

<StackPanel>
    <TextBox x:Name="Username" />
    <PasswordBox x:Name="Password" />
    <Button x:Name="Login" Content="Log in" />
</StackPanel>

public bool CanLogin(string username, string password)
{
    return !String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password);
}

public string Login(string username, string password)
{
    ...
}

Solution

  • 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);
                }
            }
        }
    }