Arseniy
Arseniy

Reputation: 304

How to paint over white line between menu bar and client area of window?

I tried to color customize menu items (pure WinAPI). But there is a line in the menu bar which does not draw with MenuInfo.hbrBack color. If the mouse cursor hover above items a part of this line is redrawn. But if I resize the window the line will return. And in the area of menu bar where no items the line drawn constantly. How can I draw over this annoying line?

the line

#include <windows.h> 

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

struct
{
    COLORREF text = RGB(200, 200, 250);
    COLORREF clientBorder = RGB(120, 0, 0); 
    COLORREF clientBackground = RGB(100, 100, 100);
    COLORREF itemBorder = RGB(0, 0, 255);
    COLORREF itemBackground = RGB(0, 120, 0);
    COLORREF pink = RGB(255, 0, 255);
} colorTheme;

int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
{
    MSG msg;

    WNDCLASSEX wc;
    wc.cbSize = sizeof(wc);
    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WndProc;
    wc.lpszMenuName = NULL;
    wc.lpszClassName = "MainWindow";
    wc.cbWndExtra = NULL;
    wc.cbClsExtra = NULL;
    wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
    wc.hIconSm = LoadIcon(NULL, IDI_WINLOGO);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = CreateSolidBrush(colorTheme.clientBackground);
    wc.hInstance = hInst;

    RegisterClassEx(&wc);

    HWND hMainWnd = CreateWindow(
        "MainWindow",
        "MainWindow",
        WS_OVERLAPPEDWINDOW,
        100, 100, 450, 120,
        (HWND)NULL, NULL, HINSTANCE(hInst), NULL);

    HMENU hMenu = CreateMenu();
    HMENU hMenuSub1 = CreatePopupMenu();
    HMENU hMenuSub2 = CreatePopupMenu();
    HMENU hMenuSub3 = CreatePopupMenu();
    AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub1, "SubMenu1");
    AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item01");
    AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item02");
    AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item03");
    AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item04");
    AppendMenu(hMenuSub1, MF_OWNERDRAW, 0, "Item05");
    AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub2, "SubMenu2");
    AppendMenu(hMenu, MF_OWNERDRAW | MF_POPUP, (UINT)hMenuSub3, "SubMenu3");
    
    MENUINFO menuInfo;
    menuInfo.cbSize = sizeof(menuInfo);
    menuInfo.fMask = MIM_BACKGROUND;
    menuInfo.hbrBack = CreateSolidBrush(colorTheme.pink);
    SetMenuInfo(hMenu, &menuInfo);
    SetMenu(hMainWnd, hMenu);

    ShowWindow(hMainWnd, nCmdShow);

    while (GetMessage(&msg, NULL, NULL, NULL)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hDC = BeginPaint(hWnd, &ps);

        HFONT hApplicationFont;
        LOGFONT applicationFont;
        applicationFont.lfHeight = 16;
        applicationFont.lfWidth = 6;
        applicationFont.lfEscapement = 0;
        applicationFont.lfOrientation = 0;
        applicationFont.lfWeight = FW_NORMAL;
        applicationFont.lfItalic = FALSE;
        applicationFont.lfUnderline = FALSE;
        applicationFont.lfStrikeOut = FALSE;
        applicationFont.lfCharSet = DEFAULT_CHARSET;
        applicationFont.lfOutPrecision = OUT_DEFAULT_PRECIS;
        applicationFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;
        applicationFont.lfQuality = ANTIALIASED_QUALITY;
        applicationFont.lfPitchAndFamily = DEFAULT_PITCH;
        strcpy_s(applicationFont.lfFaceName, "Arial");
        hApplicationFont = CreateFontIndirectA(&applicationFont);
        SelectObject(hDC, hApplicationFont);

        SelectObject(hDC, GetStockObject(DC_PEN));
        SetDCPenColor(hDC, colorTheme.clientBorder);
        SelectObject(hDC, GetStockObject(DC_BRUSH));
        SetDCBrushColor(hDC, colorTheme.clientBackground);
        RECT clientRect;
        GetClientRect(hWnd, &clientRect);
        Rectangle(hDC, 0, 0, clientRect.right, clientRect.bottom);

        EndPaint(hWnd, &ps);
        break;
    }
    case WM_MEASUREITEM:
    {
        LPMEASUREITEMSTRUCT itemStruct = (LPMEASUREITEMSTRUCT)lParam;
        const char* str = (const char*)(itemStruct->itemData);
        SIZE strSize;
        HDC hDC = GetDC(hWnd);          
        GetTextExtentPoint32(hDC, str, lstrlen(str), &strSize);
        itemStruct->itemWidth = strSize.cx;
        itemStruct->itemHeight = 30;
        ReleaseDC(hWnd, hDC);

        return TRUE;
        break;
    }
    case WM_DRAWITEM:
    {
        LPDRAWITEMSTRUCT itemStruct = (LPDRAWITEMSTRUCT)lParam;
        HDC hDC = itemStruct->hDC;
        SelectObject(hDC, GetStockObject(DC_PEN));
        SetDCPenColor(hDC, colorTheme.itemBorder);
        SelectObject(hDC, GetStockObject(DC_BRUSH));
        SetDCBrushColor(hDC, colorTheme.itemBackground);
        SetTextColor(hDC, colorTheme.text);
        SetBkMode(hDC, TRANSPARENT);

        Rectangle(hDC, itemStruct->rcItem.left,
                       itemStruct->rcItem.top,
                       itemStruct->rcItem.right,
                       itemStruct->rcItem.bottom + 1);
        DrawText(hDC, (const char*)(itemStruct->itemData), -1, &(itemStruct->rcItem), DT_SINGLELINE | DT_CENTER | DT_VCENTER);
        break;
    }
    case WM_DESTROY:
    {
        PostQuitMessage(NULL);
        break;
    }
    default:
        return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return NULL;
}

