为什么WPF PasswordBox有时会变空,而有时却不会?

4
我提供了一个示例程序:当在PasswordBox中按Tab键进入并输入文本,然后按Tab键退出时,似乎会导致PasswordBox变为空白。但是在PasswordBox中双击、输入文本并按Tab键不会出现这种情况。为什么会发生这种情况?
编辑:我发现这也会发生在TextBox上,所以这不是PasswordBox特有的错误。
重现每种情况的步骤:
使密码消失
- 点击“新建”。 - 单击第一个名字字段。 - 输入一些内容。 - 按Tab键。 - 输入一些内容。 - 按Tab键。 - 按两次Tab键(否则Save事件会触发)。 - 输入一些内容。 - 注意到点而不是文本 - PasswordBox正常工作! - 按Tab键。 - 让你的朋友惊叹,因为PasswordBox变成空白了!
使密码不消失
- 点击“新建”。 - 单击第一个名字字段。 - 输入一些内容。 - 按Tab键。 - 输入一些内容。 - 在密码字段中双击。 - 输入一些内容。 - 注意到点而不是文本 - PasswordBox正常工作! - 按Tab键。 - 让你的朋友惊叹...等等,PasswordBox没有变空?WTF?
示例代码:
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>

编辑

使用数据模板来设置CellTemplate和EditingCellTemplate,我的解决方法很简单-始终显示一些内容(在这种情况下是椭圆形)。这样,无论何时PasswordBox失去焦点,它都会改变回椭圆形。我认为这比有时看到弹珠字符有时看到空白更好。David Edey的DataGrid_PreparingCellForEdit函数完美地解决了双击问题。

以下是我的数据模板。

    <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>

我能想到唯一可能导致你的问题的原因是,DataGrid正在重建项目。你是否在任何地方调用了“Refresh”或者对“DataList”进行了操作以重新加载该行? - TyCobb
据我所知没有。样例代码就是一切。在我提问之前,我想确保我可以在一个简单的、全新的程序中重新创建它。我们正在使用 Visual Studio 2013 和 .NET 4.5。我认为这可能是 PasswordBox 中的一个 bug,但我希望我只是错过了某个属性或其他东西。 - James
1个回答

3
我认为这个问题与有两个单独的模板有关:一个用于编辑(CellEditingTemplate),另一个用于显示(CellTemplate)。解决方法通常是使用绑定,让这两个单独的模板都绑定到同一块数据上,例如:http://msdn.microsoft.com/en-us/library/system.windows.controls.datagridtemplatecolumn.celltemplate(v=vs.110).aspx 但当然,由于安全原因,PasswordBox 不允许您绑定到 Password (因为 Password 不是依赖属性)。如果这不是一个巨大的问题(我并不完全理解为什么会是,但我猜他们不希望明文密码在没有人知道自己在做什么的情况下通过绑定传播),那么您可以按照这里文章的后半部分创建一个绑定:http://wpftutorial.net/PasswordBox.html - 使用自定义静态依赖属性。

所以我在下面的代码中实现了该过程,并通过添加处理程序到PreparingCellForEdit事件来修复了密码框无法正确聚焦并需要两个选项卡的错误,如https://dev59.com/x3RB5IYBdhLWcg3wAjXR#2835464

总的来说,我工作样本的示例代码如下:

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(); }
    }

而 xaml 则为:

<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>

然后,从http://wpftutorial.net/PasswordBox.html中添加PasswordHelper类到您的命名空间,就可以了。


双击 PasswordBox 和通过 Tab 键进入有什么不同?肯定有一些属性或其他东西在双击时被设置了,而在通过 Tab 键进入时没有被设置。 - James
1
我认为这取决于你使用CellEditingTemplate还是CellTemplate。我猜双击和按Tab键在后台的行为不同,可能会直接进入编辑模板,并且当你离开时不会离开它?我重现了这个问题,但无法找到根本原因,我认为原因是模板改变,因此文本没有绑定而丢失。这里讨论了一些相关信息:http://www.databaseskill.com/1322706/ - David E

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接