Fix for the DPI issue described in #26

This commit is contained in:
Robin Krom
2020-08-05 23:51:49 +02:00
parent 850f625f23
commit de6a2b2e25
12 changed files with 197 additions and 26 deletions

View File

@@ -21,6 +21,7 @@
//
// THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE
using System.Diagnostics.Contracts;
using System.Windows.Interop;
namespace Hardcodet.Wpf.TaskbarNotification.Interop
@@ -30,12 +31,18 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// </summary>
public static class SystemInfo
{
/// <summary>
/// Make sure the initial value is calculated at the first access
/// </summary>
static SystemInfo()
{
UpdateFactors();
UpdateDpiFactors();
}
internal static void UpdateFactors()
/// <summary>
/// This calculates the current DPI values and sets this into the DpiFactorX/DpiFactorY values
/// </summary>
internal static void UpdateDpiFactors()
{
using (var source = new HwndSource(new HwndSourceParameters()))
{
@@ -59,5 +66,20 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// Returns the DPI Y Factor
/// </summary>
public static double DpiFactorY { get; private set; } = 1;
/// <summary>
/// Scale the supplied point to the current DPI settings
/// </summary>
/// <param name="point"></param>
/// <returns>Point</returns>
[Pure]
public static Point ScaleWithDpi(this Point point)
{
return new Point
{
X = (int)(point.X / DpiFactorX),
Y = (int)(point.Y / DpiFactorY)
};
}
}
}

View File

@@ -51,13 +51,6 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// </summary>
/// <param name="point">Point</param>
/// <returns>Point</returns>
public static Point GetDeviceCoordinates(Point point)
{
return new Point
{
X = (int)(point.X / SystemInfo.DpiFactorX),
Y = (int)(point.Y / SystemInfo.DpiFactorY)
};
}
public static Point GetDeviceCoordinates(Point point) => point.ScaleWithDpi();
}
}

View File

@@ -9,7 +9,6 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// </summary>
public delegate IntPtr WindowProcedureHandler(IntPtr hWnd, uint uMsg, IntPtr wParam, IntPtr lParam);
/// <summary>
/// Win API WNDCLASS struct - represents a single window.
/// Used to receive window messages.

View File

@@ -6,10 +6,10 @@
// modify it under the terms of the Code Project Open License (CPOL);
// either version 1.0 of the License, or (at your option) any later
// version.
//
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -70,7 +70,7 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// <summary>
/// Handle for the message window.
/// </summary>
/// </summary>
internal IntPtr MessageWindowHandle { get; private set; }
/// <summary>
@@ -223,9 +223,21 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// <param name="lParam">Provides information about the event.</param>
private void ProcessWindowMessage(uint msg, IntPtr wParam, IntPtr lParam)
{
if (msg != CallbackMessageId) return;
// Check if it was a callback message
if (msg != CallbackMessageId)
{
// It was not a callback message, but make sure it's not something else we need to process
switch ((WindowsMessages) msg)
{
case WindowsMessages.WM_DPICHANGED:
Debug.WriteLine("DPI Change");
SystemInfo.UpdateDpiFactors();
break;
}
return;
}
var message = (WindowsMessages) lParam.ToInt32();
var message = (WindowsMessages)lParam.ToInt32();
Debug.WriteLine("Got message " + message);
switch (message)
{
@@ -338,7 +350,7 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);

View File

@@ -6,10 +6,10 @@
// modify it under the terms of the Code Project Open License (CPOL);
// either version 1.0 of the License, or (at your option) any later
// version.
//
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -37,8 +37,8 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// <summary>
/// Notifies a window that the user clicked the right mouse button (right-clicked) in the window.
/// See <a href="https://docs.microsoft.com/en-us/windows/win32/menurc/wm-contextmenu">WM_CONTEXTMENU message</a>
///
/// In case of a notify icon:
///
/// In case of a notify icon:
/// If a user selects a notify icon's shortcut menu with the keyboard, the Shell now sends the associated application a WM_CONTEXTMENU message. Earlier versions send WM_RBUTTONDOWN and WM_RBUTTONUP messages.
/// See <a href="https://docs.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shell_notifyiconw">Shell_NotifyIcon function</a>
/// </summary>
@@ -57,7 +57,7 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// Posted when the user presses the left mouse button while the cursor is in the client area of a window.
/// If the mouse is not captured, the message is posted to the window beneath the cursor.
/// Otherwise, the message is posted to the window that has captured the mouse.
///
///
/// See <a href="https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttondown">WM_LBUTTONDOWN message</a>
/// </summary>
WM_LBUTTONDOWN = 0x0201,
@@ -134,6 +134,13 @@ namespace Hardcodet.Wpf.TaskbarNotification.Interop
/// </summary>
WM_MBUTTONDBLCLK = 0x0209,
/// <summary>
/// Sent when the effective dots per inch (dpi) for a window has changed.
/// The DPI is the scale factor for a window.
/// There are multiple events that can cause the DPI to change.
/// </summary>
WM_DPICHANGED = 0x02e0,
/// <summary>
/// Used to define private messages for use by private window classes, usually of the form WM_USER+x, where x is an integer value.
/// </summary>

View File

@@ -6,10 +6,10 @@
// modify it under the terms of the Code Project Open License (CPOL);
// either version 1.0 of the License, or (at your option) any later
// version.
//
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -561,7 +561,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
HasDropShadow = false,
BorderThickness = new Thickness(0),
Background = System.Windows.Media.Brushes.Transparent,
// setting the
// setting the
StaysOpen = true,
Content = TrayToolTip
};
@@ -1049,7 +1049,7 @@ namespace Hardcodet.Wpf.TaskbarNotification
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);

