草庐IT

c# - 切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY

coder 2024-06-13 原文

我正在用 C# 编写一个使用原始输入的鼠标对象。该设备注册并获取数据和所有这些东西,所以它在这方面是有效的。然而,在这个对象上我有一个名为“Exclusive”的属性,它是为了模仿直接输入中的独占模式。

当我将此属性切换为 TRUE 时,我调用 RegisterRawInputDevices 并将 RAWINPUTDEVICE 的 dwFlags 成员设置为:RIDEV_CAPTUREMOUSE | RIDEV_NOLEGACY。当我将该属性设置为 FALSE 时,我将其设置为 0。

现在的问题是当我从鼠标按钮按下/向上事件执行此操作时。在我的鼠标对象上,我分配鼠标按钮按下事件以将 Exclusive 设置为 TRUE,而在鼠标弹起时我将其设置为 FALSE。当我运行应用程序时,事件会触发,独占模式会被设置和重置。这是奇怪的事情开始发生的地方:

  1. 鼠标弹起事件和独占模式禁用后,窗口不响应窗口装饰中的鼠标悬停事件(例如,关闭按钮不会突出显示,我无法点击它) .我也无法通过按 ALT+F4 退出应用程序。但是,当我单击窗口一次或两次时,常规窗口行为又回来了。

  2. 应用程序关闭后,Windows 资源管理器和其他应用程序窗口会做出相同的 react 。我必须多次左右单击才能使它们恢复正常。

  3. 在极少数情况下,窗口会因为一些奇怪的原因而失去焦点。这让一切都陷入排他状态的困惑之中(代码设置为在窗口停用时解除绑定(bind)设备,并在再次激活时恢复它)。正如我之前所说,这是一种非常罕见的情况,但仍然存在很大问题。

当我使用按键按下和按键弹起事件设置/重置独占模式时,一切正常,并且以上都没有发生。这真是令人费解。

我已经在两台计算机上尝试过此代码,使用不同的鼠标,一台运行 Windows 7 x64,另一台运行 Windows 8.1 x64。

在过去的几天里,我对此进行了大量搜索,但一无所获,所以我想知道是否有人对它为何以这种方式运行有任何想法?我没有设置正确的标志吗?像这样一遍又一遍地调用 RegisterRawInputDevices 会导致问题吗?

这是我用来测试问题的示例程序的代码:

_mouse = _input.CreatePointingDevice(_form);
_keyboard = _input.CreateKeyboard(_form);

_mouse.PointingDeviceDown += (sender, args) =>
                             {
                                 if ((args.Buttons & PointingDeviceButtons.Right) != PointingDeviceButtons.Right)
                                 {
                                     return;
                                 }

                                 _mouse.Exclusive = true;
                             };

_mouse.PointingDeviceMove += (sender, args) =>
                             {
                                 _form.Text = string.Format("{0}x{1}", args.Position.X, args.Position.Y);
                             };

_mouse.PointingDeviceUp += (sender, args) =>
                           {
                               if ((args.Buttons & PointingDeviceButtons.Right) != PointingDeviceButtons.Right)
                               {
                                   return;
                               }

                               _mouse.CursorVisible = true;
                               _mouse.Exclusive = false;
                           };

这是我用来注册和取消注册鼠标的代码:

/// <summary>
/// Function to bind the input device.
/// </summary>
protected override void BindDevice()
{
    BoundControl.MouseLeave -= Owner_MouseLeave;

    UnbindDevice();

    if (_messageFilter != null)
    {
        _messageFilter.RawInputPointingDeviceData -= GetRawData;
        _messageFilter.RawInputPointingDeviceData += GetRawData;
    }

    _device.UsagePage = HIDUsagePage.Generic;
    _device.Usage = (ushort)HIDUsage.Mouse;
    _device.Flags = RawInputDeviceFlags.None;

    // Enable background access.
    if (AllowBackground)
    {
        _device.Flags |= RawInputDeviceFlags.InputSink;
    }

    // Enable exclusive access.
    if (Exclusive)
    {
        _device.Flags |= RawInputDeviceFlags.CaptureMouse | RawInputDeviceFlags.NoLegacy;
    }

    _device.WindowHandle = BoundControl.Handle;

    // Attempt to register the device.
    if (!Win32API.RegisterRawInputDevices(_device))
    {
        throw new GorgonException(GorgonResult.DriverError, Resources.GORINP_RAW_CANNOT_BIND_POINTING_DEVICE);
    }

    if (!Exclusive)
    {
        OnWindowBound(BoundControl);
    }
}

    /// <summary>
    /// Function to unbind the input device.
    /// </summary>
    protected override void UnbindDevice()
    {
        if (_messageFilter != null)
        {
            _messageFilter.RawInputPointingDeviceData -= GetRawData;
        }

        _device.UsagePage = HIDUsagePage.Generic;
        _device.Usage = (ushort)HIDUsage.Mouse;
        _device.Flags = RawInputDeviceFlags.Remove;
        _device.WindowHandle = IntPtr.Zero;

        // Attempt to register the device.
        if (!Win32API.RegisterRawInputDevices(_device))
        {
            throw new GorgonException(GorgonResult.DriverError, Resources.GORINP_RAW_CANNOT_UNBIND_POINTING_DEVICE);
        }

        BoundControl.MouseLeave -= Owner_MouseLeave;
    }

