2013-07-01 11 views
5

In iOS quando si digita una password in un campo viene visualizzata l'ultima lettera del campo ma viene offuscata quando si digita il carattere successivo. C'è un modo per duplicare questo comportamento in WPF?Casella password mascherata personalizzata in WPF

+3

Questo è considerato una cattiva pratica se stai facendo questo su un'app desktop.Considera il fatto che su un telefono la gente che ti guarda da dietro è molto meno di un grande monitor su un desktop. Quindi, a meno che tu non intenda farlo su un telefono o un tablet Windows. Pensa di nuovo. – Viv

+2

@Viv ha senso, ma sfortunatamente, non sto dettando i requisiti in questo scenario. – aceinthehole

+0

@Viv plus, questa applicazione può essere utilizzata da persone che potrebbero tendere verso il lato ipovedente, potrebbe avere un po 'più senso in quella situazione. – aceinthehole

risposta

11

Ok se l'utilizzo per una cosa del genere in un'applicazione desktop è giustificato, quindi è possibile fare qualcosa di simile al seguente.

Abbiamo avuto un requisito simile prima e questo è quello che ho fatto.

  • ho creato una consuetudine Passwordbox derivando dalla TextBox e l'aggiunta di un nuovo DP di tipo SecureString ad esso. (Più o meno lo stesso concetto come normale PasswordBox). Non perdiamo alcun vantaggio in termini di sicurezza in questo modo e possiamo personalizzare il comportamento visivo a nostro piacimento.
  • Con questo ora è possibile utilizzare lo Text dello TextBox poiché è una stringa di visualizzazione e contenere la password effettiva nel backend SecureString DP e collegarlo alla VM.
  • gestiamo le PreviewTextInput e PreviewKeyDown eventi per gestire tutte le modifiche al testo nel controllo, tra cui cose come Key.Back, Key.Delete e il fastidioso Key.Space (che non passa attraverso la PreviewTextInput

iOS tatto:

altro paio di cose da notare un comportamento esatto iOS.

  1. l'ultimo carattere viene visualizzato solo quando si aggiungono nuovi caratteri alla "fine della stringa corrente" (FlowDirection indipendente)
  2. La modifica di caratteri tra una stringa esistente non ha alcun effetto sulla maschera.
  3. L'ultimo carattere visualizzato dipende dal timer (diventa un "*" dopo un determinato periodo se lasciato inattivo)
  4. Tutte le operazioni di copia-incolla disabilitate nel controllo.

I primi 2 punti possono essere gestiti abbastanza facilmente quando si rilevano i cambiamenti di testo, per l'ultimo possiamo usare un DispatcherTimer per lavorare con la stringa di visualizzazione di conseguenza.

Quindi mettere insieme tutto questo si finisce con:

/// <summary> 
/// This class contains properties for CustomPasswordBox 
/// </summary> 
internal class CustomPasswordBox : TextBox { 
    #region Member Variables 
    /// <summary> 
    /// Dependency property to hold watermark for CustomPasswordBox 
    /// </summary> 
    public static readonly DependencyProperty PasswordProperty = 
    DependencyProperty.Register(
     "Password", typeof(SecureString), typeof(CustomPasswordBox), new UIPropertyMetadata(new SecureString())); 

    /// <summary> 
    /// Private member holding mask visibile timer 
    /// </summary> 
    private readonly DispatcherTimer _maskTimer; 
    #endregion 

    #region Constructors 
    /// <summary> 
    /// Initialises a new instance of the LifeStuffPasswordBox class. 
    /// </summary> 
    public CustomPasswordBox() { 
    PreviewTextInput += OnPreviewTextInput; 
    PreviewKeyDown += OnPreviewKeyDown; 
    CommandManager.AddPreviewExecutedHandler(this, PreviewExecutedHandler); 
    _maskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1) }; 
    _maskTimer.Tick += (sender, args) => MaskAllDisplayText(); 
    } 
    #endregion 

    #region Commands & Properties 
    /// <summary> 
    /// Gets or sets dependency Property implementation for Password 
    /// </summary> 
    public SecureString Password { 
    get { 
     return (SecureString)GetValue(PasswordProperty); 
    } 

    set { 
     SetValue(PasswordProperty, value); 
    } 
    } 
    #endregion 

    #region Methods 
    /// <summary> 
    /// Method to handle PreviewExecutedHandler events 
    /// </summary> 
    /// <param name="sender">Sender object</param> 
    /// <param name="executedRoutedEventArgs">Event Text Arguments</param> 
    private static void PreviewExecutedHandler(object sender, ExecutedRoutedEventArgs executedRoutedEventArgs) { 
    if (executedRoutedEventArgs.Command == ApplicationCommands.Copy || 
     executedRoutedEventArgs.Command == ApplicationCommands.Cut || 
     executedRoutedEventArgs.Command == ApplicationCommands.Paste) { 
     executedRoutedEventArgs.Handled = true; 
    } 
    } 

    /// <summary> 
    /// Method to handle PreviewTextInput events 
    /// </summary> 
    /// <param name="sender">Sender object</param> 
    /// <param name="textCompositionEventArgs">Event Text Arguments</param> 
    private void OnPreviewTextInput(object sender, TextCompositionEventArgs textCompositionEventArgs) { 
    AddToSecureString(textCompositionEventArgs.Text); 
    textCompositionEventArgs.Handled = true; 
    } 

    /// <summary> 
    /// Method to handle PreviewKeyDown events 
    /// </summary> 
    /// <param name="sender">Sender object</param> 
    /// <param name="keyEventArgs">Event Text Arguments</param> 
    private void OnPreviewKeyDown(object sender, KeyEventArgs keyEventArgs) { 
    Key pressedKey = keyEventArgs.Key == Key.System ? keyEventArgs.SystemKey : keyEventArgs.Key; 
    switch (pressedKey) { 
     case Key.Space: 
     AddToSecureString(" "); 
     keyEventArgs.Handled = true; 
     break; 
     case Key.Back: 
     case Key.Delete: 
     if (SelectionLength > 0) { 
      RemoveFromSecureString(SelectionStart, SelectionLength); 
     } else if (pressedKey == Key.Delete && CaretIndex < Text.Length) { 
      RemoveFromSecureString(CaretIndex, 1); 
     } else if (pressedKey == Key.Back && CaretIndex > 0) { 
      int caretIndex = CaretIndex; 
      if (CaretIndex > 0 && CaretIndex < Text.Length) 
      caretIndex = caretIndex - 1; 
      RemoveFromSecureString(CaretIndex - 1, 1); 
      CaretIndex = caretIndex; 
     } 

     keyEventArgs.Handled = true; 
     break; 
    } 
    } 

    /// <summary> 
    /// Method to add new text into SecureString and process visual output 
    /// </summary> 
    /// <param name="text">Text to be added</param> 
    private void AddToSecureString(string text) { 
    if (SelectionLength > 0) { 
     RemoveFromSecureString(SelectionStart, SelectionLength); 
    } 

    foreach (char c in text) { 
     int caretIndex = CaretIndex; 
     Password.InsertAt(caretIndex, c); 
     MaskAllDisplayText(); 
     if (caretIndex == Text.Length) { 
     _maskTimer.Stop(); 
     _maskTimer.Start(); 
     Text = Text.Insert(caretIndex++, c.ToString()); 
     } else { 
     Text = Text.Insert(caretIndex++, "*"); 
     } 
     CaretIndex = caretIndex; 
    } 
    } 

    /// <summary> 
    /// Method to remove text from SecureString and process visual output 
    /// </summary> 
    /// <param name="startIndex">Start Position for Remove</param> 
    /// <param name="trimLength">Length of Text to be removed</param> 
    private void RemoveFromSecureString(int startIndex, int trimLength) { 
    int caretIndex = CaretIndex; 
    for (int i = 0; i < trimLength; ++i) { 
     Password.RemoveAt(startIndex); 
    } 

    Text = Text.Remove(startIndex, trimLength); 
    CaretIndex = caretIndex; 
    } 

    private void MaskAllDisplayText() { 
    _maskTimer.Stop(); 
    int caretIndex = CaretIndex; 
    Text = new string('*', Text.Length); 
    CaretIndex = caretIndex; 
    } 
    #endregion 
} 

campione di lavoro:

Download Link

È possibile digitare qualcosa nel controllo e verificare il valore memorizzato mostrato sotto di esso.

In questo esempio, ho aggiunto un nuovo DP di tipo string per mostrare solo che il controllo funziona correttamente. Non vorresti avere quel DP (HiddenText) nel tuo codice live, ma mi auguro che l'esempio aiuti ad analizzare se la classe effettivamente funziona :)

+0

Wow, grazie Viv! Questo è esattamente il tipo di cosa che stavo cercando! Grazie per aver aiutato un principiante WPF. – aceinthehole

+0

Per qualche motivo non riesco a collegare il '' Password''-DP. Semplicemente non si aggiorna mai. SecureString non è semplicemente collegabile? Uso SecureString ovunque nella mia applicazione fino a quando non devo memorizzarlo su un file. Quindi ottengo la stringa temporanea e la crittografo istantaneamente con una chiave definita. Lo stesso, quando leggo il valore. Quindi SecureString è adatto a questo tipo di design, penso. Ma non posso aggiornarlo con Bindings = / – ecth

Problemi correlati