【问题标题】:Move window when external application's window moves当外部应用程序的窗口移动时移动窗口
【发布时间】:2018-07-23 20:28:50
【问题描述】:

我有一个始终位于顶部的应用程序(基本上是一个状态显示),我想跟踪另一个程序,并且始终位于最小化按钮的左侧。

我可以使用以下代码获得代表“目标”进程的Rect,然后我可以将其与偏移量配对以生成叠加层的初始位置。

获取 HWnd IntPtr:

private IntPtr HWnd = Process.GetProcessesByName("targetapplication")[0].MainWindowHandle; 

user32.dll声明函数:

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);

以及相应的struct

[StructLayout(LayoutKind.Sequential)]
private struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

然后按需调用。

但是,我想避免不断地轮询这个值,所以我想挂接到目标应用程序,并在目标窗口移动时做出响应。

查看user32.dll 文档,我看到的唯一方法是使用SetWindowsHookEx()。但是,我不完全确定如何从这里拦截事件。

我相信目标应用程序是基于WinForms 构建的,但我不能确定。因此,让我响应目标的 Move 事件或直接响应某些 Windows 消息的解决方案都会很有用。

关于如何进行的任何想法?

【问题讨论】:

  • 考虑使用SetWinEventHook() 而不是SetWindowsHookEx()
  • @Remy Lebeau 我想知道你是否有机会在这样的环境中测试SetWinEventHook()。您可能必须注册多个(或一系列)事件才能“跟随”一个窗口,并且,AFAIK,SetWinEventHook() 有很多开销。这会导致同步双窗运动出现明显的滞后吗?
  • 第一个null表示hook proc不包含在.dll中,应该是IntPtr.ZerotargetPID 是 process.Id (我想你是这个意思)。第二个null我认为最好用GetWindowThreadProcessId(process.MainWindowHandle, IntPtr.Zero)代替,否则你会得到当前桌面上所有线程的事件。最后一个 null 表示定义挂钩位置的标志。由于您有 IntPtr.Zero 作为句柄,因此这些标志应该是 WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD
  • 嗯,很高兴你做到了。如果你愿意,我可以发布我用SetWinEventHook() 挂钩窗口的方法,以进行比较。但如果你想发布一个自我回答,没关系。出于同样的原因,也许我会在之后发布我的。
  • 那好吧,我去准备点东西。我认为这是一件你不经常看到的有趣的事情。

标签: c# winforms winapi pinvoke user32


【解决方案1】:

一种将 Windows 窗体连接到另一个进程(在本例中为记事本)并跟随进程主窗口的移动的方法,以创建一种可以与进程交互的工具栏,使用 SetWinEventHook()


编辑
GetWindowRect() 替换为DwmGetWindowAttribute(),将DWMWA_EXTENDED_FRAME_BOUNDS 作为DWMWINDOWATTRIBUTE,仍然返回RECT。这是因为GetWindowRect() 不是 DpiAware,它可能会在某些情况下返回虚拟化度量,此外它还包括特定配置中的 Window 投影。

在此处了解有关 DpiAwarenes、屏幕布局和 VirtualScreen 的更多信息:
Using SetWindowPos with multiple monitors

还重构了 Hook 辅助类和 NativeMethods 声明。


▶ 要移动到前台工具窗口,如动画中一样,当被钩住的窗口变为前台窗口时,还要钩住 EVENT_SYSTEM_FOREGROUND 事件:当前台出现时,您会收到通知窗口发生变化,然后与你要挂钩的 Window 的句柄进行比较。


结果的直观表示:

Form类初始化过程:

