Reputation: 545
I am including a sample program: it seems that when tabbing into the PasswordBox, entering text and tabbing out causes the PasswordBox to go blank. But double clicking in the PasswordBox, entering text and tabbing out does not. Why does this happen?
Edit: I discovered this also happens with a TextBox, so it isn't a bug specific to PasswordBox.
Steps to recreate each scenario:
Sample Code:
using System;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
public sealed class MyData {
private ObservableCollection<MyDataRow> dataList;
public ObservableCollection<MyDataRow> DataList { get { return dataList; } }
public MyData() { dataList = new ObservableCollection<MyDataRow>(); }
public void AddBlankRow() { DataList.Add(new MyDataRow(this)); }
}
public sealed class MyDataRow {
private readonly MyData myData;
public MyDataRow(MyData myData) { this.myData = myData; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
}
}
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace WpfApplication1 {
public partial class MainWindow : Window {
private MyData Data { get { return (MyData)DataContext; } }
public MainWindow() { InitializeComponent(); }
private void PasswordBox_PasswordChanged(object sender, RoutedEventArgs e) {
PasswordBox pb = (PasswordBox)sender;
if (pb != null) {
MyDataRow row = pb.DataContext as MyDataRow;
if (row != null) { row.Password = pb.Password; }
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) { DataContext = new MyData(); }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { }
private void SaveExecute(object sender, ExecutedRoutedEventArgs e) { }
private void NewExecute(object sender, ExecutedRoutedEventArgs e) { Data.AddBlankRow(); }
private void CancelExecute(object sender, ExecutedRoutedEventArgs e) { Close(); }
}
}
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" Closing="Window_Closing">
<Window.CommandBindings>
<CommandBinding Command="Save" Executed="SaveExecute" />
<CommandBinding Command="New" Executed="NewExecute" />
<CommandBinding Command="Close" Executed="CancelExecute" />
</Window.CommandBindings>
<Grid Margin="0,10,0,0">
<DataGrid ItemsSource="{Binding DataList}" ColumnWidth="*" Margin="10,0,9,38" HorizontalAlignment="Stretch"
AutoGenerateColumns="False" GridLinesVisibility="Horizontal"
HeadersVisibility="Column" HorizontalGridLinesBrush="LightGray" CanUserReorderColumns="False" Background="White" >
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTemplateColumn Header="Password" >
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<PasswordBox PasswordChanged="PasswordBox_PasswordChanged" BorderThickness="0"
Height="23" HorizontalAlignment="Stretch"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button
Content="_New" Command="New"
Width="75" Height="23" Margin="10,10,10,10"
HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Button
Content="_Save" Command="Save"
IsDefault="True"
IsEnabled="True"
Width="75" Height="23" Margin="10,10,91,10"
HorizontalAlignment="Right" VerticalAlignment="Bottom" />
<Button
Content="Cancel" Command="Close"
IsEnabled="True"
Width="75" Height="23" Margin="10,10,10,10"
HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</Window>
Using data templates for both the CellTemplate and the EditingCellTemplate, My work around is simply to be consistent - always show something, (in this case an elipse). That way, no matter what, when the PasswordBox loses focus it changes back to the elipse. I think this is better than sometimes seeing bullet characters and sometimes seeing blank. David Edey's DataGrid_PreparingCellForEdit works perfectly for solving the double tab problem.
Here's my data templates.
<Grid.Resources>
<DataTemplate x:Key="PasswordTemplate" >
<Label BorderThickness="0" Height="23" HorizontalAlignment="Stretch" Content="..."/>
</DataTemplate>
<DataTemplate x:Key="EditingPasswordTemplate" >
<PasswordBox PasswordChanged="PasswordBox_PasswordChanged" BorderThickness="0"
Height="23" HorizontalAlignment="Stretch" />
</DataTemplate>
</Grid.Resources>
Upvotes: 4
Views: 1976
Reputation: 1444
I believe the issue is related to there being two separate templates: one for editing (CellEditingTemplate
), and one for displaying (CellTemplate
). The normal way around it is to use bindings, and have the two separate templates both bind to the same piece of data, as per: http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn.celltemplate(v=vs.110).aspx
But of course, PasswordBox
doesn't let you bind to Password
(because Password
is not a Dependency Property) for security reasons. If that's not a massive concern (I don't entirely understand why it would be, but I guess they don't want plaintext passwords flying around in bindings without people aware of what they're doing), then you can create a binding as per the second half of the article here: http://wpftutorial.net/PasswordBox.html - using a custom static dependency property.
So I've implemented that prcedure in the code below, and I've also fixed the bug with the password box not focusing correctly and requiring two tabs, by adding a handler to the PreparingCellForEdit
event, as per https://stackoverflow.com/a/2835464/3940783
All in all, the sample code for my working sample is:
using System;
using System.Collections.ObjectModel;
namespace WpfApplication1 {
public sealed class MyData {
private ObservableCollection<MyDataRow> dataList;
public ObservableCollection<MyDataRow> DataList { get { return dataList; } }
public MyData() { dataList = new ObservableCollection<MyDataRow>(); }
public void AddBlankRow() { DataList.Add(new MyDataRow(this)); }
}
public sealed class MyDataRow {
private readonly MyData myData;
public MyDataRow(MyData myData) { this.myData = myData; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Password { get; set; }
}
}
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
namespace WpfApplication1 {
public partial class MainWindow : Window
{
public MainWindow() { InitializeComponent(); }
private MyData Data { get { return (MyData)DataContext; } }
void DataGrid_PreparingCellForEdit(object sender, DataGridPreparingCellForEditEventArgs e)
{
var inputElement = VisualTreeHelper.GetChild(e.EditingElement, 0) as PasswordBox;
if (inputElement != null)
{
Keyboard.Focus(inputElement);
}
}
private void Window_Loaded(object sender, RoutedEventArgs e) { DataContext = new MyData(); }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { }
private void SaveExecute(object sender, ExecutedRoutedEventArgs e) { }
private void NewExecute(object sender, ExecutedRoutedEventArgs e) { Data.AddBlankRow(); }
private void CancelExecute(object sender, ExecutedRoutedEventArgs e) { Close(); }
}
And the xaml is then:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:w="clr-namespace:WpfApplication1"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded" WindowStartupLocation="CenterScreen" Closing="Window_Closing">
<Window.CommandBindings>
<CommandBinding Command="Save" Executed="SaveExecute" />
<CommandBinding Command="New" Executed="NewExecute" />
<CommandBinding Command="Close" Executed="CancelExecute" />
</Window.CommandBindings>
<Grid Margin="0,10,0,0">
<DataGrid ItemsSource="{Binding DataList, Mode=OneTime}" ColumnWidth="*" Margin="10,0,9,38" HorizontalAlignment="Stretch"
AutoGenerateColumns="False" GridLinesVisibility="Horizontal"
HeadersVisibility="Column" HorizontalGridLinesBrush="LightGray" CanUserReorderColumns="False" Background="White"
PreparingCellForEdit="DataGrid_PreparingCellForEdit">
<DataGrid.Resources>
<DataTemplate x:Key="PasswordTemplate">
<PasswordBox w:PasswordHelper.Attach="True" w:PasswordHelper.Password="{Binding Password, Mode=TwoWay}"
BorderThickness="0" Height="23" HorizontalAlignment="Stretch" Width="130" />
</DataTemplate>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}"/>
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}"/>
<DataGridTemplateColumn Header="Password" CellTemplate="{StaticResource PasswordTemplate}"
CellEditingTemplate="{StaticResource PasswordTemplate}" />
</DataGrid.Columns>
</DataGrid>
<Button
Content="_New" Command="New"
Width="75" Height="23" Margin="10,10,10,10"
HorizontalAlignment="Left" VerticalAlignment="Bottom"/>
<Button
Content="_Save" Command="Save"
IsDefault="True"
IsEnabled="True"
Width="75" Height="23" Margin="10,10,91,10"
HorizontalAlignment="Right" VerticalAlignment="Bottom" />
<Button
Content="Cancel" Command="Close"
IsEnabled="True"
Width="75" Height="23" Margin="10,10,10,10"
HorizontalAlignment="Right" VerticalAlignment="Bottom"/>
</Grid>
</Window>
And then add the PasswordHelper
class from http://wpftutorial.net/PasswordBox.html to your namespace, and voila.
Upvotes: 3