Skip to content

Commit 406bbb0

Browse files
authored
Merge pull request #4573 from mansellan/4555
Fix for High-DPI bug
2 parents 77fa481 + 42a33ed commit 406bbb0

File tree

10 files changed

+224
-3
lines changed

10 files changed

+224
-3
lines changed

Rubberduck.Core/Common/IOperatingSystem.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,6 @@
33
public interface IOperatingSystem
44
{
55
void ShowFolder(string folderPath);
6+
WindowsVersion? GetOSVersion();
67
}
78
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace Rubberduck.Common.WinAPI
4+
{
5+
/// <summary>
6+
/// Native functions from SHCore.dll
7+
/// </summary>
8+
public static class SHCore
9+
{
10+
/// <summary>
11+
/// Sets the DPI awareness level of the current process.
12+
/// </summary>
13+
/// <param name="awareness">DPI awareness level.</param>
14+
/// <returns>HRESULT of S_OK, E_INVALIDARG or E_ACCESSDENIED.</returns>
15+
/// <remarks>
16+
/// Only the first DPI awareness call made by a process will have effect, subsequent calls are disregarded.
17+
/// Thus, calling this method before WPF loads will override the default WPF DPI awareness behavior.
18+
/// </remarks>
19+
[DllImport("SHCore.dll", SetLastError = true)]
20+
public static extern bool SetProcessDpiAwareness(PROCESS_DPI_AWARENESS awareness);
21+
}
22+
23+
/// <summary>
24+
/// Describes DPI awareness of a process.
25+
/// </summary>
26+
public enum PROCESS_DPI_AWARENESS
27+
{
28+
/// <summary>
29+
/// Process is not DPI aware.
30+
/// </summary>
31+
Process_DPI_Unaware = 0,
32+
33+
/// <summary>
34+
/// Process is aware of the System DPI (monitor 1).
35+
/// </summary>
36+
Process_System_DPI_Aware = 1,
37+
38+
/// <summary>
39+
/// Process is aware of the DPI of individual monitors.
40+
/// </summary>
41+
Process_Per_Monitor_DPI_Aware = 2
42+
}
43+
44+
}
Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,56 @@
1-
using System.Diagnostics;
1+
using System;
2+
using System.Diagnostics;
23
using System.IO;
4+
using System.Management;
5+
using System.Runtime.CompilerServices;
6+
using NLog;
37