Upvotes: 1

Views: 1010

Answers (2)

adzm
adzm

Reputation: 4126

If you are using themes / visual styles, which pretty much everything is nowadays, you can't override a lot of the menu styling without using a workaround like https://github.com/adzm/win32-custom-menubar-aero-theme which also uses the same approach to get rid of the white line. Note that you will need to handle this in WM_NCPAINT and WM_NCACTIVATE.

Upvotes: 0

jwezorek
jwezorek

Reputation: 9525

It seems to be part of the non-client area of the window. If that's the case then to paint there you need to handle WM_NCPAINT.

It is a single pixel line above the window's client area, so for example if I add the following code to your program I can paint it in red.

// ... in the WNDPROC
case WM_NCPAINT:
{
    auto result = DefWindowProc(hWnd, WM_NCPAINT, wParam, lParam);

    HDC hdc = GetWindowDC(hWnd); 
    RECT r = GetNonclientMenuBorderRect(hWnd);
    HBRUSH red = CreateSolidBrush(RGB(255, 0, 0));
    FillRect(hdc, &r, red);
    DeleteObject(red);
    ReleaseDC(hWnd, hdc);

    return result;
}

// ... elsewhere
RECT MapRectFromClientToWndCoords(HWND hwnd, const RECT& r)
{
    RECT wnd_coords = r;

    // map to screen
    MapWindowPoints(hwnd, NULL, reinterpret_cast<POINT*>(&wnd_coords), 2);

    RECT scr_coords;
    GetWindowRect(hwnd, &scr_coords);

    // map to window coords by substracting the window coord origin in
    // screen coords.
    OffsetRect(&wnd_coords, -scr_coords.left, -scr_coords.top);

    return wnd_coords;
}

RECT GetNonclientMenuBorderRect(HWND hwnd)
{
    RECT r;
    GetClientRect(hwnd, &r);
    r = MapRectFromClientToWndCoords(hwnd, r);
    int y = r.top - 1;
    return {
        r.left,
        y,
        r.right,
        y+1
    };
}

Now an issue with the above code is that it is over-painting the rectangle after the default non-client painting is done. In theory this could flicker; in practice I don't notice a flicker. If it did flicker, however, a safer way to do this would be to modify the WPARAM you pass to DefWindowProc(hWnd, WM_NCPAINT, ... ) such that it is the handle to a region that is the region passed to WM_NCPAINT minus the rectangle you want to paint. This doesnt seem necessary to me, for whatever reason.

Upvotes: 1

Related Questions