using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public partial class Form1 : Form
{
    private IntPtr notepadhWnd;
    private IntPtr hWinEventHook;
    private Process targetProc = null;

    protected Hook.WinEventDelegate WinEventDelegate;
    static GCHandle GCSafetyHandle;

    public Form1()
    {
        InitializeComponent();
        WinEventDelegate = new Hook.WinEventDelegate(WinEventCallback);
        GCSafetyHandle = GCHandle.Alloc(WinEventDelegate);

        targetProc = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);

        try {
            if (targetProc != null) {
                notepadhWnd = targetProc.MainWindowHandle;

                if (notepadhWnd != IntPtr.Zero) {
                    uint targetThreadId = Hook.GetWindowThread(notepadhWnd);

                    hWinEventHook = Hook.WinEventHookOne(
                        NativeMethods.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE, 
                        WinEventDelegate, (uint)targetProc.Id, targetThreadId);

                    var rect = Hook.GetWindowRectangle(notepadhWnd);
                    // Of course, set the Form.StartPosition to Manual
                    Location = new Point(rect.Right, rect.Top);
                }
            }
        }
        catch (Exception ex) {
            // Log and message or
            throw;
        }
    }

    protected void WinEventCallback(
        IntPtr hWinEventHook, 
        NativeMethods.SWEH_Events eventType, 
        IntPtr hWnd, 
        NativeMethods.SWEH_ObjectId idObject, 
        long idChild, uint dwEventThread, uint dwmsEventTime)
    {
        if (hWnd == notepadhWnd && 
            eventType == NativeMethods.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE && 
            idObject == (NativeMethods.SWEH_ObjectId)NativeMethods.SWEH_CHILDID_SELF) 
        {
            var rect = Hook.GetWindowRectangle(hWnd);
            Location = new Point(rect.Right, rect.Top);
        }
    }

    protected override void OnFormClosing(FormClosingEventArgs e)
    {
        if (GCSafetyHandle.IsAllocated) {
            GCSafetyHandle.Free();
        }
        Hook.WinEventUnhook(hWinEventHook);
        base.OnFormClosing(e);
    }

    protected override void OnShown(EventArgs e)
    {
        if (targetProc == null) {
            this.Hide();
            MessageBox.Show("Notepad not found!", "Target Missing", MessageBoxButtons.OK, MessageBoxIcon.Hand);
            this.Close();
        }
        else {
            Size = new Size(50, 140);
            targetProc.Dispose();
        }
        base.OnShown(e);
    }

用于引用 Windows API 方法的支持类:

using System.Runtime.InteropServices;
using System.Security.Permissions;

public class Hook
{
    public delegate void WinEventDelegate(
        IntPtr hWinEventHook,
        NativeMethods.SWEH_Events eventType,
        IntPtr hwnd,
        NativeMethods.SWEH_ObjectId idObject,
        long idChild,
        uint dwEventThread,
        uint dwmsEventTime
    );

    public static IntPtr WinEventHookRange(
        NativeMethods.SWEH_Events eventFrom, NativeMethods.SWEH_Events eventTo,
        WinEventDelegate eventDelegate,
        uint idProcess, uint idThread)
    {
        new UIPermission(UIPermissionWindow.AllWindows).Demand();
        return NativeMethods.SetWinEventHook(
            eventFrom, eventTo,
            IntPtr.Zero, eventDelegate,
            idProcess, idThread,
            NativeMethods.WinEventHookInternalFlags);
    }

    public static IntPtr WinEventHookOne(
        NativeMethods.SWEH_Events eventId,
        WinEventDelegate eventDelegate,
        uint idProcess,
        uint idThread)
    {
        new UIPermission(UIPermissionWindow.AllWindows).Demand();
        return NativeMethods.SetWinEventHook(
            eventId, eventId,
            IntPtr.Zero, eventDelegate,
            idProcess, idThread,
            NativeMethods.WinEventHookInternalFlags);
    }

    public static bool WinEventUnhook(IntPtr hWinEventHook) => 
        NativeMethods.UnhookWinEvent(hWinEventHook);

    public static uint GetWindowThread(IntPtr hWnd)
    {
        new UIPermission(UIPermissionWindow.AllWindows).Demand();
        return NativeMethods.GetWindowThreadProcessId(hWnd, IntPtr.Zero);
    }

    public static NativeMethods.RECT GetWindowRectangle(IntPtr hWnd)
    {
        NativeMethods.DwmGetWindowAttribute(hWnd, 
            NativeMethods.DWMWINDOWATTRIBUTE.DWMWA_EXTENDED_FRAME_BOUNDS, 
            out NativeMethods.RECT rect, Marshal.SizeOf<NativeMethods.RECT>());
        return rect;
    }
}

public static class NativeMethods
{
    [StructLayout(LayoutKind.Sequential)]
    public struct RECT
    {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }

    public static long SWEH_CHILDID_SELF = 0;