48
namespace Rubberduck.Common
59
{
610
public sealed class WindowsOperatingSystem : IOperatingSystem
711
{
12+
public static readonly ILogger _Logger = LogManager.GetCurrentClassLogger();
13+
814
public void ShowFolder(string folderPath)
915
{
1016
if (!Directory.Exists(folderPath))
1117
{
1218
Directory.CreateDirectory(folderPath);
1319
}
1420

15-
using (Process.Start(folderPath)) { }
21+
using (Process.Start(folderPath))
22+
{
23+
}
24+
}
25+
26+
27+
public WindowsVersion? GetOSVersion()
28+
{
29+
try
30+
{
31+
var wmiEnum = new ManagementObjectSearcher("root\\CIMV2", "SELECT Version FROM Win32_OperatingSystem")
32+
.Get().GetEnumerator();
33+
wmiEnum.MoveNext();
34+
var versionString = wmiEnum.Current.Properties["Version"].Value as string;
35+
36+
var versionElements = versionString?.Split('.');
37+
38+
if (versionElements?.Length >= 3 &&
39+
int.TryParse(versionElements[0], out var major) &&
40+
int.TryParse(versionElements[1], out var minor) &&
41+
int.TryParse(versionElements[2], out var build))
42+
{
43+
return new WindowsVersion(major, minor, build);
44+
}
45+
}
46+
catch (Exception exception)
47+
{
48+
_Logger.Warn(exception, "Unable to determine OS Version");
49+
return null;
50+
}
51+
_Logger.Warn("Unable to determine OS Version");
52+
return null;
1653
}
1754
}
1855
}
56+
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
using System;
2+
3+
namespace Rubberduck.Common
4+
{
5+
public struct WindowsVersion : IComparable<WindowsVersion>, IEquatable<WindowsVersion>
6+
{
7+
public static readonly WindowsVersion Windows10 = new WindowsVersion(10, 0, 10240);
8+
public static readonly WindowsVersion Windows81 = new WindowsVersion(6, 3, 9200);
9+
public static readonly WindowsVersion Windows8 = new WindowsVersion(6, 2, 9200);
10+
public static readonly WindowsVersion Windows7_SP1 = new WindowsVersion(6, 1, 7601);
11+
public static readonly WindowsVersion WindowsVista_SP2 = new WindowsVersion(6, 0, 6002);
12+
13+
public WindowsVersion(int major, int minor, int build)
14+
{
15+
Major = major;
16+
Minor = minor;
17+
Build = build;
18+
}
19+
20+
public int Major { get; }
21+
public int Minor { get; }
22+
public int Build { get; }
23+
24+
25+
public int CompareTo(WindowsVersion other)
26+
{
27+
var majorComparison = Major.CompareTo(other.Major);
28+
if (majorComparison != 0)
29+
{
30+
return majorComparison;
31+
}
32+
33+
var minorComparison = Minor.CompareTo(other.Minor);
34+
35+
return minorComparison != 0
36+
? minorComparison
37+
: Build.CompareTo(other.Build);
38+
}
39+
40+
public bool Equals(WindowsVersion other)
41+
{
42+
return Major == other.Major && Minor == other.Minor && Build == other.Build;
43+
}
44+
45+
public override bool Equals(object other)
46+
{
47+
return other is WindowsVersion otherVersion && Equals(otherVersion);
48+
}
49+
50+
public override int GetHashCode()
51+
{
52+
unchecked
53+
{
54+
var hashCode = Major;
55+
hashCode = (hashCode * 397) ^ Minor;
56+
hashCode = (hashCode * 397) ^ Build;
57+
return hashCode;
58+
}
59+
}
60+
61+
public static bool operator ==(WindowsVersion os1, WindowsVersion os2)
62+
{
63+
return os1.CompareTo(os2) == 0;
64+
}
65+
66+
public static bool operator !=(WindowsVersion os1, WindowsVersion os2)
67+
{
68+
return os1.CompareTo(os2) != 0;
69+
}
70+
71+
public static bool operator <(WindowsVersion os1, WindowsVersion os2)
72+
{
73+
return os1.CompareTo(os2) < 0;
74+
}
75+
76+
public static bool operator >(WindowsVersion os1, WindowsVersion os2)
77+
{
78+
return os1.CompareTo(os2) > 0;
79+
}
80+
81+
public static bool operator <=(WindowsVersion os1, WindowsVersion os2)
82+
{
83+
return os1.CompareTo(os2) <= 0;
84+
}
85+
86+
public static bool operator >=(WindowsVersion os1, WindowsVersion os2)
87+
{
88+
return os1.CompareTo(os2) >= 0;
89+
}
90+
}
91+
}

Rubberduck.Core/Rubberduck.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
<Reference Include="PresentationCore" />
3030
<Reference Include="PresentationFramework" />
3131
<Reference Include="PresentationFramework.Aero" />
32+
<Reference Include="System.Management" />
3233
<Reference Include="System.Net.Http" />
3334
<Reference Include="System.Windows.Forms" />
3435
<Reference Include="System.Xaml" />

Rubberduck.Core/Settings/GeneralSettings.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ public interface IGeneralSettings
1818
int AutoSavePeriod { get; set; }
1919
bool UserEditedLogLevel { get; set; }
2020
int MinimumLogLevel { get; set; }
21+
bool SetDpiUnaware { get; set; }
2122
List<ExperimentalFeatures> EnableExperimentalFeatures { get; set; }
2223
}
2324

@@ -56,6 +57,8 @@ public int MinimumLogLevel
5657
}
5758
}
5859

60+
public bool SetDpiUnaware { get; set; }
61+
5962
public List<ExperimentalFeatures> EnableExperimentalFeatures { get; set; } = new List<ExperimentalFeatures>();
6063

6164
public GeneralSettings()
@@ -79,7 +82,8 @@ public bool Equals(GeneralSettings other)
7982
UserEditedLogLevel == other.UserEditedLogLevel &&
8083
MinimumLogLevel == other.MinimumLogLevel &&
8184
EnableExperimentalFeatures.All(a => other.EnableExperimentalFeatures.Contains(a)) &&
82-
EnableExperimentalFeatures.Count == other.EnableExperimentalFeatures.Count;
85+
EnableExperimentalFeatures.Count == other.EnableExperimentalFeatures.Count &&
86+
SetDpiUnaware == other.SetDpiUnaware;
8387
}
8488
}
8589
}

