Search code examples
c++winapimfcaerodwm

An Aero caption title bar issue using DWM API on the windows 10


In order to draw the icon on the caption title bar, I have refereed this MSDN article and used DWM API to create my customize client area by calling DwmExtendFrameIntoClientArea.

my code:

CMainFrame::CMainFrame()
{
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;
    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    BOOL fDwmEnabled = FALSE;
    if (SUCCEEDED(DwmIsCompositionEnabled(&fDwmEnabled)))
        TRACE0("DWM is enabled\n");

    TCHAR szLogoPath[MAX_PATH];
    GetModuleFileName ( GetModuleHandle(NULL), szLogoPath, _countof(szLogoPath)     );
    PathRemoveFileSpec ( szLogoPath );
    PathAppend ( szLogoPath, _T("lena.bmp") );
    m_pLogoImage = m_pLogoImage->FromFile ( CT2CW(szLogoPath) );
    if(NULL == m_pLogoImage)
        TRACE0("load image fail\n");
}

void CMainFrame::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS* lpncsp)
{
    int xFrame = 2; 
    int yFrame = 2; 
    int nTHight = 30; 
    NCCALCSIZE_PARAMS * p; 
    RECT * rc; 
    RECT aRect;
    RECT bRect;
    RECT acRect;
    p = (NCCALCSIZE_PARAMS *)lpncsp; 
    
    CopyRect(&bRect,&p->rgrc[1]); 
    CopyRect(&aRect,&p->rgrc[0]);

    acRect.left = aRect.left + xFrame;
    acRect.top = aRect.top - nTHight;
    acRect.right = aRect.right - xFrame;
    acRect.bottom = aRect.bottom - yFrame;
    CopyRect(&p->rgrc[0],&acRect);
    CopyRect(&p->rgrc[1],&aRect);
    CopyRect(&p->rgrc[2],&bRect);
    CFrameWnd::OnNcCalcSize(TRUE, lpncsp);
}

 LRESULT CMainFrame::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);
 }

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)
{
    if(cs.hMenu!=NULL)
    {
    ::DestroyMenu(cs.hMenu);    
        cs.hMenu = NULL ;    
    }
    if( !CFrameWnd::PreCreateWindow(cs) )
        return FALSE;
    // TODO: Modify the Window class or styles here by modifying
    //  the CREATESTRUCT cs
    cs.style = WS_CAPTION | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_OVERLAPPED| WS_SYSMENU | WS_THICKFRAME;
    cs.dwExStyle &= ~WS_EX_CLIENTEDGE;
    cs.lpszClass = AfxRegisterWndClass(0);

    return TRUE;
}

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};
            /*margins.cyTopHeight = 30;
            margins.cxLeftWidth = 0;
            margins.cxRightWidth = 0;
            margins.cyBottomHeight = 0;*/
            HRESULT hr = DwmExtendFrameIntoClientArea(m_hWnd, &margins);
            if (!SUCCEEDED(hr))
               TRACE0("Failed in DwmExtendFrameIntoClientArea\n");
        }
    }
}

void CMainFrame::OnNcPaint()
{
    CFrameWnd::OnPaint();
    CDC* dc = GetWindowDC();
    RECT rcClient;
    GetWindowRect(&rcClient);
    dc->FillSolidRect(0,0,RECTWIDTH(rcClient),RECTHEIGHT(rcClient),RGB(255,0,0));

    CPaintDC gdc(this); // device context for painting
    Graphics gr(gdc.m_hDC);
    gr.DrawImage ( m_pLogoImage, 0, 0 );
    ReleaseDC(dc);

}

The result under Windows 7 is fine. enter image description here

However, my window appears another unknown caption title bar under Windows 10. enter image description here

enter image description here

I found out the unknown caption is caused by WS_THICKFRAME in the cs.style. If I remove WS_THICKFRAME, the unknown cation bar will disappear, but I cannot resizing the border of my window. Furthermore, my program cannot capture the minimum, maximum and the close button message on my custom caption bar anymore. I want to remove the unknown title bar without any side effect. Does anyone could provide me a good solution or suggestion?

Best Regards,


Solution

  • When using DwmExtendFrameIntoClientArea, it means frame is extended in to client area. It is no longer in non-client area. So there is no need to override OnNcPaint, you can do all of the painting in OnPaint

    void CMainFrame::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(0, 0, 255));
    
        Gdiplus::Image *image = Gdiplus::Image::FromFile(L"file.jpg");
        Gdiplus::Graphics gr(dc);
        gr.DrawImage(image, 0, 0);
        delete image;
    }
    

    Use a member variable CRect m_border to keep track of border's thickness. You can use AdjustWindowRectEx to find the thickness of the borders.

    void CMainFrame::OnActivate(UINT nState, CWnd* pWndOther, BOOL bMinimized)
    {
        CFrameWnd::OnActivate(nState, pWndOther, bMinimized);
    
        titlebar_height = 100;
        //find border thickness
        if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_THICKFRAME)
        {
            m_border = { 0,0,0,0 };
            AdjustWindowRectEx(&m_border, GetWindowLongPtr(m_hWnd,
                    GWL_STYLE) & ~WS_CAPTION, FALSE, NULL);
            m_border.left = abs(m_border.left);
            m_border.top = abs(m_border.top);
        }
        else if (GetWindowLongPtr(m_hWnd, GWL_STYLE) & WS_BORDER)
        {
            m_border = { 1,1,1,1 };
        }
        else
        {
            m_border = { 0,0,0,0 };
        }
    
        //Extend frame in to client area
        MARGINS margins = { 0 };
        margins.cyTopHeight = titlebar_height; //<<=== *** edited
        DwmExtendFrameIntoClientArea(m_hWnd, &margins);
        SetWindowPos(NULL, 0, 0, 0, 0, 
                SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE | SWP_FRAMECHANGED);
    }
    

    m_border will be for example {7,7,7,7};

    Allow Windows to do the painting on left, right, bottom border. The top border is the only one changed

    void CMainFrame::OnNcCalcSize(BOOL validate, NCCALCSIZE_PARAMS FAR* lpncsp)
    {
        if (validate)
        {
            lpncsp->rgrc[0].left += m_border.left;
            lpncsp->rgrc[0].right -= m_border.right;
            lpncsp->rgrc[0].bottom -= m_border.bottom;
        }
        else
        {
            CFrameWnd::OnNcCalcSize(validate, lpncsp);
        }
    }
    

    see also How to glow the minimum. maximum and close button?