(UWP) 如何使密码显示按钮始终可见并使其始终在 PasswordBox 中工作

(UWP) How to make the password reveal button always visible and make it work always in a PasswordBox

我在我的 UWP 应用程序中使用了 PasswordBox,我想在其中始终显示密码显示按钮。此外,按下时它应该按预期工作并且应该显示密码。

我修改了 PasswordBox 控件的默认样式,将 Password Reveal 按钮的可见性设置为 true。它可以工作,并且该按钮现在始终可见。但问题是密码显示功能只有在清除密码并从头开始输入时才有效。

<Style x:Key="PasswordBoxStyle" TargetType="PasswordBox">
        <Setter Property="Foreground" Value="Black"/>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="PasswordBox">
                    <Grid Background="Transparent">
                        <Grid.Resources>
                            <Style x:Name="RevealButtonStyle" TargetType="Button">
                                <Setter Property="Template">
                                    <Setter.Value>
                                        <ControlTemplate TargetType="Button">
                                            <Grid x:Name="ButtonLayoutGrid" BorderBrush="{ThemeResource TextBoxButtonBorderThemeBrush}"
                                                    BorderThickness="{TemplateBinding BorderThickness}"
                                                    Background="{ThemeResource TextBoxButtonBackgroundThemeBrush}">
                                                <VisualStateManager.VisualStateGroups>
                                                    <VisualStateGroup x:Name="CommonStates">
                                                        <VisualState x:Name="Normal" />
                                                        <VisualState x:Name="PointerOver">
                                                            <Storyboard>
                                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement"
                                                             Storyboard.TargetProperty="Foreground">
                                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                                                                </ObjectAnimationUsingKeyFrames>
                                                            </Storyboard>
                                                        </VisualState>
                                                        <VisualState x:Name="Pressed">
                                                            <Storyboard>
                                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonLayoutGrid"
                                                             Storyboard.TargetProperty="Background">
                                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAccentBrush}" />
                                                                </ObjectAnimationUsingKeyFrames>
                                                                <ObjectAnimationUsingKeyFrames Storyboard.TargetName="GlyphElement"
                                                             Storyboard.TargetProperty="Foreground">
                                                                    <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlHighlightAltChromeWhiteBrush}" />
                                                                </ObjectAnimationUsingKeyFrames>
                                                            </Storyboard>
                                                        </VisualState>
                                                        <VisualState x:Name="Disabled">
                                                            <Storyboard>
                                                                <DoubleAnimation Storyboard.TargetName="ButtonLayoutGrid"
                                               Storyboard.TargetProperty="Opacity"
                                               To="0"
                                               Duration="0" />
                                                            </Storyboard>
                                                        </VisualState>
                                                    </VisualStateGroup>
                                                </VisualStateManager.VisualStateGroups>
                                                <TextBlock x:Name="GlyphElement"
                                  Foreground="{ThemeResource SystemControlForegroundChromeBlackMediumBrush}"
                                  VerticalAlignment="Center"
                                  HorizontalAlignment="Center"
                                  FontStyle="Normal"
                                  FontSize="16"
                                  Text="&#xE052;"
                                  FontFamily="{ThemeResource SymbolThemeFontFamily}"
                                  AutomationProperties.AccessibilityView="Raw"/>
                                            </Grid>
                                        </ControlTemplate>
                                    </Setter.Value>
                                </Setter>
                            </Style>
                        </Grid.Resources>
                        <VisualStateManager.VisualStateGroups>
                            <VisualStateGroup x:Name="CommonStates">
                                <VisualState x:Name="Disabled">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="HeaderContentPresenter"
                                                   Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseMediumLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundElement"
                                                 Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledTransparentBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement"
                                                 Storyboard.TargetProperty="Background">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="BorderElement"
                                                 Storyboard.TargetProperty="BorderBrush">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledBaseLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="ContentElement"
                                                 Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="PlaceholderTextContentPresenter"
                                                 Storyboard.TargetProperty="Foreground">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="{ThemeResource SystemControlDisabledChromeDisabledLowBrush}" />
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Normal">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Gray"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderThickness" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="0.5"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="White"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="PointerOver">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Gray"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderThickness" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="White"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="Focused">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderBrush" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="Gray"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="BorderThickness" Storyboard.TargetName="BorderElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="1"/>
                                        </ObjectAnimationUsingKeyFrames>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Background" Storyboard.TargetName="BackgroundElement">
                                            <DiscreteObjectKeyFrame KeyTime="0" Value="White"/>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                            </VisualStateGroup>
                            <VisualStateGroup x:Name="ButtonStates">
                                <VisualState x:Name="ButtonVisible">
                                    <Storyboard>
                                        <ObjectAnimationUsingKeyFrames Storyboard.TargetName="RevealButton"
                                                 Storyboard.TargetProperty="Visibility">
                                            <DiscreteObjectKeyFrame KeyTime="0">
                                                <DiscreteObjectKeyFrame.Value>
                                                    <Visibility>Visible</Visibility>
                                                </DiscreteObjectKeyFrame.Value>
                                            </DiscreteObjectKeyFrame>
                                        </ObjectAnimationUsingKeyFrames>
                                    </Storyboard>
                                </VisualState>
                                <VisualState x:Name="ButtonCollapsed" />
                            </VisualStateGroup>
                        </VisualStateManager.VisualStateGroups>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="*" />
                            <ColumnDefinition Width="Auto" />
                        </Grid.ColumnDefinitions>
                        <Grid.RowDefinitions>
                            <RowDefinition Height="Auto" />
                            <RowDefinition Height="*" />
                        </Grid.RowDefinitions>
                        <Border x:Name="BackgroundElement"
                  Grid.Row="1"
                  Background="{TemplateBinding Background}"
                  Margin="{TemplateBinding BorderThickness}"
                  Opacity="{ThemeResource TextControlBackgroundRestOpacity}"
                  Grid.ColumnSpan="2"
                  Grid.RowSpan="1"/>
                        <Border x:Name="BorderElement"
                  Grid.Row="1"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  Grid.ColumnSpan="2"
                  Grid.RowSpan="1"/>
                        <ContentPresenter x:Name="HeaderContentPresenter"
                            x:DeferLoadStrategy="Lazy"
                            Visibility="Collapsed"
                            Grid.Row="0"
                            Foreground="{ThemeResource SystemControlForegroundBaseHighBrush}"
                            Margin="0,0,0,8"
                            Grid.ColumnSpan="2"
                            Content="{TemplateBinding Header}"
                            ContentTemplate="{TemplateBinding HeaderTemplate}"
                            FontWeight="Normal" />
                        <ScrollViewer x:Name="ContentElement"
                  Grid.Row="1"
                        HorizontalScrollMode="{TemplateBinding ScrollViewer.HorizontalScrollMode}"
                        HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
                        VerticalScrollMode="{TemplateBinding ScrollViewer.VerticalScrollMode}"
                        VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}"
                        IsHorizontalRailEnabled="{TemplateBinding ScrollViewer.IsHorizontalRailEnabled}"
                        IsVerticalRailEnabled="{TemplateBinding ScrollViewer.IsVerticalRailEnabled}"
                        Margin="{TemplateBinding BorderThickness}"
                        Padding="{TemplateBinding Padding}"
                        IsTabStop="False"
                        ZoomMode="Disabled"
                        AutomationProperties.AccessibilityView="Raw"/>
                        <ContentControl x:Name="PlaceholderTextContentPresenter"
                        Grid.Row="1"
                        Foreground="{ThemeResource SystemControlPageTextBaseMediumBrush}"
                        Margin="{TemplateBinding BorderThickness}"
                        Padding="{TemplateBinding Padding}"
                        IsTabStop="False"
                        Grid.ColumnSpan="2"
                        Content="{TemplateBinding PlaceholderText}"
                        IsHitTestVisible="False"/>
                        <Button x:Name="RevealButton"
                  Grid.Row="1"
                  Style="{StaticResource RevealButtonStyle}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  Margin="{ThemeResource HelperButtonThemePadding}"
                  IsTabStop="False"
                  Grid.Column="1"
                  Visibility="Visible"
                  FontSize="{TemplateBinding FontSize}"
                  VerticalAlignment="Stretch"
                  MinWidth="34" />
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