这是处理 WM_INPUT 消息的代码:

/// <summary>
/// Object representing a message loop filter.
/// </summary>
internal class MessageFilter
    : System.Windows.Forms.IMessageFilter
{
    #region Events.
    /// <summary>
    /// Event fired when a raw input keyboard event occours.
    /// </summary>
    public event EventHandler<RawInputKeyboardEventArgs> RawInputKeyboardData = null;
    /// <summary>
    /// Event fired when a pointing device event occurs.
    /// </summary>
    public event EventHandler<RawInputPointingDeviceEventArgs> RawInputPointingDeviceData = null;
    /// <summary>
    /// Event fired when an HID event occurs.
    /// </summary>
    public event EventHandler<RawInputHIDEventArgs> RawInputHIDData = null;
    #endregion

    #region Variables.
    private readonly int _headerSize = DirectAccess.SizeOf<RAWINPUTHEADER>();   // Size of the input data in bytes.
    #endregion

    #region IMessageFilter Members
    /// <summary>
    /// Filters out a message before it is dispatched.
    /// </summary>
    /// <param name="m">The message to be dispatched. You cannot modify this message.</param>
    /// <returns>
    /// true to filter the message and stop it from being dispatched; false to allow the message to continue to the next filter or control.
    /// </returns>
    public bool PreFilterMessage(ref System.Windows.Forms.Message m)
    {
        // Handle raw input messages.
        if ((WindowMessages)m.Msg != WindowMessages.RawInput)
        {
            return false;
        }

        unsafe
        {
            int dataSize = 0;

            // Get data size.           
            int result = Win32API.GetRawInputData(m.LParam, RawInputCommand.Input, IntPtr.Zero, ref dataSize, _headerSize);

            if (result == -1)
            {
                throw new GorgonException(GorgonResult.CannotRead, Resources.GORINP_RAW_CANNOT_READ_DATA);
            }

            // Get actual data.
            var rawInputPtr = stackalloc byte[dataSize];
            result = Win32API.GetRawInputData(m.LParam, RawInputCommand.Input, (IntPtr)rawInputPtr, ref dataSize, _headerSize);

            if ((result == -1) || (result != dataSize))
            {
                throw new GorgonException(GorgonResult.CannotRead, Resources.GORINP_RAW_CANNOT_READ_DATA);
            }

            var rawInput = (RAWINPUT*)rawInputPtr;

            switch (rawInput->Header.Type)
            {
                case RawInputType.Mouse:
                    if (RawInputPointingDeviceData != null)
                    {
                        RawInputPointingDeviceData(this,
                                                   new RawInputPointingDeviceEventArgs(rawInput->Header.Device, ref rawInput->Union.Mouse));
                    }
                    break;
                case RawInputType.Keyboard:
                    if (RawInputKeyboardData != null)
                    {
                        RawInputKeyboardData(this, new RawInputKeyboardEventArgs(rawInput->Header.Device, ref rawInput->Union.Keyboard));
                    }
                    break;
                default:
                    if (RawInputHIDData != null)
                    {
                        var HIDData = new byte[rawInput->Union.HID.Size * rawInput->Union.HID.Count];
                        var hidDataPtr = ((byte*)rawInput) + _headerSize + 8;

                        fixed (byte* buffer = &HIDData[0])
                        {
                            DirectAccess.MemoryCopy(buffer, hidDataPtr, HIDData.Length);
                        }

                        RawInputHIDData(this, new RawInputHIDEventArgs(rawInput->Header.Device, ref rawInput->Union.HID, HIDData));
                    }
                    break;
            }
        }

        return false;
    }
    #endregion
}

下面是处理 WM_INPUT 后触发鼠标事件的代码:

/// <summary>
/// Function to retrieve and parse the raw pointing device data.
/// </summary>
/// <param name="sender">Sender of the event.</param>
/// <param name="e">Event data to examine.</param>
private void GetRawData(object sender, RawInputPointingDeviceEventArgs e)
{
    if ((BoundControl == null) || (BoundControl.Disposing))
    {
        return;
    }

    if ((_deviceHandle != IntPtr.Zero) && (_deviceHandle != e.Handle))
    {
        return;
    }

    if ((Exclusive) && (!Acquired))
    {
        // Attempt to recapture.
        if (BoundControl.Focused)
        {
            Acquired = true;
        }
        else
        {
            return;
        }
    }

    // Do nothing if we're outside and we have exclusive mode turned off.
    if (!Exclusive)
    {
        if (!WindowRectangle.Contains(BoundControl.PointToClient(System.Windows.Forms.Cursor.Position))) 
        {
            _outside = true;
            return;
        }

        if (_outside) 
        {
            // If we're back inside place position at the entry point.
            _outside = false;
            Position = BoundControl.PointToClient(System.Windows.Forms.Cursor.Position);
        }
    }

    // Get wheel data.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MouseWheel) != 0)
    {
        OnPointingDeviceWheelMove((short)e.PointingDeviceData.ButtonData);
    }

    // If we're outside of the delay, then restart double click cycle.
    if (_doubleClicker.Milliseconds > DoubleClickDelay)
    {
        _doubleClicker.Reset();
        _clickCount = 0;
    }

    // Get button data.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.LeftDown) != 0)
    {
        BeginDoubleClick(PointingDeviceButtons.Left);
        OnPointingDeviceDown(PointingDeviceButtons.Left);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.RightDown) != 0)
    {
        BeginDoubleClick(PointingDeviceButtons.Right);
        OnPointingDeviceDown(PointingDeviceButtons.Right);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MiddleDown) != 0)
    {
        BeginDoubleClick(PointingDeviceButtons.Middle);
        OnPointingDeviceDown(PointingDeviceButtons.Middle);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button4Down) != 0)
    {
        BeginDoubleClick(PointingDeviceButtons.Button4);
        OnPointingDeviceDown(PointingDeviceButtons.Button4);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button5Down) != 0)
    {
        BeginDoubleClick(PointingDeviceButtons.Button5);
        OnPointingDeviceDown(PointingDeviceButtons.Button5);
    }

    // If we have an 'up' event on the buttons, remove the flag.
    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.LeftUp) != 0)
    {
        if (IsDoubleClick(PointingDeviceButtons.Left))
        {
            _clickCount += 1;
        }
        else
        {
            _doubleClicker.Reset();
            _clickCount = 0;
        }

        OnPointingDeviceUp(PointingDeviceButtons.Left, _clickCount);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.RightUp) != 0)
    {
        if (IsDoubleClick(PointingDeviceButtons.Right))
        {
            _clickCount += 1;
        }
        else
        {
            _doubleClicker.Reset();
            _clickCount = 0;
        }

        OnPointingDeviceUp(PointingDeviceButtons.Right, _clickCount);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.MiddleUp) != 0)
    {
        if (IsDoubleClick(PointingDeviceButtons.Middle))
        {
            _clickCount += 1;
        }
        else
        {
            _doubleClicker.Reset();
            _clickCount = 0;
        }

        OnPointingDeviceUp(PointingDeviceButtons.Middle, _clickCount);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button4Up) != 0)
    {
        if (IsDoubleClick(PointingDeviceButtons.Button4))
        {
            _clickCount += 1;
        }
        else
        {
            _doubleClicker.Reset();
            _clickCount = 0;
        }

        OnPointingDeviceUp(PointingDeviceButtons.Button4, _clickCount);
    }

    if ((e.PointingDeviceData.ButtonFlags & RawMouseButtons.Button5Up) != 0)
    {
        if (IsDoubleClick(PointingDeviceButtons.Button5))
        {
            _clickCount += 1;
        }
        else
        {
            _doubleClicker.Reset();
            _clickCount = 0;
        }

        OnPointingDeviceUp(PointingDeviceButtons.Button5, _clickCount);
    }

    // Fire events.
    RelativePosition = new PointF(e.PointingDeviceData.LastX, e.PointingDeviceData.LastY);
    OnPointingDeviceMove(new PointF(Position.X + e.PointingDeviceData.LastX, Position.Y + e.PointingDeviceData.LastY), false);
    UpdateCursorPosition();
}

最佳答案

好吧,经过几天拉扯我遗漏的小头发后,我找不到任何韵律或理由来解释为什么会发生这种情况。因此,我设计了一个相当丑陋的 hack 来伪造独占模式。

首先,我从设备注册中删除了 NOLEGACY 和 CAPTUREMOUSE 标志,然后我将光标锁定到通过 Cursor.Position 接收输入的窗口的中心。然后我修改了我的窗口消息过滤器以丢弃 WM_MOUSEMOVE 和 WM_KEYDOWN 之类的窗口消息,这样当设备处于独占模式时它们就不会被窗口拦截(处理 ALT+F4 的系统命令除外)。

