Move window when external application's window moves

半城伤御伤魂 提交于 2019-11-26 22:59:36

问题


I've got an always on-top application (basically a status display) that I want to follow around another program and always sit just to the left of the minimize button.

I can get the Rect representing the "target" process using the following code which I can then pair with an offset to generate the initial position of my overlay.

Get the HWnd IntPtr:

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

Declare the function from user32.dll:

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

And the appropriate struct:

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

And then call it on demand.

However, I would like to avoid constantly polling this value, so I would like to hook into the target application and respond whenever the target window is moved.

Looking around the user32.dll documentation, the only way I can see for doing this is using SetWindowsHookEx(). I'm not entirely sure of how I'd go about intercepting an event from here however.

I believe the target application is built off of WinForms but I cannot be sure. So solutions that let me respond to the target's Move event or directly to some Windows Message would both be useful.

Any ideas on how I can proceed?


回答1:


This one shows how to hook a Windows Form to another process (Notepad, in this case) and follow the movements of the process Main Window, to create sort of Toolbar that can interact with the process.

The main API function used is SetWinEventHook()

A visual representation of the results:

The Form class initialization procedure:

public partial class Form1 : Form
{
    private IntPtr NotepadhWnd;
    private IntPtr hWinEventHook;
    private Process Target;
    private RECT rect = new RECT();
    protected Hook.WinEventDelegate WinEventDelegate;
    static GCHandle GCSafetyHandle;

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

        try
        {
            Target = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);
            if (Target != null)
            {
                NotepadhWnd = Target.MainWindowHandle;
                uint TargetThreadId = Hook.GetWindowThread(NotepadhWnd);

                if (NotepadhWnd != IntPtr.Zero)
                {
                    hWinEventHook = Hook.WinEventHookOne(Hook.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE, 
                                                         WinEventDelegate, 
                                                         (uint)Target.Id, 
                                                         TargetThreadId);
                    rect = Hook.GetWindowRect(NotepadhWnd);
                    this.Location = new Point(rect.Right, rect.Top);
                }
            }
        }
        catch (Exception ex)
        {
            //ErrorManager.Logger(this, this.InitializeComponent(), ex.HResult, ex.Data, DateTime.Now);
            throw ex;
        }
    }

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

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        GCSafetyHandle.Free();
        Hook.WinEventUnhook(hWinEventHook);
    }

    private void Form1_Shown(object sender, EventArgs e)
    {
        if (Target == null)
        {
            this.Hide();
            MessageBox.Show("Notepad not found!", "Target Missing", MessageBoxButtons.OK, MessageBoxIcon.Hand);
            this.Close();
        }
        else
        {
            this.Size = new Size(50, 140);
        }
    }

The support classes used to reference the Windows API methods:

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

public class Hook
{
    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
    }

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

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

    public static IntPtr WinEventHookRange(SWEH_Events _eventFrom, 
                                           SWEH_Events _eventTo, 
                                           WinEventDelegate _delegate, 
                                           uint idProcess, uint idThread)
    {
        new UIPermission(UIPermissionWindow.AllWindows).Demand();
        return UnsafeNativeMethods.SetWinEventHook(_eventFrom, _eventTo, 
                                                   IntPtr.Zero, _delegate, 
                                                   idProcess, idThread, 
                                                   WinEventHookInternalFlags);
    }

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

    public static bool WinEventUnhook(IntPtr hWinEventHook)
    {
        return UnsafeNativeMethods.UnhookWinEvent(hWinEventHook);
    }

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

    public static RECT GetWindowRect(IntPtr hWnd)
    {
        RECT rect = new RECT();
        bool _result = SafeNativeMethods.GetWindowRect(hWnd, ref rect);
        return rect;
    }
}

[SuppressUnmanagedCodeSecurity]
internal static class SafeNativeMethods
{
    [DllImport("user32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool GetWindowRect(IntPtr hWnd, ref RECT lpRect);
}

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

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

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

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



回答2:


Thanks to @Jimi for his help here. The following method worked.

First, store a reference to the target process:

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

Then get the handle to the main window:

IntPtr _tagetHWnd = _target.MainWindowHandle;

Then initialise the hook:

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

Where SetWinEventHook is declared as such:

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

And the constants involved are:

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;

Then in my TargetMoved method I get check the new Window location and move my overlay.

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);
}

Where GetWindowRect() is defined by:

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

And Rect is defined by:

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

So when you put it all together, the entire class now looks like this:

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
}
}


来源:https://stackoverflow.com/questions/48767318/move-window-when-external-applications-window-moves

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!