Move window when external application's window moves

前端 未结 2 1723
死守一世寂寞
死守一世寂寞 2020-11-29 06:18

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.

相关标签:
2条回答
  • 2020-11-29 06:38

    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 a 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 targetProc;
        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
            {
                targetProc = Process.GetProcessesByName("notepad").FirstOrDefault(p => p != null);
                if (targetProc != null)
                {
                    notepadhWnd = targetProc.MainWindowHandle;
                    uint targetThreadId = Hook.GetWindowThread(notepadhWnd);
    
                    if (notepadhWnd != IntPtr.Zero)
                    {
                        hWinEventHook = Hook.WinEventHookOne(Hook.SWEH_Events.EVENT_OBJECT_LOCATIONCHANGE, 
                                                             WinEventDelegate, 
                                                             (uint)targetProc.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)
        {
            if (GCSafetyHandle.IsAllocated) {
                GCSafetyHandle.Free();
            }
            Hook.WinEventUnhook(hWinEventHook);
        }
    
        private void Form1_Shown(object sender, EventArgs e)
        {
            if (targetProc == 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);
    }
    
    0 讨论(0)
  • 2020-11-29 06:57

    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
        }
    }
    
    0 讨论(0)
提交回复
热议问题