//// 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 "SampleRichEditControl.h" CSampleRichEditWindow::CSampleRichEditWindow() : // Initialize scale factor to 100% m_numerator(100), m_denominator(100), // Initialize for loading rich edit module m_hmodRichEdit(nullptr), m_hWnd(nullptr) { } CSampleRichEditWindow::~CSampleRichEditWindow() { if (m_hWnd) { DestroyWindow(m_hWnd); m_hWnd = nullptr; } if (m_hmodRichEdit) { FreeLibrary(m_hmodRichEdit); m_hmodRichEdit = nullptr; } } // // This method creates and initializes a child window for the Rich Edit control. // Notes: // The rich edit window will be a child window to the hWnd passed in. // The window will be positioned using default child window values, // and rely on the caller to [re-]position the window as needed. // HRESULT CSampleRichEditWindow::Initialize( _In_ HWND parentHWD ) { HRESULT hr = S_OK; BOOL fResult = FALSE; // Load MSFTEDIT.dll. This library is required for Rich Edit v4.1 or higher. m_hmodRichEdit = LoadLibrary(L"msftedit.dll"); if (!m_hmodRichEdit) { hr = HRESULT_FROM_WIN32(GetLastError()); } // Create the rich edit window. if (SUCCEEDED(hr)) { m_hWnd = CreateWindowEx(WS_EX_LAYERED, // Make this a layered window. MSFTEDIT_CLASS, // Associate with the rich edit class. // Default text to use in window. L"Enter text here.", // Define as a visible child window. // Also set flags to provide a multi-line rich edit experience. WS_VISIBLE | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS | WS_VSCROLL | ES_WANTRETURN | ES_MULTILINE, // Use default x, y, width and height as these will be set later. CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, parentHWD, // Set parent as requested. NULL, // Do not attach to a menu. GetModuleHandleW(NULL), // Associate to the calling application. NULL // No additional structure needed to create this window. ); } if (!m_hWnd) { hr = HRESULT_FROM_WIN32(GetLastError()); } // Set the rich edit control to be semi-transparent. if (SUCCEEDED(hr)) { fResult = SetLayeredWindowAttributes(m_hWnd, 0, 255, LWA_ALPHA); if (!fResult) { hr = HRESULT_FROM_WIN32(GetLastError()); } } // Set this window to be on top of its siblings. if (SUCCEEDED(hr)) { fResult = SetWindowPos(m_hWnd, HWND_TOP, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE ); if (!fResult) { hr = HRESULT_FROM_WIN32(GetLastError()); } } // Populate the rich edit control. if (SUCCEEDED(hr)) { hr = ResetDefaultContent(); } // Cleanup code. if (FAILED(hr)) { if (m_hWnd) { fResult = DestroyWindow(m_hWnd); m_hWnd = nullptr; } if (m_hmodRichEdit) { fResult = FreeLibrary(m_hmodRichEdit); m_hmodRichEdit = nullptr; } } return hr; } // // Position the rich edit control based on coordinates provided. // HRESULT CSampleRichEditWindow::Position( _In_ UINT x, _In_ UINT y, _In_ UINT Width, _In_ UINT Height ) { HRESULT hr = S_OK; BOOL fResult; // Move the Rich Edit window to its location. fResult = MoveWindow(m_hWnd, // Move myself x, // New position of the left side of the window y, // New position of the top side of the window Width, // New width Height, // New height TRUE); // Repaint the window if (!fResult) { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // // Position the rich edit control relative to the dpi and RECT provided. // HRESULT CSampleRichEditWindow::PositionRelativeToRect( _In_ RECT relativeRect ) { // Store target dimenions. UINT childX; // Position of the left side of the window. UINT childY; // Position of the top side of the window. UINT childcX; // Width of the window. UINT childcY; // Height of the window. // Define offsets relative to the RECT provided. UINT parentHeight = 0; UINT offsetTop = 5; UINT offsetBottom = 5; UINT offsetRight = 10; UINT offsetLeft = 5; // Set the bounds for the child window based on offsets from RECT. parentHeight = relativeRect.bottom - relativeRect.top; childX = relativeRect.left + offsetLeft; childY = relativeRect.top + offsetTop; childcX = relativeRect.right - offsetRight; childcY = parentHeight - offsetTop - offsetBottom; return Position(childX, childY, childcX, childcY); } // // Respond to an explicit DPI change, including updates to the zoom factor as needed. // As the rich edit control uses a numerator/denominator pair that must not exceed 64, // this class will provide numerator/denominator that will generate an implied fraction // between 0 and 1. // HRESULT CSampleRichEditWindow::OnDPIChanged( _In_ float dpi ) { HRESULT hr = S_OK; // Only take action if DPI is different from what control has stored. if (m_currentDpi != dpi) { m_numerator = (UINT) ((dpi / default_dpi) * 100.0F); hr = ApplyZoomFactor(); } if (SUCCEEDED(hr)) { // Update current DPI. m_currentDpi = dpi; } return hr; } void CSampleRichEditWindow::SetDefaultDPI(float dpi) { m_currentDpi = dpi; default_dpi = dpi; } HRESULT CSampleRichEditWindow::IncrementFontSize() { return UpdateFontSize(CD_INCREMENT); } HRESULT CSampleRichEditWindow::DecrementFontSize() { return UpdateFontSize(CD_DECREMENT); } // // Enables caller to toggle the bold setting of the text in the control. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetFormatBold() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); if (((charFormat.dwMask & CFM_BOLD) == CFM_BOLD) && ((charFormat.dwEffects & CFE_BOLD) == CFE_BOLD)) { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_BOLD; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } else { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_BOLD; charFormat.dwEffects |= CFE_BOLD; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to toggle the italic setting of the text in the control. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetFormatItalic() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); if (((charFormat.dwMask & CFM_ITALIC) == CFM_ITALIC) && ((charFormat.dwEffects & CFE_ITALIC) == CFE_ITALIC)) { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_ITALIC; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } else { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_ITALIC; charFormat.dwEffects |= CFE_ITALIC; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to toggle the underline setting of the text in the control. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetFormatUnderline() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); if (((charFormat.dwMask & CFM_UNDERLINE) == CFM_UNDERLINE) && ((charFormat.dwEffects & CFE_UNDERLINE) == CFE_UNDERLINE)) { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_UNDERLINE; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } else { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_UNDERLINE; charFormat.dwEffects |= CFE_UNDERLINE; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the color of the selected text in the control. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetFormatColor() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); if (((charFormat.dwMask & CFM_COLOR) == CFM_COLOR) && (((charFormat.dwEffects & CFE_AUTOCOLOR) != CFE_AUTOCOLOR) && (charFormat.crTextColor==RGB(255,0,0)))) { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_COLOR; lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } else { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_COLOR; charFormat.crTextColor = RGB(255, 0, 0); lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the background color of the selected text in the control. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetFormatBackgroundColor() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); if (((charFormat.dwMask & CFM_BACKCOLOR) == CFM_BACKCOLOR) && (((charFormat.dwEffects & CFE_AUTOCOLOR) != CFE_AUTOCOLOR) && (charFormat.crBackColor == RGB(255, 255, 0)))) { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_BACKCOLOR; charFormat.crBackColor = RGB(255, 255, 255); lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } else { ZeroMemory(&charFormat, sizeof(charFormat)); charFormat.cbSize = sizeof(charFormat); charFormat.dwMask = CFM_BACKCOLOR; charFormat.crBackColor = RGB(255, 255, 0); lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &charFormat); } hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the alignment of the selected paragraph. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::SetAlignment( _In_ WORD wAlignment ) { HRESULT hr = S_OK; LRESULT lr; PARAFORMAT2 paraFormat; ZeroMemory(¶Format, sizeof(paraFormat)); paraFormat.cbSize = sizeof(paraFormat); paraFormat.dwMask = PFM_ALIGNMENT; paraFormat.wAlignment = wAlignment; lr = SendMessage(m_hWnd, EM_SETPARAFORMAT, 0, (LPARAM) ¶Format); hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the selected paragraph(s) as bulleted. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETPARAFORMAT message. // HRESULT CSampleRichEditWindow::SetBulleted() { HRESULT hr = S_OK; LRESULT lr; PARAFORMAT2 paraFormat; ZeroMemory(¶Format, sizeof(paraFormat)); paraFormat.cbSize = sizeof(paraFormat); lr = SendMessage(m_hWnd, EM_GETPARAFORMAT, 0, (LPARAM) ¶Format); if (((paraFormat.dwMask & PFM_NUMBERING) == PFM_NUMBERING) && (paraFormat.wNumbering == PFN_BULLET)) { paraFormat.wNumbering = 0; } else { paraFormat.wNumbering = PFN_BULLET; } paraFormat.dwMask = PFM_NUMBERING; lr = SendMessage(m_hWnd, EM_SETPARAFORMAT, 0, (LPARAM) ¶Format); hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the selected paragraph(s) as numbered. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETPARAFORMAT message. // HRESULT CSampleRichEditWindow::SetNumbered() { HRESULT hr = S_OK; LRESULT lr; PARAFORMAT2 paraFormat; ZeroMemory(¶Format, sizeof(paraFormat)); paraFormat.cbSize = sizeof(paraFormat); lr = SendMessage(m_hWnd, EM_GETPARAFORMAT, 0, (LPARAM) ¶Format); if (((paraFormat.dwMask & PFM_NUMBERING) == PFM_NUMBERING) && (paraFormat.wNumbering == 7)) { paraFormat.wNumbering = 0; } else { paraFormat.wNumbering = 7; } paraFormat.dwMask = PFM_NUMBERING; lr = SendMessage(m_hWnd, EM_SETPARAFORMAT, 0, (LPARAM) ¶Format); hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // Enables caller to set the alignment of the selected paragraph. // In order to format the text in the control, we need to construct // the parameters required to deliver the EM_SETCHARFORMAT message. // HRESULT CSampleRichEditWindow::IncrementIndent( _In_ INT lIncrement ) { HRESULT hr = S_OK; LRESULT lr; PARAFORMAT2 paraFormat; ZeroMemory(¶Format, sizeof(paraFormat)); paraFormat.cbSize = sizeof(paraFormat); lr = SendMessage(m_hWnd, EM_GETPARAFORMAT, 0, (LPARAM) ¶Format); paraFormat.dwMask = PFM_STARTINDENT; paraFormat.dxStartIndent = (paraFormat.dxStartIndent + lIncrement < 0) ? 0 : paraFormat.dxStartIndent + lIncrement; lr = SendMessage(m_hWnd, EM_SETPARAFORMAT, 0, (LPARAM) ¶Format); hr = HRESULT_FROM_WIN32(GetLastError()); return hr; } // // This method restores the contents of the rich edit control to defaults. // Default configuration includes text content, as well as formatting. // HRESULT CSampleRichEditWindow::ResetDefaultContent() { HRESULT hr = S_OK; PCWSTR richEditText = NULL; // Set text to display in rich edit control from resource file. if (SUCCEEDED(hr)) { hr = GetResourceString(IDS_LOREM_IPSUM, &richEditText); } if (SUCCEEDED(hr)) { hr = SetText(richEditText); } // Initialize the default format for the rich edit control. if (SUCCEEDED(hr)) { hr = SetDefaultFormat(); } // Make sure control is formatted correctly for current DPI. if (SUCCEEDED(hr)) { hr = ApplyZoomFactor(); } return hr; } // // This method retrieves a string based on the requested resource ID. // Once the string is converted from resource ID to string, this method calls // the overload to apply the text to the rich edit control. // HRESULT CSampleRichEditWindow::GetResourceString( _In_ UINT stringID, _Outptr_ PCWSTR * stringText ) { HRESULT hr = S_OK; BOOL fResult = FALSE; int stringSize = 0; HMODULE hMod = nullptr; // Get a non-ref counted handle to the current module. // Module handle is required to use LoadString. fResult = GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, NULL, &hMod); if (!fResult) { hr = HRESULT_FROM_WIN32(GetLastError()); } // Per MSDN documentation, we can retrieve a read-only pointer by // passing in a PWSTR and a zero-size buffer. if (fResult) { stringSize = LoadStringW(hMod, stringID, (PWSTR)stringText, 0); } if (stringSize <= 0) { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // // This method explictly applies the text requested to the // rich edit control. // HRESULT CSampleRichEditWindow::SetText( _In_ PCWSTR stringText ) { HRESULT hr = S_OK; LRESULT lr; ASSERT(m_hWnd); ASSERT(stringText); lr = SendMessage(m_hWnd, WM_SETTEXT, (WPARAM) NULL, (LPARAM) stringText); if (!lr) { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // // This method resets the rich edit control to default values // for text formatting. This method will use the default values for rich edit, // except for bold and font color, which will be set explicitly. // HRESULT CSampleRichEditWindow::SetDefaultFormat() { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); // Gather the default settings that exist on the rich edit control. charFormat.cbSize = sizeof(CHARFORMAT2) ; lr = SendMessage(m_hWnd, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM) &charFormat); if (lr) { // Set text to be bold. charFormat.dwMask = CFM_BOLD; // Set text color to be black. charFormat.dwMask |= CFM_COLOR; charFormat.crTextColor = RGB(0, 0, 0); // Set font size using inline conversion to twips. charFormat.dwMask |= CFM_SIZE; charFormat.yHeight = (LONG) (DEFAULT_FONT_PT * TWIPS_PER_PT); // Notify the rich edit control of the requested format. lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM) &charFormat); } else { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // // This method provides an internal implementation to explicitly // increment or decrement font size. // HRESULT CSampleRichEditWindow::UpdateFontSize( _In_ CHANGE_DIRECTION direction ) { HRESULT hr = S_OK; LRESULT lr; CHARFORMAT2 charFormat; ZeroMemory(&charFormat, sizeof(charFormat)); // Gather the settings that exist on the rich edit control. charFormat.cbSize = sizeof(charFormat); lr = SendMessageW(m_hWnd, EM_GETCHARFORMAT, SCF_DEFAULT, (LPARAM) &charFormat); // Note that only the font size is changing. if (lr) { charFormat.dwMask = CFM_SIZE; if (direction == CD_INCREMENT) { charFormat.yHeight = (LONG) (charFormat.yHeight * FONT_STEP); } else { charFormat.yHeight = (LONG) (charFormat.yHeight / FONT_STEP); } // Notify rich edit control of new format. lr = SendMessage(m_hWnd, EM_SETCHARFORMAT, SCF_ALL, (LPARAM) &charFormat); } else { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; } // // Set the zoom factor based on values stored in rich edit member variables. // Once the zoom factor is applied, update the window to redraw. // HRESULT CSampleRichEditWindow::ApplyZoomFactor() { HRESULT hr = S_OK; LRESULT lr; lr = SendMessage(m_hWnd, EM_SETZOOM, (WPARAM) m_numerator, (LPARAM) m_denominator); if (!lr) { hr = HRESULT_FROM_WIN32(GetLastError()); } return hr; }