Move Window When External Application's Window Moves

Move window when external application's window moves

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

C# Move Windows In Sync

The easiest way is to move your other form when the C# form moves, which you can detect by adding a handler for the Control.Move event:

private void ControlMovedHandler(object sender, EventArgs e)
{
MoveWindow(otherWindowHandle, Location.X, Location.Y, ...);
}

You can use the form editor to automatically add the event handler to the main form.


As far as getting your window to move when the other one does, it's much more difficult; one way would be to have a timer/loop constantly check to see if the other window's position changed, then move yourself accordingly. Also, you need to be careful that you don't get stuck in an infinite loop of moving the windows to the same location (i.e. A moves, which moves B, which then tries to move A, which then tries to move B, etc).

Move all open windows from any process to center of main screen

You can use EnumWindows if the you want to move all the windows, and not only open forms owned by the application.

[DllImport("USER32.DLL")]
public static extern bool EnumWindows(EnumWindowsProc enumFunc, int lParam);

Then, in your code, you can iterate throuh Opened (and visible) Windows :

IntPtr lShellWindow = NativeMethods.GetShellWindow();
List<IntPtr> listWindows = new List<IntPtr>();

NativeMethods.EnumWindows(delegate(IntPtr hWnd, int lParam)
{
if (hWnd == lShellWindow) return true;
if (!NativeMethods.IsWindowVisible(hWnd)) return true;

// may be some other checks

lWindows.Add(hWnd);
return true;
}, 0);

As you can see, I also used IsWindowVisible and GetShellWindow

[DllImport("USER32.DLL")]
public static extern bool IsWindowVisible(IntPtr hWnd);

[DllImport("USER32.DLL")]
public static extern IntPtr GetShellWindow();

Then, with the list of Handles, you can move the windows with SetWindowPos

[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, SetWindowPosFlags uFlags);

[Flags()]
public enum SetWindowPosFlags : uint
{
/// <summary>If the calling thread and the thread that owns the window are attached to different input queues,
/// the system posts the request to the thread that owns the window. This prevents the calling thread from
/// blocking its execution while other threads process the request.</summary>
/// <remarks>SWP_ASYNCWINDOWPOS</remarks>
AsynchronousWindowPosition = 0x4000,
/// <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
/// <remarks>SWP_DEFERERASE</remarks>
DeferErase = 0x2000,
/// <summary>Draws a frame (defined in the window's class description) around the window.</summary>
/// <remarks>SWP_DRAWFRAME</remarks>
DrawFrame = 0x0020,
/// <summary>Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to
/// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE
/// is sent only when the window's size is being changed.</summary>
/// <remarks>SWP_FRAMECHANGED</remarks>
FrameChanged = 0x0020,
/// <summary>Hides the window.</summary>
/// <remarks>SWP_HIDEWINDOW</remarks>
HideWindow = 0x0080,
/// <summary>Does not activate the window. If this flag is not set, the window is activated and moved to the
/// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
/// parameter).</summary>
/// <remarks>SWP_NOACTIVATE</remarks>
DoNotActivate = 0x0010,
/// <summary>Discards the entire contents of the client area. If this flag is not specified, the valid
/// contents of the client area are saved and copied back into the client area after the window is sized or
/// repositioned.</summary>
/// <remarks>SWP_NOCOPYBITS</remarks>
DoNotCopyBits = 0x0100,
/// <summary>Retains the current position (ignores X and Y parameters).</summary>
/// <remarks>SWP_NOMOVE</remarks>
IgnoreMove = 0x0002,
/// <summary>Does not change the owner window's position in the Z order.</summary>
/// <remarks>SWP_NOOWNERZORDER</remarks>
DoNotChangeOwnerZOrder = 0x0200,
/// <summary>Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to
/// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent
/// window uncovered as a result of the window being moved. When this flag is set, the application must
/// explicitly invalidate or redraw any parts of the window and parent window that need redrawing.</summary>
/// <remarks>SWP_NOREDRAW</remarks>
DoNotRedraw = 0x0008,
/// <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
/// <remarks>SWP_NOREPOSITION</remarks>
DoNotReposition = 0x0200,
/// <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
/// <remarks>SWP_NOSENDCHANGING</remarks>
DoNotSendChangingEvent = 0x0400,
/// <summary>Retains the current size (ignores the cx and cy parameters).</summary>
/// <remarks>SWP_NOSIZE</remarks>
IgnoreResize = 0x0001,
/// <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
/// <remarks>SWP_NOZORDER</remarks>
IgnoreZOrder = 0x0004,
/// <summary>Displays the window.</summary>
/// <remarks>SWP_SHOWWINDOW</remarks>
ShowWindow = 0x0040,
}

