1

I'm trying to define new colors in some regions of a CPropertySheet (mfc libaries). What I've tried is to overload OnCtlColorand define a new background color. This methods works well but it doesn't colorize the region I want.

In the next image you can see what I get with my method.

Image of the control

In this image you can see 4 colorized regions:

  1. Red: Region that I can colorize using OnCtlColor
  2. Dark Gray and black: Region that I can colorize using OnCtlColor of the object CPropertyPage
  3. Light Gray (Indicated with a blue arrow): Region I want to colorize
  4. White margin: Region I want to colorize too.

I don't know what to do to colorize all regions using this libraries or using any Customizable object. Any help will be appreciated.

Thanks!

Update 1

After the answer of Adrian it looks like this

However, there's still one region we cannot colorize.

Answer

Before trying a lot of combinations, I've done the next two objects which allows me to define the colors I need. You can find all source code behind. The result of this code can be checked in this picture

PropertyPage

Header

class CustomPropertyPage : public CPropertyPage
{   
    public:
        static const COLORREF PROPERTYPAGE_BACKGROUND = RGB(68, 74, 80);
        DECLARE_MESSAGE_MAP()

    public:
        CustomPropertyPage(UINT nIDTemplate);
        afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);
};

cpp

CustomPropertyPage::CustomPropertyPage(UINT nIDTemplate) : CPropertyPage(nIDTemplate)
{
}

BEGIN_MESSAGE_MAP(CustomPropertyPage, CPropertyPage)
    ON_WM_CTLCOLOR()
END_MESSAGE_MAP()

HBRUSH CustomPropertyPage::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    if (pWnd->GetDlgCtrlID() != 0)
        return CPropertyPage::OnCtlColor(pDC, pWnd, nCtlColor);

    HBRUSH hbr = CreateSolidBrush(PROPERTYPAGE_BACKGROUND_COLOR);
    return hbr;
}

PropertySheet

Header

class CustomPropertySheet : public CPropertySheet
{
    DECLARE_MESSAGE_MAP()

    public:
        CustomPropertySheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage);
        virtual BOOL OnInitDialog();
        afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
        afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor);

    private:
        void Draw_Background(CDC *pDC);
};

cpp

CustomPropertySheet::CustomPropertySheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage) : CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
}

BEGIN_MESSAGE_MAP(CustomPropertySheet, CPropertySheet)
    ON_WM_CTLCOLOR()
    ON_WM_DRAWITEM()
END_MESSAGE_MAP()

BOOL CustomPropertySheet::OnInitDialog()
{
    BOOL answer = CPropertySheet::OnInitDialog();

    CWnd* pTab = GetDlgItem(AFX_IDC_TAB_CONTROL);
    SetWindowLongPtr(pTab->m_hWnd, GWL_STYLE, GetWindowLongPtr(pTab->m_hWnd, GWL_STYLE) | TCS_OWNERDRAWFIXED);
    pTab->RedrawWindow();

    return answer;
}

void CustomPropertySheet::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct)
{
    if (nIDCtl == AFX_IDC_TAB_CONTROL)
    {
        CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
        Draw_Background(pDC);

        CRect rc(lpDrawItemStruct->rcItem);
        rc.bottom += 1;
        pDC->FillSolidRect(rc, CEasyPropertyPage::PROPERTYPAGE_BACKGROUND);
        pDC->SetTextColor(GENERIC_TEXT_COLOR);
        pDC->SetBkMode(TRANSPARENT);

        char  text[256];
        TCITEM tci = { TCIF_TEXT | TCIF_STATE, 0, 0, text, 255, -1, 0 };
        HWND tcw = ::GetDlgItem(m_hWnd, nIDCtl);
        int i, tic = int(::SendMessage(tcw, TCM_GETITEMCOUNT, 0, 0));
        for (i = 0; i < tic; ++i) 
        {
            if (lpDrawItemStruct->itemState & ODS_SELECTED)
            {
                CRect tir;
                ::SendMessage(tcw, TCM_GETITEM, WPARAM(i), LPARAM(&tci));
                ::SendMessage(tcw, TCM_GETITEMRECT, WPARAM(i), LPARAM(&tir));
                pDC->DrawText(text, tir, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
            }
        }
    }

    else CPropertySheet::OnDrawItem(nIDCtl, lpDrawItemStruct);
}

HBRUSH CustomPropertySheet::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    if (pWnd->GetDlgCtrlID() != 0)
        return CPropertySheet::OnCtlColor(pDC, pWnd, nCtlColor);

    HBRUSH hbr = CreateSolidBrush(GENERIC_BACKGROUND_COLOR);
    return hbr;
}