    //SetWinEventHook() flags
    public enum SWEH_dwFlags : uint
    {
        WINEVENT_OUTOFCONTEXT = 0x0000,     // Events are ASYNC
        WINEVENT_SKIPOWNTHREAD = 0x0001,    // Don't call back for events on installer's thread
        WINEVENT_SKIPOWNPROCESS = 0x0002,   // Don't call back for events on installer's process
        WINEVENT_INCONTEXT = 0x0004         // Events are SYNC, this causes your dll to be injected into every process
    }

    //SetWinEventHook() events
    public enum SWEH_Events : uint
    {
        EVENT_MIN = 0x00000001,
        EVENT_MAX = 0x7FFFFFFF,
        EVENT_SYSTEM_SOUND = 0x0001,
        EVENT_SYSTEM_ALERT = 0x0002,
        EVENT_SYSTEM_FOREGROUND = 0x0003,
        EVENT_SYSTEM_MENUSTART = 0x0004,
        EVENT_SYSTEM_MENUEND = 0x0005,
        EVENT_SYSTEM_MENUPOPUPSTART = 0x0006,
        EVENT_SYSTEM_MENUPOPUPEND = 0x0007,
        EVENT_SYSTEM_CAPTURESTART = 0x0008,
        EVENT_SYSTEM_CAPTUREEND = 0x0009,
        EVENT_SYSTEM_MOVESIZESTART = 0x000A,
        EVENT_SYSTEM_MOVESIZEEND = 0x000B,
        EVENT_SYSTEM_CONTEXTHELPSTART = 0x000C,
        EVENT_SYSTEM_CONTEXTHELPEND = 0x000D,
        EVENT_SYSTEM_DRAGDROPSTART = 0x000E,
        EVENT_SYSTEM_DRAGDROPEND = 0x000F,
        EVENT_SYSTEM_DIALOGSTART = 0x0010,
        EVENT_SYSTEM_DIALOGEND = 0x0011,
        EVENT_SYSTEM_SCROLLINGSTART = 0x0012,
        EVENT_SYSTEM_SCROLLINGEND = 0x0013,
        EVENT_SYSTEM_SWITCHSTART = 0x0014,
        EVENT_SYSTEM_SWITCHEND = 0x0015,
        EVENT_SYSTEM_MINIMIZESTART = 0x0016,
        EVENT_SYSTEM_MINIMIZEEND = 0x0017,
        EVENT_SYSTEM_DESKTOPSWITCH = 0x0020,
        EVENT_SYSTEM_END = 0x00FF,
        EVENT_OEM_DEFINED_START = 0x0101,
        EVENT_OEM_DEFINED_END = 0x01FF,
        EVENT_UIA_EVENTID_START = 0x4E00,
        EVENT_UIA_EVENTID_END = 0x4EFF,
        EVENT_UIA_PROPID_START = 0x7500,
        EVENT_UIA_PROPID_END = 0x75FF,
        EVENT_CONSOLE_CARET = 0x4001,
        EVENT_CONSOLE_UPDATE_REGION = 0x4002,
        EVENT_CONSOLE_UPDATE_SIMPLE = 0x4003,
        EVENT_CONSOLE_UPDATE_SCROLL = 0x4004,
        EVENT_CONSOLE_LAYOUT = 0x4005,
        EVENT_CONSOLE_START_APPLICATION = 0x4006,
        EVENT_CONSOLE_END_APPLICATION = 0x4007,
        EVENT_CONSOLE_END = 0x40FF,
        EVENT_OBJECT_CREATE = 0x8000,               // hwnd ID idChild is created item
        EVENT_OBJECT_DESTROY = 0x8001,              // hwnd ID idChild is destroyed item
        EVENT_OBJECT_SHOW = 0x8002,                 // hwnd ID idChild is shown item
        EVENT_OBJECT_HIDE = 0x8003,                 // hwnd ID idChild is hidden item
        EVENT_OBJECT_REORDER = 0x8004,              // hwnd ID idChild is parent of zordering children
        EVENT_OBJECT_FOCUS = 0x8005,                // hwnd ID idChild is focused item
        EVENT_OBJECT_SELECTION = 0x8006,            // hwnd ID idChild is selected item (if only one), or idChild is OBJID_WINDOW if complex
        EVENT_OBJECT_SELECTIONADD = 0x8007,         // hwnd ID idChild is item added
        EVENT_OBJECT_SELECTIONREMOVE = 0x8008,      // hwnd ID idChild is item removed
        EVENT_OBJECT_SELECTIONWITHIN = 0x8009,      // hwnd ID idChild is parent of changed selected items
        EVENT_OBJECT_STATECHANGE = 0x800A,          // hwnd ID idChild is item w/ state change
        EVENT_OBJECT_LOCATIONCHANGE = 0x800B,       // hwnd ID idChild is moved/sized item
        EVENT_OBJECT_NAMECHANGE = 0x800C,           // hwnd ID idChild is item w/ name change
        EVENT_OBJECT_DESCRIPTIONCHANGE = 0x800D,    // hwnd ID idChild is item w/ desc change
        EVENT_OBJECT_VALUECHANGE = 0x800E,          // hwnd ID idChild is item w/ value change
        EVENT_OBJECT_PARENTCHANGE = 0x800F,         // hwnd ID idChild is item w/ new parent
        EVENT_OBJECT_HELPCHANGE = 0x8010,           // hwnd ID idChild is item w/ help change
        EVENT_OBJECT_DEFACTIONCHANGE = 0x8011,      // hwnd ID idChild is item w/ def action change
        EVENT_OBJECT_ACCELERATORCHANGE = 0x8012,    // hwnd ID idChild is item w/ keybd accel change
        EVENT_OBJECT_INVOKED = 0x8013,              // hwnd ID idChild is item invoked
        EVENT_OBJECT_TEXTSELECTIONCHANGED = 0x8014, // hwnd ID idChild is item w? test selection change
        EVENT_OBJECT_CONTENTSCROLLED = 0x8015,
        EVENT_SYSTEM_ARRANGMENTPREVIEW = 0x8016,
        EVENT_OBJECT_END = 0x80FF,
        EVENT_AIA_START = 0xA000,
        EVENT_AIA_END = 0xAFFF
    }