and in your code :

internal static void MoveWindow(IntPtr intPtr, int X, int Y)
{
NativeMethods.SetWindowPos(intPtr, IntPtr.Zero, X, Y, 0 , 0, NativeMethods.SetWindowPosFlags.DoNotChangeOwnerZOrder | NativeMethods.SetWindowPosFlags.IgnoreResize);
}

You can improve this if you want all windows centered, but that's a good start ;)

Edit :
To improve, you can get size of each windows in the EnumWindows

List<AppWindow> listWindows = new List<AppWindow>();

NativeMethods.EnumWindows(delegate(IntPtr hWnd, int lParam)
{
if (hWnd == lShellWindow) return true;
if (!NativeMethods.IsWindowVisible(hWnd)) return true;

// may be some other checks

NativeMethods.RECT rct;

if (NativeMethods.GetWindowRect(hWnd, out rct))
{
listWindows.Add(new AppWindow()
{
Handle = hWnd,
Width = rct.Right - rct.Left,
Height = rct.Bottom - rct.Top,
});
}
return true;
}, 0);

Use GetWindowRect and RECT :

[DllImport("user32.dll", SetLastError = true)]
public static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
public int Left; // x position of upper-left corner
public int Top; // y position of upper-left corner
public int Right; // x position of lower-right corner
public int Bottom; // y position of lower-right corner
}

and a class named AppWindow :

internal class AppWindow
{
private IntPtr handle ;
private Int32 height;
private Int32 width;

public IntPtr Handle
{
get { return this.handle; }
set { this.handle = value; }
}

public Int32 Height
{
get { return this.height; }
set { this.height= value; }
}

public Int32 Width
{
get { return this.width; }
set { this.width= value; }
}
}

Now, you can calculate where to move the windows

Edit :
How to get center of the primary screen ? Easy with WinForms :

int width = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Width;
int height = System.Windows.Forms.Screen.PrimaryScreen.WorkingArea.Height;

Now, the center of the screen is width/2and height/2

Edit : (to conclude)

foreach (AppWindow app in listWindows)
{
this.MoveWindow(app.Handle, (this.width / 2) - (app.Width/2), (this.height / 2) - (app.Height/2));
}

and I think it's good ... (I haven't try exactly this solution, but it's very close of one of my projects)

SetWindowPos moves window to different position every time

You should remove these lines:

NativePoint point = new NativePoint { x = x, y = y };

ClientToScreen(handler, ref point);

And change your call to:

SetWindowPos(handler, -1, x, y, 380, 250, 0);

Calling ClientToScreen() makes no sense as the coordinates you have are already screen coordinates.

Your window gets different positions every time because when you call ClientToScreen() it will create new coordinates based on the window's current position. This means every time the function is called the coordinates will be different.


Also, if you want to take the taskbar size into account you should utilize the Screen.WorkingArea property instead of the SystemParameters.PrimaryScreen***:

int x = (int)(Screen.PrimaryScreen.WorkingArea.Width - 380);
int y = (int)(Screen.PrimaryScreen.WorkingArea.Height - 250);

Move 2 windows forms together unless the second form is minimized

You can add a filter with a help of Where: we want to move all child forms that are in Normal (neither Minimized nor Maximized) window state

Form[] formsToAdjust = Application
.OpenForms
.OfType<ChildForm>()
.Where(child => child.WindowState == FormWindowState.Normal)
.ToArray();

then business as usual:

  if (m_PreviousLocation.X != int.MinValue)
foreach (var form in formsToAdjust)
form.Location = new Point(
form.Location.X + Location.X - m_PreviousLocation.X,
form.Location.Y + Location.Y - m_PreviousLocation.Y
);

m_PreviousLocation = Location;


Related Topics



Leave a reply



Submit