c++mfccpropertysheetcmfcpropertypage

How can I define new colors in a CPropertySheet?


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);
}

Solution

  • 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.