我們也許會有一些奇怪的需求,比如說禁止一個外部程式的視窗大小更改。
如果我們沒法修改外部程式的程式碼,那要怎麼做呢?
當然,我們可以通過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();
隨筆到此結束