View File

@@ -5,6 +5,7 @@
<RootNamespace>Samples</RootNamespace>
<AssemblyTitle>Sample Project</AssemblyTitle>
<Product>Sample Project</Product>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NotifyIconWpf\NotifyIconWpf.csproj" />

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<!-- Make sure windows Vista and above treat Greenshot as "DPI Aware" See: http://msdn.microsoft.com/en-us/library/ms633543.aspx -->
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">false</gdiScaling>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<maxversiontested Id="10.0.18363.0"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!--Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
</application>
</compatibility>
<!-- Set UAC level to "asInvoker" and disable registry virtualization -->
<asmv2:trustInfo>
<asmv2:security>
<asmv3:requestedPrivileges>
<!--
The presence of the "requestedExecutionLevel" node will disable
file and registry virtualization on Vista. See:
http://msdn.microsoft.com/en-us/library/aa965884%28v=vs.85%29.aspx
Use the "level" attribute to specify the User Account Control level:
asInvoker = Never prompt for elevation
requireAdministrator = Always prompt for elevation
highestAvailable = Prompt for elevation when started by administrator,
but do not prompt for administrator password when started by
standard user.
-->
<asmv3:requestedExecutionLevel level="asInvoker" />
</asmv3:requestedPrivileges>
</asmv2:security>
</asmv2:trustInfo>
</assembly>

View File

@@ -5,6 +5,7 @@
<RootNamespace>Windowless_Sample</RootNamespace>
<AssemblyTitle>Windowless Sample</AssemblyTitle>
<Product>Windowless Sample</Product>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NotifyIconWpf\NotifyIconWpf.csproj" />

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<!-- Make sure windows Vista and above treat Greenshot as "DPI Aware" See: http://msdn.microsoft.com/en-us/library/ms633543.aspx -->
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">false</gdiScaling>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<maxversiontested Id="10.0.18363.0"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!--Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
</application>
</compatibility>
<!-- Set UAC level to "asInvoker" and disable registry virtualization -->
<asmv2:trustInfo>
<asmv2:security>
<asmv3:requestedPrivileges>
<!--
The presence of the "requestedExecutionLevel" node will disable
file and registry virtualization on Vista. See:
http://msdn.microsoft.com/en-us/library/aa965884%28v=vs.85%29.aspx
Use the "level" attribute to specify the User Account Control level:
asInvoker = Never prompt for elevation
requireAdministrator = Always prompt for elevation
highestAvailable = Prompt for elevation when started by administrator,
but do not prompt for administrator password when started by
standard user.
-->
<asmv3:requestedExecutionLevel level="asInvoker" />
</asmv3:requestedPrivileges>
</asmv2:security>
</asmv2:trustInfo>
</assembly>

View File

@@ -6,6 +6,7 @@
<AssemblyTitle>WindowsFormsSample</AssemblyTitle>
<Product>WindowsFormsSample</Product>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<ApplicationManifest>app.manifest</ApplicationManifest>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\NotifyIconWpf\NotifyIconWpf.csproj" />

View File

@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv2="urn:schemas-microsoft-com:asm.v2" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<!-- Make sure windows Vista and above treat Greenshot as "DPI Aware" See: http://msdn.microsoft.com/en-us/library/ms633543.aspx -->
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">false</gdiScaling>
</asmv3:windowsSettings>
</asmv3:application>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- Windows 10 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
<maxversiontested Id="10.0.18363.0"/>
<!-- Windows 8.1 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />
<!--Windows 8 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />
<!-- Windows 7 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />
</application>
</compatibility>
<!-- Set UAC level to "asInvoker" and disable registry virtualization -->
<asmv2:trustInfo>
<asmv2:security>
<asmv3:requestedPrivileges>
<!--
The presence of the "requestedExecutionLevel" node will disable
file and registry virtualization on Vista. See:
http://msdn.microsoft.com/en-us/library/aa965884%28v=vs.85%29.aspx
Use the "level" attribute to specify the User Account Control level:
asInvoker = Never prompt for elevation
requireAdministrator = Always prompt for elevation
highestAvailable = Prompt for elevation when started by administrator,
but do not prompt for administrator password when started by
standard user.
-->
<asmv3:requestedExecutionLevel level="asInvoker" />
</asmv3:requestedPrivileges>
</asmv2:security>
</asmv2:trustInfo>
</assembly>