From 861f3a0ca737f37cd4ce91c629ce4f58ed2db9c3 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Fri, 29 Jul 2022 13:20:55 -0700 Subject: [PATCH] Wpf/WinForms: Fix PointTo/FromScreen in system dpi awareness mode --- src/Eto.WinForms/Win32.dpi.cs | 68 +++++++++++++---- src/Eto.Wpf/Forms/MouseHandler.cs | 10 +-- src/Eto.Wpf/Forms/WpfFrameworkElement.cs | 39 +++++++++- src/Eto.Wpf/Forms/WpfWindow.cs | 56 +++++++------- src/Eto.Wpf/LogicalScreenHelper.cs | 12 ++- test/Eto.Test.Wpf/UnitTests/ScreenTests.cs | 2 +- .../Sections/Behaviors/ScreenSection.cs | 73 +++++++++++++++++-- .../UnitTests/Forms/Controls/ControlTests.cs | 20 ++++- 8 files changed, 212 insertions(+), 68 deletions(-) diff --git a/src/Eto.WinForms/Win32.dpi.cs b/src/Eto.WinForms/Win32.dpi.cs index f854f2f5bc..5cba0257d9 100644 --- a/src/Eto.WinForms/Win32.dpi.cs +++ b/src/Eto.WinForms/Win32.dpi.cs @@ -64,11 +64,11 @@ public static uint GetWindowDpi(IntPtr hwnd, bool onlyIfSupported = true) return dpiX; } - public static Eto.Drawing.Point LogicalToScreen(this Eto.Drawing.PointF point) + public static Eto.Drawing.Point LogicalToScreen(this Eto.Drawing.PointF point, Eto.Forms.Screen screen = null, bool usePerMonitor = true) { - var screen = Eto.Forms.Screen.FromPoint(point); + screen = screen ?? Eto.Forms.Screen.FromPoint(point); var sdscreen = ScreenHandler.GetControl(screen); - var pixelSize = sdscreen.GetLogicalPixelSize(); + var pixelSize = sdscreen.GetLogicalPixelSize(usePerMonitor); var location = sdscreen.GetBounds().Location; var screenBounds = screen.Bounds; @@ -78,16 +78,16 @@ public static Eto.Drawing.Point LogicalToScreen(this Eto.Drawing.PointF point) return Drawing.Point.Round(new Drawing.PointF(x, y)); } - public static Eto.Drawing.PointF ScreenToLogical(this Eto.Drawing.Point point, swf.Screen sdscreen = null) + public static Eto.Drawing.PointF ScreenToLogical(this Eto.Drawing.Point point, swf.Screen sdscreen = null, bool usePerMonitor = true) { - return ScreenToLogical(point.ToSD(), sdscreen); + return ScreenToLogical(point.ToSD(), sdscreen, usePerMonitor); } - public static Eto.Drawing.PointF ScreenToLogical(this sd.Point point, swf.Screen sdscreen = null) + public static Eto.Drawing.PointF ScreenToLogical(this sd.Point point, swf.Screen sdscreen = null, bool usePerMonitor = true) { sdscreen = sdscreen ?? swf.Screen.FromPoint(point); var location = sdscreen.GetLogicalLocation(); - var pixelSize = sdscreen.GetLogicalPixelSize(); + var pixelSize = sdscreen.GetLogicalPixelSize(usePerMonitor); var sdscreenBounds = sdscreen.GetBounds(); var x = location.X + (point.X - sdscreenBounds.X) / pixelSize; @@ -97,12 +97,12 @@ public static Eto.Drawing.PointF ScreenToLogical(this sd.Point point, swf.Screen return new Drawing.PointF(x, y); } - public static Eto.Drawing.RectangleF ScreenToLogical(this Eto.Drawing.Rectangle rect, swf.Screen screen) + public static Eto.Drawing.RectangleF ScreenToLogical(this Eto.Drawing.Rectangle rect, swf.Screen sdscreen, bool usePerMonitor = true) { - screen = screen ?? swf.Screen.FromPoint(rect.Location.ToSD()); - var location = screen.GetLogicalLocation(); - var pixelSize = screen.GetLogicalPixelSize(); - var screenBounds = screen.GetBounds(); + sdscreen = sdscreen ?? swf.Screen.FromPoint(rect.Location.ToSD()); + var location = sdscreen.GetLogicalLocation(); + var pixelSize = sdscreen.GetLogicalPixelSize(usePerMonitor); + var screenBounds = sdscreen.GetBounds(); return new Eto.Drawing.RectangleF( location.X + (rect.X - screenBounds.X) / pixelSize, location.Y + (rect.Y - screenBounds.Y) / pixelSize, @@ -117,6 +117,10 @@ public static Eto.Drawing.RectangleF GetLogicalBounds(this swf.Screen screen) { return new Eto.Drawing.RectangleF(GetLogicalLocation(screen), GetLogicalSize(screen)); } + + public static bool IsSystemDpiAware => Win32.GetProcessDpiAwareness(IntPtr.Zero, out var awareness) == 0 && awareness == Win32.PROCESS_DPI_AWARENESS.SYSTEM_DPI_AWARE; + + public static float SystemDpi => Win32.GetDpiForSystem() / 96f; class ScreenHelper : LogicalScreenHelper { @@ -147,7 +151,7 @@ public sd.Rectangle GetWorkingArea(swf.Screen screen) } - public override float GetLogicalPixelSize(swf.Screen screen) + public override float GetLogicalPixelSize(swf.Screen screen, bool usePerMonitor = true) { if (!MonitorDpiSupported) { @@ -161,7 +165,7 @@ public override float GetLogicalPixelSize(swf.Screen screen) var mon = MonitorFromPoint(screen.Bounds.Location, MONITOR.DEFAULTTONEAREST); // use per-monitor aware dpi awareness to get ACTUAL dpi here - var oldDpiAwareness = SetThreadDpiAwarenessContextSafe(DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); + var oldDpiAwareness = usePerMonitor ? SetThreadDpiAwarenessContextSafe(DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2) : DPI_AWARENESS_CONTEXT.NONE; uint dpiX, dpiY; GetDpiForMonitor(mon, MDT.EFFECTIVE_DPI, out dpiX, out dpiY); @@ -183,7 +187,7 @@ public override float GetLogicalPixelSize(swf.Screen screen) public static Eto.Drawing.SizeF GetLogicalSize(this swf.Screen screen) => locationHelper.GetLogicalSize(screen); - public static float GetLogicalPixelSize(this swf.Screen screen) => locationHelper.GetLogicalPixelSize(screen); + public static float GetLogicalPixelSize(this swf.Screen screen, bool usePerMonitor = true) => locationHelper.GetLogicalPixelSize(screen, usePerMonitor); public static void GetMonitorInfo(this swf.Screen screen, ref MONITORINFOEX info) { @@ -228,6 +232,40 @@ public static DPI_AWARENESS_CONTEXT SetThreadDpiAwarenessContextSafe(DPI_AWARENE return DPI_AWARENESS_CONTEXT.NONE; return SetThreadDpiAwarenessContext(dpiContext); } + + public static swf.Screen GetScreenFromWindow(IntPtr nativeHandle) + { + if (nativeHandle == IntPtr.Zero) + return swf.Screen.PrimaryScreen; + + return swf.Screen.FromHandle(nativeHandle); + + // var monitorPtr = Win32.MonitorFromWindow(nativeHandle, MONITOR.DEFAULTTONEAREST); + // var info = new MONITORINFOEX(); + // Win32.GetMonitorInfo(new HandleRef(null, monitorPtr), info); + // var monitorBounds = info.rcMonitor.ToSD(); + // foreach (var screen in swf.Screen.AllScreens) + // { + // if (screen.Bounds == monitorBounds) + // return screen; + // } + // return swf.Screen.PrimaryScreen; + } + + + public static T ExecuteInDpiAwarenessContext(Func func) + { + var oldDpiAwareness = Win32.SetThreadDpiAwarenessContextSafe(Win32.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); + try + { + return func(); + } + finally + { + if (oldDpiAwareness != Win32.DPI_AWARENESS_CONTEXT.NONE) + Win32.SetThreadDpiAwarenessContextSafe(oldDpiAwareness); + } + } [DllImport("User32.dll")] public static extern DPI_AWARENESS_CONTEXT GetThreadDpiAwarenessContext(); diff --git a/src/Eto.Wpf/Forms/MouseHandler.cs b/src/Eto.Wpf/Forms/MouseHandler.cs index d60be8b00f..dacdb3c1f6 100755 --- a/src/Eto.Wpf/Forms/MouseHandler.cs +++ b/src/Eto.Wpf/Forms/MouseHandler.cs @@ -18,19 +18,13 @@ public PointF Position get { var screen = swf.Screen.FromPoint(swf.Control.MousePosition); - var oldDpiAwareness = Win32.SetThreadDpiAwarenessContextSafe(Win32.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); - var result = swf.Control.MousePosition; - if (oldDpiAwareness != Win32.DPI_AWARENESS_CONTEXT.NONE) - Win32.SetThreadDpiAwarenessContextSafe(oldDpiAwareness); + var result = Win32.ExecuteInDpiAwarenessContext(() => swf.Control.MousePosition); return result.ScreenToLogical(screen); } set { var pos = value.LogicalToScreen(); - var oldDpiAwareness = Win32.SetThreadDpiAwarenessContextSafe(Win32.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); - swf.Cursor.Position = Point.Round(pos).ToSD(); - if (oldDpiAwareness != Win32.DPI_AWARENESS_CONTEXT.NONE) - Win32.SetThreadDpiAwarenessContextSafe(oldDpiAwareness); + Win32.ExecuteInDpiAwarenessContext(() => swf.Cursor.Position = Point.Round(pos).ToSD()); } } diff --git a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs index 61a1a589d4..49fa178bc8 100755 --- a/src/Eto.Wpf/Forms/WpfFrameworkElement.cs +++ b/src/Eto.Wpf/Forms/WpfFrameworkElement.cs @@ -204,6 +204,13 @@ public virtual Size Size return newSize.Value; if (!Widget.Loaded) return UserPreferredSize.ToEtoSize(); + // if (Win32.IsSystemDpiAware && !double.IsNaN(Control.ActualWidth) && !double.IsNaN(Control.ActualHeight)) + // { + // // convert system dpi to logical + // var sizef = new SizeF((float)Control.ActualWidth, (float)Control.ActualHeight) / (Win32.GetDpiForSystem() / 96f) * SwfScreen.GetLogicalPixelSize(); + // return Size.Round(sizef); + // } + return Control.GetSize(); } set @@ -938,20 +945,46 @@ public void MapPlatformCommand(string systemAction, Command command) { } + System.Windows.Forms.Screen SwfScreen => Win32.GetScreenFromWindow(Widget.ParentWindow?.NativeHandle ?? IntPtr.Zero); + public PointF PointFromScreen(PointF point) { if (!ContainerControl.IsLoaded) return point; - point = point.LogicalToScreen(); - return ContainerControl.PointFromScreen(point.ToWpf()).ToEto(); + // ensure we're connected to a presentation source + var presentationSource = sw.PresentationSource.FromVisual(ContainerControl) as HwndSource; + if (presentationSource == null) + return point; + + point = point.LogicalToScreen(Widget.ParentWindow?.Screen); + point = Win32.ExecuteInDpiAwarenessContext(() => ContainerControl.PointFromScreen(point.ToWpf())).ToEto(); + + if (Win32.IsSystemDpiAware) + { + point = point * Win32.SystemDpi / Win32.GetLogicalPixelSize(SwfScreen); + } + return point; + } public PointF PointToScreen(PointF point) { if (!ContainerControl.IsLoaded) return point; - return ContainerControl.PointToScreen(point.ToWpf()).ToEtoPoint().ScreenToLogical(); + + // ensure we're connected to a presentation source + var presentationSource = sw.PresentationSource.FromVisual(ContainerControl) as HwndSource; + if (presentationSource == null) + return point; + + if (Win32.IsSystemDpiAware) + { + point = point / Win32.SystemDpi * Win32.GetLogicalPixelSize(SwfScreen); + } + var pt = Win32.ExecuteInDpiAwarenessContext(() => ContainerControl.PointToScreen(point.ToWpf())); + + return pt.ToEtoPoint().ScreenToLogical(SwfScreen); } public Point Location diff --git a/src/Eto.Wpf/Forms/WpfWindow.cs b/src/Eto.Wpf/Forms/WpfWindow.cs index e105772b9c..86a99973a8 100644 --- a/src/Eto.Wpf/Forms/WpfWindow.cs +++ b/src/Eto.Wpf/Forms/WpfWindow.cs @@ -637,13 +637,13 @@ public override Size Size if (handle != IntPtr.Zero && Control.IsLoaded) { // WPF doesn't always report the correct size when maximized - Win32.RECT rect; - if (Win32.GetWindowRect(handle, out rect)) + var rect = Win32.ExecuteInDpiAwarenessContext(() => Win32.GetWindowRect(handle, out var r) ? r : (Win32.RECT?)null); + if (rect != null) { var scale = DpiScale; return new Size( - (int)Math.Round(rect.width * scale), - (int)Math.Round(rect.height * scale) + (int)Math.Round(rect.Value.width * scale), + (int)Math.Round(rect.Value.height * scale) ); } } @@ -676,19 +676,27 @@ protected bool LocationSet set { Widget.Properties.Set(WpfWindow.LocationSet_Key, value); } } - System.Windows.Forms.Screen SwfScreen + System.Windows.Forms.Screen SwfScreen => Win32.GetScreenFromWindow(NativeHandle); + + double DpiScale { get { - var handle = NativeHandle; - if (handle == IntPtr.Zero) - return System.Windows.Forms.Screen.PrimaryScreen; - return System.Windows.Forms.Screen.FromHandle(handle); + var source = sw.PresentationSource.FromVisual(Control); + double scale; + if (source != null) + { + scale = source.CompositionTarget.TransformFromDevice.M22; + if (Win32.IsSystemDpiAware) + { + scale = scale * Win32.SystemDpi / SwfScreen.GetLogicalPixelSize(); + } + } + else scale = 1f / (Widget.Screen ?? Screen.PrimaryScreen).LogicalPixelSize; + return scale; } } - double DpiScale => sw.PresentationSource.FromVisual(Control)?.CompositionTarget.TransformFromDevice.M22 ?? 1f / Screen.PrimaryScreen.LogicalPixelSize; - public new Point Location { get @@ -698,23 +706,14 @@ System.Windows.Forms.Screen SwfScreen var handle = NativeHandle; if (handle != IntPtr.Zero) { - Point? location = null; // Left/Top doesn't always report correct location when maximized, so use Win32 when we can. - var oldDpiAwareness = Win32.SetThreadDpiAwarenessContextSafe(Win32.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); - try - { - Win32.RECT rect; - if (Win32.GetWindowRect(handle, out rect)) - location = new Point(rect.left, rect.top); - } - finally + var rect = Win32.ExecuteInDpiAwarenessContext(() => Win32.GetWindowRect(handle, out var r) ? r : (Win32.RECT?)null); + + if (rect != null) { - if (oldDpiAwareness != Win32.DPI_AWARENESS_CONTEXT.NONE) - Win32.SetThreadDpiAwarenessContextSafe(oldDpiAwareness); + var location = new Point(rect.Value.left, rect.Value.top); + return Point.Round(location.ScreenToLogical(SwfScreen)); } - - if (location != null) - return Point.Round(location.Value.ScreenToLogical(SwfScreen)); } // in WPF, left/top of a window is transformed by the (current) screen dpi, which makes absolutely no sense. var left = Control.Left; @@ -762,12 +761,9 @@ void Control_SourceInitialized(object sender, EventArgs e) void SetLocation(PointF location) { var handle = NativeHandle; - var loc = location.LogicalToScreen(); - var oldDpiAwareness = Win32.SetThreadDpiAwarenessContextSafe(Win32.DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2); - Win32.SetWindowPos(NativeHandle, IntPtr.Zero, loc.X, loc.Y, 0, 0, Win32.SWP.NOSIZE | Win32.SWP.NOACTIVATE); - if (oldDpiAwareness != Win32.DPI_AWARENESS_CONTEXT.NONE) - Win32.SetThreadDpiAwarenessContextSafe(oldDpiAwareness); + var loc = location.LogicalToScreen(); + Win32.ExecuteInDpiAwarenessContext(() => Win32.SetWindowPos(NativeHandle, IntPtr.Zero, loc.X, loc.Y, 0, 0, Win32.SWP.NOSIZE | Win32.SWP.NOACTIVATE)); } public WindowState WindowState diff --git a/src/Eto.Wpf/LogicalScreenHelper.cs b/src/Eto.Wpf/LogicalScreenHelper.cs index 115d19880d..39377985ed 100644 --- a/src/Eto.Wpf/LogicalScreenHelper.cs +++ b/src/Eto.Wpf/LogicalScreenHelper.cs @@ -19,9 +19,17 @@ public abstract class LogicalScreenHelper public abstract Eto.Drawing.SizeF GetLogicalSize(T screen); - public abstract float GetLogicalPixelSize(T screen); + public abstract float GetLogicalPixelSize(T screen, bool usePerMonitor = true); - public virtual float GetMaxLogicalPixelSize() => AllScreens.Max((Func)GetLogicalPixelSize); + public virtual float GetMaxLogicalPixelSize() + { + float logicalPixelSize = 0; + foreach (var screen in AllScreens) + { + logicalPixelSize = Math.Max(logicalPixelSize, GetLogicalPixelSize(screen)); + } + return logicalPixelSize; + } public Eto.Drawing.PointF GetLogicalLocation(T screen) { diff --git a/test/Eto.Test.Wpf/UnitTests/ScreenTests.cs b/test/Eto.Test.Wpf/UnitTests/ScreenTests.cs index 56b1ab197a..a6b2dd0ac6 100644 --- a/test/Eto.Test.Wpf/UnitTests/ScreenTests.cs +++ b/test/Eto.Test.Wpf/UnitTests/ScreenTests.cs @@ -33,7 +33,7 @@ public class TestLogicalScreenHelper : LogicalScreenHelper public override sd.Rectangle GetBounds(TestScreen screen) => screen.Bounds; - public override float GetLogicalPixelSize(TestScreen screen) => screen.LogicalPixelSize; + public override float GetLogicalPixelSize(TestScreen screen, bool usePerMonitor = true) => screen.LogicalPixelSize; public override SizeF GetLogicalSize(TestScreen screen) => screen.LogicalSize.ToEto(); } diff --git a/test/Eto.Test/Sections/Behaviors/ScreenSection.cs b/test/Eto.Test/Sections/Behaviors/ScreenSection.cs index 87e3762f0a..a8783f7b7f 100755 --- a/test/Eto.Test/Sections/Behaviors/ScreenSection.cs +++ b/test/Eto.Test/Sections/Behaviors/ScreenSection.cs @@ -132,10 +132,11 @@ public void SavePosition(Window window) private void showWindowsAtCorners_CheckedChanged(object sender, EventArgs e) { CloseCornerWindows(); - Form CreateWindow() + Form CreateWindow(string title) { var form = new Form { + Title = title, WindowStyle = WindowStyle.None, Resizable = false, Size = new Size(100, 100), @@ -157,41 +158,70 @@ Form CreateWindow() return; var restoreForm = new Form { + Title = "RestoreForm", Content = "This form should restore its size and position", Resizable = true, ClientSize = new Size(450, 300), ShowActivated = false }; - restoreForm.LocationChanged += (_, __) => Invalidate(); - restoreForm.SizeChanged += (_, __) => Invalidate(); + var adjacentForm = new Form + { + Title = "AdjacentForm", + ShowActivated = false, + Resizable = false, + WindowStyle = WindowStyle.Utility, + Closeable = false, + Maximizable = false, + Minimizable = false, + Content = new Splitter { Panel1 = new Panel(), Panel2 = new Panel() } + }; + + void SetAdjacentSize() + { + if (adjacentForm == null) + return; + adjacentForm.Location = Point.Round(restoreForm.Content.PointToScreen(restoreForm.Content.Bounds.TopRight)); + adjacentForm.Size = new Size(100, restoreForm.Content.Height); + } + + restoreForm.LocationChanged += (_, __) => { Invalidate(); SetAdjacentSize(); }; + restoreForm.SizeChanged += (_, __) => { Invalidate(); SetAdjacentSize(); }; RestorePosition(restoreForm); - restoreForm.Closing += (_, __) => SavePosition(restoreForm); + restoreForm.Closing += (_, __) => { SavePosition(restoreForm); adjacentForm.Close(); adjacentForm = null; }; + restoreForm.Shown += (_, __) => + { + SetAdjacentSize(); + adjacentForm?.Show(); + }; restoreForm.Show(); cornerWindows.Add(restoreForm); + cornerWindows.Add(adjacentForm); + int i = 0; foreach (var screen in Screen.Screens) { - var topLeft = CreateWindow(); + var topLeft = CreateWindow($"TL{i}"); topLeft.Location = Point.Truncate(screen.Bounds.TopLeft); topLeft.Show(); cornerWindows.Add(topLeft); - var topRight = CreateWindow(); + var topRight = CreateWindow($"TR{i}"); topRight.Location = Point.Truncate(screen.Bounds.TopRight - new Size(topRight.Width, 0)); topRight.Show(); cornerWindows.Add(topRight); - var bottomLeft = CreateWindow(); + var bottomLeft = CreateWindow($"BL{i}"); bottomLeft.Location = Point.Truncate(screen.Bounds.BottomLeft - new Size(0, bottomLeft.Height)); bottomLeft.Show(); cornerWindows.Add(bottomLeft); - var bottomRight = CreateWindow(); + var bottomRight = CreateWindow($"BR{i}"); bottomRight.Location = Point.Truncate(screen.Bounds.BottomRight - bottomLeft.Size); bottomRight.Show(); cornerWindows.Add(bottomRight); + i++; } } @@ -275,6 +305,33 @@ void DrawWindow(Window window) pe.Graphics.FillRectangle(new Color(Colors.LightSkyBlue, 0.8f), windowBounds); } pe.Graphics.DrawRectangle(Colors.White, windowBounds); + + if (window.Content != null) + { + // Console.Write($"Window: {window.Title}, Content.Size: {window.Content.Size}, "); + var contentRect = new RectangleF(window.Content.Size); + var contentRectScreen = window.Content.RectangleToScreen(contentRect); + + var roundTripRect = window.Content.RectangleFromScreen(contentRectScreen); + var roundTripRectScreen = window.Content.RectangleToScreen(roundTripRect); + + pe.Graphics.DrawRectangle(Colors.Gray, (contentRectScreen * scale) + offset); + + if (roundTripRect != contentRect) + { + // note: this can be off by a small amount due to window's dpi scaling + if (Math.Abs(roundTripRect.X - contentRect.X) >= 1 + || Math.Abs(roundTripRect.Y - contentRect.Y) >= 1 + || Math.Abs(roundTripRect.Width - contentRect.Width) >= 1 + || Math.Abs(roundTripRect.Height - contentRect.Height) >= 1) + { + + // round tripping to screen/from screen didn't work! + pe.Graphics.DrawRectangle(Colors.Red, (roundTripRectScreen * scale) + offset); + } + } + } + } DrawWindow(ParentWindow); diff --git a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs index db59f70675..314f26a898 100644 --- a/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs +++ b/test/Eto.Test/UnitTests/Forms/Controls/ControlTests.cs @@ -303,6 +303,8 @@ public void ControlsShouldNotHaveIntrinsicPadding(IControlTypeInfo info public void PointToScreenShouldWorkOnSecondaryScreen() { bool wasClicked = false; + PointF? controlPoint = null; + PointF? rountripPoint = null; Form childForm = null; try { @@ -317,6 +319,16 @@ public void PointToScreenShouldWorkOnSecondaryScreen() form.Shown += (sender, e) => { + controlPoint = PointF.Empty; + var screenPoint = textBox.PointToScreen(PointF.Empty); + rountripPoint = Point.Truncate(textBox.PointFromScreen(screenPoint)); + + if (controlPoint != rountripPoint) + { + form.Close(); + return; + } + childForm = new Form { WindowStyle = WindowStyle.None, @@ -325,9 +337,14 @@ public void PointToScreenShouldWorkOnSecondaryScreen() Resizable = false, BackgroundColor = Colors.Red, Topmost = true, - Location = Point.Round(textBox.PointToScreen(PointF.Empty)), + Location = Point.Round(screenPoint), Size = textBox.Size }; + form.LocationChanged += (sender2, e2) => + { + childForm.Location = Point.Round(textBox.PointToScreen(PointF.Empty)); + childForm.Size = textBox.Size; + }; var b = new Button { Text = "Click Me!" }; b.Click += (sender2, e2) => { @@ -352,6 +369,7 @@ public void PointToScreenShouldWorkOnSecondaryScreen() if (childForm != null) Application.Instance.Invoke(() => childForm.Close()); } + Assert.AreEqual(controlPoint, rountripPoint, "Point could not round trip to screen then back"); Assert.IsTrue(wasClicked, "The test completed without clicking the button"); }