Skip to content

Instantly share code, notes, and snippets.

Created July 31, 2013 14:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save anonymous/6122631 to your computer and use it in GitHub Desktop.
Save anonymous/6122631 to your computer and use it in GitHub Desktop.
Updated version of the DialogCenteringService class for my answer http://stackoverflow.com/a/15369563/579817 which takes monitor working areas into account
public class DialogCenteringService : IDisposable
{
private readonly IWin32Window owner;
private readonly HookProc hookProc;
private readonly IntPtr hHook = IntPtr.Zero;
public DialogCenteringService(IWin32Window owner)
{
if (owner == null) throw new ArgumentNullException("owner");
this.owner = owner;
hookProc = DialogHookProc;
hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, hookProc, IntPtr.Zero, GetCurrentThreadId());
}
private IntPtr DialogHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return CallNextHookEx(hHook, nCode, wParam, lParam);
}
CWPRETSTRUCT msg = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT));
IntPtr hook = hHook;
if (msg.message == (int)CbtHookAction.HCBT_ACTIVATE)
{
try
{
CenterWindow(msg.hwnd);
}
finally
{
UnhookWindowsHookEx(hHook);
}
}
return CallNextHookEx(hook, nCode, wParam, lParam);
}
public void Dispose()
{
UnhookWindowsHookEx(hHook);
}
private void CenterWindow(IntPtr hChildWnd)
{
Rectangle? recParent = GetWindowRect(owner.Handle);
if (recParent == null)
{
return;
}
CenterWindow(hChildWnd, recParent.Value);
}
public static Rectangle? GetWindowRect(IntPtr hWnd)
{
Rectangle rect = new Rectangle(0, 0, 0, 0);
bool success = GetWindowRect(hWnd, ref rect);
if (!success)
{
return null;
}
return rect;
}
public static Rectangle GetCenterRectangle(Rectangle recParent, Rectangle recChild)
{
int width = recChild.Width - recChild.X;
int height = recChild.Height - recChild.Y;
Point ptCenter = new Point(0, 0);
ptCenter.X = recParent.X + ((recParent.Width - recParent.X) / 2);
ptCenter.Y = recParent.Y + ((recParent.Height - recParent.Y) / 2);
Point ptStart = new Point(0, 0);
ptStart.X = (ptCenter.X - (width / 2));
ptStart.Y = (ptCenter.Y - (height / 2));
// get centered rectangle
Rectangle centeredRectangle = new Rectangle(ptStart.X, ptStart.Y, width, height);
// fit the window to the screen
Screen parentScreen = Screen.FromRectangle(recParent);
Rectangle workingArea = parentScreen.WorkingArea;
// various collision checks
if (workingArea.X > centeredRectangle.X)
{
centeredRectangle = new Rectangle(workingArea.X, centeredRectangle.Y, centeredRectangle.Width, centeredRectangle.Height);
}
if (workingArea.Y > centeredRectangle.Y)
{
centeredRectangle = new Rectangle(centeredRectangle.X, workingArea.Y, centeredRectangle.Width, centeredRectangle.Height);
}
if (workingArea.Right < centeredRectangle.Right)
{
centeredRectangle = new Rectangle(workingArea.Right - centeredRectangle.Width, centeredRectangle.Y, centeredRectangle.Width, centeredRectangle.Height);
}
if (workingArea.Bottom < centeredRectangle.Bottom)
{
centeredRectangle = new Rectangle(centeredRectangle.X, workingArea.Bottom - centeredRectangle.Height, centeredRectangle.Width, centeredRectangle.Height);
}
return centeredRectangle;
}
public static void CenterWindow(IntPtr hChildWnd, Rectangle recParent)
{
Rectangle? recChild = GetWindowRect(hChildWnd);
if (recChild == null)
{
return;
}
Rectangle centerRectangle = GetCenterRectangle(recParent, recChild.Value);
Task.Factory.StartNew(() => SetWindowPos(hChildWnd, (IntPtr)0, centerRectangle.X, centerRectangle.Y, centerRectangle.Width, centerRectangle.Height, SetWindowPosFlags.SWP_ASYNCWINDOWPOS | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOOWNERZORDER | SetWindowPosFlags.SWP_NOZORDER));
}
// some p/invoke
// ReSharper disable InconsistentNaming
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
public delegate void TimerProc(IntPtr hWnd, uint uMsg, UIntPtr nIDEvent, uint dwTime);
private const int WH_CALLWNDPROCRET = 12;
// ReSharper disable EnumUnderlyingTypeIsInt
private enum CbtHookAction : int
// ReSharper restore EnumUnderlyingTypeIsInt
{
// ReSharper disable UnusedMember.Local
HCBT_MOVESIZE = 0,
HCBT_MINMAX = 1,
HCBT_QS = 2,
HCBT_CREATEWND = 3,
HCBT_DESTROYWND = 4,
HCBT_ACTIVATE = 5,
HCBT_CLICKSKIPPED = 6,
HCBT_KEYSKIPPED = 7,
HCBT_SYSCOMMAND = 8,
HCBT_SETFOCUS = 9
// ReSharper restore UnusedMember.Local
}
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, ref Rectangle lpRect);
[DllImport("user32.dll")]
private static extern int MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);
[DllImport("User32.dll")]
public static extern UIntPtr SetTimer(IntPtr hWnd, UIntPtr nIDEvent, uint uElapse, TimerProc lpTimerFunc);
[DllImport("User32.dll")]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
public static extern int GetWindowTextLength(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int maxLength);
[DllImport("user32.dll")]
public static extern int EndDialog(IntPtr hDlg, IntPtr nResult);
[StructLayout(LayoutKind.Sequential)]
public struct CWPRETSTRUCT
{
public IntPtr lResult;
public IntPtr lParam;
public IntPtr wParam;
public uint message;
public IntPtr hwnd;
};
// ReSharper restore InconsistentNaming
}
@sonicmouse
Copy link

