C# 強行鎖定 第三方 外部 應用程式窗體視窗的解析度尺寸大小 禁止滑鼠拖拽改變視窗大小

田所浩託TrosuoTro發表於2021-06-10

我們也許會有一些奇怪的需求,比如說禁止一個外部程式的視窗大小更改。

如果我們沒法修改外部程式的程式碼,那要怎麼做呢?

當然,我們可以通過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();

 

 

隨筆到此結束

 

相關文章