    //SetWinEventHook() Object Ids
    public enum SWEH_ObjectId : long
    {
        OBJID_WINDOW = 0x00000000,
        OBJID_SYSMENU = 0xFFFFFFFF,
        OBJID_TITLEBAR = 0xFFFFFFFE,
        OBJID_MENU = 0xFFFFFFFD,
        OBJID_CLIENT = 0xFFFFFFFC,
        OBJID_VSCROLL = 0xFFFFFFFB,
        OBJID_HSCROLL = 0xFFFFFFFA,
        OBJID_SIZEGRIP = 0xFFFFFFF9,
        OBJID_CARET = 0xFFFFFFF8,
        OBJID_CURSOR = 0xFFFFFFF7,
        OBJID_ALERT = 0xFFFFFFF6,
        OBJID_SOUND = 0xFFFFFFF5,
        OBJID_QUERYCLASSNAMEIDX = 0xFFFFFFF4,
        OBJID_NATIVEOM = 0xFFFFFFF0
    }

    public enum DWMWINDOWATTRIBUTE : uint
    {
        DWMWA_NCRENDERING_ENABLED = 1,      // [get] Is non-client rendering enabled/disabled
        DWMWA_NCRENDERING_POLICY,           // [set] DWMNCRENDERINGPOLICY - Non-client rendering policy - Enable or disable non-client rendering
        DWMWA_TRANSITIONS_FORCEDISABLED,    // [set] Potentially enable/forcibly disable transitions
        DWMWA_ALLOW_NCPAINT,                // [set] Allow contents rendered In the non-client area To be visible On the DWM-drawn frame.
        DWMWA_CAPTION_BUTTON_BOUNDS,        // [get] Bounds Of the caption button area In window-relative space.
        DWMWA_NONCLIENT_RTL_LAYOUT,         // [set] Is non-client content RTL mirrored
        DWMWA_FORCE_ICONIC_REPRESENTATION,  // [set] Force this window To display iconic thumbnails.
        DWMWA_FLIP3D_POLICY,                // [set] Designates how Flip3D will treat the window.
        DWMWA_EXTENDED_FRAME_BOUNDS,        // [get] Gets the extended frame bounds rectangle In screen space
        DWMWA_HAS_ICONIC_BITMAP,            // [set] Indicates an available bitmap When there Is no better thumbnail representation.
        DWMWA_DISALLOW_PEEK,                // [set] Don't invoke Peek on the window.
        DWMWA_EXCLUDED_FROM_PEEK,           // [set] LivePreview exclusion information
        DWMWA_CLOAK,                        // [set] Cloak Or uncloak the window
        DWMWA_CLOAKED,                      // [get] Gets the cloaked state Of the window. Returns a DWMCLOACKEDREASON object
        DWMWA_FREEZE_REPRESENTATION,        // [set] BOOL, Force this window To freeze the thumbnail without live update
        PlaceHolder1,
        PlaceHolder2,
        PlaceHolder3,
        DWMWA_ACCENTPOLICY = 19
    }

