问题
您遇到的问题是在 WPF 应用程序中按 Alt 会导致内置行为将焦点从当前项移动到 Access Key Manager。要查看此行为,请按 Alt,然后按 Space,您将看到系统菜单弹出。
为了更清楚地看到它,在窗口中添加一个按钮,内容在您想成为“访问键”的字符之前带有下划线
<Button Content="_Test" />
然后按Alt,您会看到按钮文本变为带有下划线的字母T。然后,如果您按下 T,按钮将被按下。这是行动中的可访问性。
蛮力解决方案
修复您的代码的最简单、最快捷的方法是通过将 Alt 键标记为 Handled 来禁用可访问性通过 Window 的 KeyDown 事件或 PreviewKeyDown 事件。
注意:这将禁用 WPF 窗口中的所有可访问性,我不建议这样做。
WPF 窗口
<Window x:Class="WpfApp1.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"
KeyDown="Window_KeyDown"
Title="MainWindow" Height="450" Width="800">
<Grid>
<ScrollViewer x:Name="ScrollViewer">
<Border Background="Blue" Height="5000"></Border>
</ScrollViewer>
</Grid>
</Window>
代码背后
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.System && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)))
e.Handled = true;
}
}
}
请注意,我们基本上挂钩到窗口的按键按下事件以捕获所有按键,然后我们监听该按键是否是系统键(可能包括 F1、F2 等功能键),如果是我们检查是否按下了任一 Alt 键。
我们进行第一次检查是为了稍微加快逻辑速度,而不是检查 Alt 键,除非我们首先知道它是系统键。否则,您在整个键盘上按下的每个键(例如在文本框中输入)都会涉及对 Alt 键的检查并减慢速度。
智能解决方案
为了达到您想要的效果,您确实需要能够检测您的用户是否处于您的“模式”或“焦点”,当用户开始按 Alt 时,他们处于您希望禁用的模式访问密钥。
确定此模式的一种简单方法是仅在滚动查看器获得焦点时禁用 Alt。然后,您的逻辑将更改为像这样进行一次额外的检查。
private void Window_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.System && (Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt))
&& e.OriginalSource == ScrollViewer) // <-- Added this check
e.Handled = true;
}
然后您将仅在视图具有焦点时禁用 Alt(例如已单击)。
如果您想检测模式(禁用 Alt 操作)在 Alt 键已被按下(例如在滚动开始后检测它)之后,您的挑战会变得更加困难.这样做的原因是,只要按下 Alt,访问键的行为就会开始(按下键时),因此事后移除该行为会更加困难。
可疑的解决方案
所以在写完上面的内容后,我认为最好给你一个后替代风格检测的例子。所以就在这里。这只是一个经过深思熟虑和测试的示例,可能有问题(主要是 Task.Delay(0) 依赖于在聚焦之前对先前发布的系统密钥消息的处理)但似乎工作正常。
现在您可以正常按Alt,也可以按Alt然后开始滚动,松开访问键后散焦。
WPF 窗口
<Window x:Class="WpfApp1.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"
KeyDown="Window_KeyDown"
KeyUp="Window_KeyUp"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Open"/>
<MenuItem Header="_Close"/>
<MenuItem Header="_Save"/>
</MenuItem>
</Menu>
<ScrollViewer x:Name="ScrollViewer" PreviewMouseWheel="ScrollViewer_PreviewMouseWheel">
<Border Background="Blue" Height="5000"></Border>
</ScrollViewer>
</DockPanel>
</Window>
代码背后
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
namespace WpfApp1
{
public partial class MainWindow : Window
{
private bool mHasScrolledWithSystemKeyDown = false;
private bool mSystemKeyIsDown = false;
private IInputElement mLastFocusedControl;
public MainWindow()
{
InitializeComponent();
}
private void Window_KeyDown(object sender, KeyEventArgs e)
{
// If the system key is down...
if (e.Key == Key.System)
// Track it
mSystemKeyIsDown = true;
}
private void ScrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
{
// If the system key is down while scrolling...
if (mSystemKeyIsDown)
{
// If it is the first scroll since it being down...
if (!mHasScrolledWithSystemKeyDown)
// Remember currently focused item
mLastFocusedControl = Keyboard.FocusedElement;
// And flag as scrolled with system key down
mHasScrolledWithSystemKeyDown = true;
// Prevent scroll...
e.Handled = true;
// TODO: Zoom
}
}
private void Window_KeyUp(object sender, KeyEventArgs e)
{
// On system key up...
if (e.Key == Key.System)
{
// Track it
mSystemKeyIsDown = false;
// If we had scrolled with the system key down...
if (mHasScrolledWithSystemKeyDown)
// Cause a small delay to allow this key up to process
Task.Delay(0).ContinueWith((t) => Dispatcher.Invoke(() =>
{
// Then focus the last control to "close" the system menu gracefully
mLastFocusedControl?.Focus();
}));
// Flag the has scrolled to false to start again
mHasScrolledWithSystemKeyDown = false;
}
}
}
}
结束时
请注意,如果您在按下 Alt 后尝试检测然后取消该操作,请注意使用任何消息泵命令(如 SendMessage)只是发送 VM_KEYUP Alt 的命令将无法用作在按住时反复物理按下的按钮,并且消息泵也有许多怪癖,您确实需要知道自己在做什么才能不破坏其他东西。