You were right. I tested it out quickly and these forms are not compatible and can’t be cast to each other in a straightforward way. It’s a dead end.
If anyone is interested in the fix to the original question, here it is:
/// <summary>
/// Converts a point relative to a control to screen coordinates, handling DPI scaling
/// </summary>
/// <param name="owner">The control that owns the point</param>
/// <param name="point">The point to convert, relative to the control</param>
/// <returns>The converted screen coordinate point</returns>
public static Point PointToScreen(Control owner, PointF point)
{
var wpfElement = owner.ControlObject as System.Windows.FrameworkElement;
PointF pt;
if (Win32Helpers.IsSystemDpiAware)
{
var logicalPixelSize = owner.ParentWindow.Screen.LogicalPixelSize;
var systemDpi = Win32Helpers.SystemDpi;
point = point / systemDpi * logicalPixelSize;
// Convert to screen coordinates in DPI-aware context
pt = Win32Helpers.ExecuteInDpiAwarenessContext(() => wpfElement.PointToScreen(point.ToWpf())).ToEto();
// WPF does not take into account the location of the element in the form...
var rootVisual = wpfElement.GetVisualParents()
.OfType<System.Windows.UIElement>()
.Last();
var location = wpfElement.TranslatePoint(new System.Windows.Point(0, 0), rootVisual).ToEto();
pt += (location * logicalPixelSize) - (location * systemDpi);
}
else
{
// Simple conversion for non-DPI-aware scenario
pt = Win32Helpers.ExecuteInDpiAwarenessContext(() => wpfElement.PointToScreen(point.ToWpf())).ToEto();
}
return new Point(EtoHelpers.ScreenToLogical(Point.Truncate(pt)));
}
And the Win32Helpers
class
// This backports a PointToScreen fix introduced in Eto 2.7.2
// https://github.com/picoe/Eto/pull/2336
#region DPI Awareness
/// <summary>
/// Specifies the DPI awareness context for a thread
/// </summary>
public enum DPI_AWARENESS_CONTEXT
{
NONE = 0,
UNAWARE = -1,
SYSTEM_AWARE = -2,
PER_MONITOR_AWARE = -3,
PER_MONITOR_AWARE_v2 = -4,
UNAWARE_GDISCALED = -5
}
/// <summary>
/// Specifies the DPI awareness level of a process
/// </summary>
public enum PROCESS_DPI_AWARENESS : uint
{
UNAWARE = 0,
SYSTEM_DPI_AWARE = 1,
PER_MONITOR_DPI_AWARE = 2
}
[DllImport("User32.dll")]
private static extern DPI_AWARENESS_CONTEXT SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT dpiContext);
[DllImport("User32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsProcessDPIAware();
[DllImport("user32.dll")]
public static extern uint GetDpiForSystem();
[DllImport("shcore.dll")]
public static extern uint GetProcessDpiAwareness(IntPtr handle, out PROCESS_DPI_AWARENESS awareness);
[DllImport("gdi32.dll")]
public static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr LoadLibrary(string library);
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern bool FreeLibrary(IntPtr moduleHandle);
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, BestFitMapping = false, SetLastError = true, ExactSpelling = true)]
private static extern IntPtr GetProcAddress(IntPtr moduleHandle, string method);
/// <summary>
/// Gets whether per-monitor thread DPI awareness is supported
/// </summary>
public static bool PerMonitorThreadDpiSupported => perMonitorThreadDpiSupported.Value;
/// <summary>
/// Gets whether per-monitor DPI awareness is supported
/// </summary>
public static bool PerMonitorDpiSupported => perMonitorDpiSupported.Value;
/// <summary>
/// Gets the system DPI scaling factor relative to the default 96 DPI
/// </summary>
/// <remarks>
/// For systems with per-monitor DPI support, uses GetDpiForSystem.
/// For older systems, falls back to GetDeviceCaps with LOGPIXELSX.
/// The result is normalized to a scaling factor where 1.0 represents 96 DPI.
/// </remarks>
public static float SystemDpi => (PerMonitorThreadDpiSupported ? GetDpiForSystem() : (uint)GetDeviceCaps(IntPtr.Zero, 88 /*LOGPIXELSX*/)) / 96f;
/// <summary>
/// Gets whether the current process is system DPI aware
/// </summary>
public static bool IsSystemDpiAware => PerMonitorDpiSupported ?
(GetProcessDpiAwareness(IntPtr.Zero, out var awareness) == 0 && awareness == PROCESS_DPI_AWARENESS.SYSTEM_DPI_AWARE) :
IsProcessDPIAware();
private static readonly Lazy<bool> perMonitorThreadDpiSupported = new Lazy<bool>(() => MethodExists("User32.dll", "SetThreadDpiAwarenessContext"));
private static readonly Lazy<bool> perMonitorDpiSupported = new Lazy<bool>(() => MethodExists("shcore.dll", "SetProcessDpiAwareness"));
/// <summary>
/// Checks if a specific method exists in a Windows DLL
/// </summary>
/// <param name="module">The DLL module name</param>
/// <param name="method">The method name to check</param>
/// <returns>True if the method exists, false otherwise</returns>
public static bool MethodExists(string module, string method)
{
var moduleHandle = LoadLibrary(module);
if (moduleHandle == IntPtr.Zero)
return false;
try
{
return GetProcAddress(moduleHandle, method) != IntPtr.Zero;
}
finally
{
FreeLibrary(moduleHandle);
}
}
/// <summary>
/// Executes a function within a specific DPI awareness context
/// </summary>
/// <typeparam name="T">The return type of the function</typeparam>
/// <param name="func">The function to execute</param>
/// <returns>The result of the function execution</returns>
public static T ExecuteInDpiAwarenessContext<T>(Func<T> func)
{
var oldDpiAwareness = SetThreadDpiAwarenessContextSafe(DPI_AWARENESS_CONTEXT.PER_MONITOR_AWARE_v2);
try
{
return func();
}
finally
{
if (oldDpiAwareness != DPI_AWARENESS_CONTEXT.NONE)
SetThreadDpiAwarenessContextSafe(oldDpiAwareness);
}
}
/// <summary>
/// Safely sets the thread DPI awareness context
/// </summary>
/// <param name="dpiContext">The desired DPI awareness context</param>
/// <returns>The previous DPI awareness context</returns>
public static DPI_AWARENESS_CONTEXT SetThreadDpiAwarenessContextSafe(DPI_AWARENESS_CONTEXT dpiContext)
{
if (!PerMonitorThreadDpiSupported)
return DPI_AWARENESS_CONTEXT.NONE;
return SetThreadDpiAwarenessContext(dpiContext);
}
/// <summary>
/// Gets the visual parent elements of a WPF dependency object
/// </summary>
/// <param name="element">The dependency object to get parents for</param>
/// <returns>An enumerable of parent dependency objects</returns>
public static IEnumerable<System.Windows.DependencyObject> GetVisualParents(this System.Windows.DependencyObject element)
{
var parent = System.Windows.Media.VisualTreeHelper.GetParent(element);
while (parent != null)
{
yield return parent;
parent = System.Windows.Media.VisualTreeHelper.GetParent(parent);
}
}
#endregion