//// 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 "SampleDynamicDpiWindow.h" #include "SampleRichEditControl.h" #include "SampleCustomColorFontButton.h" using namespace std; using namespace Microsoft::WRL; namespace UILayout { static const float TitlePositionX = 20.0F; static const float TitlePositionY = 10.0F; static const float SubTitlePositionY = 60.0F; static const float SmallXOffset = 42.0f; static const float ButtonPositionX = 20.0F; static const float ButtonPositionY = 130.0F; static const float ButtonPpemSize = 24.0F; }; // // Initializes the elements that will be used to draw the application UI. // Unless otherwise specified, each of these elements is intended to // resize, relayout or show different UI in response to DPI changes. // CSampleDynamicDpiWindow::CSampleDynamicDpiWindow() { m_RichEditWindow.SetDefaultDPI(GetDpiForWindow()); WCHAR szPath[MAX_PATH]; DX::ThrowIfFailed(GetModuleFileName(NULL, szPath, MAX_PATH) == 0 ? E_FAIL : S_OK); auto path = wstring(szPath); auto found = path.find_last_of('\\'); m_resourcePath = path.substr(0, found); // DPI string will be used to display information about the DPI mode detected. m_dpiString = (L""); // Create a bitmap that will show a different image based on DPI detected. m_bitmap = make_shared(100.0F, 130.0F); m_engine.AddElement(m_bitmap); // Prepare static text that will resize based on DPI. auto title = make_shared(UILayout::TitlePositionX, UILayout::TitlePositionY, 450.0F, 50.0F, 30.0F); title->SetText(wstring(L"Direct2D Per-monitor DPI Aware")); m_engine.AddElement(title); // Create static text that displays DPI information and will resize based on DPI. m_CurrentDpi = make_shared(UILayout::TitlePositionX, UILayout::SubTitlePositionY, 650.0F, 50.0F, 20.0F); m_engine.AddElement(m_CurrentDpi); // Buttons to change font size of rich edit control. auto button = make_shared(UILayout::ButtonPositionX + 0 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::IncreaseSize); button->SetClickHandler([&]() { m_RichEditWindow.IncrementFontSize(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 1 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::DecreaseSize); button->SetClickHandler([&]() { m_RichEditWindow.DecrementFontSize(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 2 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::BulletList); button->SetClickHandler([&]() { m_RichEditWindow.SetBulleted(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 3 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::NumberList); button->SetClickHandler([&]() { m_RichEditWindow.SetNumbered(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 4 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Bold); button->SetClickHandler([&]() { m_RichEditWindow.SetFormatBold(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 5 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Underline); button->SetClickHandler([&]() { m_RichEditWindow.SetFormatUnderline(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 6 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Italics); button->SetClickHandler([&]() { m_RichEditWindow.SetFormatItalic(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 7 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Highlight); button->SetClickHandler([&]() { m_RichEditWindow.SetFormatBackgroundColor(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 8 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::SetColor); button->SetClickHandler([&]() { m_RichEditWindow.SetFormatColor(); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 9 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::LeftJustify); button->SetClickHandler([&]() { m_RichEditWindow.SetAlignment(PFA_LEFT); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 10 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::CenterJustify); button->SetClickHandler([&]() { m_RichEditWindow.SetAlignment(PFA_CENTER); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 11 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::RightJustify); button->SetClickHandler([&]() { m_RichEditWindow.SetAlignment(PFA_RIGHT); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 12 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Outdent); button->SetClickHandler([&]() { m_RichEditWindow.IncrementIndent(-720); }); m_engine.AddElement(button); button = make_shared(UILayout::ButtonPositionX + 13 * UILayout::SmallXOffset, UILayout::ButtonPositionY, UILayout::ButtonPpemSize, CustomGlyphId::Indent); button->SetClickHandler([&]() { m_RichEditWindow.IncrementIndent(720); }); m_engine.AddElement(button); } CSampleDynamicDpiWindow::~CSampleDynamicDpiWindow() { m_deviceResources->RegisterDeviceNotify(nullptr); } // // This method initializes member variables and objects required by this class. // This method will be called once base class initialize functionality completes. // HRESULT CSampleDynamicDpiWindow::Initialize( _In_ RECT bounds, _In_ std::wstring title ) { HRESULT hr = S_OK; // First run Initialize method for base class. hr = CSampleDesktopWindow::Initialize(bounds, title); // Create and initialize rich edit control as a child of this window. if (SUCCEEDED(hr)) { hr = m_RichEditWindow.Initialize(this->m_hWnd); } // Register this class as a DeviceLostHandler. This enables the app to release and recreate the // device specific resources associated with the app. m_deviceResources->RegisterDeviceNotify(this); return hr; } // // This method is called when the m_deviceResources class determines that there has been an error in the // underlying graphics hardware device. All device dependent resources should be released on this event. // void CSampleDynamicDpiWindow::OnDeviceLost() { ReleaseDeviceResources(); } // // This method is called when the m_deviceResources class is recovering from an error in the // underlying graphics hardware device. A new device has been created and all device dependent // resources should be recreated on this event. // void CSampleDynamicDpiWindow::OnDeviceRestored() { CreateDeviceResources(); } void CSampleDynamicDpiWindow::CreateDeviceIndependentResources() { // Load the custom fonts. m_deviceResources->LoadFonts(m_resourcePath); m_engine.CreateDeviceIndependentResources(m_deviceResources); } void CSampleDynamicDpiWindow::ReleaseDeviceIndependentResources() { m_engine.ReleaseDeviceIndependentResources(); } void CSampleDynamicDpiWindow::CreateDeviceResources() { m_engine.CreateDeviceResources(); } void CSampleDynamicDpiWindow::ReleaseDeviceResources() { m_engine.ReleaseDeviceResources(); } // // Draw client area. // Function overrides virtual member function to // draw member components defined in DPI window class. // void CSampleDynamicDpiWindow::Draw() { HRESULT hr = S_OK; BOOL fResult = FALSE; RECT rect = { }; auto d2dContext = m_deviceResources->GetD2DDeviceContext(); auto sizeF = d2dContext->GetSize(); float deviceResourcesDPI = m_deviceResources->GetDpi(); // Prepare elements for drawing. if (m_dpiString.length() == 0) { RebuildCurrentDpiString(); m_CurrentDpi->SetText(m_dpiString); } // Set layout position for bitmap relative to the window. float left = (sizeF.width * m_deviceResources->GetDpi() / 96.0F) - m_bitmap->GetWidth() - 35.0F; m_bitmap->SetPosition(left, 25.0F); // Set Rich edit control relative to bitmap. // GetClientRect returns the client area in physical pixels. fResult = ::GetClientRect(m_hWnd, &rect); // Position the offset of the rich edit window in DIPs. rect.top += (LONG) (180 * (deviceResourcesDPI / 96)); if (fResult) { hr = m_RichEditWindow.PositionRelativeToRect(rect); } // For each member element, issue drawing instructions. if (SUCCEEDED(hr)) { m_engine.Draw(); } } void CSampleDynamicDpiWindow::OnPointerUp( _In_ float x, _In_ float y ) { m_engine.PointerUp(x, y); } void CSampleDynamicDpiWindow::OnPointerDown( _In_ float x, _In_ float y ) { m_engine.PointerDown(x, y); } void CSampleDynamicDpiWindow::OnPointerUpdate( _In_ float x, _In_ float y ) { m_engine.PointerUpdate(x, y); } void CSampleDynamicDpiWindow::OnDisplayChange() { RECT current; ZeroMemory(¤t, sizeof(current)); GetWindowRect(¤t); float oldDpix, oldDpiy, newDpi; m_deviceResources->GetD2DDeviceContext()->GetDpi(&oldDpix, &oldDpiy); newDpi = GetDpiForWindow(); if (oldDpix != newDpi) { auto newRect = CalcWindowRectNewDpi(current, oldDpix, newDpi); SetNewDpi(newDpi); m_RichEditWindow.OnDPIChanged(newDpi); SetWindowPos(0, &newRect, 0); m_dpiString.clear(); } } void CSampleDynamicDpiWindow::OnDpiChange( _In_ int dpi, _In_ LPRECT newRect ) { float changedDpi = static_cast(dpi); SetNewDpi(changedDpi); m_RichEditWindow.OnDPIChanged(changedDpi); SetWindowPos(0, newRect, 0); m_dpiString.clear(); } RECT CSampleDynamicDpiWindow::CalcWindowRectNewDpi( _In_ RECT oldRect, _In_ float oldDpi, _In_ float newDpi ) { float oldWidth = static_cast(oldRect.right - oldRect.left); float oldHeight = static_cast(oldRect.bottom - oldRect.top); int newWidth = static_cast(oldWidth * newDpi / oldDpi); int newHeight = static_cast(oldHeight * newDpi / oldDpi); RECT newRect = { oldRect.left, oldRect.top, newWidth, oldRect.top + newHeight }; return newRect; } // // This method gathers DPI information detected and stores this // in the m_dpiString object. // The m_dpiString is used to display this information as part // of the application UI. // void CSampleDynamicDpiWindow::RebuildCurrentDpiString() { UINT dpix(0), dpiy(0); auto monitor = MonitorFromWindow(m_hWnd,MONITOR_DEFAULTTONEAREST); DX::ThrowIfFailed(GetDpiForMonitor(monitor, MONITOR_DPI_TYPE::MDT_EFFECTIVE_DPI, &dpix, &dpiy)); auto windowDpi = static_cast(dpix); auto hdc = ::GetDC(NULL); auto desktopDpi = GetDeviceCaps(hdc,LOGPIXELSX); ::ReleaseDC(NULL,hdc); m_dpiString.append(wstring(L"Windows 8.1 DPI mode: ")); wstringstream currentWindowDpi; currentWindowDpi << (windowDpi/96.0F*100); m_dpiString.append(currentWindowDpi.str()); m_dpiString.append(L"% "); m_dpiString.append(wstring(L"Desktop System DPI:")); float desktopScaleFactorFloat = (static_cast(desktopDpi) / 96.0F) * 100; wstringstream desktopScaleFactor; desktopScaleFactor << desktopScaleFactorFloat; m_dpiString.append(desktopScaleFactor.str()); m_dpiString.append(L"%"); } float CSampleDynamicDpiWindow::GetDpiForWindow() { auto monitor = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST); UINT newDpiX; UINT newDpiY; if (FAILED(GetDpiForMonitor(monitor, MONITOR_DPI_TYPE::MDT_EFFECTIVE_DPI, &newDpiX, &newDpiY))) { newDpiX = 96; newDpiX = 96; } return static_cast(newDpiX); }