Password Text entered should appear for half a second and then replaced by * (Masking)

Viewed 62

I have a question in WPF, We have a text box. Where the user enters a password or some confidential data.

At this point, we have overridden System.Windows.Controls.TextBox's OnTextInput event handler. We have overridden as the textbox should work normally and based on some conditions, the same textbox should work as a password text box. and I am replacing the character entered with * asterisk.

Now, we have this requirement where, the text entered should be visible for half a second and then replaced by *.

Can you kindly help me here, Thanks in Advance.

2 Answers
  1. Add TextBox_changed function to the textbox. Eg.:

     <TextBox x:Name="textBox1" HorizontalAlignment="Left" Height="52" Margin="285,150,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="179" TextChanged="TextBox_Change"/>
    
  2. Add the header using System.Threading

  3. Add to the main wpf file:

     static string password = "";
     static int len = 0;
     private void TextBox_Change(object sender, TextChangedEventArgs e)
     {
         int ind = this.textBox1.Text.Length - 1;
         if (ind+1<len)
         {
             password = password.Substring(0, password.Length - 1);
             len -= 1;
         }
         if (this.textBox1.Text.Length > 0 && this.textBox1.Text[ind]!='*')
         {
             len += 1;
             this.textBox1.Text = textBox1.Text;
             password = password + this.textBox1.Text[ind];
             Thread thread = new Thread(threadFunc);
             thread.Start((object)ind);
    
             textBox1.CaretIndex = textBox1.Text.Length;
         }
    
     }
     public void threadFunc(object ind2)
     {
         int ind = (int)ind2;
         Thread.Sleep(1500);
         Dispatcher.BeginInvoke(new Action(delegate
         {
             this.textBox1.Text = this.textBox1.Text.Substring(0, ind) + '*';
             if (this.textBox1.Text.Length - 1 > ind)
             {
                 this.textBox1.Text = this.textBox1.Text + this.textBox1.Text.Substring(ind + 1);
             }
             textBox1.CaretIndex = textBox1.Text.Length;
         }));
     }
    

the thread is important so the textbox will update and show the letters before he sleeps for half a seacond

I'd create a custom control for this. Actually, I implemented one to check if works.

CustomPasswordBox.cs

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;

namespace WpfPasswordBoxSample;

public class CustomPasswordBox : TextBox
{
    public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
        nameof(Password),
        typeof(string),
        typeof(CustomPasswordBox),
        new PropertyMetadata(string.Empty));

    public static readonly DependencyProperty HidingPasswordMillisecondsProperty = DependencyProperty.Register(
        nameof(HidingPasswordMilliseconds),
        typeof(double),
        typeof(CustomPasswordBox),
        new PropertyMetadata(500.0));

    private int nextCaret;

    private CancellationTokenSource cancellationTokenSource = new();

    public CustomPasswordBox()
    {
        TextChanged += CustomPasswordBox_TextChanged;
    }

    public double HidingPasswordMilliseconds
    {
        get => (double)GetValue(HidingPasswordMillisecondsProperty);
        set => SetValue(HidingPasswordMillisecondsProperty, value);
    }

    public string Password
    {
        get => (string)GetValue(PasswordProperty);
        set => SetValue(PasswordProperty, value);
    }

    private async Task HidePasswordAsync(CancellationToken cancellationToken)
    {
        try
        {
            await Task.Delay(TimeSpan.FromMilliseconds(HidingPasswordMilliseconds), cancellationToken);
            cancellationToken.ThrowIfCancellationRequested();

            TextChanged -= CustomPasswordBox_TextChanged;
            Text = new string('*', Text.Length);
            TextChanged += CustomPasswordBox_TextChanged;
            CaretIndex = nextCaret;
        }
        catch
        {
        }
    }

    private async void CustomPasswordBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        if (e.Changes.FirstOrDefault() is TextChange change)
        {
            this.cancellationTokenSource.Cancel();

            this.nextCaret = 0;

            if (change.AddedLength > 0)
            {
                string adding = Text.Substring(change.Offset, change.AddedLength);
                Password = Password.Insert(change.Offset, adding);
                this.nextCaret = change.Offset + change.AddedLength;
            }
            else if (change.RemovedLength > 0)
            {
                Password = Password.Remove(change.Offset, change.RemovedLength);
                this.nextCaret = change.Offset;
            }

            this.cancellationTokenSource = new();
            CancellationToken cancellationToken = this.cancellationTokenSource.Token;
            await HidePasswordAsync(cancellationToken);
        }
    }
}

And you can use it like this.

MainWindow.xaml

<Window x:Class="WpfPasswordBoxSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfPasswordBoxSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <StackPanel>
        <local:CustomPasswordBox x:Name="PasswordBox" HidingPasswordMilliseconds="500"/>
        <TextBlock Text="{Binding ElementName=PasswordBox, Path=Password, Mode=OneWay}"/>
    </StackPanel>
</Window>
Related