如果在WPF中的窗体使用AllowsTransparency="True"实现穿透效果,那么该窗体如果移动、快速渲染、控件比较多的情况,会出现卡顿,CPU暴涨的问题。
基于以上情况,可以使用另一种方式实现,由@wuty @terryK 指导:
using System.Windows;
using Annotation.Business;
namespace Demo
{
/// <summary>
/// App.xaml 的交互逻辑
/// </summary>
public partial class App : Application
{
public App()
{
var mainWindow =new ParentWindow();
mainWindow.Show();//窗体show,WindowInteropHelper才能获取句柄
var winHook = new WinHook();
winHook.InitHook();
}
}
}
<Window x:Class="Demo.ParentWindow"
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"
xmlns:local="clr-namespace:Demo"
mc:Ignorable="d" ShowInTaskbar="False"
Title="ParentWindow" Height="450" Width="800" WindowStyle="None" WindowChrome.ResizeGripDirection="None"
Background="Transparent" WindowState="Maximized" ResizeMode="NoResize">
<WindowChrome.WindowChrome>
<WindowChrome GlassFrameThickness="-1" CaptionHeight="0" UseAeroCaptionButtons="False" CornerRadius="0" NonClientFrameEdges="None" ResizeBorderThickness="0"/>
</WindowChrome.WindowChrome>
<Grid>
<Button HorizontalAlignment="Left" Width="200" Height="200" Click="ButtonBase_OnClick">击穿</Button>
</Grid>
</Window>
using System;
using System.Windows;
using System.Windows.Interop;
using Annotation.Utils;
namespace Demo
{
/// <summary>
/// ParentWindow.xaml 的交互逻辑
/// </summary>
public partial class ParentWindow : Window
{
private IntPtr _intPtr;
public ParentWindow()
{
InitializeComponent();
Loaded += ParentWindow_Loaded;
}
private void ParentWindow_Loaded(object sender, RoutedEventArgs e)
{
//注册当前的窗体为可触控的交互窗体
Register(this);
}
/// <summary>
/// 窗体的注册
/// </summary>
/// <param name="window"></param>
public void Register(Window window)
{
_intPtr = new WindowInteropHelper(window).Handle;
}
/// <summary>
/// 窗体击穿
/// </summary>
private void SetTransparentHitThrough()
{
if (_intPtr == IntPtr.Zero) throw new Exception("当前没有设置可触控的窗体");
User32.SetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE,
(IntPtr)(int)((long)User32.GetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE) | (long)User32.WS_EX_TRANSPARENT));
}
/// <summary>
/// 窗体不击穿
/// </summary>
private void SetTransparentNotHitThrough()
{
if (_intPtr == IntPtr.Zero) throw new Exception("当前没有设置可触控的窗体");
User32.SetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE, (IntPtr)(int)((long)User32.GetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE) & ~(long)User32.WS_EX_TRANSPARENT));
}
private bool _isHit = false;
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (!_isHit)
{
SetTransparentHitThrough();
_isHit = true;
}
else
{
SetTransparentNotHitThrough();
_isHit = true;
}
}
}
}
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
namespace Business
{
/// <summary>
/// 窗体的全局钩子
/// </summary>
public class WinHook
{
public event EventHandler UDiskAdded;
public event EventHandler UDiskRemoved;
public event EventHandler<MessageData> IpcMessageReceived;
public void InitHook()
{
HwndSource hwndSource = PresentationSource.FromVisual(Application.Current.MainWindow) as HwndSource;//窗口过程
hwndSource?.AddHook(WndProc);//挂钩
}
/// <summary>
/// 样式结构体
/// </summary>
private struct STYLESTRUCT
{
public int styleOld { get; set; }
public int styleNew { get; set; }
}
/// <summary>
/// 钩子函数
/// </summary>
/// <param name="hwnd"></param>
/// <param name="msg"></param>
/// <param name="wParam"></param>
/// <param name="lParam"></param>
/// <param name="handled"></param>
/// <returns></returns>
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
//想要让窗口透明穿透鼠标和触摸等,需要同时设置 WS_EX_LAYERED 和 WS_EX_TRANSPARENT 样式,
//确保窗口始终有 WS_EX_LAYERED 这个样式,并在开启穿透时设置 WS_EX_TRANSPARENT 样式
//但是WPF窗口在未设置 AllowsTransparency = true 时,会自动去掉 WS_EX_LAYERED 样式(在 HwndTarget 类中),
//如果设置了 AllowsTransparency = true 将使用WPF内置的低性能的透明实现,
//所以这里通过 Hook 的方式,在不使用WPF内置的透明实现的情况下,强行保证这个样式存在。
if (msg == (int)User32.WM.STYLECHANGING && (long)wParam == (long)User32.GetWindowLongFields.GWL_EXSTYLE)
{
var styleStruct = (STYLESTRUCT)Marshal.PtrToStructure(lParam, typeof(STYLESTRUCT));
styleStruct.styleNew |= (int)User32.WindowExStyles.WS_EX_LAYERED;
Marshal.StructureToPtr(styleStruct, lParam, false);
handled = true;
}
return hwnd;
}
}
}
namespace Business
{
/// <summary>
/// WMI实例信息
/// </summary>
public class WmiConst
{
public const int WM_DEVICECHANGE = 0x219;//U盘插入后,OS的底层会自动检测到,然后向应用程序发送“硬件设备状态改变“的消息
public const int DBT_DEVICEARRIVAL = 0x8000; //就是用来表示U盘可用的。一个设备或媒体已被插入一块,现在可用。
public const int DBT_CONFIGCHANGECANCELED = 0x0019; //要求更改当前的配置(或取消停靠码头)已被取消。
public const int DBT_CONFIGCHANGED = 0x0018; //当前的配置发生了变化,由于码头或取消固定。
public const int DBT_CUSTOMEVENT = 0x8006; //自定义的事件发生。 的Windows NT 4.0和Windows 95:此值不支持。
public const int DBT_DEVICEQUERYREMOVE = 0x8001; //审批要求删除一个设备或媒体作品。任何应用程序也不能否认这一要求,并取消删除。
public const int DBT_DEVICEQUERYREMOVEFAILED = 0x8002; //请求删除一个设备或媒体片已被取消。
public const int DBT_DEVICEREMOVECOMPLETE = 0x8004; //一个设备或媒体片已被删除。
public const int DBT_DEVICEREMOVEPENDING = 0x8003; //一个设备或媒体一块即将被删除。不能否认的。
public const int DBT_DEVICETYPESPECIFIC = 0x8005; //一个设备特定事件发生。
public const int DBT_DEVNODES_CHANGED = 0x0007; //一种设备已被添加到或从系统中删除。
public const int DBT_QUERYCHANGECONFIG = 0x0017; //许可是要求改变目前的配置(码头或取消固定)。
public const int DBT_USERDEFINED = 0xFFFF; //此消息的含义是用户定义的
public const uint GENERIC_READ = 0x80000000;
public const int GENERIC_WRITE = 0x40000000;
public const int FILE_SHARE_READ = 0x1;
public const int FILE_SHARE_WRITE = 0x2;
public const int IOCTL_STORAGE_EJECT_MEDIA = 0x2d4808;
public const int IPC_MESSAGE = 0x004A;//进程间消息
}
}
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Utils
{
public class User32
{
/// <summary>
/// 窗体的扩展样式
/// </summary>
[Flags]
public enum WindowExStyles
{
WS_EX_ACCEPTFILES = 0x10,
WS_EX_APPWINDOW = 0x40000,
WS_EX_CLIENTEDGE = 0x200,
WS_EX_COMPOSITED = 0x2000000,
WS_EX_CONTEXTHELP = 0x400,
WS_EX_CONTROLPARENT = 0x10000,
WS_EX_DLGMODALFRAME = 0x1,
WS_EX_LAYERED = 0x80000,
WS_EX_LAYOUTRTL = 0x400000,
WS_EX_LEFT = 0x0,
WS_EX_LEFTSCROLLBAR = 0x4000,
WS_EX_LTRREADING = 0x0,
WS_EX_MDICHILD = 0x40,
WS_EX_NOACTIVATE = 0x8000000,
WS_EX_NOINHERITLAYOUT = 0x100000,
WS_EX_NOPARENTNOTIFY = 0x4,
WS_EX_NOREDIRECTIONBITMAP = 0x200000,
WS_EX_OVERLAPPEDWINDOW = 0x300,
WS_EX_PALETTEWINDOW = 0x188,
WS_EX_RIGHT = 0x1000,
WS_EX_RIGHTSCROLLBAR = 0x0,
WS_EX_RTLREADING = 0x2000,
WS_EX_STATICEDGE = 0x20000,
WS_EX_TOOLWINDOW = 0x80,
WS_EX_TOPMOST = 0x8,
WS_EX_TRANSPARENT = 0x20,
WS_EX_WINDOWEDGE = 0x100
}
public enum WM
{
NULL = 0,
CREATE = 1,
DESTROY = 2,
MOVE = 3,
SIZE = 5,
ACTIVATE = 6,
SETFOCUS = 7,
KILLFOCUS = 8,
ENABLE = 10,
SETREDRAW = 11,
SETTEXT = 12,
GETTEXT = 13,
GETTEXTLENGTH = 14,
PAINT = 0xF,
CLOSE = 0x10,
QUERYENDSESSION = 17,
QUERYOPEN = 19,
ENDSESSION = 22,
QUIT = 18,
ERASEBKGND = 20,
SYSCOLORCHANGE = 21,
SHOWWINDOW = 24,
WININICHANGE = 26,
SETTINGCHANGE = 26,
DEVMODECHANGE = 27,
ACTIVATEAPP = 28,
FONTCHANGE = 29,
TIMECHANGE = 30,
CANCELMODE = 0x1F,
SETCURSOR = 0x20,
MOUSEACTIVATE = 33,
CHILDACTIVATE = 34,
QUEUESYNC = 35,
GETMINMAXINFO = 36,
PAINTICON = 38,
ICONERASEBKGND = 39,
NEXTDLGCTL = 40,
SPOOLERSTATUS = 42,
DRAWITEM = 43,
MEASUREITEM = 44,
DELETEITEM = 45,
VKEYTOITEM = 46,
CHARTOITEM = 47,
SETFONT = 48,
GETFONT = 49,
SETHOTKEY = 50,
GETHOTKEY = 51,
QUERYDRAGICON = 55,
COMPAREITEM = 57,
GETOBJECT = 61,
COMPACTING = 65,
COMMNOTIFY = 68,
WINDOWPOSCHANGING = 70,
WINDOWPOSCHANGED = 71,
POWER = 72,
COPYDATA = 74,
CANCELJOURNAL = 75,
NOTIFY = 78,
INPUTLANGCHANGEREQUEST = 80,
INPUTLANGCHANGE = 81,
TCARD = 82,
HELP = 83,
USERCHANGED = 84,
NOTIFYFORMAT = 85,
CONTEXTMENU = 123,
STYLECHANGING = 124,
STYLECHANGED = 125,
DISPLAYCHANGE = 126,
GETICON = 0x7F,
SETICON = 0x80,
NCCREATE = 129,
NCDESTROY = 130,
NCCALCSIZE = 131,
NCHITTEST = 132,
NCPAINT = 133,
NCACTIVATE = 134,
GETDLGCODE = 135,
SYNCPAINT = 136,
NCMOUSEMOVE = 160,
NCLBUTTONDOWN = 161,
NCLBUTTONUP = 162,
NCLBUTTONDBLCLK = 163,
NCRBUTTONDOWN = 164,
NCRBUTTONUP = 165,
NCRBUTTONDBLCLK = 166,
NCMBUTTONDOWN = 167,
NCMBUTTONUP = 168,
NCMBUTTONDBLCLK = 169,
NCXBUTTONDOWN = 171,
NCXBUTTONUP = 172,
NCXBUTTONDBLCLK = 173,
INPUT_DEVICE_CHANGE = 254,
INPUT = 0xFF,
KEYFIRST = 0x100,
KEYDOWN = 0x100,
KEYUP = 257,
CHAR = 258,
DEADCHAR = 259,
SYSKEYDOWN = 260,
SYSKEYUP = 261,
SYSCHAR = 262,
SYSDEADCHAR = 263,
UNICHAR = 265,
KEYLAST = 265,
IME_STARTCOMPOSITION = 269,
IME_ENDCOMPOSITION = 270,
IME_COMPOSITION = 271,
IME_KEYLAST = 271,
INITDIALOG = 272,
COMMAND = 273,
SYSCOMMAND = 274,
TIMER = 275,
HSCROLL = 276,
VSCROLL = 277,
INITMENU = 278,
INITMENUPOPUP = 279,
GESTURE = 281,
GESTURENOTIFY = 282,
MENUSELECT = 287,
MENUCHAR = 288,
ENTERIDLE = 289,
MENURBUTTONUP = 290,
MENUDRAG = 291,
MENUGETOBJECT = 292,
UNINITMENUPOPUP = 293,
MENUCOMMAND = 294,
CHANGEUISTATE = 295,
UPDATEUISTATE = 296,
QUERYUISTATE = 297,
CTLCOLORMSGBOX = 306,
CTLCOLOREDIT = 307,
CTLCOLORLISTBOX = 308,
CTLCOLORBTN = 309,
CTLCOLORDLG = 310,
CTLCOLORSCROLLBAR = 311,
CTLCOLORSTATIC = 312,
MOUSEFIRST = 0x200,
MOUSEMOVE = 0x200,
LBUTTONDOWN = 513,
LBUTTONUP = 514,
LBUTTONDBLCLK = 515,
RBUTTONDOWN = 516,
RBUTTONUP = 517,
RBUTTONDBLCLK = 518,
MBUTTONDOWN = 519,
MBUTTONUP = 520,
MBUTTONDBLCLK = 521,
MOUSEWHEEL = 522,
XBUTTONDOWN = 523,
XBUTTONUP = 524,
XBUTTONDBLCLK = 525,
MOUSEHWHEEL = 526,
MOUSELAST = 526,
PARENTNOTIFY = 528,
ENTERMENULOOP = 529,
EXITMENULOOP = 530,
NEXTMENU = 531,
SIZING = 532,
CAPTURECHANGED = 533,
MOVING = 534,
POWERBROADCAST = 536,
DEVICECHANGE = 537,
MDICREATE = 544,
MDIDESTROY = 545,
MDIACTIVATE = 546,
MDIRESTORE = 547,
MDINEXT = 548,
MDIMAXIMIZE = 549,
MDITILE = 550,
MDICASCADE = 551,
MDIICONARRANGE = 552,
MDIGETACTIVE = 553,
MDISETMENU = 560,
ENTERSIZEMOVE = 561,
EXITSIZEMOVE = 562,
DROPFILES = 563,
MDIREFRESHMENU = 564,
POINTERDEVICECHANGE = 568,
POINTERDEVICEINRANGE = 569,
POINTERDEVICEOUTOFRANGE = 570,
TOUCH = 576,
NCPOINTERUPDATE = 577,
NCPOINTERDOWN = 578,
NCPOINTERUP = 579,
POINTERUPDATE = 581,
POINTERDOWN = 582,
POINTERUP = 583,
POINTERENTER = 585,
POINTERLEAVE = 586,
POINTERACTIVATE = 587,
POINTERCAPTURECHANGED = 588,
TOUCHHITTESTING = 589,
POINTERWHEEL = 590,
POINTERHWHEEL = 591,
IME_SETCONTEXT = 641,
IME_NOTIFY = 642,
IME_CONTROL = 643,
IME_COMPOSITIONFULL = 644,
IME_SELECT = 645,
IME_CHAR = 646,
IME_REQUEST = 648,
IME_KEYDOWN = 656,
IME_KEYUP = 657,
MOUSEHOVER = 673,
MOUSELEAVE = 675,
NCMOUSEHOVER = 672,
NCMOUSELEAVE = 674,
WTSSESSION_CHANGE = 689,
TABLET_FIRST = 704,
TABLET_LAST = 735,
DPICHANGED = 736,
CUT = 768,
COPY = 769,
PASTE = 770,
CLEAR = 771,
UNDO = 772,
RENDERFORMAT = 773,
RENDERALLFORMATS = 774,
DESTROYCLIPBOARD = 775,
DRAWCLIPBOARD = 776,
PAINTCLIPBOARD = 777,
VSCROLLCLIPBOARD = 778,
SIZECLIPBOARD = 779,
ASKCBFORMATNAME = 780,
CHANGECBCHAIN = 781,
HSCROLLCLIPBOARD = 782,
QUERYNEWPALETTE = 783,
PALETTEISCHANGING = 784,
PALETTECHANGED = 785,
HOTKEY = 786,
PRINT = 791,
PRINTCLIENT = 792,
APPCOMMAND = 793,
THEMECHANGED = 794,
CLIPBOARDUPDATE = 797,
DWMCOMPOSITIONCHANGED = 798,
DWMNCRENDERINGCHANGED = 799,
DWMCOLORIZATIONCOLORCHANGED = 800,
DWMWINDOWMAXIMIZEDCHANGE = 801,
DWMSENDICONICTHUMBNAIL = 803,
DWMSENDICONICLIVEPREVIEWBITMAP = 806,
GETTITLEBARINFOEX = 831,
HANDHELDFIRST = 856,
HANDHELDLAST = 863,
AFXFIRST = 864,
AFXLAST = 895,
PENWINFIRST = 896,
PENWINLAST = 911,
APP = 0x8000,
USER = 0x400
}
public enum GetWindowLongFields
{
GWL_EXSTYLE = -20,
GWL_HINSTANCE = -6,
GWL_HWNDPARENT = -8,
GWL_ID = -12,
GWL_STYLE = -16,
GWL_USERDATA = -21,
GWL_WNDPROC = -4
}
/// <summary>
/// 未找到App的错误码
/// </summary>
public const uint AppNotFoundCode = 0x800401F5;
public const uint WS_EX_LAYERED = 0x80000;
public const int WS_EX_TRANSPARENT = 0x20;
public const int GWL_STYLE = (-16);
public const int GWL_EXSTYLE = -20;
public const int LWA_ALPHA = 0;
public const int SW_HIDE = 0;
public const int SW_NORMAL = 1;
public const int SW_MAXIMIZE = 3;
public const int SW_SHOWNOACTIVATE = 4;
public const int SW_SHOW = 5;
public const int SW_MINIMIZE = 6;
public const int SW_RESTORE = 9;
public const int SW_SHOWDEFAULT = 10;
[DllImport("user32.dll")]
public static extern int ShowWindow(int hwnd,int nCmdShow);
[DllImport("User32.dll")]
public static extern void SetWindowLong(IntPtr handle, int oldStyle, long newStyle);
/// <summary>
/// 桌面位置设置
/// </summary>
/// <param name="hwnd"></param>
/// <param name="hWndInsertAfter"></param>
/// <param name="X"></param>
/// <param name="Y"></param>
/// <param name="cx"></param>
/// <param name="cy"></param>
/// <param name="uFlags"></param>
/// <returns></returns>
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hwnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
public static extern int BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(IntPtr hwnd);
[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetForegroundWindow();
[DllImport("user32.dll", EntryPoint = "GetWindowLong")]
public static extern IntPtr GetWindowLong32(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
public static extern IntPtr GetWindowLong64(IntPtr hWnd, int nIndex);
[DllImport("user32.dll", EntryPoint = "SetWindowLong")]
public static extern IntPtr SetWindowLong32(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
public static extern IntPtr SetWindowLong64(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
public static IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
{
if (IntPtr.Size == 8)
return SetWindowLong64(hWnd, (int)nIndex, dwNewLong);
else
return SetWindowLong32(hWnd, (int)nIndex, dwNewLong);
}
[DllImport("user32.dll", EntryPoint = "SendMessage")]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
{
if (IntPtr.Size == 8)
return GetWindowLong64(hWnd, (int)nIndex);
else
return GetWindowLong32(hWnd, (int)nIndex);
}
[DllImport("user32", EntryPoint = "SetLayeredWindowAttributes")]
public static extern int SetLayeredWindowAttributes(
IntPtr hwnd,
int crKey,
int bAlpha,
int dwFlags
);
public const uint GwOwner = 4;
public delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern int GetWindowThreadProcessId(IntPtr hWnd, out IntPtr lpdwProcessId);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("User32.dll", CharSet = CharSet.Auto)]
public static extern bool IsWindowVisible(IntPtr hWnd);
public delegate bool EnumThreadDelegate(IntPtr hWnd, IntPtr lParam);
[DllImport("user32.dll")]
public static extern bool EnumThreadWindows(uint dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowTextW(IntPtr hWnd, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpString, int nMaxCount);
/// <summary>
/// 通过进程的Id获取当前的进程窗体的句柄
/// </summary>
/// <param name="processId"></param>
/// <returns></returns>
public static IntPtr GetProcessHandle(int processId)
{
IntPtr processPtr = IntPtr.Zero;
EnumWindows((hWnd, lParam) =>
{
IntPtr pid;
GetWindowThreadProcessId(hWnd, out pid);
if (pid == lParam && IsWindowVisible(hWnd) && GetParentHandle(hWnd)== hWnd )
{
processPtr = hWnd;
return true;
}
return true;
}, new IntPtr(processId));
return processPtr;
}
/// <summary>
/// 通过进程的Id集合获取当前的进程窗体的句柄
/// </summary>
/// <param name="processId"></param>
/// <returns></returns>
public static List<IntPtr> GetProcessHandles(int processId)
{
List<IntPtr> processPtrs = new List<IntPtr>();
EnumWindows((hWnd, lParam) =>
{
IntPtr pid;
GetWindowThreadProcessId(hWnd, out pid);
if (pid == lParam && IsWindowVisible(hWnd) && GetParentHandle(hWnd) == hWnd)
{
processPtrs.Add(hWnd);
return true;
}
return true;
}, new IntPtr(processId));
return processPtrs;
}
[DllImport("user32.dll")]
public static extern IntPtr GetParent(IntPtr hWnd);
private static IntPtr GetParentHandle(IntPtr currentAppHandle)
{
var next = GetParent(currentAppHandle);
var cur = currentAppHandle;
while (next != IntPtr.Zero)
{
cur = next;
next = GetParent(cur);
}
return cur;
}
/// <summary>
/// 获取窗体句柄
/// </summary>
/// <param name="hwnd"></param>
/// <param name="nIndex"></param>
/// <returns></returns>
[DllImport("user32.dll", EntryPoint = "GetWindowLongA", SetLastError = true)]
public static extern int GetWindowLong(IntPtr hwnd, int nIndex);
/// <summary>
/// 获取该进程匹配到的窗体Title的句柄
/// </summary>
/// <param name="process"></param>
/// <param name="windowTitle"></param>
/// <returns></returns>
public static IntPtr GetProcessHandle(Process process, string windowTitle)
{
var handle = IntPtr.Zero;
bool isBreak = false;
foreach (ProcessThread thread in process.Threads)
{
if (isBreak) break;
EnumThreadWindows((uint)thread.Id, (hWnd, lParam) =>
{
StringBuilder sb = new StringBuilder(256);
GetWindowTextW(hWnd, sb, sb.Capacity);
if (sb.ToString() == windowTitle)
{
handle = hWnd;
isBreak = true;
return false;
}
return true;
}, IntPtr.Zero);
}
return handle;
}
}
}
想要让窗口透明穿透鼠标和触摸等,需要同时设置 WS_EX_LAYERED 和 WS_EX_TRANSPARENT 样式,确保窗口始终有 WS_EX_LAYERED 这个样式,并在开启穿透时设置 WS_EX_TRANSPARENT 样式但是WPF窗口在未设置 AllowsTransparency = true 时,
会自动去掉 WS_EX_LAYERED 样式(在 HwndTarget 类中),如果设置了 AllowsTransparency = true 将使用WPF内置的低性能的透明实现,所以这里通过 Hook 的方式,在不使用WPF内置的透明实现的情况下,强行保证这个样式存在。
上面的方式虽然可以实现穿透效果,但是是整个窗体都穿透(点击控件都穿透了),所以会无法点击控件操作。上面的例子按钮点击成穿透状态下无法再点击。可以从设计层面解决该问题:有性能问题的控件窗体使用win32显示,
增加多一个窗体使用AllowsTransparency="True"实现穿透效果,没有性能问题的控件放在该窗体上(例如按钮等)。这样子就可以实现控件不穿透,透明的部分穿透的效果。
实现如下:
1. 增加多一个窗体
<Window x:Class="Demo.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"
xmlns:local="clr-namespace:Demo"
mc:Ignorable="d" AllowsTransparency="True" Background="Transparent" ResizeMode="NoResize"
Title="MainWindow" Height="450" Width="800" WindowState="Maximized" WindowStyle="None">
<Grid>
<Button HorizontalAlignment="Center" Width="200" Height="200" Click="ButtonBase_OnClick">击穿</Button>
</Grid>
</Window>
using System.Windows;
namespace Demo
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private bool _isHit = false;
private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
if (Application.Current.MainWindow is ParentWindow parentWindow)
{
if (!_isHit)
{
_isHit = true;
}
else
{
_isHit = false;
}
parentWindow.TransparentHitThrough(_isHit);
}
}
}
}
2. 原先的窗体改成
using System;
using System.Windows;
using System.Windows.Interop;
using Annotation.Utils;
namespace Demo
{
/// <summary>
/// ParentWindow.xaml 的交互逻辑
/// </summary>
public partial class ParentWindow : Window
{
private MainWindow _mainWindow;
public ParentWindow()
{
InitializeComponent();
_mainWindow = new MainWindow();
Loaded += ParentWindow_Loaded;
IsVisibleChanged += MainWindow_IsVisibleChanged;
}
/// <summary>
/// 窗体击穿
/// </summary>
private void SetTransparentHitThrough()
{
if (_intPtr == IntPtr.Zero) throw new Exception("当前没有设置可触控的窗体");
User32.SetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE,
(IntPtr)(int)((long)User32.GetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE) | (long)User32.WS_EX_TRANSPARENT));
}
/// <summary>
/// 窗体不击穿
/// </summary>
private void SetTransparentNotHitThrough()
{
if (_intPtr == IntPtr.Zero) throw new Exception("当前没有设置可触控的窗体");
User32.SetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE, (IntPtr)(int)((long)User32.GetWindowLongPtr(_intPtr, User32.GWL_EXSTYLE) & ~(long)User32.WS_EX_TRANSPARENT));
}
public void TransparentHitThrough(bool isHit)
{
if (isHit)
{
SetTransparentHitThrough();
}
else
{
SetTransparentNotHitThrough();
}
}
private IntPtr _intPtr;
private void MainWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if ((bool)e.NewValue)
{
//注册当前的窗体为可触控的交互窗体,Owner为父窗体
_intPtr = new WindowInteropHelper(this).Handle;
}
}
private void ParentWindow_Loaded(object sender, RoutedEventArgs e)
{
_mainWindow.Owner = this;
_mainWindow.Show();
}
}
}