Skip to content

Commit

Permalink
feat: 优化滚动, 文本和密码框实现
Browse files Browse the repository at this point in the history
  • Loading branch information
SlimeNull committed Jul 2, 2024
1 parent a6251e6 commit b44519e
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 31 deletions.
68 changes: 55 additions & 13 deletions EleCho.WpfSuite/Controls/PasswordBox.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
using System;
using System.Security;
using System.Text;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;

namespace EleCho.WpfSuite
{
/// <summary>
/// PasswordBox
/// </summary>
public class PasswordBox : TextBox
{

private readonly StringBuilder _passwordBuilder;
private readonly StringBuilder _maskTextBuilder;

private readonly DispatcherTimer _maskTimer;

Expand All @@ -28,22 +33,39 @@ public PasswordBox()
{
PreviewTextInput += OnPreviewTextInput;
PreviewKeyDown += OnPreviewKeyDown;

CommandManager.AddPreviewExecutedHandler(this, PreviewExecutedHandler);

_passwordBuilder = new();
_maskTextBuilder = new();

_maskTimer = new DispatcherTimer { Interval = new TimeSpan(0, 0, 0, 1) };
_maskTimer.Tick += (sender, args) => MaskAllDisplayText();
}



/// <summary>
/// Mask text
/// </summary>
public string Mask
{
get { return (string)GetValue(MaskProperty); }
set { SetValue(MaskProperty, value); }
}

public SecureString Password
/// <summary>
/// Password
/// </summary>
public string Password
{
get => (SecureString)GetValue(PasswordProperty);
get => (string)GetValue(PasswordProperty);
set => SetValue(PasswordProperty, value);
}



/// <summary>
/// Mask text immediately after input
/// </summary>
public bool AlwaysMask
{
get { return (bool)GetValue(AlwaysMaskProperty); }
Expand All @@ -53,9 +75,22 @@ public bool AlwaysMask




/// <summary>
/// The DependencyProperty of <see cref="Mask"/> property
/// </summary>
public static readonly DependencyProperty MaskProperty =
DependencyProperty.Register(nameof(Mask), typeof(string), typeof(PasswordBox), new PropertyMetadata("*"));

/// <summary>
/// The DependencyProperty of <see cref="Password"/> property
/// </summary>
public static readonly DependencyProperty PasswordProperty =
DependencyProperty.Register(nameof(Password), typeof(SecureString), typeof(PasswordBox), new UIPropertyMetadata(new SecureString()));
DependencyProperty.Register(nameof(Password), typeof(string), typeof(PasswordBox), new UIPropertyMetadata(string.Empty));

/// <summary>
/// The DependencyProperty of <see cref="AlwaysMask"/> property
/// </summary>
public static readonly DependencyProperty AlwaysMaskProperty =
DependencyProperty.Register(nameof(AlwaysMask), typeof(bool), typeof(PasswordBox), new PropertyMetadata(true));

Expand Down Expand Up @@ -152,20 +187,25 @@ private void AddToSecureString(string text)
foreach (char c in text)
{
int caretIndex = CaretIndex;
Password.InsertAt(caretIndex, c);
_passwordBuilder.Insert(caretIndex, c);
MaskAllDisplayText();

if (caretIndex == Text.Length)
{
_maskTimer.Stop();
_maskTimer.Start();

Text = Text.Insert(caretIndex++, c.ToString());
}
else
{
Text = Text.Insert(caretIndex++, "*");
Text = Text.Insert(caretIndex++, Mask);
}

CaretIndex = caretIndex;
}

SetValue(PasswordProperty, _passwordBuilder.ToString());
}

/// <summary>
Expand All @@ -176,20 +216,22 @@ private void AddToSecureString(string text)
private void RemoveFromSecureString(int startIndex, int trimLength)
{
int caretIndex = CaretIndex;
for (int i = 0; i < trimLength; ++i)
{
Password.RemoveAt(startIndex);
}
_passwordBuilder.Remove(startIndex, trimLength);

Text = Text.Remove(startIndex, trimLength);
CaretIndex = caretIndex;
SetValue(PasswordProperty, _passwordBuilder.ToString());
}

private void MaskAllDisplayText()
{
_maskTimer.Stop();
int caretIndex = CaretIndex;
Text = new string('*', Text.Length);

_maskTextBuilder.Clear();
_maskTextBuilder.Insert(0, Mask, Text.Length);

Text = _maskTextBuilder.ToString();
CaretIndex = caretIndex;
}
}
Expand Down
12 changes: 6 additions & 6 deletions EleCho.WpfSuite/Controls/PasswordBoxResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
SnapsToDevicePixels="True">

<Grid x:Name="PART_ContentContainer">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
TextElement.FontSize="26"
TextElement.Foreground="Red"/>
<ws:ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
TextElement.FontSize="26"
TextElement.Foreground="Red"/>
<Border Padding="{TemplateBinding Padding}" >
<TextBlock x:Name="PART_Placeholder"
Text="{TemplateBinding Placeholder}"
Expand Down
52 changes: 46 additions & 6 deletions EleCho.WpfSuite/Controls/ScrollViewer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,14 @@ static ScrollViewer()
private int _lastHorizontalScrollingDelta = 0;
private long _lastScrollingTick;

private FrameworkElement? _scrollContentPrensenter;
private FrameworkElement? _scrollContentPresenter;

/// <inheritdoc/>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();

_scrollContentPrensenter = GetTemplateChild("PART_ScrollContentPresenter") as FrameworkElement;
_scrollContentPresenter = GetTemplateChild("PART_ScrollContentPresenter") as FrameworkElement;
}

