diff --git a/.gitignore b/.gitignore index 4ce6fdd..3ae2e29 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,7 @@ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ +./NativeHelpers/Debug/ # Visual Studio 2015/2017 cache/options directory .vs/ diff --git a/NativeHelpers/AssemblyInfo.cpp b/NativeHelpers/AssemblyInfo.cpp new file mode 100644 index 0000000..60db916 --- /dev/null +++ b/NativeHelpers/AssemblyInfo.cpp @@ -0,0 +1,40 @@ +#include "pch.h" + +using namespace System; +using namespace System::Reflection; +using namespace System::Runtime::CompilerServices; +using namespace System::Runtime::InteropServices; +using namespace System::Security::Permissions; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly:AssemblyTitleAttribute("NativeHelpers")]; +[assembly:AssemblyDescriptionAttribute("")]; +[assembly:AssemblyConfigurationAttribute("")]; +[assembly:AssemblyCompanyAttribute("")]; +[assembly:AssemblyProductAttribute("NativeHelpers")]; +[assembly:AssemblyCopyrightAttribute("Copyright (c)")]; +[assembly:AssemblyTrademarkAttribute("")]; +[assembly:AssemblyCultureAttribute("")]; + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the value or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly:AssemblyVersionAttribute("1.0.*")]; + +[assembly:ComVisible(false)]; + +[assembly:CLSCompliantAttribute(true)]; + +//[assembly:SecurityPermission(SecurityAction::RequestMinimum, UnmanagedCode = true)]; diff --git a/NativeHelpers/NativeHelpers.vcxproj b/NativeHelpers/NativeHelpers.vcxproj new file mode 100644 index 0000000..b6501ca --- /dev/null +++ b/NativeHelpers/NativeHelpers.vcxproj @@ -0,0 +1,108 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {938E65D0-EC9C-4181-87CA-A43D4747610D} + v4.5 + ManagedCProj + NativeHelpers + + + + + + + + + NativeHelpers + 10.0.18362.0 + + + + DynamicLibrary + true + v142 + true + Unicode + + + DynamicLibrary + false + v142 + true + Unicode + + + + + + + + + + + + + true + + + false + + + + Level4 + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + Use + pch.h + + + true + + + + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + Use + pch.h + + + + true + + + + + + + + + + + + + + + + + + + + Create + Create + + + + + + \ No newline at end of file diff --git a/NativeHelpers/NativeHelpers.vcxproj.filters b/NativeHelpers/NativeHelpers.vcxproj.filters new file mode 100644 index 0000000..c57cbda --- /dev/null +++ b/NativeHelpers/NativeHelpers.vcxproj.filters @@ -0,0 +1,38 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/NativeHelpers/PerMonitorDPIHelpers.cpp b/NativeHelpers/PerMonitorDPIHelpers.cpp new file mode 100644 index 0000000..8c8ccc0 --- /dev/null +++ b/NativeHelpers/PerMonitorDPIHelpers.cpp @@ -0,0 +1,75 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + + +//Main helper class that provides helper functions used by the PerMonitorDPIWindow class + +#include "pch.h" +#include "PerMonitorDPIHelpers.h" + + +// Returns the DPI awareness of the current process + +PROCESS_DPI_AWARENESS NativeHelpers::PerMonitorDPIHelper::GetPerMonitorDPIAware() +{ + + PROCESS_DPI_AWARENESS awareness; + HANDLE hProcess; + hProcess = OpenProcess(PROCESS_ALL_ACCESS, false, GetCurrentProcessId()); + auto result = GetProcessDpiAwareness(hProcess, &awareness); + + if (S_OK != result) + { + throw gcnew System::Exception(L"Unable to read process DPI level"); + } + return awareness; +} + +//Sets the current process as Per_Monitor_DPI_Aware. Returns True if the process was marked as Per_Monitor_DPI_Aware + +BOOL NativeHelpers::PerMonitorDPIHelper::SetPerMonitorDPIAware() +{ + auto result = SetProcessDpiAwareness(PROCESS_DPI_AWARENESS::PROCESS_PER_MONITOR_DPI_AWARE); + + if (S_OK != result) + { + return FALSE; + } + return TRUE; +} + +//Returns the DPI of the window handle passed in the parameter + +double NativeHelpers::PerMonitorDPIHelper::GetDpiForWindow(IntPtr hwnd) +{ + return GetDpiForHwnd(static_cast(hwnd.ToPointer())); +} + +double NativeHelpers::PerMonitorDPIHelper::GetDpiForHwnd(HWND hWnd) +{ + auto monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST); + UINT newDpiX; + UINT newDpiY; + if (FAILED(GetDpiForMonitor(monitor, MONITOR_DPI_TYPE::MDT_EFFECTIVE_DPI, &newDpiX, &newDpiY))) + { + newDpiX = 96; + newDpiY = 96; + } + return ((double) newDpiX); +} + + +//Returns the system DPI + +double NativeHelpers::PerMonitorDPIHelper::GetSystemDPI() +{ + int newDpiX(0); + auto hDC = GetDC(NULL); + newDpiX = GetDeviceCaps(hDC, LOGPIXELSX); + ReleaseDC(NULL, hDC); + return (double)newDpiX; +} diff --git a/NativeHelpers/PerMonitorDPIHelpers.h b/NativeHelpers/PerMonitorDPIHelpers.h new file mode 100644 index 0000000..b413ed5 --- /dev/null +++ b/NativeHelpers/PerMonitorDPIHelpers.h @@ -0,0 +1,30 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + +#pragma once +#include "pch.h" + +using namespace System; + +namespace NativeHelpers +{ + public ref class PerMonitorDPIHelper + { + public: + + static BOOL SetPerMonitorDPIAware(); + + static PROCESS_DPI_AWARENESS GetPerMonitorDPIAware(); + + static double GetDpiForWindow(IntPtr hwnd); + + static double GetSystemDPI(); + + private: + static double GetDpiForHwnd(HWND hWnd); + }; +} diff --git a/NativeHelpers/PerMonitorDPIWindow.cpp b/NativeHelpers/PerMonitorDPIWindow.cpp new file mode 100644 index 0000000..cb9adc8 --- /dev/null +++ b/NativeHelpers/PerMonitorDPIWindow.cpp @@ -0,0 +1,155 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + +#include "pch.h" +#include "PerMonitorDPIWindow.h" + +namespace NativeHelpers +{ + + //Constructor; sets the current process as Per_Monitor_DPI_Aware + PerMonitorDPIWindow::PerMonitorDPIWindow(void) + { + Loaded += gcnew System::Windows::RoutedEventHandler(this, &NativeHelpers::PerMonitorDPIWindow::OnLoaded); + if (PerMonitorDPIHelper::SetPerMonitorDPIAware()) + { + m_perMonitorEnabled = true; + } + else + { + throw gcnew System::Exception(L"Enabling Per-monitor DPI Failed. Do you have [assembly: DisableDpiAwareness] in your assembly manifest [AssemblyInfo.cs]?"); + } + + } + + PerMonitorDPIWindow::~PerMonitorDPIWindow() + { + } + + //OnLoaded Handler: Adjusts the window size and graphics and text size based on current DPI of the Window + + void PerMonitorDPIWindow::OnLoaded(Object^ , RoutedEventArgs^ ) + { + // WPF has already scaled window size, graphics and text based on system DPI. In order to scale the window based on monitor DPI, update the + // window size, graphics and text based on monitor DPI. For example consider an application with size 600 x 400 in device independent pixels + // - Size in device independent pixels = 600 x 400 + // - Size calculated by WPF based on system/WPF DPI = 192 (scale factor = 2) + // - Expected size based on monitor DPI = 144 (scale factor = 1.5) + + // Similarly the graphics and text are updated updated by applying appropriate scale transform to the top level node of the WPF application + + // Important Note: This method overwrites the size of the window and the scale transform of the root node of the WPF Window. Hence, + // this sample may not work "as is" if + // - The size of the window impacts other portions of the application like this WPF Window being hosted inside another application. + // - The WPF application that is extending this class is setting some other transform on the root visual; the sample may + // overwrite some other transform that is being applied by the WPF application itself. + + + + + if (m_perMonitorEnabled) + { + m_source = (HwndSource^) PresentationSource::FromVisual((Visual^) this); + HwndSourceHook^ hook = gcnew HwndSourceHook(this, &PerMonitorDPIWindow::HandleMessages); + m_source->AddHook(hook); + + + //Calculate the DPI used by WPF; this is same as the system DPI. + + m_wpfDPI = 96.0 * m_source->CompositionTarget->TransformToDevice.M11; + + //Get the Current DPI of the monitor of the window. + + m_currentDPI = NativeHelpers::PerMonitorDPIHelper::GetDpiForWindow(m_source->Handle); + + //Calculate the scale factor used to modify window size, graphics and text + m_scaleFactor = m_currentDPI / m_wpfDPI; + + //Update Width and Height based on the on the current DPI of the monitor + + Width = Width * m_scaleFactor; + Height = Height * m_scaleFactor; + + //Update graphics and text based on the current DPI of the monitor + + UpdateLayoutTransform(m_scaleFactor); + } + } + + //Called when the DPI of the window changes. This method adjusts the graphics and text size based on the new DPI of the window + void PerMonitorDPIWindow::OnDPIChanged() + { + m_scaleFactor = m_currentDPI / m_wpfDPI; + UpdateLayoutTransform(m_scaleFactor); + DPIChanged(this, EventArgs::Empty); + } + + + void PerMonitorDPIWindow::UpdateLayoutTransform(double scaleFactor) + { + // Adjust the rendering graphics and text size by applying the scale transform to the top level visual node of the Window + if (m_perMonitorEnabled) + { + auto child = GetVisualChild(0); + if (m_scaleFactor != 1.0) { + ScaleTransform^ dpiScale = gcnew ScaleTransform(scaleFactor, scaleFactor); + child->SetValue(Window::LayoutTransformProperty, dpiScale); + } + else + { + child->SetValue(Window::LayoutTransformProperty, nullptr); + } + } + } + + + // Message handler of the Per_Monitor_DPI_Aware window. The handles the WM_DPICHANGED message and adjusts window size, graphics and text + // based on the DPI of the monitor. The window message provides the new window size (lparam) and new DPI (wparam) + + IntPtr PerMonitorDPIWindow::HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool% ) + { + double oldDpi; + switch (msg) + { + case WM_DPICHANGED: + LPRECT lprNewRect = (LPRECT)lParam.ToPointer(); + SetWindowPos(static_cast(hwnd.ToPointer()), 0, lprNewRect->left, lprNewRect->top, lprNewRect->right - lprNewRect->left, lprNewRect->bottom - lprNewRect->top, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE); + oldDpi = m_currentDPI; + m_currentDPI = static_cast(LOWORD(wParam.ToPointer())); + if (oldDpi != m_currentDPI) + { + OnDPIChanged(); + } + break; + } + return IntPtr::Zero; + } + + System::String^ PerMonitorDPIWindow::GetCurrentDpiConfiguration() + { + System::Text::StringBuilder^ stringBuilder = gcnew System::Text::StringBuilder(); + + auto awareness = NativeHelpers::PerMonitorDPIHelper::GetPerMonitorDPIAware(); + + auto systemDpi = NativeHelpers::PerMonitorDPIHelper::GetSystemDPI(); + + switch (awareness) + { + case PROCESS_DPI_AWARENESS::PROCESS_DPI_UNAWARE: + + stringBuilder->AppendFormat(gcnew System::String(L"Application is DPI Unaware. Using {0} DPI."), systemDpi); + break; + case PROCESS_DPI_AWARENESS::PROCESS_SYSTEM_DPI_AWARE: + stringBuilder->AppendFormat(gcnew System::String(L"Application is System DPI Aware. Using System DPI:{0}."), systemDpi); + break; + case PROCESS_DPI_AWARENESS::PROCESS_PER_MONITOR_DPI_AWARE: + stringBuilder->AppendFormat(gcnew System::String(L"Application is Per-Monitor DPI Aware. Using \tmonitor DPI = {0} \t(System DPI = {1})."), m_currentDPI, systemDpi); + break; + } + return stringBuilder->ToString(); + } +} \ No newline at end of file diff --git a/NativeHelpers/PerMonitorDPIWindow.h b/NativeHelpers/PerMonitorDPIWindow.h new file mode 100644 index 0000000..6cce719 --- /dev/null +++ b/NativeHelpers/PerMonitorDPIWindow.h @@ -0,0 +1,81 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + + +#pragma once +#include "PerMonitorDPIHelpers.h" +using namespace System; +using namespace System::Windows::Interop; +using namespace System::Windows; +using namespace System::Windows::Media; +using namespace System::Security::Permissions; + +namespace NativeHelpers +{ + public ref class PerMonitorDPIWindow : public Window + { + public: + PerMonitorDPIWindow(void); + + event EventHandler^ DPIChanged; + + System::String^ GetCurrentDpiConfiguration(); + + property double CurrentDPI + { + double get() + { + return m_currentDPI; + } + } + + property double WpfDPI + { + double get() + { + return m_wpfDPI; + } + void set(double value) + { + m_wpfDPI = value; + } + } + + property double ScaleFactor + { + double get() + { + return m_scaleFactor; + } + } + + protected: + ~PerMonitorDPIWindow(); + + [EnvironmentPermissionAttribute(SecurityAction::LinkDemand, Unrestricted = true)] + void OnLoaded(Object^ sender, RoutedEventArgs^ args); + + void OnDPIChanged(); + + void UpdateLayoutTransform(double scaleFactor); + + IntPtr HandleMessages(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, bool %handled); + + private: + System::Boolean m_perMonitorEnabled; + + double m_currentDPI; + + double m_systemDPI; + + double m_wpfDPI; + + double m_scaleFactor; + + System::Windows::Interop::HwndSource^ m_source; + }; +} diff --git a/NativeHelpers/pch.cpp b/NativeHelpers/pch.cpp new file mode 100644 index 0000000..72dd2fa --- /dev/null +++ b/NativeHelpers/pch.cpp @@ -0,0 +1,13 @@ +//// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF +//// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO +//// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A +//// PARTICULAR PURPOSE. +//// +//// Copyright (c) Microsoft Corporation. All rights reserved + + +// stdafx.cpp : source file that includes just the standard includes +// NativeHelpers.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "pch.h" \ No newline at end of file diff --git a/NativeHelpers/pch.h b/NativeHelpers/pch.h new file mode 100644 index 0000000..a52e8bd --- /dev/null +++ b/NativeHelpers/pch.h @@ -0,0 +1,12 @@ +// stdafx.h : include file for standard system include files, +// or project specific include files that are used frequently, +// but are changed infrequently + +#pragma once + +#include +#include + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "shcore.lib") +#pragma comment(lib, "Gdi32.lib") diff --git a/Text-Grab.sln b/Text-Grab.sln index 1b7b64e..7d690bd 100644 --- a/Text-Grab.sln +++ b/Text-Grab.sln @@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Text-Grab", "Text-Grab\Text EndProject Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "Text-Grab-Package", "Text-Grab-Package\Text-Grab-Package.wapproj", "{CE37F469-F629-4B49-84BA-37A1DA192C60}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NativeHelpers", "NativeHelpers\NativeHelpers.vcxproj", "{938E65D0-EC9C-4181-87CA-A43D4747610D}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -71,6 +73,18 @@ Global {CE37F469-F629-4B49-84BA-37A1DA192C60}.Release|x86.ActiveCfg = Release|x86 {CE37F469-F629-4B49-84BA-37A1DA192C60}.Release|x86.Build.0 = Release|x86 {CE37F469-F629-4B49-84BA-37A1DA192C60}.Release|x86.Deploy.0 = Release|x86 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|Any CPU.ActiveCfg = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|ARM.ActiveCfg = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|ARM64.ActiveCfg = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|x64.ActiveCfg = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|x86.ActiveCfg = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Debug|x86.Build.0 = Debug|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|Any CPU.ActiveCfg = Release|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|ARM.ActiveCfg = Release|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|ARM64.ActiveCfg = Release|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|x64.ActiveCfg = Release|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|x86.ActiveCfg = Release|Win32 + {938E65D0-EC9C-4181-87CA-A43D4747610D}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Text-Grab/AssemblyInfo.cs b/Text-Grab/AssemblyInfo.cs index 8b5504e..ef1df71 100644 --- a/Text-Grab/AssemblyInfo.cs +++ b/Text-Grab/AssemblyInfo.cs @@ -1,4 +1,5 @@ using System.Windows; +using System.Windows.Media; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located @@ -8,3 +9,4 @@ using System.Windows; //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] +[assembly: DisableDpiAwareness] \ No newline at end of file diff --git a/Text-Grab/MainWindow.xaml b/Text-Grab/MainWindow.xaml index fc7fdbf..9856e21 100644 --- a/Text-Grab/MainWindow.xaml +++ b/Text-Grab/MainWindow.xaml @@ -1,10 +1,11 @@ - - + diff --git a/Text-Grab/MainWindow.xaml.cs b/Text-Grab/MainWindow.xaml.cs index 24e1be5..9561f68 100644 --- a/Text-Grab/MainWindow.xaml.cs +++ b/Text-Grab/MainWindow.xaml.cs @@ -15,13 +15,14 @@ using Windows.Globalization; using Windows.Media.Ocr; using Windows.System.UserProfile; using BitmapDecoder = Windows.Graphics.Imaging.BitmapDecoder; +using NativeHelpers; namespace Text_Grab { /// /// Interaction logic for MainWindow.xaml /// - public partial class MainWindow : Window + public partial class MainWindow : PerMonitorDPIWindow { public MainWindow() { diff --git a/Text-Grab/Text-Grab.csproj b/Text-Grab/Text-Grab.csproj index dbe34f5..33af222 100644 --- a/Text-Grab/Text-Grab.csproj +++ b/Text-Grab/Text-Grab.csproj @@ -7,12 +7,17 @@ true true t_ICON.ico + Text_Grab.App x86 + + x86 + + tlbimp @@ -27,6 +32,10 @@ + + + + True