优化更新代码,添加界面功能并整合

This commit is contained in:
GG Z
2025-02-10 20:53:40 +08:00
parent 83b846f15f
commit 978e03a67f
1389 changed files with 95739 additions and 22200 deletions

View File

@@ -0,0 +1,281 @@
// TODO: This is an initial implementation and requires the necessary corrections, tests and adjustments.
/* TextProperty contains asterisks OR raw password if IsPasswordRevealed is set to true
PasswordProperty always contains raw password */
using System.Windows.Controls;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
/// <summary>
/// The modified password control.
/// </summary>
public partial class PasswordBox : WPFluent.Controls.TextBox
{
/// <summary>
/// Identifies the <see cref="IsPasswordRevealed"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsPasswordRevealedProperty = DependencyProperty.Register(
nameof(IsPasswordRevealed),
typeof(bool),
typeof(PasswordBox),
new PropertyMetadata(false, OnIsPasswordRevealedChanged));
/// <summary>
/// Identifies the <see cref="PasswordChanged"/> routed event.
/// </summary>
public static readonly RoutedEvent PasswordChangedEvent = EventManager.RegisterRoutedEvent(
nameof(PasswordChanged),
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(PasswordBox));
/// <summary>
/// Identifies the <see cref="PasswordChar"/> dependency property.
/// </summary>
public static readonly DependencyProperty PasswordCharProperty = DependencyProperty.Register(
nameof(PasswordChar),
typeof(char),
typeof(PasswordBox),
new PropertyMetadata('*', OnPasswordCharChanged));
/// <summary>
/// Identifies the <see cref="Password"/> dependency property.
/// </summary>
public static readonly DependencyProperty PasswordProperty = DependencyProperty.Register(
nameof(Password),
typeof(string),
typeof(PasswordBox),
new PropertyMetadata(string.Empty, OnPasswordChanged));
/// <summary>
/// Identifies the <see cref="RevealButtonEnabled"/> dependency property.
/// </summary>
public static readonly DependencyProperty RevealButtonEnabledProperty = DependencyProperty.Register(
nameof(RevealButtonEnabled),
typeof(bool),
typeof(PasswordBox),
new PropertyMetadata(true));
private bool _lockUpdatingContents;
private readonly PasswordHelper _passwordHelper;
public PasswordBox()
{
_lockUpdatingContents = false;
_passwordHelper = new PasswordHelper(this);
}
/// <summary>
/// Event fired from this text box when its inner content has been changed.
/// </summary>
/// <remarks>
/// It is redirected from inner TextContainer.Changed event.
/// </remarks>
public event RoutedEventHandler PasswordChanged
{
add => AddHandler(PasswordChangedEvent, value);
remove => RemoveHandler(PasswordChangedEvent, value);
}
/// <summary>
/// Called if the reveal mode is changed in the during the run.
/// </summary>
private static void OnIsPasswordRevealedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordRevealModeChanged();
}
/// <summary>
/// Called when <see cref="Password"/> is changed.
/// </summary>
private static void OnPasswordChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordChanged();
}
/// <summary>
/// Called if the character is changed in the during the run.
/// </summary>
private static void OnPasswordCharChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if(d is not PasswordBox control)
{
return;
}
control.OnPasswordCharChanged();
}
private void UpdateTextContents(bool isTriggeredByTextInput)
{
if(_lockUpdatingContents)
{
return;
}
if(IsPasswordRevealed)
{
if(Password == Text)
{
return;
}
_lockUpdatingContents = true;
if(isTriggeredByTextInput)
{
SetCurrentValue(PasswordProperty, Text);
}
else
{
SetCurrentValue(TextProperty, Password);
CaretIndex = Text.Length;
}
RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
_lockUpdatingContents = false;
return;
}
var caretIndex = CaretIndex;
var newPasswordValue = _passwordHelper.GetPassword();
if(isTriggeredByTextInput)
{
newPasswordValue = _passwordHelper.GetNewPassword();
}
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, new string(PasswordChar, newPasswordValue.Length));
SetCurrentValue(PasswordProperty, newPasswordValue);
CaretIndex = caretIndex;
RaiseEvent(new RoutedEventArgs(PasswordChangedEvent));
_lockUpdatingContents = false;
}
/// <summary>
/// Is called when <see cref="Password"/> property is changing.
/// </summary>
protected virtual void OnPasswordChanged() { UpdateTextContents(false); }
/// <summary>
/// Is called when <see cref="PasswordChar"/> property is changing.
/// </summary>
protected virtual void OnPasswordCharChanged()
{
// If password is currently revealed,
// do not replace displayed text with asterisks
if(IsPasswordRevealed)
{
return;
}
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, new string(PasswordChar, Password.Length));
_lockUpdatingContents = false;
}
protected virtual void OnPasswordRevealModeChanged()
{
_lockUpdatingContents = true;
SetCurrentValue(TextProperty, IsPasswordRevealed ? Password : new string(PasswordChar, Password.Length));
_lockUpdatingContents = false;
}
/// <summary>
/// Triggered by clicking a button in the control template.
/// </summary>
/// <param name="parameter">Additional parameters.</param>
protected override void OnTemplateButtonClick(string? parameter)
{
System.Diagnostics.Debug
.WriteLine(
$"INFO: {typeof(PasswordBox)} button clicked with param: {parameter}",
"WPFluent.PasswordBox");
switch(parameter)
{
case "reveal":
SetCurrentValue(IsPasswordRevealedProperty, !IsPasswordRevealed);
_ = Focus();
CaretIndex = Text.Length;
break;
default:
base.OnTemplateButtonClick(parameter);
break;
}
}
/// <inheritdoc/>
protected override void OnTextChanged(TextChangedEventArgs e)
{
UpdateTextContents(true);
if(_lockUpdatingContents)
{
base.OnTextChanged(e);
}
else
{
SetPlaceholderTextVisibility();
RevealClearButton();
}
}
/// <summary>
/// Gets a value indicating whether the password is revealed.
/// </summary>
public bool IsPasswordRevealed
{
get => (bool)GetValue(IsPasswordRevealedProperty);
private set => SetValue(IsPasswordRevealedProperty, value);
}
/// <summary>
/// Gets or sets currently typed text represented by asterisks.
/// </summary>
public string Password { get => (string)GetValue(PasswordProperty); set => SetValue(PasswordProperty, value); }
/// <summary>
/// Gets or sets character used to mask the password.
/// </summary>
public char PasswordChar
{
get => (char)GetValue(PasswordCharProperty);
set => SetValue(PasswordCharProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether to display the password reveal button.
/// </summary>
public bool RevealButtonEnabled
{
get => (bool)GetValue(RevealButtonEnabledProperty);
set => SetValue(RevealButtonEnabledProperty, value);
}
}

View File

@@ -0,0 +1,331 @@
<!--
This Source Code Form is subject to the terms of the MIT License.
If a copy of the MIT was not distributed with this file, You can obtain one at https://opensource.org/licenses/MIT.
Copyright (C) Leszek Pomianowski and WPF UI Contributors.
All Rights Reserved.
Based on Microsoft XAML for Win UI
Copyright (c) Microsoft Corporation. All Rights Reserved.
-->
<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:WPFluent.Controls"
xmlns:system="clr-namespace:System;assembly=mscorlib">
<Thickness x:Key="PasswordBoxBorderThemeThickness">1,1,1,1</Thickness>
<Thickness x:Key="PasswordBoxAccentBorderThemeThickness">0,0,0,1</Thickness>
<Thickness x:Key="PasswordBoxLeftIconMargin">10,8,0,0</Thickness>
<Thickness x:Key="PasswordBoxRightIconMargin">0,8,10,0</Thickness>
<Thickness x:Key="PasswordBoxButtonMargin">0,5,4,0</Thickness>
<Thickness x:Key="PasswordBoxButtonPadding">0,0,0,0</Thickness>
<system:Double x:Key="PasswordBoxButtonHeight">24</system:Double>
<system:Double x:Key="PasswordBoxButtonIconSize">14</system:Double>
<ContextMenu x:Key="DefaultPasswordBoxContextMenu">
<MenuItem Command="ApplicationCommands.Paste" />
</ContextMenu>
<Style x:Key="DefaultPasswordBoxStyle" TargetType="{x:Type PasswordBox}">
<!-- Universal WPF UI focus -->
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
<!-- Universal WPF UI focus -->
<!-- Universal WPF UI ContextMenu -->
<Setter Property="ContextMenu" Value="{DynamicResource DefaultPasswordBoxContextMenu}" />
<!-- Universal WPF UI ContextMenu -->
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
<Setter Property="CaretBrush" Value="{DynamicResource TextControlForeground}" />
<Setter Property="Background" Value="{DynamicResource TextControlBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlElevationBorderBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource PasswordBoxBorderThemeThickness}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="Border.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type PasswordBox}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Border
x:Name="ContentBorder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}">
<Grid
Margin="{TemplateBinding Padding}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<controls:PassiveScrollViewer
x:Name="PART_ContentHost"
Style="{DynamicResource DefaultTextBoxScrollViewerStyle}"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
<!-- The Accent Border is a separate element so that changes to the border thickness do not affect the position of the element -->
<Border
x:Name="AccentBorder"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="{StaticResource PasswordBoxAccentBorderThemeThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="AccentBorder" Property="BorderThickness" Value="0,0,0,2" />
<Setter TargetName="AccentBorder" Property="BorderBrush" Value="{DynamicResource TextControlFocusedBorderBrush}" />
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundFocused}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsFocused" Value="False" />
</MultiTrigger.Conditions>
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundPointerOver}" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Cursor" Value="IBeam" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundDisabled}" />
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" />
<Setter TargetName="AccentBorder" Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="DefaultUiPasswordBoxStyle" TargetType="{x:Type controls:PasswordBox}">
<!-- Universal WPF UI focus -->
<Setter Property="FocusVisualStyle" Value="{DynamicResource DefaultControlFocusVisualStyle}" />
<!-- Universal WPF UI focus -->
<!-- Universal WPF UI ContextMenu -->
<Setter Property="ContextMenu" Value="{DynamicResource DefaultPasswordBoxContextMenu}" />
<!-- Universal WPF UI ContextMenu -->
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
<Setter Property="CaretBrush" Value="{DynamicResource TextControlForeground}" />
<Setter Property="Background" Value="{DynamicResource TextControlBackground}" />
<Setter Property="BorderBrush" Value="{DynamicResource TextControlElevationBorderBrush}" />
<Setter Property="BorderThickness" Value="{StaticResource PasswordBoxBorderThemeThickness}" />
<Setter Property="FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Top" />
<Setter Property="MinHeight" Value="{DynamicResource TextControlThemeMinHeight}" />
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="Border.CornerRadius" Value="{DynamicResource ControlCornerRadius}" />
<Setter Property="ClearButtonEnabled" Value="True" />
<Setter Property="RevealButtonEnabled" Value="True" />
<Setter Property="IconPlacement" Value="Left" />
<Setter Property="SnapsToDevicePixels" Value="True" />
<Setter Property="OverridesDefaultStyle" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type controls:PasswordBox}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalAlignment}" VerticalAlignment="{TemplateBinding VerticalAlignment}">
<Border
x:Name="ContentBorder"
MinWidth="{TemplateBinding MinWidth}"
MinHeight="{TemplateBinding MinHeight}"
Padding="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter
x:Name="ControlIconLeft"
Grid.Column="0"
Margin="{StaticResource PasswordBoxLeftIconMargin}"
VerticalAlignment="Top"
Content="{TemplateBinding Icon}"
TextElement.FontSize="16"
TextElement.Foreground="{TemplateBinding Foreground}" />
<Grid Grid.Column="1" Margin="{TemplateBinding Padding}">
<controls:PassiveScrollViewer
x:Name="PART_ContentHost"
Style="{DynamicResource DefaultTextBoxScrollViewerStyle}"
TextElement.Foreground="{TemplateBinding Foreground}" />
<TextBlock
x:Name="PlaceholderTextBox"
Margin="0"
Padding="1,0"
VerticalAlignment="Top"
Foreground="{DynamicResource TextControlPlaceholderForeground}"
Text="{TemplateBinding PlaceholderText}" />
</Grid>
<!-- Buttons and Icons have no padding from the main element to allow absolute positions if height is larger than the text entry zone -->
<controls:Button
x:Name="ClearButton"
Grid.Column="2"
Width="{StaticResource PasswordBoxButtonHeight}"
Height="{StaticResource PasswordBoxButtonHeight}"
Margin="{StaticResource PasswordBoxButtonMargin}"
Padding="{StaticResource PasswordBoxButtonPadding}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Appearance="Secondary"
Background="Transparent"
BorderBrush="Transparent"
Command="{Binding Path=TemplateButtonCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="clear"
Cursor="Arrow"
Foreground="{DynamicResource TextControlButtonForeground}"
IsTabStop="False">
<controls:Button.Icon>
<controls:SymbolIcon FontSize="{StaticResource PasswordBoxButtonIconSize}" Symbol="Dismiss24" />
</controls:Button.Icon>
</controls:Button>
<controls:Button
x:Name="RevealButton"
Grid.Column="3"
Width="{StaticResource PasswordBoxButtonHeight}"
Height="{StaticResource PasswordBoxButtonHeight}"
Margin="{StaticResource PasswordBoxButtonMargin}"
Padding="{StaticResource PasswordBoxButtonPadding}"
HorizontalAlignment="Center"
VerticalAlignment="Top"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Appearance="Secondary"
Background="Transparent"
BorderBrush="Transparent"
Command="{Binding Path=TemplateButtonCommand, RelativeSource={RelativeSource TemplatedParent}}"
CommandParameter="reveal"
Cursor="Arrow"
Foreground="{DynamicResource TextControlButtonForeground}"
IsTabStop="False">
<controls:Button.Icon>
<controls:SymbolIcon FontSize="{StaticResource PasswordBoxButtonIconSize}" Symbol="Eye24" />
</controls:Button.Icon>
</controls:Button>
<ContentPresenter
x:Name="ControlIconRight"
Grid.Column="4"
Margin="{StaticResource PasswordBoxRightIconMargin}"
VerticalAlignment="Top"
Content="{TemplateBinding Icon}"
TextElement.FontSize="16"
TextElement.Foreground="{TemplateBinding Foreground}" />
</Grid>
</Border>
<!-- The Accent Border is a separate element so that changes to the border thickness do not affect the position of the element -->
<Border
x:Name="AccentBorder"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
BorderBrush="{DynamicResource ControlStrokeColorDefaultBrush}"
BorderThickness="{StaticResource PasswordBoxAccentBorderThemeThickness}"
CornerRadius="{TemplateBinding Border.CornerRadius}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="CurrentPlaceholderEnabled" Value="False">
<Setter TargetName="PlaceholderTextBox" Property="Visibility" Value="Collapsed" />
</Trigger>
<Trigger Property="ShowClearButton" Value="False">
<Setter TargetName="ClearButton" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ClearButton" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="ClearButtonEnabled" Value="False">
<Setter TargetName="ClearButton" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ClearButton" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="RevealButtonEnabled" Value="False">
<Setter TargetName="RevealButton" Property="Visibility" Value="Collapsed" />
<Setter TargetName="RevealButton" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="IsPasswordRevealed" Value="True">
<Setter TargetName="RevealButton" Property="Icon">
<Setter.Value>
<controls:SymbolIcon Symbol="EyeOff24" />
</Setter.Value>
</Setter>
</Trigger>
<Trigger Property="IconPlacement" Value="Left">
<Setter TargetName="ControlIconRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlIconRight" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="IconPlacement" Value="Right">
<Setter TargetName="ControlIconLeft" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlIconLeft" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="Icon" Value="{x:Null}">
<Setter TargetName="ControlIconRight" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlIconRight" Property="Margin" Value="0" />
<Setter TargetName="ControlIconLeft" Property="Visibility" Value="Collapsed" />
<Setter TargetName="ControlIconLeft" Property="Margin" Value="0" />
</Trigger>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="AccentBorder" Property="BorderThickness" Value="0,0,0,2" />
<Setter TargetName="AccentBorder" Property="BorderBrush" Value="{DynamicResource TextControlFocusedBorderBrush}" />
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundFocused}" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsEnabled" Value="True" />
<Condition Property="IsMouseOver" Value="True" />
<Condition Property="IsFocused" Value="False" />
</MultiTrigger.Conditions>
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundPointerOver}" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="True">
<Setter Property="Cursor" Value="IBeam" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter TargetName="ContentBorder" Property="Background" Value="{DynamicResource TextControlBackgroundDisabled}" />
<Setter TargetName="ContentBorder" Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" />
<Setter TargetName="AccentBorder" Property="BorderBrush" Value="{DynamicResource TextControlBorderBrushDisabled}" />
<Setter TargetName="ControlIconLeft" Property="TextElement.Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
<Setter TargetName="ControlIconRight" Property="TextElement.Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
<Setter TargetName="PlaceholderTextBox" Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
<Setter Property="Foreground" Value="{DynamicResource TextControlForegroundDisabled}" />
<Setter Property="ClearButtonEnabled" Value="False" />
<Setter Property="RevealButtonEnabled" Value="False" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style BasedOn="{StaticResource DefaultPasswordBoxStyle}" TargetType="{x:Type PasswordBox}" />
<Style BasedOn="{StaticResource DefaultUiPasswordBoxStyle}" TargetType="{x:Type controls:PasswordBox}" />
</ResourceDictionary>

