【问题标题】:Caliburn.Micro support for PasswordBox?Caliburn.Micro 是否支持 PasswordBox?
【发布时间】:2015-06-03 21:58:18
【问题描述】:

http://caliburnmicro.com 的 Caliburn.Micro 主页提出以下声明,但我无法使用从该示例中想到的任何变体使 CM 与 PasswordBox 控件一起使用。无论如何都看不到这将如何工作,因为名称的大小写不同。有没有人有一个 CM 示例可以让我获得 PasswordBox 的值?是否需要特定版本的 CM?我正在运行 CM 的 1.5.2 版。理想情况下不使用附加属性,但如果可以使用 CM 并且唯一的方法就可以了。请不要就安全问题进行讲座,因为这对我来说不是问题。


使用参数和保护方法自动在视图和视图模型之间应用方法

<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)
{
    ...
}

【问题讨论】:

    标签: c# caliburn.micro passwordbox


    【解决方案1】:

    这是一个更加简化的示例,包括一个绑定约定,以便在 Caliburn.Micro Just Works™ 中绑定 PasswordBox

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

    然后,在您的引导程序中:

    public sealed class Bootstrapper : BootstrapperBase
    {
        public Bootstrapper()
        {
            Initialize();
    
            ConventionManager.AddElementConvention<PasswordBox>(
                PasswordBoxHelper.BoundPasswordProperty,
                "Password",
                "PasswordChanged");
        }
    
        // other bootstrapper stuff here
    }
    

    【讨论】:

    • 你的(很棒的)代码,奇怪的是,在每种类型中都将插入符号移动到框的开头。您需要在PasswordChanged 方法的末尾添加:stackoverflow.com/a/1046920/6776
    • 正如 cosmo0 指出的那样,我在 PasswordChanged 函数的最后一行添加了以下行,以设置正确的插入符号位置与字符串的开头:password.GetType().GetMethod("Select" , BindingFlags.Instance | BindingFlags.NonPublic).Invoke(password, new object[] { password.Password.Length, 0 });
    • @FMM 我用 Chris 和 comso 的建议修改了代码。
    • 谁能举例说明如何使用它。
    • 对匆忙的(像我一样)的一点注意:如果您使用"" 预设支持 PasswordBox 值的视图模型属性,则永远不会调用助手并且永远不会更新您的虚拟机的属性当用户输入或更改密码时。这是因为FrameworkPropertyMetadata 的默认值设置为string.Empty。将其设置为 null 以使其在这种情况下工作。
    【解决方案2】:

    这里提供的解决方案似乎是不必要的复杂。

    我们可以非常轻松地使用 Caliburn.Micro 操作将我们的密码发送到 ViewModel。

    XAML:

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

    视图模型:

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

    然后记得清除密码字段,以免它们留在内存中。

    注意:显然,此解决方案不允许您从 ViewModel 轻松更改密码,如果有必要,那么最好使用附加属性方法。

    【讨论】:

    • 太棒了!谢谢,莎琳。绝对是一个非常简单的解决方案!
    【解决方案3】:

    我只能让它与依赖属性一起工作,有效地绕过了 Caliburn.Micro 提供的约定绑定优点。我承认这不是您的理想,但实际上这是我经常使用的解决方案。我相信当我在历史上遇到这个障碍时,我在 StackOverflow 上找到了 this post,这使我朝着这个方向前进。供您参考:

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

    然后,在您的 XAML 中:

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

    并且 pwbx 在 Window 标签上作为命名空间被发现:

    <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">
    

    视图模型:

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

    【讨论】:

    • 谢谢,我会在今天晚些时候在太多会议结束后尝试这样做,并希望返回并标记为答案。希望我知道他们就该示例的工作方式所声称的内容。也许这些约定仅在新的 2.0 版本中有效,但我现在还没有准备好转换到那个版本。我明白他们故意没有 PasswordBox 的依赖属性。
    • 我尝试了添加依赖属性的其他变体以及您的,在这两种情况下,即使您的附加属性代码被调用,我声明的 UserPassword 也没有在 ViewModel 代码中设置。您如何声明、设置和访问实际的 UserPassword 变量?您使用的是 Caliburn Micro 还是后面的代码?我想我不知道如何在 Caliburn Micro ViewModel 的上下文中执行此操作。谢谢。
    • 我想我终于通过添加以下代码解决了这个问题-
    • @Dave 很高兴你知道了。我在代码中没有任何东西,而是作为视图模型中的属性。它像任何其他属性一样被访问。在我的 XAML 中,您可以看到该属性有效地绕过了 Caliburn.Micro 的约定绑定并直接设置它。我会说 CM 在其中没有任何作用,但是我确实有一个 CanLogin 保护子句绑定到登录按钮,当 ZZZ 是 CM 操作时,它确实利用了 CM 的“CanZZZ”保护操作,按名称绑定到 VM 的相同方法名字。
    • 代码未追加,因此重试 - 我向 CM ViewModel 添加了一个对话框属性,该属性从 XAML 中定义的视图中获取 UserPassword 对象。 public PasswordBox Password { get { return mView.UserPassword; }
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-08
    • 1970-01-01
    • 1970-01-01
    • 2021-06-07
    • 2020-04-23
    • 2019-08-28
    • 2015-07-04
    相关资源
    最近更新 更多