WPF 使用Background="Transparent"+AllowsTransparency="True"实现穿透效果,窗体多次渲染会有性能问题,可以使用Win32设置窗体穿透,并从设计层面避免性能问题

发布时间 2023-05-24 17:19:01作者: log9527

如果在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;
using System.Windows;
using System.Windows.Interop;
using Annotation.Utils;

namespace Demo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        private IntPtr _intPtr;
        public MainWindow()
        {
            InitializeComponent();

            IsVisibleChanged += MainWindow_IsVisibleChanged;
        }

        private void MainWindow_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            //注册当前的窗体为可触控的交互窗体,Owner为父窗体
            Register(Owner);
        }

        /// <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;
            }
        }
    }
}

2. 原先的窗体改成

using System.Windows;namespace Demo
{
    /// <summary>
    /// ParentWindow.xaml 的交互逻辑
    /// </summary>
    public partial class ParentWindow : Window
    {
        private MainWindow _mainWindow;
        public ParentWindow()
        {
            InitializeComponent();

            _mainWindow = new MainWindow();
            Loaded += ParentWindow_Loaded;
        }

        private void ParentWindow_Loaded(object sender, RoutedEventArgs e)
        {
            _mainWindow.Owner = this;
            _mainWindow.Show();
        }
    }
}