Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e9388bf
Rewrite current UI test and add new UI test to test failing scenario
nicolaihenriksen Nov 4, 2022
4d9c233
Refactor PasswordBoxAssist to use behavior
nicolaihenriksen Nov 4, 2022
90471ba
Refactor PasswordBox reveal style TextBox focus- and text selection code
nicolaihenriksen Nov 4, 2022
0a9f4f8
Undo wrong change in TestBase
nicolaihenriksen Nov 4, 2022
bc349fa
Whitespace commit to force re-run
nicolaihenriksen Nov 4, 2022
bd746b4
Increase delay in test
nicolaihenriksen Nov 4, 2022
b282e6c
Rollback of increased test delay
nicolaihenriksen Nov 4, 2022
dda88f8
Update MaterialDesignThemes.Wpf/Behaviors/PasswordBoxRevealTextBoxBeh…
nicolaihenriksen Nov 5, 2022
7d32386
Update MaterialDesignThemes.Wpf/Behaviors/PasswordBoxRevealTextBoxBeh…
nicolaihenriksen Nov 5, 2022
e1ddcf8
Extract PasswordBoxBehavior into its own class
nicolaihenriksen Nov 5, 2022
847d6ab
Cache PropertyInfos and MethodInfos in PasswordBoxRevealTextBoxBehavior
nicolaihenriksen Nov 5, 2022
417c69c
Selecting minimum required version of dependency and updating nuspec
nicolaihenriksen Nov 5, 2022
4b78de6
Convert Password binding to opt-in feature
nicolaihenriksen Nov 6, 2022
3fdb918
File scoped namespace
nicolaihenriksen Nov 6, 2022
e89ce21
Behavior classes renames and change from internal to public
nicolaihenriksen Nov 6, 2022
21d7373
Use latest version of Xaml.Behaviors and update nuspec file
nicolaihenriksen Nov 6, 2022
da46c37
Attempting fix of pipeline
Keboo Nov 7, 2022
8124883
Adding screenshots for debugging
Keboo Nov 7, 2022
fac1639
Attempt at fixing failing UI test
nicolaihenriksen Nov 7, 2022
b52a9ce
Adding more screenshots for debugging
nicolaihenriksen Nov 7, 2022
100bad6
Activate test window on loaded event
nicolaihenriksen Nov 7, 2022
bfbd3d5
Yet another attempt at getting test window shown
nicolaihenriksen Nov 7, 2022
afbc730
Removing debugging screenshots
nicolaihenriksen Nov 7, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Refactor PasswordBox reveal style TextBox focus- and text selection code
The old focus code did not work that well because keyboard focus was not really possible to set via the Style. With the introduction of behaviors, we can now improve that a lot.
  • Loading branch information
nicolaihenriksen committed Nov 7, 2022
commit 90471babd006c791fd9746f1622ee923bc4df24e
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System.Reflection;
using System.Windows.Documents;
using Microsoft.Xaml.Behaviors;

namespace MaterialDesignThemes.Wpf.Behaviors;

internal class PasswordBoxRevealTextBoxBehavior : Behavior<TextBox>
{
private static readonly DependencyProperty SelectionProperty = DependencyProperty.RegisterAttached(
"Selection", typeof(TextSelection), typeof(PasswordBoxRevealTextBoxBehavior), new UIPropertyMetadata(default(TextSelection)));
private static void SetSelection(DependencyObject obj, TextSelection? value) => obj.SetValue(SelectionProperty, value);
private static TextSelection? GetSelection(DependencyObject obj) => (TextSelection?)obj.GetValue(SelectionProperty);

internal static readonly DependencyProperty PasswordBoxProperty = DependencyProperty.Register(
nameof(PasswordBox), typeof(PasswordBox), typeof(PasswordBoxRevealTextBoxBehavior), new PropertyMetadata(default(PasswordBox)));

internal PasswordBox? PasswordBox
{
get => (PasswordBox) GetValue(PasswordBoxProperty);
set => SetValue(PasswordBoxProperty, value);
}

protected override void OnAttached()
{
base.OnAttached();
AssociatedObject.IsVisibleChanged += AssociatedObjectOnIsVisibleChanged;
if (PasswordBox != null)
{
var info = typeof(PasswordBox).GetProperty("Selection", BindingFlags.NonPublic | BindingFlags.Instance);
var selection = info?.GetValue(PasswordBox, null) as TextSelection;
SetSelection(AssociatedObject, selection);
}
}

protected override void OnDetaching()
{
base.OnDetaching();
if (AssociatedObject != null)
{
AssociatedObject.ClearValue(SelectionProperty);
AssociatedObject.IsVisibleChanged -= AssociatedObjectOnIsVisibleChanged;
}
}

private void AssociatedObjectOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (AssociatedObject.IsVisible)
{
AssociatedObject.SelectionLength = 0;
var selection = GetPasswordBoxSelection();
AssociatedObject.SelectionStart = selection.SelectionStart;
AssociatedObject.SelectionLength = selection.SelectionEnd;
Keyboard.Focus(AssociatedObject);
}
else if (PasswordBox != null)
{
SetPasswordBoxSelection(AssociatedObject.SelectionStart, AssociatedObject.SelectionLength);
Keyboard.Focus(PasswordBox);
}
}