sonicmouse commented Oct 24, 2019

There are issues with your positioning code. It derives from the issues with mixing a managed Rectangle and a native RECT. Here is the fixed code:

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

namespace YOUR_NAMESPACE_HERE
{
	public sealed class DialogCenteringService : IDisposable
	{
		private readonly IWin32Window _owner;
		private readonly Win32Native.HookProc _hookProc;
		private IntPtr _hHook;

		public DialogCenteringService(IWin32Window owner)
		{
			_owner = owner ?? throw new ArgumentNullException(nameof(owner));
			_hookProc = DialogHookProc;
			_hHook = Win32Native.SetWindowsHookEx(Win32Native.WH_CALLWNDPROCRET, _hookProc, IntPtr.Zero, Win32Native.GetCurrentThreadId());
		}

		#region Disposing
		~DialogCenteringService()
		{
			Dispose(false);
		}

		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		private void Dispose(bool disposing)
		{
			if (disposing)
			{
				// if you have managed resources, get rid of them now
			}
			if (_hHook != IntPtr.Zero)
			{
				Win32Native.UnhookWindowsHookEx(_hHook);
				_hHook = IntPtr.Zero;
			}
		}
		#endregion

		private IntPtr DialogHookProc(int nCode, IntPtr wParam, IntPtr lParam)
		{
			if (nCode < 0)
			{
				return Win32Native.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
			}

			var msg = (Win32Native.CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32Native.CWPRETSTRUCT));

			if (msg.message == (int)Win32Native.CbtHookAction.HCBT_ACTIVATE)
			{
				try
				{
					CenterWindow(msg.hwnd);
				}
				finally
				{
					Win32Native.UnhookWindowsHookEx(_hHook);
					_hHook = IntPtr.Zero;
				}
			}

			return Win32Native.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
		}

		private bool CenterWindow(IntPtr hChildWnd)
		{
			var recParent = GetWindowRect(_owner.Handle);
			return recParent != null ? CenterWindow(hChildWnd, recParent.Value) : false;
		}