请注意,我已更改“显示密码”按钮的以下样式,将可见性设置为 true(默认情况下处于折叠状态):

<Button x:Name="RevealButton"
                  Grid.Row="1"
                  Style="{StaticResource RevealButtonStyle}"
                  BorderThickness="{TemplateBinding BorderThickness}"
                  Margin="{ThemeResource HelperButtonThemePadding}"
                  IsTabStop="False"
                  Grid.Column="1"
                  Visibility="Visible"
                  FontSize="{TemplateBinding FontSize}"
                  VerticalAlignment="Stretch"
                  MinWidth="34" />

这是设计的,可以参考PasswordBox:

The password reveal button is shown only when the PasswordBox receives focus for the first time and a character is entered. If the PasswordBox loses focus and then regains focus, the reveal button is not shown again unless the password is cleared and character entry starts over.

默认情况下它的行为是这样的,即使你让 RevealButton 始终可见,现在的问题是这个 Button 不能正常工作。

官方推荐的方法是创建一个类似的UI,例如CheckBox,让用户切换显示模式。我还注意到你在样式中将默认的 ToggleButton 更改为 Button ,如果你坚持使用这个 Button 来切换显示模式,你可以例如在后面编写代码这个:

public Page21()
{
    this.InitializeComponent();
    this.Loaded += Page21_Loaded;
}