    public static SWEH_dwFlags WinEventHookInternalFlags =
        SWEH_dwFlags.WINEVENT_OUTOFCONTEXT |
        SWEH_dwFlags.WINEVENT_SKIPOWNPROCESS |
        SWEH_dwFlags.WINEVENT_SKIPOWNTHREAD;

    [DllImport("dwmapi.dll", SetLastError = true)]
    internal static extern int DwmGetWindowAttribute(IntPtr hwnd, DWMWINDOWATTRIBUTE dwAttribute, out RECT pvAttribute, int cbAttribute);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    [DllImport("user32.dll")]
    internal static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr voidProcessId);

    [DllImport("user32.dll", SetLastError = false)]
    internal static extern IntPtr SetWinEventHook(
        SWEH_Events eventMin,
        SWEH_Events eventMax,
        IntPtr hmodWinEventProc,
        Hook.WinEventDelegate lpfnWinEventProc,
        uint idProcess, uint idThread,
        SWEH_dwFlags dwFlags);

    [DllImport("user32.dll", SetLastError = false)]
    internal static extern bool UnhookWinEvent(IntPtr hWinEventHook);
}

【讨论】:

  • 从 gif 中我可以看到覆盖表单会随着所有者窗口的移动而瞬间移动。这令人印象深刻,我无法在我的项目中复制它;运动总是有轻微的滞后。我几乎要放弃并接受滞后。我将尝试对此提出一个连贯的问题。
  • @The Most Curious Thing 您是否使用了此处显示的代码(我的意思是完全正确)?请注意,该应用程序需要是 DpiAware(请参阅此处的注释:Using SetWindowPos with multiple monitors),并且 this => idObject == (Hook.SWEH_ObjectId)Hook.SWEH_CHILDID_SELF) 也需要在那里,否则您的挂钩表单会分心(本身:)。不应实施其他调整大小/移动事件(连续触发)(因为,不会分心)。
  • 感谢您的回复!您的代码确实对我有用,我目前正在“向上”工作以识别我的问题。我很欣赏这些值得深思的东西。
  • 对于后人来说,这是与我的渲染同步间隔完全无关的问题。
【解决方案2】:

感谢@Jimi 在这里提供的帮助。以下方法有效。

首先,存储一个对目标进程的引用:

Process _target = Process.GetProcessesByName("target")[0];

然后获取主窗口的句柄:

IntPtr _tagetHWnd = _target.MainWindowHandle;

然后初始化钩子:

SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, TargetMoved, (uint)_foxview.Id,
            GetWindowThreadProcessId(_foxview.MainWindowHandle, IntPtr.Zero), WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);

SetWinEventHook 是这样声明的:

[DllImport("user32.dll")]
private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

而涉及的常量是:

private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
private const int HT_CAPTION = 0x2;
private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
private const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
private const int WM_NCLBUTTONDOWN = 0xA1;

然后在我的TargetMoved 方法中,我检查新窗口位置并移动我的覆盖。

