Move window when external application's window moves
Asked Answered
T

2

12

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?

Timothea answered 13/2, 2018 at 12:53 Comment(14)
Consider using SetWinEventHook() instead of SetWindowsHookEx()Grondin
@Remy Lebeau I was wondering whether you had the chance to test SetWinEventHook() in a context like this. You probably have to register multple (or range of) events to "follow" a window and, AFAIK, SetWinEventHook() has a lot of overhead. Could this cause perceivable lags in the synchronized twin-window movement?Reeva
@Jimi: Registering for just the EVENT_OBJECT_LOCATIONCHANGE event ("An object has changed location, shape, or size") should suffice. And SetWinEventHook() is safer and has less overhead than SetWindowsHookEx()Grondin
@Remy Lebeau You'll probably also need EVENT_SYSTEM_MINIMIZExxx, EVENT_SYSTEM_MOVESIZExxx. But I was referring more to the "timings". SetWinEventHook() with WH_CALLWNDPROC lets you receive the events before they are processed, so you are one step ahead. I'm saying this because I'm not sure, with SetWinEventHook(), where you are "positioned" in the message queue.Reeva
@Jimi: "SetWinEventHook() with WH_CALLWNDPROC ..." - you mean SetWindowsHookEx()Grondin
@Remy Lebeau Yes (lol) of course, I pasted the wrong "string".Reeva
How would I go about implementing SetWinEventHook()? So far I have NativeMethods.SetWinEventHook(NativeMethods.EVENT_OBJECT_LOCATIONCHANGE, NativeMethods.EVENT_OBJECT_LOCATIONCHANGE, null, MyEventDelegate, targetPID, null, null); where the nulls are arguments I don't know how to fillTimothea
The first null indicates that the hook proc is not contained in a .dll, should be IntPtr.Zero. targetPID is the process.Id (I think you mean that). The second null I think is best substituted with GetWindowThreadProcessId(process.MainWindowHandle, IntPtr.Zero), otherwise you'll get the events of all the threads on the current desktop. The last null represents the flags that define the hook position. Since you have IntPtr.Zero as handle, those flags should be WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNPROCESS | WINEVENT_SKIPOWNTHREAD.Reeva
Did you get it to work? If you need a hand, just let me know.Reeva
@Jimi, I was away from work yesterday but have got it working today thanks to your help. Would you like me to write up the answer or let you do it?Timothea
Well, I'm glad you did it. If you want, I can post the methods I use to hook a window with SetWinEventHook(), for comparison. But if you want to post a self-answer, it's ok. Maybe I'll post mine after, for the same reason.Reeva
That makes sense, we can post both, mine for reference and yours for posterityTimothea
Allright then, I'll prepare something. I think it's an interesting matter that you don't see very often.Reeva
I'm relatively new to the Windows API side of things so your help has been pretty invaluable, I appreciate itTimothea
R
32

A method 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 a Toolbar that can interact with the process, using SetWinEventHook().


To get the hooked Window bounds, GetWindowRect() is not recommended. Better call DwmGetWindowAttribute() passing DWMWA_EXTENDED_FRAME_BOUNDS as the DWMWINDOWATTRIBUTE, which still returns a RECT. This because GetWindowRect() is not DpiAware and it may return a virtualized measure in some contexts, plus it includes the Window drop shadow in specific configurations.

Read more about DpiAwarenes, Screen layout and VirtualScreen here:
Using SetWindowPos with multiple monitors

Also refactored the Hook helper class and the NativeMethods declarations.


▶ To move to foreground the Tool Window, as in the animation, when the hooked Window becomes the Foreground Window, also hook the EVENT_SYSTEM_FOREGROUND event: you'll receive a notification when the Foreground Window changes, then compare with the handle of Window you're hooking.


A visual representation of the results:

SetWinEventHook sample image

The Form class initialization procedure:

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)
    {
        base.OnFormClosing(e);
        if (!e.Cancel) {
            if (GCSafetyHandle.IsAllocated) GCSafetyHandle.Free();
            Hook.WinEventUnhook(hWinEventHook);
        }
    }

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

The support classes used to reference the Windows API methods:

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)
    {
        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)
    {
        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)
    {
        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 Rectangle ToRectangle() => Rectangle.FromLTRB(Left, Top, Right, 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);
}
Reeva answered 15/2, 2018 at 17:16 Comment(4)
From the gif I can see that the overlay form moves instantaneously in response to the owner window moving. This is inpressive and I cannot replicate it in my project; there's always a slight lag in movement. I was close to giving up and accepting the lag. I'll try to formulate a coherent question regarding this.Sulfapyridine
@The Most Curious Thing Did you use the code as shown here (exactly, I mean)? Note that the application needs to be DpiAware (see the notes here: Using SetWindowPos with multiple monitors) and this => idObject == (Hook.SWEH_ObjectId)Hook.SWEH_CHILDID_SELF) also needs to be there, otherwise your hooked Form gets distracted (by itself :). No other resize/move events (triggered continuously) should be implemented (because, well, no distractions).Reeva
Thank you for your response! Your code indeed works for me and I'm currently working "upward" from it to identify the problem with mine. I appreciate the food-for-thought.Sulfapyridine
For posterity, it was a completely unrelated issue related to my rendering sync interval.Sulfapyridine
T
3

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
    }
}
Timothea answered 15/2, 2018 at 16:54 Comment(1)
I thought you'ld make it a self-answer. Thanks. I have to say, maybe you're new to Win API, but you've got it pretty good. One thing. In your WinEventDelegate (TargetMoved), it may happen that you get two different window handles (both the Main process and the Main Child Window in sequence). Take a look at what I implemented in the same situation, it might save you some time. For the rest, it's pretty much the same code.Reeva

© 2022 - 2024 — McMap. All rights reserved.