如何在VS扩展中通过DTE/扩展性获取Visual Studio IDE的屏幕位置与尺寸?
在VS扩展中获取IDE主窗口位置并让WinForms浮动工具栏跟随
核心思路
要获取VS IDE整个框架的位置和尺寸,关键是拿到主窗口的句柄(而非内部面板句柄),再通过Win32 API或DTE事件实现监听与位置同步。
步骤1:获取VS主窗口句柄
VS的DTE对象提供MainWindow.HWnd属性,这就是IDE主框架的窗口句柄:
EnvDTE.DTE dte = (EnvDTE.DTE)Package.GetGlobalService(typeof(EnvDTE.DTE)); IntPtr mainWindowHandle = new IntPtr(dte.MainWindow.HWnd);
步骤2:获取主窗口的位置与尺寸
通过Win32 API GetWindowRect获取主窗口的屏幕坐标和尺寸,先定义必要的结构体和API导入:
using System.Runtime.InteropServices; using System.Drawing; [StructLayout(LayoutKind.Sequential)] public struct RECT { public int Left; public int Top; public int Right; public int Bottom; } [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); // 获取主窗口信息 if (GetWindowRect(mainWindowHandle, out RECT mainWindowRect)) { int windowWidth = mainWindowRect.Right - mainWindowRect.Left; int windowHeight = mainWindowRect.Bottom - mainWindowRect.Top; Point windowLocation = new Point(mainWindowRect.Left, mainWindowRect.Top); // 此处已获取主窗口的位置和尺寸 }
步骤3:监听主窗口的移动与尺寸变化
方法A:使用DTE内置事件
利用DTE的WindowEvents监听主窗口的移动和resize事件,仅处理主窗口的变化:
private EnvDTE.WindowEvents _windowEvents; // 扩展初始化时订阅事件 public void Initialize() { _windowEvents = dte.Events.WindowEvents; _windowEvents.WindowMoved += OnMainWindowMoved; _windowEvents.WindowResized += OnMainWindowResized; } private void OnMainWindowMoved(EnvDTE.Window window) { if (window.Equals(dte.MainWindow)) { UpdateFloatingToolbarPosition(); } } private void OnMainWindowResized(EnvDTE.Window window) { if (window.Equals(dte.MainWindow)) { UpdateFloatingToolbarPosition(); } } // 更新浮动工具栏位置的逻辑 private void UpdateFloatingToolbarPosition() { IntPtr mainWindowHandle = new IntPtr(dte.MainWindow.HWnd); if (GetWindowRect(mainWindowHandle, out RECT rect)) { // 示例:将工具栏放在主窗口右上角外侧,偏移10px int toolbarX = rect.Right + 10; int toolbarY = rect.Top + 10; yourFloatingToolBarForm.Location = new Point(toolbarX, toolbarY); } }
方法B:Win32消息钩子(更实时)
如果DTE事件响应不够及时,可通过Win32窗口钩子监听WM_MOVE和WM_SIZE消息,注意钩子的安装与卸载:
private IntPtr _hookHandle; private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam); private HookProc _hookDelegate; [DllImport("user32.dll", SetLastError = true)] private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId); [DllImport("user32.dll")] private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam); [DllImport("user32.dll", SetLastError = true)] private static extern bool UnhookWindowsHookEx(IntPtr hhk); [DllImport("kernel32.dll")] private static extern IntPtr GetModuleHandle(string lpModuleName); private const int WH_CALLWNDPROC = 4; private const int WM_MOVE = 0x0003; private const int WM_SIZE = 0x0005; // 安装钩子 public void InstallWindowHook() { _hookDelegate = HookCallback; using (Process curProcess = Process.GetCurrentProcess()) using (ProcessModule curModule = curProcess.MainModule) { _hookHandle = SetWindowsHookEx(WH_CALLWNDPROC, _hookDelegate, GetModuleHandle(curModule.ModuleName), (uint)curProcess.Threads[0].Id); } } // 钩子回调 private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0) { var msg = Marshal.PtrToStructure<CWPSTRUCT>(lParam); if (msg.hwnd == mainWindowHandle && (msg.message == WM_MOVE || msg.message == WM_SIZE)) { UpdateFloatingToolbarPosition(); } } return CallNextHookEx(_hookHandle, nCode, wParam, lParam); } [StructLayout(LayoutKind.Sequential)] public struct CWPSTRUCT { public IntPtr lParam; public IntPtr wParam; public uint message; public IntPtr hwnd; } // 卸载钩子(扩展关闭时调用) public void UninstallWindowHook() { if (_hookHandle != IntPtr.Zero) { UnhookWindowsHookEx(_hookHandle); _hookHandle = IntPtr.Zero; } }
注意事项
- 扩展关闭时务必取消DTE事件订阅或卸载Win32钩子,防止内存泄漏。
- 可将WinForms窗口设置为
TopMost = true,确保工具栏始终在VS窗口上方(按需调整)。 - 测试时覆盖VS窗口移动、resize、多显示器切换等场景,确保位置同步准确。
内容的提问来源于stack exchange,提问作者Sam Marrocco
相关产品推荐
相关产品推荐