private void TargetMoved(IntPtr hWinEventHook, uint eventType, IntPtr lParam, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
{
    Rect newLocation = new Rect();
    GetWindowRect(_foxViewHWnd, ref newLocation);
    Location = new Point(newLocation.Right - (250 + _currentUser.Length * 7), newLocation.Top + 5);
}

GetWindowRect() 定义为:

[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetWindowRect(IntPtr hWnd, ref Rect lpRect);

Rect 定义为:

[StructLayout(LayoutKind.Sequential)]
private struct Rect
{
    public readonly int Left;
    public readonly int Top;
    public readonly int Right;
    public readonly int Bottom;
}

所以当你把它们放在一起时,整个类现在看起来像这样:

using System;
using System.Diagnostics;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using UserMonitor;

namespace OnScreenOverlay
{
    public partial class Overlay : Form
    {
        #region Public Fields
    
        public const string UserCache = @"redacted";
    
        #endregion Public Fields
    
        #region Private Fields
    
        private const uint EVENT_OBJECT_LOCATIONCHANGE = 0x800B;
        private const uint WINEVENT_OUTOFCONTEXT = 0x0000;
        private const uint WINEVENT_SKIPOWNPROCESS = 0x0002;
        private const uint WINEVENT_SKIPOWNTHREAD = 0x0001;
        private readonly Process _foxview;
        private readonly IntPtr _foxViewHWnd;
        private readonly UserMon _monitor;
        private string _currentUser;
    
        #endregion Private Fields
    
        #region Public Constructors
    
        public Overlay()
        {
            InitializeComponent();
            _target= Process.GetProcessesByName("target")[0];
            if (_foxview == null)
            {
                MessageBox.Show("No target detected... Closing");
                Close();
            }
            _targetHWnd = _target.MainWindowHandle;
            InitializeWinHook();
            StartPosition = FormStartPosition.Manual;
            Location = new Point(Screen.PrimaryScreen.Bounds.Left + 20, Screen.PrimaryScreen.Bounds.Bottom - 20);
            ShowInTaskbar = false;
            _monitor = new UserMon(UserCache);
            _monitor.UserChanged += (s, a) =>
            {
                _currentUser = a.Value;
                if (pictBox.InvokeRequired)
                {
                    pictBox.Invoke((MethodInvoker)delegate { pictBox.Refresh(); });
                }
            };
            _currentUser = _monitor.GetUser();
        }
    
        #endregion Public Constructors
    
    
    
        #region Private Delegates
    
        private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
                            IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
    
        #endregion Private Delegates
    
    
    
        #region Private Methods
    
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowRect(IntPtr hWnd, ref Rect lpRect);
    
        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr processId);
    
        [DllImport("user32.dll")]
        private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
                hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
            uint idThread, uint dwFlags);
    
        private void InitializeWinHook()
        {
            SetWinEventHook(EVENT_OBJECT_LOCATIONCHANGE, EVENT_OBJECT_LOCATIONCHANGE, IntPtr.Zero, TargetMoved, (uint)_foxview.Id,
                GetWindowThreadProcessId(_foxview.MainWindowHandle, IntPtr.Zero), WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD);
        }
    
        private void Overlay_FormClosing(object sender, FormClosingEventArgs e)
        {
            _monitor.Dispose();
        }
    
        private void pictBox_Paint(object sender, PaintEventArgs e)
        {
            using (Font myFont = new Font("Arial", 8))
            {
                e.Graphics.DrawString($"User: {_currentUser}", myFont, Brushes.LimeGreen, new Point(2, 2));
            }
        }
    
        private void TargetMoved(IntPtr hWinEventHook, uint eventType, IntPtr lParam, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
        {
            Rect newLocation = new Rect();
            GetWindowRect(_foxViewHWnd, ref newLocation);
            Location = new Point(newLocation.Right - (250 + _currentUser.Length * 7), newLocation.Top + 5);
        }
    
        #endregion Private Methods
    
    
    
        #region Private Structs
    
        [StructLayout(LayoutKind.Sequential)]
        private struct Rect
        {
            public readonly int Left;
            public readonly int Top;
            public readonly int Right;
            public readonly int Bottom;
        }
    
        #endregion Private Structs
    }
}

【讨论】:

  • 我以为你会自己回答。谢谢。我不得不说,也许您是 Win API 的新手,但您已经掌握了它。一样东西。在您的WinEventDelegate (TargetMoved) 中,您可能会获得两个不同的窗口句柄(依次为主进程和主子窗口)。看看我在相同情况下实现的内容,它可能会为您节省一些时间。其余部分的代码几乎相同。
猜你喜欢
  • 1970-01-01
  • 2016-10-10
  • 2023-03-22
  • 2011-03-06
  • 1970-01-01
  • 2020-11-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多