The idea is to take a simple textbox control, which already has a bindable text property, implement a password character set I found, and suppress all cut, copy, and paste operations. In addition we will add some new dependency properties that will control the characters that can be entered. This allows the consumer to have numeric or alpha only password, or different combinations.
Because the password control is simply a textbox control with some extra properties, we can easily implement it as a user control. We don't need the power of a custom control. This means we don't have to worry about template binding everything.
Let's start by creating a new UserControl project called CustomControls. I'm going to write the project in C# and target Framework 4.0.
Rename UserControl1 to BindablePassword.
We will base our control on a TextBox so replace the contents of BindablePassword.xaml with this...
<TextBox x:Class="CustomControls.BindablePassword"
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"
FontFamily="pack://application:,,,./Fonts/#Password"
CommandManager.PreviewExecuted="TextBox_PreviewExecuted"
PreviewTextInput="TextBox_PreviewTextInput"
ContextMenu="{x:Null}">
</TextBox>
TextBox_PreviewExecuted will suppress any cut, copy, or paste actions. This prevents someone copying the password from the textbox and pasting it into, say, notepad, thus revealing the password.
TextBox_PreviewTextInput will look at the character being typed and cancel the operation if it isn't the correct type.
ContextMenu prevents the TextBox's context menu being displayed because the default context menu has the Cut, Copy, and Paste commands in it.
Let's look at the code behind. This specifies three new dependency properties that control the types of characters that can be entered. The consumer can specify these properties in its XAML or code behind. As you can see, it would be very easy to define different classes of characters to support.
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace CustomControls
{
public partial class BindablePassword : TextBox
{
static readonly DependencyProperty AllowDigitsProperty;
static readonly DependencyProperty AllowAlphaProperty;
static readonly DependencyProperty AllowSpecialProperty;
public bool
AllowDigits
{
get { return (bool)GetValue(AllowDigitsProperty); }
set { SetValue(AllowDigitsProperty, value); }
}
public bool
AllowAlpha
{
get { return (bool)GetValue(AllowAlphaProperty); }
set { SetValue(AllowAlphaProperty, value); }
}
public bool
AllowSpecial
{
get { return (bool)GetValue(AllowSpecialProperty); }
set { SetValue(AllowSpecialProperty, value); }
}
public BindablePassword()
{
InitializeComponent();
}
static BindablePassword()
{
AllowDigitsProperty = DependencyProperty.Register("AllowDigits", typeof(bool), typeof(BindablePassword), new FrameworkPropertyMetadata(false));
AllowAlphaProperty = DependencyProperty.Register("AllowAlpha", typeof(bool), typeof(BindablePassword), new FrameworkPropertyMetadata(false));
AllowSpecialProperty = DependencyProperty.Register("AllowSpecial", typeof(bool), typeof(BindablePassword), new FrameworkPropertyMetadata(false));
}
private void
TextBox_PreviewExecuted(object sender, ExecutedRoutedEventArgs e)
{
// Suppress cut, copy, and paste
if (e.Command == ApplicationCommands.Cut ||
e.Command == ApplicationCommands.Copy ||
e.Command == ApplicationCommands.Paste)
e.Handled = true;
}
private void
TextBox_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
// Suppress unwanted characters
String c = e.Text;
if ("1234567890".Contains(c) && AllowDigits) return;
if ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".Contains(c) && AllowAlpha) return;
if ("~`!@#$%^&*()_-+={[}]|\\:;\"'<,>.?/".Contains(c) && AllowSpecial) return;
e.Handled = true;
}
}
}
In the TestBindablePassword project, add a reference to CustomControls. Set TestBindablePassword as the default project. Your solution explorer now looks like this...
We will use the password control three times in our MainWindow, one for digits only, one for alpha only, and one for special characters only. Each password control will have its text property bound and a label to the right will be bound to the same property so you can see the password as you enter it. This would not be a good design in real life!
Change the MainWindow.xaml to look like this...
<Window x:Class="TestBindablePassword.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:TestBindablePassword"
xmlns:custom="clr-namespace:CustomControls;assembly=CustomControls"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Height="22">
<TextBlock Text="Digits" Width="100"/>
<custom:BindablePassword Text="{Binding DigitPassword, UpdateSourceTrigger=PropertyChanged}" Width="100" AllowDigits="True"/>
<TextBlock Text="{Binding Path=DigitPassword}" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Height="22">
<TextBlock Text="Alpha" Width="100"/>
<custom:BindablePassword Text="{Binding AlphaPassword, UpdateSourceTrigger=PropertyChanged}" Width="100" AllowAlpha="True"/>
<TextBlock Text="{Binding Path=AlphaPassword}" Margin="20,0,0,0"/>
</StackPanel>
<StackPanel Orientation="Horizontal" Height="22">
<TextBlock Text="Special" Width="100"/>
<custom:BindablePassword Text="{Binding SpecialPassword, UpdateSourceTrigger=PropertyChanged}" Width="100" AllowSpecial="True"/>
<TextBlock Text="{Binding Path=SpecialPassword}" Margin="20,0,0,0"/>
</StackPanel>
</StackPanel>
</Window>
using System;
using System.ComponentModel;
using System.Windows;
namespace TestBindablePassword
{
public partial class MainWindow : Window, INotifyPropertyChanged
{
public event PropertyChangedEventHandler
PropertyChanged;
private void
NotifyPropertyChanged(String PropertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
}
}
private string
_DigitPassword = "";
public string
DigitPassword
{
get { return
_DigitPassword; }
set
{
_DigitPassword = value;
NotifyPropertyChanged("DigitPassword");
}
}
private string
_AlphaPassword = "";
public string
AlphaPassword
{
get { return
_AlphaPassword; }
set
{
_AlphaPassword = value;
NotifyPropertyChanged("AlphaPassword");
}
}
private string
_SpecialPassword = "";
public string
SpecialPassword
{
get { return
_SpecialPassword; }
set
{
_SpecialPassword = value;
NotifyPropertyChanged("SpecialPassword");
}
}
public MainWindow()
{
InitializeComponent();
}
}
}