我正在使用 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#语言规范构建的。
因此,我对此主题进行了广泛的反复试验,并得出结论。...
没有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作为参考,但这没有什么区别。
我真的很为难。