		private static Rectangle? GetWindowRect(IntPtr hWnd)
		{
			var rect = new Win32Native.RECT();
			if(Win32Native.GetWindowRect(hWnd, ref rect))
			{
				return new Rectangle(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
			}
			return null;
		}

		private static bool CenterWindow(IntPtr hChildWnd, Rectangle recParent)
		{
			var recChild = GetWindowRect(hChildWnd);
			if (recChild != null)
			{
				var centeredPoint = GetCenteredPoint(recParent, recChild.Value);
				return Win32Native.SetWindowPos(
					hChildWnd,
					IntPtr.Zero,
					centeredPoint.X, centeredPoint.Y, -1, -1,
					Win32Native.SetWindowPosFlags.SWP_ASYNCWINDOWPOS | Win32Native.SetWindowPosFlags.SWP_NOSIZE |
					Win32Native.SetWindowPosFlags.SWP_NOACTIVATE | Win32Native.SetWindowPosFlags.SWP_NOOWNERZORDER |
					Win32Native.SetWindowPosFlags.SWP_NOZORDER);
			}
			return false;
		}

		private static Point GetCenteredPoint(Rectangle recParent, Rectangle recChild)
		{
			var ptParentCenter = new Point
			{
				X = recParent.X + (recParent.Width / 2),
				Y = recParent.Y + (recParent.Height / 2)
			};

			var ptStart = new Point
			{
				X = ptParentCenter.X - (recChild.Width / 2),
				Y = ptParentCenter.Y - (recChild.Height / 2)
			};

			// get centered rectangle
			var recCentered = new Rectangle(ptStart.X, ptStart.Y, recChild.Width, recChild.Height);

			// find the working area of the parent
			var workingArea = Screen.FromRectangle(recParent).WorkingArea;

			// make sure child window isn't spanning across mulitiple screens
			if (workingArea.X > recCentered.X)
			{
				recCentered = new Rectangle(workingArea.X, recCentered.Y, recCentered.Width, recCentered.Height);
			}
			if (workingArea.Y > recCentered.Y)
			{
				recCentered = new Rectangle(recCentered.X, workingArea.Y, recCentered.Width, recCentered.Height);
			}
			if (workingArea.Right < recCentered.Right)
			{
				recCentered = new Rectangle(workingArea.Right - recCentered.Width, recCentered.Y, recCentered.Width, recCentered.Height);
			}
			if (workingArea.Bottom < recCentered.Bottom)
			{
				recCentered = new Rectangle(recCentered.X, workingArea.Bottom - recCentered.Height, recCentered.Width, recCentered.Height);
			}

			return new Point(recCentered.X, recCentered.Y);
		}

		#region Native/Unsafe
		private static class Win32Native
		{
			public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
			public const int WH_CALLWNDPROCRET = 12;

			public enum CbtHookAction
			{
				HCBT_MOVESIZE,
				HCBT_MINMAX,
				HCBT_QS,
				HCBT_CREATEWND,
				HCBT_DESTROYWND,
				HCBT_ACTIVATE,
				HCBT_CLICKSKIPPED,
				HCBT_KEYSKIPPED,
				HCBT_SYSCOMMAND,
				HCBT_SETFOCUS
			}

			[Flags]
			public enum SetWindowPosFlags : uint
			{
				SWP_ASYNCWINDOWPOS = 0x4000U,
				SWP_DEFERERASE = 0x2000U,
				SWP_DRAWFRAME = 0x0020U,
				SWP_FRAMECHANGED = 0x0020U,
				SWP_HIDEWINDOW = 0x0080U,
				SWP_NOACTIVATE = 0x0010U,
				SWP_NOCOPYBITS = 0x0100U,
				SWP_NOMOVE = 0x0002U,
				SWP_NOOWNERZORDER = 0x0200U,
				SWP_NOREDRAW = 0x0008U,
				SWP_NOREPOSITION = 0x0200U,
				SWP_NOSENDCHANGING = 0x0400U,
				SWP_NOSIZE = 0x0001U,
				SWP_NOZORDER = 0x0004U,
				SWP_SHOWWINDOW = 0x0040U
			}

			[StructLayout(LayoutKind.Sequential)]
			public struct RECT
			{
				public int left;
				public int top;
				public int right;
				public int bottom;
			}

			[StructLayout(LayoutKind.Sequential)]
			public struct CWPRETSTRUCT
			{
				public IntPtr lResult;
				public IntPtr lParam;
				public IntPtr wParam;
				public uint message;
				public IntPtr hwnd;
			};

			[DllImport("kernel32.dll")]
			public static extern int GetCurrentThreadId();

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

			[DllImport("user32.dll")]
			[return: MarshalAs(UnmanagedType.Bool)]
			public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SetWindowPosFlags uFlags);

			[DllImport("user32.dll")]
			public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

			[DllImport("user32.dll")]
			public static extern int UnhookWindowsHookEx(IntPtr idHook);

			[DllImport("user32.dll")]
			public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
		}
		#endregion
	}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment