Skip to content

Commit

Permalink
Wpf: Support built-in .NET 4.6.2 per-monitor DPI functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
cwensley committed Sep 23, 2016
1 parent e4dea08 commit 5a3c79b
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 37 deletions.
7 changes: 6 additions & 1 deletion Source/Eto.Test/Eto.Test.Wpf/Eto.Test.Wpf - net45.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,12 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="app.manifest" />
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<None Include="app.manifest">
<SubType>Designer</SubType>
</None>
<None Include="packages.config" />
</ItemGroup>
<Import Project="..\..\Shared\Common.targets" />
Expand Down
7 changes: 7 additions & 0 deletions Source/Eto.Test/Eto.Test.Wpf/Startup.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Eto.Wpf.Forms.Controls;
using System;
using System.Windows.Media;

namespace Eto.Test.Wpf
{
Expand All @@ -10,6 +11,12 @@ static void Main(string[] args)
{
var platform = new Eto.Wpf.Platform();

// optional - enables GDI text display mode
/*
Style.Add<Eto.Wpf.Forms.FormHandler>(null, handler => TextOptions.SetTextFormattingMode(handler.Control, TextFormattingMode.Display));
Style.Add<Eto.Wpf.Forms.DialogHandler>(null, handler => TextOptions.SetTextFormattingMode(handler.Control, TextFormattingMode.Display));
*/

var app = new TestApplication(platform);
app.TestAssemblies.Add(typeof(Startup).Assembly);
app.Run();
Expand Down
8 changes: 8 additions & 0 deletions Source/Eto.Test/Eto.Test.Wpf/app.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup><supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/></startup>
<runtime>
<!-- Enable .NET 4.6.2 fixes for high-dpi even though we are built for .NET 4.5 -->
<AppContextSwitchOverrides value="Switch.MS.Internal.DoNotApplyLayoutRoundingToMarginsAndBorderThickness=false;Switch.System.Windows.DoNotScaleForDpiChanges=false" />
</runtime>
</configuration>
57 changes: 34 additions & 23 deletions Source/Eto.Test/Eto.Test.Wpf/app.manifest
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<asmv1:assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv1="urn:schemas-microsoft-com:asm.v1" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
<security>
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
<!-- UAC Manifest Options
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
If you want to change the Windows User Account Control level replace the
requestedExecutionLevel node with one of the following.
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
Specifying requestedExecutionLevel node will disable file and registry virtualization.
If you want to utilize File and Registry Virtualization for backward
compatibility then delete the requestedExecutionLevel node.
Specifying requestedExecutionLevel element will disable file and registry virtualization.
Remove this element if your application requires this virtualization for backwards
compatibility.
-->
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
</requestedPrivileges>
Expand All @@ -23,28 +23,39 @@

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- A list of all Windows versions that this application is designed to work with.
Windows will automatically select the most compatible environment.-->
<!-- A list of the Windows versions that this application has been tested on and is
is designed to work with. Uncomment the appropriate elements and Windows will
automatically selected the most compatible environment. -->

<!-- If your application is designed to work with Windows Vista, uncomment the following supportedOS node-->
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"></supportedOS>-->
<!-- Windows Vista -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />

<!-- If your application is designed to work with Windows 7, uncomment the following supportedOS node-->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />

<!-- If your application is designed to work with Windows 8, uncomment the following supportedOS node-->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"></supportedOS>
<!-- Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />

<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />

<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />

<!-- If your application is designed to work with Windows 8.1, uncomment the following supportedOS node-->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
</application>
</compatibility>

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
<dpiAware>True/PM</dpiAware>
</asmv3:windowsSettings>
</asmv3:application>
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<!-- dpiAwareness is for Windows 10 RC1, dpiAware is to support older systems -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitor</dpiAwareness>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/PM</dpiAware>
</windowsSettings>
</application>

<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
<dependency>
Expand All @@ -59,5 +70,5 @@
/>
</dependentAssembly>
</dependency>

</asmv1:assembly>
</assembly>
3 changes: 3 additions & 0 deletions Source/Eto.WinForms/Win32.dpi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,8 @@ public static uint GetDpi(this System.Windows.Forms.Screen screen)

[DllImport("shcore.dll")]
public static extern uint SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);

[DllImport("shcore.dll")]
public static extern uint GetProcessDpiAwareness(IntPtr handle, out PROCESS_DPI_AWARENESS awareness);
}
}
62 changes: 51 additions & 11 deletions Source/Eto.Wpf/Forms/PerMonitorDpiHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
Expand All @@ -23,6 +25,33 @@ protected virtual void OnScaleChanged(EventArgs e)
ScaleChanged?.Invoke(this, e);
}

static Lazy<bool> builtInPerMonitorSupported = new Lazy<bool>(() =>
{
Win32.PROCESS_DPI_AWARENESS awareness;
if (!Win32.PerMonitorDpiSupported)
return false;
if (Win32.GetProcessDpiAwareness(IntPtr.Zero, out awareness) != 0)
return false;
if (awareness != Win32.PROCESS_DPI_AWARENESS.PER_MONITOR_DPI_AWARE)
return false;
if (typeof(Window).GetEvent("DpiChanged") == null) // .NET 4.6.2
return false;

// now check if it was disabled specifically (more .NET 4.6 apis)
var contextType = typeof(object).Assembly.GetType("System.AppContext");
if (contextType == null)
return false;
var method = contextType.GetMethod("TryGetSwitch", BindingFlags.Static | BindingFlags.Public, null, new [] { typeof(string), typeof(bool).MakeByRefType() }, null);
if (method == null)
return false;
var args = new object[] { "Switch.System.Windows.DoNotScaleForDpiChanges", null };
method.Invoke(null, args);
var doNotScaleForDpiChanges = (bool)args[1];
return !doNotScaleForDpiChanges;
});

public static bool BuiltInPerMonitorSupported => builtInPerMonitorSupported.Value;

public double Scale { get; private set; } = 1;

public double WpfScale
Expand All @@ -40,41 +69,52 @@ HwndSource HwndSource

void SetScale(uint dpi)
{

var scale = dpi / 96.0;
if (Scale == scale)
return;
Scale = scale;

// set the scale for the window content
var content = window.Content as Visual;
var content = VisualTreeHelper.GetChild(window, 0);
if (content != null)
{
var wpfScale = WpfScale;
var val = content.GetValue(FrameworkElement.LayoutTransformProperty);
content.SetValue(FrameworkElement.LayoutTransformProperty, new ScaleTransform(wpfScale, wpfScale));
}
OnScaleChanged(EventArgs.Empty);
}

static bool setProcessDpi;
static bool? dpiEventEnabled;

public PerMonitorDpiHelper(Window window)
{
this.window = window;
if (!Win32.PerMonitorDpiSupported)
return;

if (!setProcessDpi)
if (dpiEventEnabled == null)
{
Win32.SetProcessDpiAwareness(Win32.PROCESS_DPI_AWARENESS.PER_MONITOR_DPI_AWARE);
setProcessDpi = true;
dpiEventEnabled = false;
Win32.PROCESS_DPI_AWARENESS awareness;
var ret = Win32.GetProcessDpiAwareness(IntPtr.Zero, out awareness);
if (ret == 0 && awareness != Win32.PROCESS_DPI_AWARENESS.PER_MONITOR_DPI_AWARE)
{
//dpiEventEnabled |= awareness == Win32.PROCESS_DPI_AWARENESS.SYSTEM_DPI_AWARE;

ret = Win32.SetProcessDpiAwareness(Win32.PROCESS_DPI_AWARENESS.PER_MONITOR_DPI_AWARE);
dpiEventEnabled |= ret == 0;
}
}

if (this.window.IsLoaded)
AddHook();
else
this.window.Loaded += (o, e) => AddHook();
this.window.Closed += (o, e) => RemoveHook();
if (dpiEventEnabled.Value)
{
if (this.window.IsLoaded)
AddHook();
else
this.window.Loaded += (o, e) => AddHook();
this.window.Closed += (o, e) => RemoveHook();
}
}

void Window_SourceInitialized(object sender, EventArgs e)
Expand Down
21 changes: 19 additions & 2 deletions Source/Eto.Wpf/Forms/WpfWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Eto.Wpf.CustomControls;
using Eto.Wpf.Forms.Menu;
using System.ComponentModel;
using System.Reflection;

namespace Eto.Wpf.Forms
{
Expand Down Expand Up @@ -47,6 +48,8 @@ public override IntPtr NativeHandle
get { return new System.Windows.Interop.WindowInteropHelper(Control).EnsureHandle(); }
}

public static bool EnablePerMonitorDpiSupport { get; set; } = true;

public swc.DockPanel ContentPanel { get { return content; } }

public swc.DockPanel MainPanel { get { return main; } }
Expand Down Expand Up @@ -96,7 +99,7 @@ protected override void Initialize()

void SetupPerMonitorDpi()
{
if (dpiHelper == null && Win32.PerMonitorDpiSupported)
if (EnablePerMonitorDpiSupport && dpiHelper == null && Win32.PerMonitorDpiSupported && !PerMonitorDpiHelper.BuiltInPerMonitorSupported)
{
dpiHelper = new PerMonitorDpiHelper(Control);
Widget.LogicalPixelSizeChanged += (sender, e) => SetMinimumSize();
Expand All @@ -108,6 +111,8 @@ protected override void SetContentScale(bool xscale, bool yscale)
base.SetContentScale(true, true);
}

static EventInfo dpiChangedEvent = typeof(sw.Window).GetEvent("DpiChanged");

public override void AttachEvent(string id)
{
switch (id)
Expand Down Expand Up @@ -161,6 +166,13 @@ public override void AttachEvent(string id)
Control.LocationChanged += (sender, e) => Callback.OnLocationChanged(Widget, EventArgs.Empty);
break;
case Window.LogicalPixelSizeChangedEvent:
if (PerMonitorDpiHelper.BuiltInPerMonitorSupported && dpiChangedEvent != null) // .NET 4.6.2 support!
{
var method = typeof(WpfWindow<TControl, TWidget, TCallback>).GetMethod(nameof(HandleLogicalPixelSizeChanged), BindingFlags.Instance | BindingFlags.NonPublic);
dpiChangedEvent.AddEventHandler(Control, method.CreateDelegate(dpiChangedEvent.EventHandlerType, this));
break;
}
SetupPerMonitorDpi();
if (dpiHelper != null)
dpiHelper.ScaleChanged += (sender, e) => Callback.OnLogicalPixelSizeChanged(Widget, EventArgs.Empty);
break;
Expand All @@ -170,6 +182,11 @@ public override void AttachEvent(string id)
}
}

void HandleLogicalPixelSizeChanged(object sender, EventArgs e)
{
Callback.OnLogicalPixelSizeChanged(Widget, EventArgs.Empty);
}

static bool IsApplicationClosing { get; set; }

public override void OnLoad(EventArgs e)
Expand Down Expand Up @@ -586,7 +603,7 @@ public double WpfScale

public float LogicalPixelSize
{
get { return (float)(dpiHelper?.Scale ?? 1f); }
get { return (float)(dpiHelper?.Scale ?? (sw.PresentationSource.FromVisual(Control)?.CompositionTarget.TransformToDevice.M11 ?? 1.0)); }
}
}
}

0 comments on commit 5a3c79b

Please sign in to comment.