4

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:

Make the password disappear

  • Click New.
  • Single click in the first name field.
  • Type something
  • Press the TAB key
  • Type something
  • Press the TAB key
  • Press the TAB key( yes twice else the Save event fires)
  • Type something
  • Notice the dots instead of text -PasswordBox is working!
  • Press the TAB key.
  • Amaze your friends as the PasswordBox goes blank!!

Make the password NOT disappear

  • Click New.
  • Single click in the first name field.
  • Type something
  • Press the TAB key
  • Type something
  • Double click in the password field.
  • Type something
  • Notice the dots instead of text -PasswordBox is working!
  • Press the TAB key.
  • Amaze your friends as the.. wait, the PasswordBox isn't blank? WTF?

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>

EDIT

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>
James
  • 538
  • 1
  • 6
  • 23
  • The only thing I can think of that would cause your issue, is if the DataGrid is rebuilding the items. Are you calling a `Refresh` anywhere or doing something with `DataList` to make the grid reload that row? – TyCobb Sep 12 '14 at 18:25
  • Not that I know of. The sample code is everything. Before I asked the question I wanted to make sure I could recreate it in a simple, brand new program. We're using Visual Studio 2013 and .NET 4.5. I was thinking it might be a bug in PasswordBox, but I'm hoping I just missed an attribute or something. – James Sep 12 '14 at 18:38

1 Answers1

3

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.

Community
  • 1
  • 1
David E
  • 1,384
  • 9
  • 14
  • How is double clicking in the PasswordBox different from tabbing into it? There must be some property or something that gets set on double click that doesn't get set when tabbing in. – James Sep 15 '14 at 11:07
  • 1
    I believe it's the difference between when you have the `CellEditingTemplate` and when you have the `CellTemplate`.. I guess double clicking behaves differently behind-the-scenes to tabbing, and may take you straight to the editing template, and/or not go away from it when you leave? I reproduced the issue, but couldn't get to the root cause of the reason, which I believe is the template changing, and thus the text getting lost without a binding. Some relevant information is discussed here: http://www.databaseskill.com/1322706/ – David E Sep 15 '14 at 11:33