Rubberduck.Core/UI/Settings/GeneralSettings.xaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@
131131
IsChecked="{Binding CheckVersionAtStartup}" />
132132
<CheckBox Margin="5,0,0,5" Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=GeneralSettings_CompileBeforeParse}"
133133
IsChecked="{Binding CompileBeforeParse}" />
134+
<CheckBox Margin="5,0,0,5" Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=GeneralSettings_SetDpiUnaware}"
135+
IsEnabled="{Binding SetDpiUnawareEnabled}" IsChecked="{Binding SetDpiUnaware}" />
134136
<StackPanel Orientation="Horizontal"/>
135137

136138
<Label Content="{Resx ResxName=Rubberduck.Resources.RubberduckUI, Key=GeneralSettings_MinimumLogLevelLabel}" FontWeight="SemiBold" />

Rubberduck.Core/UI/Settings/GeneralSettingsViewModel.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,29 @@ public bool CompileBeforeParse
158158
}
159159
}
160160

161+
private bool _setDpiUnaware;
162+
public bool SetDpiUnaware
163+
{
164+
get => _setDpiUnaware;
165+
set
166+
{
167+
if (_setDpiUnaware != value)
168+
{
169+
_setDpiUnaware = value;
170+
OnPropertyChanged();
171+
}
172+
}
173+
}
174+
175+
public bool SetDpiUnawareEnabled
176+
{
177+
get
178+
{
179+
var osVersion = _operatingSystem.GetOSVersion();
180+
return osVersion != null && osVersion >= WindowsVersion.Windows81;
181+
}
182+
}
183+
161184
private bool SynchronizeVBESettings()
162185
{
163186
if (!_messageBox.ConfirmYesNo(RubberduckUI.GeneralSettings_CompileBeforeParse_WarnCompileOnDemandEnabled,
@@ -243,6 +266,7 @@ private Rubberduck.Settings.GeneralSettings GetCurrentGeneralSettings()
243266
CanShowSplash = ShowSplashAtStartup,
244267
CanCheckVersion = CheckVersionAtStartup,
245268
CompileBeforeParse = CompileBeforeParse,
269+
SetDpiUnaware = SetDpiUnaware,
246270
IsSmartIndenterPrompted = _indenterPrompted,
247271
IsAutoSaveEnabled = AutoSaveEnabled,
248272
AutoSavePeriod = AutoSavePeriod,
@@ -259,6 +283,7 @@ private void TransferSettingsToView(IGeneralSettings general, IHotkeySettings ho
259283
ShowSplashAtStartup = general.CanShowSplash;
260284
CheckVersionAtStartup = general.CanCheckVersion;
261285
CompileBeforeParse = general.CompileBeforeParse;
286+
SetDpiUnaware = general.SetDpiUnaware;
262287
_indenterPrompted = general.IsSmartIndenterPrompted;
263288
AutoSaveEnabled = general.IsAutoSaveEnabled;
264289
AutoSavePeriod = general.AutoSavePeriod;

Rubberduck.Main/Extension.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Windows.Threading;
1313
using Castle.Windsor;
1414
using NLog;
15+
using Rubberduck.Common.WinAPI;
1516
using Rubberduck.Root;
1617
using Rubberduck.Resources;
1718
using Rubberduck.Resources.Registration;
@@ -160,6 +161,17 @@ private void InitializeAddIn()
160161
catch (CultureNotFoundException)
161162
{
162163
}
164+
try
165+
{
166+
if (_initialSettings.SetDpiUnaware)
167+
{
168+
SHCore.SetProcessDpiAwareness(PROCESS_DPI_AWARENESS.Process_DPI_Unaware);
169+
}
170+
}
171+
catch (Exception)
172+
{
173+
Debug.Assert(false, "Could not set DPI awareness.");
174+
}
163175
}
164176
else
165177
{

Rubberduck.Resources/RubberduckUI.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1295,4 +1295,7 @@ NOTE: Restart is required for the setting to take effect.</value>
12951295
<data name="TodoMarkerTodo" xml:space="preserve">
12961296
<value>TODO</value>
12971297
</data>
1298+
<data name="GeneralSettings_SetDpiUnaware" xml:space="preserve">
1299+
<value>Set DPI Unaware</value>
1300+
</data>
12981301
</root>

0 commit comments

Comments
 (0)