2015-06-03 9 views
4

Домашняя страница Caliburn.Micro в http://caliburnmicro.com делает следующее утверждение, но я не могу заставить CM работать с элементом управления PasswordBox, используя любые варианты, о которых я могу думать из этого примера. Не видите, как это будет работать в любом случае, поскольку имена не совпадают. У кого-нибудь есть пример CM, который позволяет мне получить значение PasswordBox? Требуется ли конкретная версия CM? Я запускаю версию 1.5.2 CM. В идеале без использования Attached Properties, но если вы можете работать с CM и только тогда это нормально. Пожалуйста, не лекций по вопросам безопасности, поскольку это не проблема в моем случае.Caliburn.Micro поддержка PasswordBox?


Применение методов между вашей точки зрения и модели представления автоматически с параметрами и методами охраны

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

ответ

1

Я только был в состоянии заставить его работать со свойствами зависимостей, эффективно обходя соглашение, связывающее доброту, которую предоставляет Caliburn.Micro. Я признаю, что это не ваш идеал, но прагматично это решение, которое я регулярно использую. Я считаю, что когда я ударил эту ловушку исторически, я нашел this post на StackOverflow, который привел меня в этом направлении. Для вашего рассмотрения:

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

Спасибо, я постараюсь, что позже сегодня после многих встреч закончились, и возвращайтесь и отмечайте как ответ, надеюсь. Хотел бы я знать, что они заявляют в отношении того, как этот пример работал. Возможно, соглашения каким-то образом работают только в новой версии 2.0, но я не готов к этому прямо сейчас. Я понимаю, что у них целенаправленно не было зависимого свойства с PasswordBox. – Dave

+0

Я попробовал другой вариант добавления свойства зависимости, а также ваш, и в обоих случаях мой объявленный UserPassword не устанавливается в коде ViewModel, даже несмотря на то, что вы вызываете свой код свойства. Как вы объявляете, устанавливаете и получаете доступ к фактическому var-файлу UserPassword? Вы используете Caliburn Micro или код позади? Думаю, я не знаю, как это сделать в контексте Caliburn Micro ViewModel. Спасибо. – Dave

+0

Думаю, я, наконец, понял это, добавив следующий код: – Dave

9

Вот гораздо более упрощенный Например, в том числе обязывающее соглашение, так что PasswordBox связывание в 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 }); 
    } 

} 

Тогда в вашем загрузчике:

public sealed class Bootstrapper : BootstrapperBase 
{ 
    public Bootstrapper() 
    { 
     Initialize(); 

     ConventionManager.AddElementConvention<PasswordBox>(
      PasswordBoxHelper.BoundPasswordProperty, 
      "Password", 
      "PasswordChanged"); 
    } 

    // other bootstrapper stuff here 
} 
+0

FMM - Thx. Похож на код для более новой версии CM. Будет ли это работать в версии 1.5.2 CM? – Dave

+0

Не уверен; важная часть вышеописанного кода CM - это использование «ConventionManager»; Я сомневаюсь, что это сильно изменилось. – FMM

+0

Ваш (удивительный) код, странно, перемещает курсор в начало окна каждого типа. Вы должны добавить это: http://stackoverflow.com/a/1046920/6776 в конце метода PasswordChanged. – thomasb