private Button RevealButton;

private void Page21_Loaded(object sender, RoutedEventArgs e)
{
    RevealButton = FindChildOfType<Button>(passwordbox);
    RevealButton.Tapped += RevealButton_Tapped;
    RevealButton.ClickMode = ClickMode.Press;
    RevealButton.Click += RevealButton_Click;
    RevealButton.RightTapped += RevealButton_RightTapped;
}

private void RevealButton_RightTapped(object sender, RightTappedRoutedEventArgs e)
{
    Debug.WriteLine("RevealButton_RightTapped");
    passwordbox.PasswordRevealMode = PasswordRevealMode.Hidden;
}

private void RevealButton_Tapped(object sender, TappedRoutedEventArgs e)
{
    Debug.WriteLine("RevealButton_Tapped");
    passwordbox.PasswordRevealMode = PasswordRevealMode.Hidden;
}

private void RevealButton_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("RevealButton_Click");
    passwordbox.PasswordRevealMode = PasswordRevealMode.Visible;
}

public static T FindChildOfType<T>(DependencyObject root) where T : class
{
    var queue = new Queue<DependencyObject>();
    queue.Enqueue(root);
    while (queue.Count > 0)
    {
        DependencyObject current = queue.Dequeue();
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++)
        {
            var child = VisualTreeHelper.GetChild(current, i);
            var typedChild = child as T;
            if (typedChild != null)
            {
                return typedChild;
            }
            queue.Enqueue(child);
        }
    }
    return null;
}

这里的基本思路是先获取PasswordBox里面的RevealButton,然后修改RevealButtonClickMode,这样Click事件就可以了当按下 Button 时触发,默认情况下 ButtonTapped/ RightTapped 事件将在释放指针时触发,最后您可以更改 PasswordRevealMode 在这两个事件中。 Tapped 事件在 PC 上运行良好,但在移动设备上,我使用 RightTapped 作为 Button 的释放点。

只需找到按钮并将其可见性设置为 Visibility.Visible,即可使按钮始终可见。我假设默认模板中的按钮使用 TemplateBinding 并且它的可见性没有以编程方式切换,因为直接设置它的可见性会破坏任何现有的绑定,例如前面提到的。

我使用以下扩展方法来查找按钮:

public static T GetChildOfType<T>(this DependencyObject Object) where T : DependencyObject
{
    if (Object == null)
        return null;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(Object); i++)
    {
        var Child = VisualTreeHelper.GetChild(Object, i);
        var Result = (Child as T) ?? GetChildOfType<T>(Child);
        if (Result != null) return Result;
    }

    return null;
}

然后,当 PasswordBox 加载时,执行此操作:

private void OnLoaded(object sender, RoutedEventArgs e)
{
    var RevealButton = (sender as PasswordBox).GetChildOfType<Button>();

    if (RevealButton != null)
        RevealButton.Visibility = Visibility.Visible;
}

如果你愿意,你可以定义一个泛型 ContentControl class 到 subclass PasswordBox;否则,只需将上述 Loaded 事件附加到每个实例即可。

但有一个缺点:失去焦点后,按钮不再能够切换密码的可见性,尽管它是可见的。原因不明。