www.pudn.com > ThemedRichEdit_Redis.zip > RichEditThemed.cpp
////////////////////////////////////////////////////////////////////////////// // Themed RichEdit Class Helper for Visual C++ // // Created by Patchou, originally for Messenger Plus! // // // // RichEditThemed.cpp - Last Revision: 27/11/2004 // // Purpose: adds Windows XP theme support to Rich Edit controls // // // // This library is free as in NOT under GPL license. You can use it in any // // program, in the binary form of your choice, and without being asked to // // add unnecessary files to your redistribution package. Free source // // should neither be restrained by a 6 page license nor should it force // // you to share the source of any part of a program you do not wish to // // share. The meaning of the word freedom is the same for everyone, no // // license should have the pretension of redefining it. If you want to // // share some source code, please use a similar copyright notice, your // // peers will be grateful. // // // // Free redistribution and usage of the content of this file is permitted // // as long as the above statement is kept, unchanged, at its original // // location. // // // ////////////////////////////////////////////////////////////////////////////// #include "stdafx.h" #include "RichEditThemed.h" //This STL map is used to keep track of all the instances of the class std::mapCRichEditThemed::m_aInstances; //The theme-specific functions are imported at runtime for backward compatibility reasons //A nicer alternative if you are using Visual C++ 7.0 or above is the use of the "delay load" mechanism HMODULE CRichEditThemed::m_hUxTheme = NULL; HTHEME (WINAPI *CRichEditThemed::pOpenThemeData)(HWND, LPCWSTR) = NULL; HRESULT (WINAPI *CRichEditThemed::pCloseThemeData)(HTHEME) = NULL; HRESULT (WINAPI *CRichEditThemed::pDrawThemeBackground)(HTHEME, HDC, int, int, const RECT*, const RECT *) = NULL; HRESULT (WINAPI *CRichEditThemed::pGetThemeBackgroundContentRect)(HTHEME, HDC, int, int, const RECT *, RECT *) = NULL; BOOL (WINAPI *CRichEditThemed::pIsThemeActive)(HTHEME, int, int) = NULL; HRESULT (WINAPI *CRichEditThemed::pDrawThemeParentBackground)(HWND, HDC, RECT*) = NULL; BOOL (WINAPI *CRichEditThemed::pIsThemeBackgroundPartiallyTransparent)(HTHEME, int, int) = NULL; // // This function is the one and only public function your program must use // It needs to be called during the creation/initialisation of the parent window ////////////////////////////////////////////////////////////////////////////// bool CRichEditThemed::Attach(HWND hRichEdit) { if(IsWindow(hRichEdit)) //It is your responsibility to ensure that the handle parameter is, indeed, a richedit window { //Prevent double subclassing if(CRichEditThemed::m_aInstances.find(hRichEdit) == CRichEditThemed::m_aInstances.end()) { //If this function fails, this version of Windows doesn't support themes if(InitLibrary()) { //Note: the object will be automatically deleted when the richedit control dies CRichEditThemed *obj = new CRichEditThemed(hRichEdit); return true; } } } return false; } // ////////////////////////////////////////////////////////////////////////////// CRichEditThemed::CRichEditThemed(HWND hRichEdit) : m_hRichEdit(hRichEdit), m_bThemedBorder(false) { //Subclass the richedit control, this way, the caller doesn't have to relay the messages by itself m_aInstances[hRichEdit] = this; m_pOriginalWndProc = (WNDPROC)SetWindowLong(hRichEdit, GWL_WNDPROC, (LONG)&RichEditStyledProc); //Check the current state of the richedit control ZeroMemory(&m_rcClientPos, sizeof(RECT)); VerifyThemedBorderState(); } // ////////////////////////////////////////////////////////////////////////////// CRichEditThemed::~CRichEditThemed() { //Fail-safe: don't restore the original wndproc pointer if it has been modified since the creation of this object if((WNDPROC)GetWindowLong(m_hRichEdit, GWL_WNDPROC) == &RichEditStyledProc) SetWindowLong(m_hRichEdit, GWL_WNDPROC, (LONG)m_pOriginalWndProc); //Unload the UxTheme library if it is not needed anymore by a control m_aInstances.erase(m_hRichEdit); if(m_aInstances.empty()) { pOpenThemeData = NULL; pCloseThemeData = NULL; pDrawThemeBackground = NULL; pGetThemeBackgroundContentRect = NULL; pIsThemeActive = NULL; pDrawThemeParentBackground = NULL; pIsThemeBackgroundPartiallyTransparent = NULL; FreeLibrary(m_hUxTheme); m_hUxTheme = NULL; } } // ////////////////////////////////////////////////////////////////////////////// bool CRichEditThemed::InitLibrary() { //Are we already initialised? if(pOpenThemeData && pCloseThemeData && pDrawThemeBackground && pGetThemeBackgroundContentRect && pIsThemeActive && pDrawThemeParentBackground && pIsThemeBackgroundPartiallyTransparent) return true; //Try to get the function pointers of the UxTheme library if(!m_hUxTheme) { m_hUxTheme = LoadLibrary(_T("UxTheme.dll")); if(!m_hUxTheme) return false; } pOpenThemeData = (HTHEME (WINAPI *)(HWND, LPCWSTR))GetProcAddress(m_hUxTheme, "OpenThemeData"); pCloseThemeData = (HRESULT (WINAPI *)(HTHEME))GetProcAddress(m_hUxTheme, "CloseThemeData"); pDrawThemeBackground = (HRESULT (WINAPI *)(HTHEME, HDC, int, int, const RECT*, const RECT *))GetProcAddress(m_hUxTheme, "DrawThemeBackground"); pGetThemeBackgroundContentRect = (HRESULT (WINAPI *)(HTHEME, HDC, int, int, const RECT *, RECT *))GetProcAddress(m_hUxTheme, "GetThemeBackgroundContentRect"); pIsThemeActive = (BOOL (WINAPI *)())GetProcAddress(m_hUxTheme, "IsThemeActive"); pDrawThemeParentBackground = (HRESULT (WINAPI *)(HWND, HDC, RECT*))GetProcAddress(m_hUxTheme, "DrawThemeParentBackground"); pIsThemeBackgroundPartiallyTransparent = (BOOL (WINAPI *)(HTHEME, int, int))GetProcAddress(m_hUxTheme, "IsThemeBackgroundPartiallyTransparent"); if(pOpenThemeData && pCloseThemeData && pDrawThemeBackground && pGetThemeBackgroundContentRect && pIsThemeActive && pDrawThemeParentBackground && pIsThemeBackgroundPartiallyTransparent) return true; else return false; } // ////////////////////////////////////////////////////////////////////////////// LRESULT CALLBACK CRichEditThemed::RichEditStyledProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { //This function is the subclassed winproc of the richedit control //It is used to monitor the actions of the control, in a nice and transparent manner std::map ::iterator itCurInstance = m_aInstances.find(hwnd); if(itCurInstance != m_aInstances.end()) { //A winproc is always static, this one is common to all the richedit controls managed by this class //We need to get a pointer to the object controlling the richedit which is receiving this message CRichEditThemed *pObj = itCurInstance->second; //If you get a compilation error here, it is probably because _WIN32_WINNT is not defined to at least 0x0501 if(uMsg == WM_THEMECHANGED || uMsg == WM_STYLECHANGED) { //Someone just changed the style of the richedit control or the user changed its theme //Make sure the control is being kept up to date by verifying its state pObj->VerifyThemedBorderState(); } else if(uMsg == WM_NCPAINT) { //Let the control paint its own non-client elements (such as its scrollbars) LRESULT nOriginalReturn = CallWindowProc(pObj->m_pOriginalWndProc, hwnd, uMsg, wParam, lParam); //Draw the theme, if necessary if(pObj->OnNCPaint()) return 0; else return nOriginalReturn; } else if(uMsg == WM_ENABLE) { //Redraw the border depending on the state of the richedit control RedrawWindow(hwnd, NULL, NULL, RDW_INVALIDATE|RDW_NOCHILDREN|RDW_UPDATENOW|RDW_FRAME); } else if(uMsg == WM_NCCALCSIZE) { //If wParam is FALSE, we don't need to make any calculation if(wParam) { //Ask the control to first calculate the space it needs LRESULT nOriginalReturn = CallWindowProc(pObj->m_pOriginalWndProc, hwnd, uMsg, wParam, lParam); //Alter the size for our own border, if necessary NCCALCSIZE_PARAMS *csparam = (NCCALCSIZE_PARAMS*)lParam; if(pObj->OnNCCalcSize(csparam)) return WVR_REDRAW; else return nOriginalReturn; } } else if(uMsg == WM_DESTROY) { //Send the message now so that we can safely delete the object afterward LRESULT nToReturn = CallWindowProc(pObj->m_pOriginalWndProc, hwnd, uMsg, wParam, lParam); delete pObj; return nToReturn; } return CallWindowProc(pObj->m_pOriginalWndProc, hwnd, uMsg, wParam, lParam); } else return 0; } // ////////////////////////////////////////////////////////////////////////////// void CRichEditThemed::VerifyThemedBorderState() { bool bCurrentThemedBorder = m_bThemedBorder; m_bThemedBorder = false; //First, check if the control is supposed to have a border if(bCurrentThemedBorder || (GetWindowLong(m_hRichEdit, GWL_STYLE) & WS_BORDER || GetWindowLong(m_hRichEdit, GWL_EXSTYLE) & WS_EX_CLIENTEDGE)) { //Check if a theme is presently active if(pIsThemeActive()) { //Remove the border style, we don't want the control to draw its own border m_bThemedBorder = true; if(GetWindowLong(m_hRichEdit, GWL_STYLE) & WS_BORDER) SetWindowLong(m_hRichEdit, GWL_STYLE, GetWindowLong(m_hRichEdit, GWL_STYLE)^WS_BORDER); if(GetWindowLong(m_hRichEdit, GWL_EXSTYLE) & WS_EX_CLIENTEDGE) SetWindowLong(m_hRichEdit, GWL_EXSTYLE, GetWindowLong(m_hRichEdit, GWL_EXSTYLE)^WS_EX_CLIENTEDGE); } } //Recalculate the NC area and repaint the window SetWindowPos(m_hRichEdit, NULL, NULL, NULL, NULL, NULL, SWP_NOMOVE|SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE|SWP_FRAMECHANGED); RedrawWindow(m_hRichEdit, NULL, NULL, RDW_INVALIDATE|RDW_NOCHILDREN|RDW_UPDATENOW|RDW_FRAME); } // ////////////////////////////////////////////////////////////////////////////// bool CRichEditThemed::OnNCPaint() { try { if(m_bThemedBorder) { HTHEME hTheme = pOpenThemeData(m_hRichEdit, L"edit"); if(hTheme) { HDC hdc = GetWindowDC(m_hRichEdit); //Clip the DC so that we only draw on the non-client area RECT rcBorder; GetWindowRect(m_hRichEdit, &rcBorder); rcBorder.right -= rcBorder.left; rcBorder.bottom -= rcBorder.top; rcBorder.left = rcBorder.top = 0; RECT rcClient; memcpy(&rcClient, &rcBorder, sizeof(RECT)); rcClient.left += m_rcClientPos.left; rcClient.top += m_rcClientPos.top; rcClient.right -= m_rcClientPos.right; rcClient.bottom -= m_rcClientPos.bottom; ExcludeClipRect(hdc, rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); //Make sure the background is in a proper state if(pIsThemeBackgroundPartiallyTransparent(hTheme, EP_EDITTEXT, ETS_NORMAL)) pDrawThemeParentBackground(m_hRichEdit, hdc, &rcBorder); //Draw the border of the edit box int nState; if(!IsWindowEnabled(m_hRichEdit)) nState = ETS_DISABLED; else if(SendMessage(m_hRichEdit, EM_GETOPTIONS, NULL, NULL) & ECO_READONLY) nState = ETS_READONLY; else nState = ETS_NORMAL; pDrawThemeBackground(hTheme, hdc, EP_EDITTEXT, nState, &rcBorder, NULL); pCloseThemeData(hTheme); ReleaseDC(m_hRichEdit, hdc); return true; } } } catch(...) { //No exception is supposed to be thrown here but you can never be too safe //Trace an error here with your favorite TRACE macro } return false; } // ////////////////////////////////////////////////////////////////////////////// bool CRichEditThemed::OnNCCalcSize(NCCALCSIZE_PARAMS *csparam) { try { //Here, we indicate to Windows that the non-client area of the richedit control is not what it thinks it should be //This gives us the necessary space to draw the special border later on if(m_bThemedBorder) { //Load the theme associated with edit boxes HTHEME hTheme = pOpenThemeData(m_hRichEdit, L"edit"); if(hTheme) { bool bToReturn = false; //Get the size required by the current theme to be displayed properly RECT rcClient; ZeroMemory(&rcClient, sizeof(RECT)); HDC hdc = GetDC(GetParent(m_hRichEdit)); if(pGetThemeBackgroundContentRect(hTheme, hdc, EP_EDITTEXT, ETS_NORMAL, &csparam->rgrc[0], &rcClient) == S_OK) { //Add a pixel to every edge so that the client area is not too close to the border drawn by the theme (thus simulating a native edit box) InflateRect(&rcClient, -1, -1); m_rcClientPos.left = rcClient.left-csparam->rgrc[0].left; m_rcClientPos.top = rcClient.top-csparam->rgrc[0].top; m_rcClientPos.right = csparam->rgrc[0].right-rcClient.right; m_rcClientPos.bottom = csparam->rgrc[0].bottom-rcClient.bottom; memcpy(&csparam->rgrc[0], &rcClient, sizeof(RECT)); bToReturn = true; } ReleaseDC(GetParent(m_hRichEdit), hdc); pCloseThemeData(hTheme); return bToReturn; } } } catch(...) { //No exception is supposed to be thrown here but you can never be too safe //Trace an error here with your favorite TRACE macro } return false; }