void CustomPropertySheet::Draw_Background(CDC* pDC)
{
    CRect rect; this->GetClientRect(rect);
    pDC->FillSolidRect(rect, GENERIC_BACKGROUND_COLOR);

    rect.DeflateRect(0, 20, 0, 0);
    pDC->FillSolidRect(rect, GENERIC_BORDER_COLOR);
}
RookieCoder
  • 105
  • 11

1 Answers1

1

To customize the light gray area (which is the embedded tab control) you need to override the OnDrawItem method in your class that is derived from CPropertySheet and do your custom drawing for the control with the AFX_IDC_TAB_CONTROL identifier. Something like this:

void MyPropertySheet::OnDrawItem(int nID, LPDRAWITEMSTRUCT pDIS)
{   
    if (nID == AFX_IDC_TAB_CONTROL) {
        CDC* pDC = CDC::FromHandle(pDIS->hDC);
        CRect rc(pDIS->rcItem); rc.bottom += 1;
        pDC->FillSolidRect(rc, RGB(255, 0, 0)); // Or whatever b/g/ colour you want
        pDC->SetTextColor(RGB(0,0,0)); // Or whatever text colour you want
        pDC->SetBkMode(TRANSPARENT);
        char  text[256];
        TCITEM tci = { TCIF_TEXT | TCIF_STATE, 0, 0, text, 255, -1, 0 };
        CRect tir;
        HWND tcw = ::GetDlgItem(m_hWnd, nID);
        int i, tic = int(::SendMessage(tcw, TCM_GETITEMCOUNT, 0, 0));
        for (i = 0; i < tic; ++i) {
            ::SendMessage(tcw, TCM_GETITEM, WPARAM(i), LPARAM(&tci));
            ::SendMessage(tcw, TCM_GETITEMRECT, WPARAM(i), LPARAM(&tir));
            if (pDIS->itemState & ODS_SELECTED)
                pDC->DrawText(text, tir, DT_CENTER |DT_VCENTER | DT_SINGLELINE);
        }
        pDC->Detach();
    }
    else { // Pass other stuff to the base class
        CPropertySheet::OnDrawItem(nID, pDIS);
    }
    return;
}

Of course, be sure to add ON_WM_DRAWITEM() to the message map!

EDIT: You must also explicitly set the style of the embedded tab control to include TCS_OWNERDRAWFIXED. You can do this in the OnInitDialog override for your class.

EDIT 2: I have a slightly better way to get a pointer to the tab control, now! Also, I have added a few lines of code that act as a "cheat" to address the remaining area that needs to be coloured - by expanding the tabs to fit the width of the underlying control ...

BOOL MyPropertySheet::OnInitDialog()
{
    BOOL answer = CPropertySheet::OnInitDialog(); // Call base class first!
    // ... whatever other stuff you may wish to do
//  CWnd* pTab = GetDlgItem(AFX_IDC_TAB_CONTROL);
    CTabCtrl* pTab = GetTabControl(); // This is a bit clearer than above line!
    // The following 4 lines comprise a 'first stab' at fixing the remaining issue:
    CRect rcTab; pTab->GetWindowRect(&rcTab);
    int nItems = pTab->GetItemCount();
    int border = GetSystemMetrics(SM_CXEDGE) * 2;
    pTab->SetMinTabWidth((rcTab.Width() - border) / nItems);
    // ...
    SetWindowLongPtr(pTab->m_hWnd, GWL_STYLE, GetWindowLongPtr(pTab->m_hWnd, GWL_STYLE) | TCS_OWNERDRAWFIXED);
    pTab->RedrawWindow();
    return answer;
}

Feel free to ask for further clarification and/or explanation.

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Works really good! However, There's still one region I can't fill with my color. Those parts which aren't included in a tab still have light gray color (Part where the blue arrow is placed). Do you know what I have to do to fill that place too? – RookieCoder Oct 24 '19 at 14:05
  • @pH4ngH0st Can you post an image of what it looks like after this 'first round' of treatment? The I may be able to see how we can do something (probably quote similar) for the remaining area(s). – Adrian Mole Oct 24 '19 at 14:08
  • @pH4ngH0st Actually, I'm pretty sure I know what you mean already. In the project of mine where I snatched the code I posted from, I generally have enough 'tabs' to fill the whole area! However, I found one P/Sheet where I didn't, and that also retains the original (gray) colour! Thinking hat is now on. – Adrian Mole Oct 24 '19 at 14:21
  • @pH4ngH0st See latest edit! It's not an ideal solution, I admit - but maybe will work as a placeholder. A more robust solution may require sub-classing the tab control and overriding its `OnPaint` or `OnEraseBackground` functions. – Adrian Mole Oct 24 '19 at 15:46
  • Well, after all your code into mine, I've done one object that allows me to colorize all area. This code can be found insede the question. Thanks for all your research! – RookieCoder Oct 28 '19 at 13:42