为什么 WPF PasswordBox 不可滚动

Why WPF PasswordBox is not scrollable

谁能给我解释一下为什么 PasswordBox 控件不可滚动。

Preview

如您所见,当鼠标光标出现在 PasswordBox 上时,滚动不起作用。我添加了默认和自定义 PasswordBox,两者的行为相同。

这是我为更好地演示而创建的示例项目。

<Window 
    x:Class="PasswordBoxDemo.MainWindow"
    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"
    mc:Ignorable="d"
    Title="MainWindow" 
    Height="450"
    Width="600"
    WindowStartupLocation="CenterScreen">
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Setter Property="Padding" Value="12,8"/>
            <Setter Property="Text" Value="Sample text"/>
        </Style>

        <Style TargetType="{x:Type PasswordBox}">
            <Setter Property="Padding" Value="12,8"/>
        </Style>
    </Window.Resources>
    <ScrollViewer>
        <StackPanel Margin="10" Height="800">
            <TextBlock Margin="16" FontSize="32" TextWrapping="Wrap">
                Put your mouse on password box area and try to scroll by mouse wheel.
            </TextBlock>
            <Label>Text:</Label>
            <TextBox />
            <Label>Password:</Label>
            <PasswordBox Password="123456"/>
            <Label>Text:</Label>
            <TextBox />
            <Label>Text:</Label>
            <TextBox />
            <Label>Password:</Label>
            <PasswordBox Password="123456"/>
            <Label>Text:</Label>
            <TextBox />
            <Label>Text:</Label>
            <TextBox />
        </StackPanel>
    </ScrollViewer>
</Window>

这看起来像是在内部抑制了密码框上的滚动鼠标事件。您可以将 IsHitTestVisible 设置为 false,但这也会阻止其他鼠标事件的发生。

如果您只需要简单的解决方法,请为 PreviewMouseWheel 事件添加处理程序,如下所示:

<PasswordBox PreviewMouseWheel="PasswordBox_PreviewMouseWheel" />

为父 ScrollViewer 命名:

<ScrollViewer Name="scrollViewer1">

然后在代码中实现处理程序回调,如下所示:

private void PasswordBox_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
    if (e.Delta != 0)
    {
        scrollViewer1.ScrollToVerticalOffset(scrollViewer1.VerticalOffset - e.Delta);
    }
    e.Handled = true;
}

这是一个很大的丑陋,但有效。

我使用另一种方法向 PasswordBox 控件添加滚动行为。我觉得这样更干净。

public class PasswordBoxAssist
{
    public static ScrollViewer GetParentScrollViewer(DependencyObject obj)
        => (ScrollViewer)obj.GetValue(ParentScrollViewerProperty);

    public static void SetParentScrollViewer(DependencyObject obj, ScrollViewer value)
        => obj.SetValue(ParentScrollViewerProperty, value);

    public static readonly DependencyProperty ParentScrollViewerProperty =
        DependencyProperty.RegisterAttached("ParentScrollViewer", typeof(ScrollViewer),
            typeof(PasswordBoxAssist), new UIPropertyMetadata(default, OnParentScrollViewerChanged));

    private static void OnParentScrollViewerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var passwordBox = (PasswordBox)d;
        var scrollViewer = (ScrollViewer)e.NewValue;

        if (scrollViewer is null)
            passwordBox.PreviewMouseWheel -= OnPasswordBoxPreviewMouseWheel;
        else
            passwordBox.PreviewMouseWheel += OnPasswordBoxPreviewMouseWheel;
    }

    private static void OnPasswordBoxPreviewMouseWheel(object sender, MouseWheelEventArgs e)
    {
        var passwordBox = (PasswordBox)sender;
        var scrollViewer = GetParentScrollViewer(passwordBox);

        if (e.Delta != 0)
            scrollViewer.ScrollToVerticalOffset(scrollViewer.VerticalOffset - e.Delta);

        e.Handled = true;
    }
}
<ScrollViewer x:Name="SampleScrollViewer">
    <PasswordBox 
        [...]
        local:PasswordBoxAssist.ParentScrollViewer="{Binding ElementName=SampleScrollViewer}"
        [...]/>
</ScrollViewer>