private void CoreScrollWithWheelDelta(MouseWheelEventArgs e)
Expand All @@ -68,8 +68,8 @@ private void CoreScrollWithWheelDelta(MouseWheelEventArgs e)
return;
}

var handlesMouseWheelScrolling = _propertyHandlesMouseWheelScrollingGetter.Invoke(this);
if (!handlesMouseWheelScrolling)
if (!AlwaysHandleMouseWheelScrolling &&
!_propertyHandlesMouseWheelScrollingGetter.Invoke(this))
{
return;
}
Expand Down Expand Up @@ -102,7 +102,7 @@ private void CoreScrollWithWheelDelta(MouseWheelEventArgs e)
if (ScrollInfo is IScrollInfo scrollInfo)
{
// 考虑到 VirtualizingPanel 可能是虚拟的大小, 所以这里需要校正 Delta
scrollDelta *= scrollInfo.ViewportHeight / (_scrollContentPrensenter?.ActualHeight ?? ActualHeight);
scrollDelta *= scrollInfo.ViewportHeight / (_scrollContentPresenter?.ActualHeight ?? ActualHeight);
}

var sameDirectionAsLast = Math.Sign(e.Delta) == Math.Sign(_lastVerticalScrollingDelta);
Expand Down Expand Up @@ -152,7 +152,7 @@ private void CoreScrollWithWheelDelta(MouseWheelEventArgs e)
if (ScrollInfo is IScrollInfo scrollInfo)
{
// 考虑到 VirtualizingPanel 可能是虚拟的大小, 所以这里需要校正 Delta
scrollDelta *= scrollInfo.ViewportWidth / (_scrollContentPrensenter?.ActualWidth ?? ActualWidth);
scrollDelta *= scrollInfo.ViewportWidth / (_scrollContentPresenter?.ActualWidth ?? ActualWidth);
}

var sameDirectionAsLast = Math.Sign(e.Delta) == Math.Sign(_lastHorizontalScrollingDelta);
Expand Down Expand Up @@ -286,6 +286,19 @@ public double TouchpadScrollDeltaFactor
set { SetValue(TouchpadScrollDeltaFactorProperty, value); }
}

/// <summary>
/// Always handle mouse wheel scrolling. <br />
/// (Especially in "TextBox")
/// </summary>
public bool AlwaysHandleMouseWheelScrolling
{
get { return (bool)GetValue(AlwaysHandleMouseWheelScrollingProperty); }
set { SetValue(AlwaysHandleMouseWheelScrollingProperty, value); }
}







Expand Down Expand Up @@ -377,6 +390,27 @@ public static void SetScrollingAnimationDuration(DependencyObject obj, Duration
}


/// <summary>
/// Set value of AlwaysHandleMouseWheelScrolling property
/// </summary>
/// <param name="obj"></param>
/// <returns></returns>
public static bool GetAlwaysHandleMouseWheelScrolling(DependencyObject obj)
{
return (bool)obj.GetValue(AlwaysHandleMouseWheelScrollingProperty);
}

/// <summary>
/// Get value of AlwaysHandleMouseWheelScrolling property
/// </summary>
/// <param name="obj"></param>
/// <param name="value"></param>
public static void SetAlwaysHandleMouseWheelScrolling(DependencyObject obj, bool value)
{
obj.SetValue(AlwaysHandleMouseWheelScrollingProperty, value);
}


/// <summary>
/// The DependencyProperty of <see cref="ScrollWithWheelDelta"/> property.
/// </summary>
Expand All @@ -398,6 +432,12 @@ public static void SetScrollingAnimationDuration(DependencyObject obj, Duration
DependencyProperty.RegisterAttached(nameof(ScrollingAnimationDuration), typeof(Duration), typeof(ScrollViewer),
new FrameworkPropertyMetadata(new Duration(TimeSpan.FromMilliseconds(250)), FrameworkPropertyMetadataOptions.Inherits), ValidateScrollingAnimationDuration);

/// <summary>
/// The DependencyProperty of <see cref="AlwaysHandleMouseWheelScrolling"/> property
/// </summary>
public static readonly DependencyProperty AlwaysHandleMouseWheelScrollingProperty =
DependencyProperty.RegisterAttached(nameof(AlwaysHandleMouseWheelScrolling), typeof(bool), typeof(ScrollViewer), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.Inherits));

/// <summary>
/// The DependencyProperty of <see cref="MouseScrollDeltaFactor"/> property
/// </summary>
Expand Down
12 changes: 6 additions & 6 deletions EleCho.WpfSuite/Controls/TextBoxResources.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,12 @@
SnapsToDevicePixels="True">

<Grid x:Name="PART_ContentContainer">
<ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
TextElement.FontSize="26"
TextElement.Foreground="Red"/>
<ws:ScrollViewer x:Name="PART_ContentHost"
Focusable="false"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden"
TextElement.FontSize="26"
TextElement.Foreground="Red"/>
<Border Padding="{TemplateBinding Padding}" >
<TextBlock x:Name="PART_Placeholder"
Text="{TemplateBinding Placeholder}"
Expand Down
12 changes: 12 additions & 0 deletions WpfTest/Tests/TempPage.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,18 @@
</ws:ComboBox.Items>
</ws:ComboBox>

<ws:TextBox AcceptsReturn="True"
MaxHeight="300"
Placeholder="Multi line TextBox"/>

<ws:PasswordBox Name="passwordBox"
Placeholder="PasswordBox"/>

<StackPanel>
<TextBlock Text="PasswordValue:"/>
<TextBlock Text="{Binding ElementName=passwordBox,Path=Password}"/>
</StackPanel>

<ws:Button Background="Red"
HoverBackground="{x:Null}"
Content="Test"/>
Expand Down

0 comments on commit b44519e

Please sign in to comment.