WPF 使用Background="Transparent"+AllowsTransparency="True"实现穿透效果,窗体多次渲染会有性能问题,可以使用Win32设置窗体穿透,并从设计层面避免性能问题
如果在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(); } } }