-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Refactor PasswordBoxAssist.Password to use behaviors (issue 2930) #2932
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
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 4d9c233
Refactor PasswordBoxAssist to use behavior
nicolaihenriksen 90471ba
Refactor PasswordBox reveal style TextBox focus- and text selection code
nicolaihenriksen 0a9f4f8
Undo wrong change in TestBase
nicolaihenriksen bc349fa
Whitespace commit to force re-run
nicolaihenriksen bd746b4
Increase delay in test
nicolaihenriksen b282e6c
Rollback of increased test delay
nicolaihenriksen dda88f8
Update MaterialDesignThemes.Wpf/Behaviors/PasswordBoxRevealTextBoxBeh…
nicolaihenriksen 7d32386
Update MaterialDesignThemes.Wpf/Behaviors/PasswordBoxRevealTextBoxBeh…
nicolaihenriksen e1ddcf8
Extract PasswordBoxBehavior into its own class
nicolaihenriksen 847d6ab
Cache PropertyInfos and MethodInfos in PasswordBoxRevealTextBoxBehavior
nicolaihenriksen 417c69c
Selecting minimum required version of dependency and updating nuspec
nicolaihenriksen 4b78de6
Convert Password binding to opt-in feature
nicolaihenriksen 3fdb918
File scoped namespace
nicolaihenriksen e89ce21
Behavior classes renames and change from internal to public
nicolaihenriksen 21d7373
Use latest version of Xaml.Behaviors and update nuspec file
nicolaihenriksen da46c37
Attempting fix of pipeline
Keboo 8124883
Adding screenshots for debugging
Keboo fac1639
Attempt at fixing failing UI test
nicolaihenriksen b52a9ce
Adding more screenshots for debugging
nicolaihenriksen 100bad6
Activate test window on loaded event
nicolaihenriksen bfbd3d5
Yet another attempt at getting test window shown
nicolaihenriksen afbc730
Removing debugging screenshots
nicolaihenriksen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
19 changes: 19 additions & 0 deletions
19
MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| <UserControl x:Class="MaterialDesignThemes.UITests.Samples.PasswordBox.BoundPasswordBox" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
| xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
| xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.PasswordBox" | ||
| xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" | ||
| mc:Ignorable="d" | ||
| d:DataContext="{d:DesignInstance local:BoundPasswordBoxViewModel, IsDesignTimeCreatable=False}" | ||
| d:DesignHeight="450" d:DesignWidth="800"> | ||
| <Grid> | ||
| <PasswordBox x:Name="PasswordBox" | ||
| Width="400" | ||
| HorizontalAlignment="Center" | ||
| VerticalAlignment="Center" | ||
| Style="{StaticResource {x:Type PasswordBox}}" | ||
| materialDesign:PasswordBoxAssist.Password="{Binding Password, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> | ||
| </Grid> | ||
| </UserControl> | ||
38 changes: 38 additions & 0 deletions
38
MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBox.xaml.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| namespace MaterialDesignThemes.UITests.Samples.PasswordBox; | ||
|
|
||
| public partial class BoundPasswordBox | ||
| { | ||
|
|
||
|
|
||
| public string? ViewModelPassword | ||
| { | ||
| get => ((BoundPasswordBoxViewModel) DataContext).Password; | ||
| set => ((BoundPasswordBoxViewModel) DataContext).Password = value; | ||
| } | ||
|
|
||
|
|
||
| private bool _useRevealStyle; | ||
| public bool UseRevealStyle | ||
| { | ||
| get => _useRevealStyle; | ||
| set | ||
| { | ||
| _useRevealStyle = value; | ||
| if (_useRevealStyle) | ||
| { | ||
| PasswordBox.Style = (Style)PasswordBox.FindResource("MaterialDesignFloatingHintRevealPasswordBox"); | ||
| } | ||
| else | ||
| { | ||
| PasswordBox.ClearValue(StyleProperty); | ||
| } | ||
|
|
||
| } | ||
| } | ||
|
|
||
| public BoundPasswordBox() | ||
| { | ||
| DataContext = new BoundPasswordBoxViewModel(); | ||
| InitializeComponent(); | ||
| } | ||
| } |
9 changes: 9 additions & 0 deletions
9
MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxViewModel.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| using CommunityToolkit.Mvvm.ComponentModel; | ||
|
|
||
| namespace MaterialDesignThemes.UITests.Samples.PasswordBox; | ||
|
|
||
| internal partial class BoundPasswordBoxViewModel : ObservableObject | ||
| { | ||
| [ObservableProperty] | ||
| private string? _password; | ||
| } |
22 changes: 22 additions & 0 deletions
22
MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| <Window x:Class="MaterialDesignThemes.UITests.Samples.PasswordBox.BoundPasswordBoxWindow" | ||
| xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | ||
| xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | ||
| xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||
| xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||
| xmlns:local="clr-namespace:MaterialDesignThemes.UITests.Samples.PasswordBox" | ||
| xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes" | ||
| mc:Ignorable="d" | ||
| Height="450" | ||
| Width="800" | ||
| Title="BoundPasswordBoxWindow" | ||
| Background="{DynamicResource MaterialDesignPaper}" | ||
| FontFamily="{materialDesign:MaterialDesignFont}" | ||
| TextElement.FontSize="13" | ||
| TextElement.FontWeight="Regular" | ||
| TextElement.Foreground="{DynamicResource MaterialDesignBody}" | ||
| TextOptions.TextFormattingMode="Ideal" | ||
| TextOptions.TextRenderingMode="Auto" | ||
| WindowStartupLocation="CenterScreen" | ||
| Loaded="BoundPasswordBoxWindow_OnLoaded"> | ||
| <local:BoundPasswordBox /> | ||
| </Window> |
14 changes: 14 additions & 0 deletions
14
MaterialDesignThemes.UITests/Samples/PasswordBox/BoundPasswordBoxWindow.xaml.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| namespace MaterialDesignThemes.UITests.Samples.PasswordBox; | ||
|
|
||
| public partial class BoundPasswordBoxWindow | ||
| { | ||
| public BoundPasswordBoxWindow() => InitializeComponent(); | ||
|
|
||
| private void BoundPasswordBoxWindow_OnLoaded(object sender, RoutedEventArgs e) | ||
| { | ||
| Activate(); | ||
| Topmost = true; | ||
| Topmost = false; | ||
| Focus(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| using Microsoft.Xaml.Behaviors; | ||
|
|
||
| namespace MaterialDesignThemes.Wpf; | ||
|
|
||
| public class BehaviorCollection : FreezableCollection<Behavior> | ||
| { | ||
| protected override Freezable CreateInstanceCore() => new BehaviorCollection(); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| using Microsoft.Xaml.Behaviors; | ||
|
|
||
| namespace MaterialDesignThemes.Wpf.Behaviors; | ||
|
|
||
| internal class PasswordBoxBehavior : Behavior<PasswordBox> | ||
| { | ||
| private void PasswordBoxLoaded(object sender, RoutedEventArgs e) => PasswordBoxAssist.SetPassword(AssociatedObject, AssociatedObject.Password); | ||
|
|
||
| protected override void OnAttached() | ||
| { | ||
| base.OnAttached(); | ||
| AssociatedObject.Loaded += PasswordBoxLoaded; | ||
| } | ||
|
|
||
| protected override void OnDetaching() | ||
| { | ||
| if (AssociatedObject != null) | ||
| { | ||
| AssociatedObject.Loaded -= PasswordBoxLoaded; | ||
| } | ||
| base.OnDetaching(); | ||
| } | ||
| } |
97 changes: 97 additions & 0 deletions
97
MaterialDesignThemes.Wpf/Behaviors/PasswordBoxRevealTextBoxBehavior.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| 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); | ||
| } | ||
|
|
||
| private static PropertyInfo SelectionPropertyInfo { get; } | ||
| private static MethodInfo SelectMethodInfo { get; } | ||
| private static MethodInfo GetStartMethodInfo { get; } | ||
| private static MethodInfo GetEndMethodInfo { get; } | ||
| private static PropertyInfo GetOffsetPropertyInfo { get; } | ||
|
|
||
| static PasswordBoxRevealTextBoxBehavior() | ||
| { | ||
| SelectionPropertyInfo = typeof(PasswordBox).GetProperty("Selection", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Did not find 'Selection' property on PasswordBox"); | ||
| SelectMethodInfo = typeof(PasswordBox).GetMethod("Select", BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException("Did not find 'Select' method on PasswordBox"); | ||
| Type iTextRange = typeof(PasswordBox).Assembly.GetType("System.Windows.Documents.ITextRange") ?? throw new InvalidOperationException("Failed to find ITextRange"); | ||
| GetStartMethodInfo = iTextRange.GetProperty("Start")?.GetGetMethod() ?? throw new InvalidOperationException($"Failed to find 'Start' property on {iTextRange.FullName}"); | ||
| GetEndMethodInfo = iTextRange.GetProperty("End")?.GetGetMethod() ?? throw new InvalidOperationException($"Failed to find 'End' property on {iTextRange.FullName}"); | ||
| Type passwordTextPointer = typeof(PasswordBox).Assembly.GetType("System.Windows.Controls.PasswordTextPointer") ?? throw new InvalidOperationException("Failed to find PasswordTextPointer"); | ||
| GetOffsetPropertyInfo = passwordTextPointer.GetProperty("Offset", BindingFlags.NonPublic | BindingFlags.Instance) ?? throw new InvalidOperationException("Failed to find 'Offset' property on PasswordTextPointer"); | ||
| } | ||
|
|
||
| protected override void OnAttached() | ||
| { | ||
| base.OnAttached(); | ||
| AssociatedObject.IsVisibleChanged += AssociatedObjectOnIsVisibleChanged; | ||
| if (PasswordBox != null) | ||
| { | ||
| var selection = SelectionPropertyInfo.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); | ||
| object? start = GetStartMethodInfo.Invoke(selection, null); | ||
| object? end = GetEndMethodInfo.Invoke(selection, null); | ||
| int? startValue = GetOffsetPropertyInfo.GetValue(start, null) as int?; | ||
| int? endValue = GetOffsetPropertyInfo.GetValue(end, null) as int?; | ||
| int selectionStart = startValue ?? 0; | ||
| int selectionLength = 0; | ||
| if (endValue.HasValue) | ||
| { | ||
| selectionLength = endValue.Value - selectionStart; | ||
| } | ||
| return new PasswordBoxSelection(selectionStart, selectionLength); | ||
| } | ||
|
|
||
| private void SetPasswordBoxSelection(int selectionStart, int selectionLength) => SelectMethodInfo.Invoke(PasswordBox, new object[] { selectionStart, selectionLength }); | ||
|
|
||
| private record struct PasswordBoxSelection(int SelectionStart, int SelectionEnd); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.