Search code examples
xamluwppasswordbox

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


I am using a PasswordBox in my UWP application where I want to show the password reveal button always. Also, when pressed it should work as expected and should show the passord.

I have modified this default style of the PasswordBox control to set the visibility of Password Reveal button to true. It works and the button is always visible now. But the problem is the password reveal functionality only works when the password is cleared and typed from starting.

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

Please notice that I have changed below style for Reveal Password button to have the visibility set to true (it is collapsed by default):

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

Solution

  • This is by design, you can refer to 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.

    By default it behaves like so, even you make the RevealButton visible always, the problem now is this Button doesn't work properly.

    The official recommended method is to create a similar UI for example a CheckBox to let a user switch the reveal mode. I've also noticed that you changed the default ToggleButton to Button in the style, if you insist to use this Button for switching the reveal mode, you can for example code in behind like this:

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

    The basic idea here is firstly get the RevealButton inside the PasswordBox, then change the ClickMode of RevealButton so will the Click event be fired when the Button is being pressed, by default the Tapped/ RightTapped event of Button will be fired when the pointer is released, finally you can change the PasswordRevealMode in these two events. Tapped event works fine on PC, but on mobile, I used RightTapped for the point released of Button.