虽然这不是最优雅的解决方案,但它的工作方式正是我想要的。但是,如果有人找到更好的方法来处理这种情况,同时仍然使用 NOLEGACY/CAPTUREMOUSE 标志,我会很乐意将其标记为正确答案。

关于c# - 切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25353236/

有关c# - 切换 RIDEV_CAPTUREMOUSE 时的奇怪行为 | RIDEV_NOLEGACY的更多相关文章

  1. ruby-on-rails - Ruby on Rails with Haml - 如何从 erb 切换 - 2

    我正在从erb文件切换到HAML。我将hamlgem添加到我的系统中。我创建了app/views/layouts/application.html.haml文件。我应该只删除application.html.erb文件吗?此外,仍然有/public/index.html文件被呈现为默认页面。我想创建自己的默认index.html.haml页面。我应该把它放在哪里以及如何使系统呈现该文件而不是默认索引文件?谢谢! 最佳答案 是的,您可以删除任何已转换为HAML的View的ERB版本。至于你的另一个问题,删除public/index/h

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. c# - 如何在 ruby​​ 中调用 C# dll? - 2

    如何在ruby​​中调用C#dll? 最佳答案 我能想到几种可能性:为您的DLL编写(或找人编写)一个COM包装器,如果它还没有,则使用Ruby的WIN32OLE库来调用它;看看RubyCLR,其中一位作者是JohnLam,他继续在Microsoft从事IronRuby方面的工作。(估计不会再维护了,可能不支持.Net2.0以上的版本);正如其他地方已经提到的,看看使用IronRuby,如果这是您的技术选择。有一个主题是here.请注意,最后一篇文章实际上来自JohnLam(看起来像是2009年3月),他似乎很自在地断言RubyCL

  4. C# 到 Ruby sha1 base64 编码 - 2

    我正在尝试在Ruby中复制Convert.ToBase64String()行为。这是我的C#代码:varsha1=newSHA1CryptoServiceProvider();varpasswordBytes=Encoding.UTF8.GetBytes("password");varpasswordHash=sha1.ComputeHash(passwordBytes);returnConvert.ToBase64String(passwordHash);//returns"W6ph5Mm5Pz8GgiULbPgzG37mj9g="当我在Ruby中尝试同样的事情时,我得到了相同sha

  5. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  6. ruby-on-rails - 如何从过时的 TZInfo 标识符中获取 Rails TimeZone 名称? - 2

    已经有一个问题回答了如何将“America/Los_Angeles”转换为“PacificTime(US&Canada)”。但是我想将“美国/太平洋”和其他过时的时区转换为RailsTimeZone。我无法在图书馆中找到任何可以帮助我完成此任务的东西。 最佳答案 来自RailsActiveSupport::TimeZonedocs:TheversionofTZInfobundledwithActiveSupportonlyincludesthedefinitionsnecessarytosupportthezonesdefinedb

  7. ruby - Ruby gsub 替换中的行为不一致? - 2

    两个gsub产生不同的结果。谁能解释一下为什么?代码也可在https://gist.github.com/franklsf95/6c0f8938f28706b5644d获得.ver=9999str="\tCFBundleDevelopmentRegion\n\ten\n\tCFBundleVersion\n\t0.1.190\n\tAppID\n\t000000000000000"putsstr.gsub/(CFBundleVersion\n\t.*\.).*()/,"#{$1}#{ver}#{$2}"puts'--------'putsstr.gsub/(CFBundleVersio

  8. ruby-on-rails - Ruby 中意外的大小写行为 - 2

    我在一段非常简单的代码(如我所想)中得到了一个错误的值:org=4caseorgwhenorg=4val='H'endputsval=>nil请不要生气,我希望我错过了一些非常明显的东西,但我真的想不通。谢谢。 最佳答案 这是典型的Ruby错误。case有两种被调用的方法,一种是你传递一个东西作为分支的基础,另一种是你不传递的东西。如果您确实在case中指定了一个表达式语句然后评估所有其他条件并与===进行比较.在这种情况下org评估为false和org===false显然不是真的。所有其他情况也是如此,它们要么是真的,要么是假的。

  9. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

    假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

  10. ruby - 了解在 Ruby 中与 lambda 一起使用的 inject 行为 - 2

    我经常将预配置的lambda插入可枚举的方法中,例如“map”、“select”等。但是“注入(inject)”的行为似乎有所不同。例如与mult4=lambda{|item|item*4}然后(5..10).map&mult4给我[20,24,28,32,36,40]但是,如果我制作一个2参数lambda用于像这样的注入(inject),multL=lambda{|product,n|product*n}我想说(5..10).inject(2)&multL因为“inject”有一个可选的单个初始值参数,但这给了我......irb(main):027:0>(5..10).inject

随机推荐