【问题标题】:Alt+mousewheel zoomingAlt+鼠标滚轮缩放
【发布时间】:2018-03-29 07:15:52
【问题描述】:

我有这个代码:

private void scrollViewer_PreviewMouseWheel(object sender, MouseWheelEventArgs e)
        {
            if (Keyboard.IsKeyDown(Key.LeftAlt))
            {
                e.Handled = true;

                //initiate zoooom!
            }                    
        }  

出于缩放原因,我想实现 alt+鼠标滚轮,但我不想在我的滚动查看器上丢失常规的鼠标滚轮向上/向下。我只是想在放大时暂时禁用它。我已经尝试在 keyup 和方法结束时取消处理,但仍然没有运气。

上面的代码就像一个切换的东西。 Alt+鼠标滚轮停止滚动查看器向上/向下滚动,但在释放 alt 时,滚动查看器仍处于锁定状态。它需要第二次按 alt 才能释放这个东西。

【问题讨论】:

  • 您是否尝试过将 mousescrollevent 移动到 viewmodel 命令?比使用 LeftAlt KeyBinding 命令,设置 leftalt 被按下的标志并在 mousescroll 命令中查看标志值?
  • 我在this answer 中编写了一个 WPF 行为来执行此操作,听起来对您很有用。它甚至支持指定修饰键(如LeftAlt)。
  • @BradleyUffner 你好,我在这个项目中使用 Caliburn。实际上,我之前已经偶然发现了那篇文章,但我对使用 Interactivity 有疑问。与 Caliburn 一起使用是否安全?
  • 我以前从未使用过 Caliburn,但从我刚刚看到的快速浏览来看,我不认为它会破坏任何东西。如果 Caliburn 提供自己的 Behavior 模式,我会尝试使我的代码适应它使用的内容。
  • @BradleyUffner 遗憾的是,您的回答对我来说不起作用。在项目中添加交互会导致它无法正确编译,它说:Found conflicts between different versions of "System.Windows.Interactivity" that could not be resolved. 并且运行程序会导致 Caliburn 在 PresentationFramework.dll 中引发解析异常。

标签: c# wpf


【解决方案1】:

问题

您遇到的问题是在 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 的命令将无法用作在按住时反复物理按下的按钮,并且消息泵也有许多怪癖,您确实需要知道自己在做什么才能不破坏其他东西。

【讨论】:

  • 欢迎来到 Stack Overflow!恭喜您获得了彻底的第一个答案。
  • 如果我理解正确 Alt 键并不是您的常规键(这说明 if (e.Key == Key.LeftAlt) 不起作用,因为 e.Key 包含“系统”)。所以如果我真的想实现Alt+鼠标滚轮,哪里会不容易。这是我第一次听到 SendMessage,我会查找它。
  • 系统(Windows)截取Alt并以System键状态发送到窗口的消息泵。但是,对于您需要的,我可疑的解决方案应该可以正常工作,即使以后如果您发现问题需要一些改进。我测试了它,它完全有效
  • 经过测试。使我免于引入一些我真的不理解/永远不会理解的更可疑、更复杂的代码。干得好,非常干净且易于理解(而且内容丰富)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-05
  • 2017-10-20
  • 1970-01-01
  • 2020-01-26
  • 2016-11-08
  • 1970-01-01
相关资源
最近更新 更多