View File

@@ -0,0 +1,112 @@
using System.Diagnostics;
// ReSharper disable once CheckNamespace
namespace WPFluent.Controls;
#pragma warning disable SA1601
public partial class PasswordBox
{
private class PasswordHelper
{
private string _currentPassword;
private string _currentText;
private string _newPasswordValue;
private readonly PasswordBox _passwordBox;
public PasswordHelper(PasswordBox passwordBox)
{
_passwordBox = passwordBox;
_currentText = string.Empty;
_newPasswordValue = string.Empty;
_currentPassword = string.Empty;
}
private bool IsDeleteOption()
{
Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text");
Debug.Assert(_currentPassword == _passwordBox.Password, "_currentPassword == _passwordBox.Password");
return _currentText.Length < _currentPassword.Length;
}
private void UpdatePasswordWithInputCharacter(int insertIndex, string insertValue)
{
Debug.Assert(_currentText == _passwordBox.Text, "_currentText == _passwordBox.Text");
if(_currentText.Length == _newPasswordValue.Length)
{
// If it's a direct character replacement, remove the existing one before inserting the new one.
_newPasswordValue = _newPasswordValue.Remove(insertIndex, 1).Insert(insertIndex, insertValue);
}
else
{
_newPasswordValue = _newPasswordValue.Insert(insertIndex, insertValue);
}
}
public string GetNewPassword()
{
_currentPassword = GetPassword();
_newPasswordValue = _currentPassword;
_currentText = _passwordBox.Text;
var selectionIndex = _passwordBox.SelectionStart;
var passwordChar = _passwordBox.PasswordChar;
var newCharacters = _currentText.Replace(passwordChar.ToString(), string.Empty);
bool isDeleted = false;
if(IsDeleteOption())
{
_newPasswordValue = _currentPassword.Remove(
selectionIndex,
_currentPassword.Length - _currentText.Length);
isDeleted = true;
}
switch(newCharacters.Length)
{
case > 1:
{
var index = _currentText.IndexOf(newCharacters[0]);
_newPasswordValue =
index > _newPasswordValue.Length - 1
? $"{_newPasswordValue}{newCharacters}"
: _newPasswordValue.Insert(index, newCharacters);
break;
}
case 1:
{
for(int i = 0; i < _currentText.Length; i++)
{
if(_currentText[i] == passwordChar)
{
continue;
}
UpdatePasswordWithInputCharacter(i, _currentText[i].ToString());
break;
}
break;
}
case 0 when !isDeleted:
{
// The input is a PasswordChar, which is to be inserted at the designated position.
int insertIndex = selectionIndex - 1;
UpdatePasswordWithInputCharacter(insertIndex, passwordChar.ToString());
break;
}
}
return _newPasswordValue;
}
public string GetPassword() => _passwordBox.Password ?? string.Empty;
}
}