我們也許會有一些奇怪的需求,比如說禁止一個外部程序的窗口大小更改。
如果我們沒法修改外部程序的代碼,那要怎么做呢?
當然,我們可以通過DLL注入目標程序的方式去Hook或registry一個事件來檢測,但這也太麻煩了吧。
如果想做非侵入式的,那就需要用到Windows下的系統函數去完成工作。
查來查去,最好用的是MoveWindow函數
1 MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint)
可以看到,這個函數要求傳入一個IntPtr類型的句柄、一組int類型的坐標、以及int類型的窗口的寬度和高度和最后的bool類型的是否刷新顯示。
句柄大家應該都知道是什么,相當於是ID身份證一樣的存在,坐標就是指你把窗口移動到屏幕上的那個坐標。高寬不必說,這就是我們要改的。至於刷新顯示我們也無需過多理解,一個性能問題罷了。
首先我們要獲取坐標,先寫一個窗口枚舉器
/*窗口句柄枚舉器*/ public static class WindowsEnumerator { private delegate bool EnumWindowsProc(IntPtr windowHandle, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] private static extern bool EnumWindows(EnumWindowsProc callback, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] private static extern bool EnumChildWindows(IntPtr hWndStart, EnumWindowsProc callback, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); [DllImport("user32.dll", SetLastError = true)] static extern int GetWindowTextLength(IntPtr hWnd); private static List<IntPtr> handles = new List<IntPtr>(); private static string targetName; public static List<IntPtr> GetWindowHandles(string target) { targetName = target; EnumWindows(EnumWindowsCallback, IntPtr.Zero); return handles; } private static bool EnumWindowsCallback(IntPtr HWND, IntPtr includeChildren) { StringBuilder name = new StringBuilder(GetWindowTextLength(HWND) + 1); GetWindowText(HWND, name, name.Capacity); if (name.ToString() == targetName) handles.Add(HWND); EnumChildWindows(HWND, EnumWindowsCallback, IntPtr.Zero); return true; } }
調用方法是
WindowsEnumerator.GetWindowHandles("窗口名字")
然后這個方法返回的是一個數組,我們需要用到foreach去遍歷里面的東西
foreach (var item in WindowsEnumerator.GetWindowHandles("窗口名字"))
這個item的數據類型是IntPtr,也就是moveWindow函數所需的第一個參數hWnd——句柄
我們接着看第二組參數,需要傳入X和Y,也就是窗口移動到屏幕上的位置。
如果把這個寫死,你的窗口就無法移動了,只會固定在一個地方。
所以我們需要動態的去獲取窗口當前位置,然后把位置傳入給moveWindow方法所需的X和Y參數。
我們需要用到GetWindowRect函數,需要傳入hWnd和RECT
[DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect); [StructLayout(LayoutKind.Sequential)] public struct RECT //定位當前窗口位置 { public int x; //最左坐標 public int y; //最上坐標 }
然后我們需要在執行MoveWindow函數之前執行getWindowRect函數,放到上面寫的foreach里就好了。
記得new一個RECT結構體出來。
Rect rect = new Rect();
GetWindowRect(item, ref rect);
因為我們禁止窗口修改不是一次性的,需要循環去檢測,我們把檢測方法寫個死循環While(true)就好了,然后開辟新的線程去執行這個死循環。
因為C# 是有垃圾回收策略的,我們無需擔心性能開支過大所造成的的問題,畢竟就是寫着玩,學習學習(難道這有人有這個需求嗎不會把不會把?)
總結一下就是如下代碼:
RECT rect = new RECT(); //禁止修改窗口 public void Pck(){ while (true){ foreach (var item in WindowsEnumerator.GetWindowHandles("Minecraft 1.12.2")){ GetWindowRect(item, ref rect);//獲取窗口在屏幕上的坐標 MoveWindow(item, rect.x, rect.y, 1024, 768, true); //鎖定為1024*768分辨率 } Thread.Sleep(2000); //2秒檢測一次 } }
然后調用的時候直接:
Thread thread = new Thread(new ThreadStart(Pck)); thread.Start();
隨筆到此結束