private PasswordBoxSelection GetPasswordBoxSelection()
{
var selection = GetSelection(AssociatedObject);
var typeTextRange = selection?.GetType().GetInterfaces().FirstOrDefault(i => i.Name == "ITextRange");
object? start = typeTextRange?.GetProperty("Start")?.GetGetMethod()?.Invoke(selection, null);
object? end = typeTextRange?.GetProperty("End")?.GetGetMethod()?.Invoke(selection, null);
int? startValue = start?.GetType().GetProperty("Offset", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(start, null) as int?;
int? endValue = end?.GetType().GetProperty("Offset", BindingFlags.NonPublic | BindingFlags.Instance)?.GetValue(end, null) as int?;
int selectionStart = startValue.GetValueOrDefault(0);
int selectionLength = 0;
if (endValue.HasValue)
{
selectionLength = endValue.Value - startValue.GetValueOrDefault(0);
}
return new PasswordBoxSelection(selectionStart, selectionLength);
}

private void SetPasswordBoxSelection(int selectionStart, int selectionLength) => typeof(PasswordBox).GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic)?.Invoke(PasswordBox, new object[] { selectionStart, selectionLength });

private struct PasswordBoxSelection
{
public readonly int SelectionStart;
public readonly int SelectionEnd;

public PasswordBoxSelection(int selectionStart, int selectionEnd)
{
SelectionStart = selectionStart;
SelectionEnd = selectionEnd;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:MaterialDesignThemes.Wpf.Converters"
xmlns:internal="clr-namespace:MaterialDesignThemes.Wpf.Internal"
Expand Down Expand Up @@ -640,7 +640,11 @@
Style="{StaticResource MaterialDesignRawTextBox}"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(wpf:PasswordBoxAssist.Password), UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
UseLayoutRounding="{TemplateBinding UseLayoutRounding}"
Visibility="{Binding ElementName=ContentGrid, Path=(wpf:PasswordBoxAssist.IsPasswordRevealed), Converter={StaticResource BooleanToVisibilityConverter}}" />
Visibility="{Binding ElementName=ContentGrid, Path=(wpf:PasswordBoxAssist.IsPasswordRevealed), Converter={StaticResource BooleanToVisibilityConverter}}">
<behaviors:Interaction.Behaviors>
<internalbehaviors:PasswordBoxRevealTextBoxBehavior PasswordBox="{Binding RelativeSource={RelativeSource TemplatedParent}}" />
</behaviors:Interaction.Behaviors>
</TextBox>
</Grid>

<wpf:SmartHint x:Name="Hint"
Expand Down Expand Up @@ -723,15 +727,6 @@
</Canvas>
</Grid>
<ControlTemplate.Triggers>
<!-- Transfer focus to the RevealPasswordTextBox when the password is revealed and the PasswordBox gains focus -->
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsFocused" Value="True" />
<Condition Property="wpf:PasswordBoxAssist.IsPasswordRevealed" Value="True" />
</MultiTrigger.Conditions>
<Setter TargetName="ContentGrid" Property="FocusManager.FocusedElement" Value="{Binding ElementName=RevealPasswordTextBox}" />
</MultiTrigger>

<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="wpf:HintAssist.IsFloating" Value="True" />
Expand Down