如何正确允许最顶部窗口的透明部分的点击区域?

时间:2019-03-16 23:07:07

标签: c# windows winapi directx

我正在使用 C#/ DirectX 中的项目的叠加层组件。

该项目在设计上没有使用 WinForms WPF

此叠加层是混合的,在这里我想通过 DirectX 将自定义UI组件呈现到我的窗口,并传递与底层窗口/应用程序无关的所有输入(我不拥有) 。我基本上想要一个透明的全屏窗口,该窗口充当另一个应用程序顶部的可交互层,在该窗口中,我可以确定消耗的输入事件以及允许通过的事件。

此叠加层通过 RegisterClassEx / CreateWindowEx WIN API调用创建 HWND 。通常出于测试目的,我传入一个布尔变量来确定是否要启用“通过”(基本上将 WS_EX_TRANSPARENT 添加到创建标志中)。我的DX代码通过CreateSwapChainForHwnd呈现到窗口。

参考代码段:

        // prepare WNDPROC-equivalent code for processing WM_* messages

        wndProc = windowProcedure;  
        RuntimeHelpers.PrepareDelegate(wndProc);
        wndProcPointer = Marshal.GetFunctionPointerForDelegate(wndProc);

        // prepare window class registration structure

        PInvoke.WNDCLASSEX wndClassEx = new PInvoke.WNDCLASSEX()
        {
            cbSize = PInvoke.WNDCLASSEX.Size(),
            style = 0,
            lpfnWndProc = wndProcPointer,
            cbClsExtra = 0,
            cbWndExtra = 0,
            hInstance = IntPtr.Zero,
            hIcon = IntPtr.Zero,
            hCursor = PInvoke.LoadCursor(IntPtr.Zero, (int)PInvoke.IDC_STANDARD_CURSORS.IDC_ARROW),
            hbrBackground = IntPtr.Zero,
            lpszMenuName = randomMenuName,
            lpszClassName = randomClassName,
            hIconSm = IntPtr.Zero
        };

        // Register window class via WINAPI 

        PInvoke.RegisterClassEx(ref wndClassEx);

        // Prepare window basic style flags (WS_*)

        WS style = (WS.WS_POPUP | WS.WS_VISIBLE);

        // Prepare window extended style flags (WS_EX_*)

        WSEx exStyle;

        if (_Topmost)
        {
            if (_AllowPassthrough)
                exStyle = (WSEx.WS_EX_TOPMOST | WSEx.WS_EX_TRANSPARENT | WSEx.WS_EX_LAYERED | WSEx.WS_EX_TOOLWINDOW | WSEx.WS_EX_NOACTIVATE); 
            else
                exStyle = (WSEx.WS_EX_TOPMOST | WSEx.WS_EX_LAYERED | WSEx.WS_EX_TOOLWINDOW | WSEx.WS_EX_NOACTIVATE); 
        }
        else
        {
            if (_AllowPassthrough)
                exStyle = (WSEx.WS_EX_TRANSPARENT | WSEx.WS_EX_LAYERED | WSEx.WS_EX_TOOLWINDOW | WSEx.WS_EX_NOACTIVATE);
            else
                exStyle = (WSEx.WS_EX_LAYERED | WSEx.WS_EX_TOOLWINDOW | WSEx.WS_EX_NOACTIVATE);
        }

        // Create window via WINAPI 

        _WindowHandle = PInvoke.CreateWindowEx(
            (uint)exStyle,
            randomClassName,
            randomWindowName,
            (uint)style, 
            _Position.X, _Position.Y,
            _Size.X, _Size.Y,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero,
            IntPtr.Zero
            );

        // SetLayeredWindowAttributes is required to define transparency 

            // BOOL SetLayeredWindowAttributes( HWND hwnd, COLORREF crKey, BYTE bAlpha, DWORD dwFlags);
            // Flag: LWA_ALPHA      0x00000002    Use bAlpha to determine the opacity of the layered window.
            // Flag: LWA_COLORKEY   0x00000001    Use crKey as the transparency color. 

        PInvoke.SetLayeredWindowAttributes(_WindowHandle, 0, 255, 0x2);
        PInvoke.UpdateWindow(_WindowHandle);

最透明,最顶部的 DirectX 11 窗口可以很好地呈现并从视觉角度正常运行。

如果我将_AllowPassthrough设置为TRUE,则所有输入都将传递到基础窗口。

如果我将_AllowPassthrough设置为FALSE,则不会有任何输入向下传递到基础窗口。

我正在使用 SetWindowsHookEx() API函数来获取低级的鼠标和键盘信息,但是当我在确定输入后调用 CallNextHookEx()时,似乎出现了与我无关,它永远不会到达底层窗口进行处理(如果_AllowPassthrough为假)或永远不会被阻塞/消耗(如果_AllowPassthrough为真)。

在Hook过程中,我返回(IntPtr)1; 以阻止进一步处理,或者返回PInvoke.CallNextHookEx(hookHandle,nCode,wParam,lParam); 让别人来处理。

我的大部分研究仅使我进入了有关完全“点击”覆盖窗口的讨论或文章;不是在某些区域中部分点击的窗口。

我猜想这与我的“ WS_EX_LAYERED”窗口样式的实现有关。

我应该使用“ WS_EX_NOREDIRECTIONBITMAP”样式选项(用于Windows合成引擎)而不是“ WS_EX_LAYERED”(基于SetLayeredWindowAttributes进行像素命中测试)吗?

如果有任何影响,我正在使用 sharpDX 作为我的 DirectX 包装器库。我的项目是通过 VS2017 并使用.NET Framework 4.7.1上的最新C#语言规范构建的。

编辑3/19/2019:

因此,我对此主题进行了广泛的反复试验,并得出结论。...

没有WS_LAYERED,鼠标输入/命中测试/等等,无论如何都不会落入基础窗口。

使用WS_LAYERED,我的窗口停止接收任何WM_NCHITTEST或其他与光标相关的消息。我可以通过使用WH_MOUSE_LL挂钩(和关联的LowLevelMouseProc()消息处理程序)来获取这些信息,但是使用事件并返回非零值(以防止系统将消息传递给挂钩链的其余部分或目标)窗口过程”)仍然允许其他“非鼠标相关”消息通过,因此,即使尝试通过消耗MOUSE_MOVE并使用SetCursorPos()移动光标来抑制它,也会导致下方的窗口获得悬停或鼠标的通知结束(我可以看到突出显示的按钮和弹出的工具提示)。

如果我不使用SetCursorPos(),鼠标将锁定在适当的位置,因为移动光标所需的代码看不到鼠标的移动。我不确定SetCursorPos()是否会引起MOUSE_MOVE事件,但我认为不会,因为我还是在使用这些事件(并使用“ last == current”检查来防止锁定。)我不知道该怎么办其他系统事件正在生成。在线严重缺乏有关输入消息队列的低级性质的信息。

至于“非alpha”颜色,这似乎没有什么区别。不论不透明,任何绘制的颜色仍会通过鼠标输入传递。

我已经尝试了前面提到的WS_EX_NOREDIRECTIONBITMAP并重构了我的DX渲染代码以改为使用Composition Engine,并使用了引人注目的MSDN article by Kenny Kerr作为参考,但这没有什么区别。

我真的很为难。

0 个答案:

没有答案