6

Hello and happy new year, (it is acceptable to say it until Thursday)

I am trying to change the color of the tabs in the CTabCtrl class. I am trying to create my own ReskinCTablCtrl so that I can just call it in separate classes and easily use it throughout my program.

Currently I am able to change the background color of the CTabCtrl but I cannot modify the tab's themselves.

I used ON_WM_ERASEBKGND() for painting the background and it worked without a problem:

BOOL ReskinCTabCtrl::OnEraseBkgnd(CDC* pDC)
{
    CRect rect;
    GetClientRect(&rect);
    CBrush myBrush(RGB(51, 51, 51));    // dialog background color
    BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
    pDC->SetBkColor(RGB(51, 51, 51));
    pDC->FillRect(&rect, &myBrush);
    return bRes;
}

However, I have been unsuccesfull at changing the tab colors themselves. They are still the default MFC colors. I have tried to implement ON_WM_PAINT() and ON_WM_DRAWITEM() without any success. I think I can get to the specific tab rect with using both OnDraw and DrawItem similar to the second link example that I have posted in the end of this question.

void ReskinCTabCtrl::OnPaint() {

    ...

    // paint the tabs first and then the borders
    int nTab = GetItemCount();
    int nSel = GetCurSel();

    if (!nTab) // no pages added
        return;

    while (nTab--)
    {
        if (nTab != nSel)
        {
            dis.itemID = nTab;
            dis.itemState = 0;

            VERIFY(GetItemRect(nTab, &dis.rcItem));

            dis.rcItem.bottom -= 2;
            DrawItem(&dis);
            DrawItemBorder(&dis);
        }
    }

    ...

}

I would really appreciate at least some direction to go about this problem, perhaps some more examples or what methods I should focus on using. I don't need the tabs to be different colors, maybe there is an easy way of doing this?

I've been trying to follow some examples like the following links but I still couldn't figure out the right way to do it.

https://support.microsoft.com/en-us/help/179909/how-to-change-the-background-color-of-a-tab-control

https://www.codeproject.com/Articles/1786/Ownerdraw-Tab-Controls-Borders-and-All

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
Kemal Tezer Dilsiz
  • 3,739
  • 5
  • 24
  • 43
  • 3
    Why don't you use a `CMFCTabCtrl` instead? `CTabCtrl` is obsolete as hell, and with `CMFCTabCtrl` you don't need to handle hide/show and enable/disable controls logic when switching around Tabs. BTW, I tried `m_tabControl.SetTabBkColor(0, RGB(255, 0, 0)); ` on a `CMFCTabCtrl` here and got the result https://i.imgur.com/2VT3yTA.png , which means it only paints the frame around. It seems to me your best bet is to handle `ON_WM_CTLCOLOR()` https://stackoverflow.com/a/31348831/383779 of the inner form. – sergiol Jan 02 '18 at 18:57
  • I'll try to switch to that, thank you, just trying to keep everything as simple as possible since it is the beginning of a big project. – Kemal Tezer Dilsiz Jan 02 '18 at 19:11
  • You can try to handle `ON_WM_CTLCOLOR` before switching. May be you have to re-route messages from container window to inner form. – sergiol Jan 02 '18 at 19:13
  • @sergiol It is my understanding that `CMFCTabCtrl` should not be used with a `CDialog`? If it was I would use it. – Andrew Truckle Mar 11 '23 at 20:55
  • 1
    @AndrewTruckle As far as I know, I don't know why you should not use a `CMFCTabCtrl` inside a `CDialog`. The image I put on my 1. comment is such a case, and it works fine. – sergiol Mar 11 '23 at 23:37

1 Answers1

7

Enable OwnerDraw for tab control, either in resource editor, or set TCS_OWNERDRAWFIXED in OnInitDialog

CTabCtrl has message reflection for WM_DRAWITEM therefore we don't want to override WM_DRAWITEM/OnDrawItem from parent class. Instead override in CTabCtrl::DrawItem(LPDRAWITEMSTRUCT).

Unfortunately the result is rather ugly. It's sort of like overriding DrawItem in a button.

If Visual Style is available and enabled, then you can override CTabCtrl::OnPaint and draw everything manually. Example:

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

    dc.SelectObject(GetFont());

    CPen pen, pen_active;
    COLORREF color_off = RGB(240, 240, 240);
    COLORREF color_active = RGB(200, 240, 240);
    CBrush brush_off, brush_active;
    brush_off.CreateSolidBrush(color_off);
    brush_active.CreateSolidBrush(color_active);
    pen.CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
    pen_active.CreatePen(PS_SOLID, 1, color_active);

    CRect rcitem;
    GetItemRect(0, &rcitem);

    CRect rc;
    GetClientRect(&rc);
    rc.bottom = rcitem.bottom;
    dc.FillSolidRect(&rc, GetSysColor(COLOR_3DFACE));

    GetClientRect(&rc);
    rc.top = rcitem.bottom - 1;
    dc.SelectObject(&pen);
    dc.SelectObject(&brush_active);
    dc.Rectangle(&rc);

    for(int i = 0; i < GetItemCount(); i++)
    {
        dc.SelectObject(&pen);
        if(i == GetCurSel())
        {
            dc.SelectObject(&brush_active);
            dc.SetBkColor(color_active);
        }
        else
        {
            dc.SelectObject(&brush_off);
            dc.SetBkColor(color_off);
        }

        GetItemRect(i, &rcitem);
        rcitem.right++;
        dc.Rectangle(&rcitem);

        if(i == GetCurSel())
        {
            dc.SelectObject(pen_active);
            dc.MoveTo(rcitem.left+1, rcitem.bottom - 1);
            dc.LineTo(rcitem.right, rcitem.bottom - 1);
        }

        TCITEM item = { 0 };
        wchar_t buf[32];
        item.pszText = buf;
        item.cchTextMax = 32;
        item.mask = TCIF_TEXT;
        GetItem(i, &item);
        dc.DrawText(buf, &rcitem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    }
}

BOOL CMyTabCtrl::OnEraseBkgnd(CDC*)
{
    return TRUE;
}

enter image description here

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • I was able to attain the result you showed in the picture, i'll try to further improve it. I am trying to get all the tabs to be the same color at all times! However, it changed the background color to default so I'll try to fix that for myself too. – Kemal Tezer Dilsiz Jan 03 '18 at 13:03
  • One improvement that could be made is to check for the flag `(TCS_BOTTOM == (GetStyle() & TCS_BOTTOM))` which then requires drawing the tabs upside-down instead of right-side up. – MasterHD Oct 20 '20 at 09:17