user2365346 user2365346 - 1 month ago 13
C++ Question

How to glow the minimum. maximum and close button?

I followed below guide to create a custom Aero Frame using DWM API.

Custom Window Frame Using DWM

My work:

void CMainFrame::OnActivate(UINT nState,CWnd* pWndOther,BOOL bMinimized )
{
CFrameWnd::OnActivate(nState,pWndOther,bMinimized);
BOOL fDwmEnabled = FALSE;
if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
{
if(nState == WA_ACTIVE )
{
MARGINS margins ={-1};
HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
if (!SUCCEEDED(hr));
}
}
}

void CMainFrame::OnNcPaint(){
RECT rcClient;
GetWindowRect(&rcClient);
// Inform the application of the frame change.
SetWindowPos(
NULL,
rcClient.left, rcClient.top,
RECTWIDTH(rcClient), RECTHEIGHT(rcClient),
SWP_FRAMECHANGED);
CFrameWnd::OnNcPaint();
CDC* dc = GetWindowDC();
dc->FillSolidRect(0,0,RECTWIDTH(rcClient),RECTHEIGHT(rcClient),RGB(0,0,0));
}

LRESULT CMainFrame::OnNcHitTest(CPoint p)
{
LRESULT r ;
r = CFrameWnd::OnNcHitTest( p);
if(r == HTMINBUTTON || r == HTMAXBUTTON || r == HTCLOSE)
return r;
else
r = HitTestNCA(m_hWnd,p); // this function is direct copied from above link.
return r;
}


Result:

enter image description here

I found out the minimum, maximum and close button that will not be glowed when I move the mouse on these buttons.

enter image description here

General situation:

enter image description here

How to fix this problem?

Best Regards,

Answer

DwmDefWindowProc is required to handle caption buttons. From msdn:

For caption button hit testing, DWM provides the DwmDefWindowProc function. To properly hit test the caption buttons in custom frame scenarios, messages should first be passed to DwmDefWindowProc for handling. DwmDefWindowProc returns TRUE if a message is handled and FALSE if it is not. If the message is not handled by DwmDefWindowProc, your application should handle the message itself or pass the message onto DefWindowProc.

In MFC it can work out as follows:

LRESULT cframeWnd::OnNcHitTest(CPoint p)
{
    BOOL dwm_enabled = FALSE;
    if (SUCCEEDED(DwmIsCompositionEnabled(&dwm_enabled)))
    {
        LRESULT result = 0;
        if (!DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(p.x, p.y), &result))
            result = HitTestNCA(m_hWnd, p);

        if (result == HTNOWHERE && GetForegroundWindow() != this)
        {
            return HTCAPTION;
        }

        return result;
    }

    return CWnd::OnNcHitTest(p);
}

I added a fix with GetForegroundWindow(), because the HitTestNCA function from MSDN example is wrong, it doesn't return HTCLIENT when it should. So when another window has focus, it won't switch windows upon mouse click in client area.

Also, there is a leak in OnNcPaint:

CDC* dc = GetWindowDC();

Whenever GetWindowDC() is called it should be followed by ReleaseDC. Or just use CWindowDC which has automatic cleanup. You don't actually need to override OnNcPaint because frame has been extended to "client area".

Here is a full example:

class cglassWnd : public CWnd
{
    void    OnNcCalcSize(BOOL, NCCALCSIZE_PARAMS FAR*);
    LRESULT OnNcHitTest(CPoint p);
    void    OnNcMouseLeave();
    int     OnCreate(LPCREATESTRUCT lpCreateStruct);
    void    OnActivate(UINT state, CWnd* otherWnd, BOOL minimized);
    void    OnPaint();
    CRect   borders;
    int     titlebar_height;
    DECLARE_MESSAGE_MAP()
public:
    cglassWnd();
};

BEGIN_MESSAGE_MAP(cglassWnd, CWnd)
    ON_WM_NCHITTEST()
    ON_WM_NCCALCSIZE()
    ON_WM_NCMOUSELEAVE()
    ON_WM_ACTIVATE()
    ON_WM_CREATE()
    ON_WM_PAINT()
END_MESSAGE_MAP()

cglassWnd::cglassWnd()
{
    BOOL dwm_enabled = FALSE;
    DwmIsCompositionEnabled(&dwm_enabled);
    if (!dwm_enabled)
        TRACE("Error: don't use this class, add error handling...");

    //modified height for the new title bar
    titlebar_height = 60;
}

int cglassWnd::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    int res = CWnd::OnCreate(lpCreateStruct);

    //find border thickness
    borders = { 0,0,0,0 };
    if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_THICKFRAME)
    {
        AdjustWindowRectEx(&borders, GetWindowLongPtr(m_hWnd, GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
        borders.left = abs(borders.left);
        borders.top = abs(borders.top);
    }
    else if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_BORDER)
    {
        borders = { 1,1,1,1 };
    }

    //Extend caption in to client area
    MARGINS margins = { 0 };
    margins.cyTopHeight = titlebar_height;
    DwmExtendFrameIntoClientArea(m_hWnd, &margins);

    SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);

    return res;
}

void cglassWnd::OnPaint()
{
    CPaintDC dc(this);

    //paint titlebar area (this used to be the non-client area)
    CRect rc;
    GetClientRect(&rc);
    rc.bottom = titlebar_height;

    CDC memdc;
    memdc.CreateCompatibleDC(&dc);
    BITMAPINFOHEADER bmpInfoHeader = { sizeof(BITMAPINFOHEADER), rc.Width(), -rc.Height(), 1, 32 };
    HBITMAP hbitmap = CreateDIBSection(dc, (BITMAPINFO*)(&bmpInfoHeader), DIB_RGB_COLORS, NULL, NULL, 0);
    auto oldbitmap = memdc.SelectObject(hbitmap);

    dc.BitBlt(0, 0, rc.Width(), rc.Height(), &memdc, 0, 0, SRCCOPY);
    memdc.SelectObject(oldbitmap);
    DeleteObject(hbitmap);

    //begin normal paint
    //The new client area begins below titlebar_height which we define earlier
    GetClientRect(&rc);
    rc.top = titlebar_height;
    dc.FillSolidRect(&rc, RGB(128, 128, 255));
}

void cglassWnd::OnNcCalcSize(BOOL validate, NCCALCSIZE_PARAMS FAR* sz)
{
    if (validate)
    {
        sz->rgrc[0].left += borders.left;
        sz->rgrc[0].right -= borders.right;
        sz->rgrc[0].bottom -= borders.bottom;
    }
    else
    {
        CWnd::OnNcCalcSize(validate, sz);
    }
}

LRESULT cglassWnd::OnNcHitTest(CPoint pt)
{
    LRESULT result = 0;
    if (DwmDefWindowProc(m_hWnd, WM_NCHITTEST, 0, MAKELPARAM(pt.x, pt.y), &result))
        return result;
    result = CWnd::OnNcHitTest(pt);
    if (result == HTCLIENT)
    {
        ScreenToClient(&pt);
        if (pt.y < borders.top) return HTTOP;
        if (pt.y < titlebar_height) return HTCAPTION;
    }
    return result;
}

void cglassWnd::OnNcMouseLeave()
{
    //update system buttons for when mouse moves out too fast
    LRESULT result;
    DwmDefWindowProc(m_hWnd, WM_NCMOUSELEAVE, 0, 0, &result);
    CWnd::OnNcMouseLeave();
}

void cglassWnd::OnActivate(UINT state, CWnd* otherWnd, BOOL minimized)
{
    CWnd::OnActivate(state, otherWnd, minimized);
    Invalidate(FALSE);
}