visual-c++comboboxmfcdialog

Drawing red rectangle around a combo


I have a simple dialog application in MFC with 2 combo boxes.

I am trying to use the dialogs paint handler to add a rectangle around the combos if they have no item selected:

void CFieldServiceGroupManagerDlg::OnPaint()
{
    CPaintDC dc(this); // Device context for painting

    // Lambda for drawing a red rectangle around a control
    auto drawRedRect = [&](CWnd* pCtrl)
        {
            if (!pCtrl) return;

            CRect rect;
            pCtrl->GetWindowRect(&rect);
            ScreenToClient(&rect);

            // Inflate slightly to make it visible around the border
            rect.InflateRect(2, 2);

            CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));
            CPen* pOldPen = dc.SelectObject(&redPen);
            dc.SelectStockObject(NULL_BRUSH);
            dc.Rectangle(&rect);
            dc.SelectObject(pOldPen);
        };

    // Only draw rectangles if there’s no selection
    if (m_cbGroupOverseer.GetCurSel() == CB_ERR)
        drawRedRect(&m_cbGroupOverseer);

    if (m_cbGroupAssistant.GetCurSel() == CB_ERR)
        drawRedRect(&m_cbGroupAssistant);
}

I see no rectangle even though the code is called.

You would expect it to work, because it is described here:

Draw a rectangle around a control, C++ MFC

But it doesn’t.

The rectangle seems to have correct values:

enter image description here

Update

I tried t do it the way in thecomments, but it is either flawed or my code is wrong.

I added two functions:

void CFieldServiceGroupManagerDlg::DrawRedRectAroundControl(CWnd* pCtrl)
{
    if (!pCtrl || !::IsWindow(pCtrl->GetSafeHwnd()))
        return;

    CClientDC dc(this);
    int nSavedDC = dc.SaveDC();

    CRect rect;
    pCtrl->GetWindowRect(&rect);
    ScreenToClient(&rect);
    rect.InflateRect(2, 2);

    CPen redPen(PS_SOLID, 2, RGB(255, 0, 0));
    CPen* pOldPen = dc.SelectObject(&redPen);
    CBrush* pOldBrush = static_cast<CBrush*>(dc.SelectStockObject(NULL_BRUSH));
    dc.Rectangle(&rect);

    dc.SelectObject(pOldPen);
    dc.SelectObject(pOldBrush);
    dc.RestoreDC(nSavedDC);
}

void CFieldServiceGroupManagerDlg::HighlightUnselectedCombos(bool const checkOverseer, bool const checkAssistant)
{
    if (checkOverseer && m_cbGroupOverseer.GetCurSel() == -1)
    {
        DrawRedRectAroundControl(&m_cbGroupOverseer);
    }

    if (checkAssistant && m_cbGroupAssistant.GetCurSel() == -1)
    {
        DrawRedRectAroundControl(&m_cbGroupAssistant);
    }
}

In my selection change handlers for both commobos I respectively call:

But the problem is if the rectangle is displayed (because no item is selected) and I select an item, the rectangle remains.


Solution

  • Summary

    The pen is solid. When this pen is used in any GDI drawing function that takes a bounding rectangle, the dimensions of the figure are shrunk so that it fits entirely in the bounding rectangle, taking into account the width of the pen. This applies only to geometric pens.


    Workthrough

    This is how I addressed the issue in the end:

    1. I added two member variables to the class:
    CRect m_rcOverseerHighlightBounds;
    CRect m_rcAssistantHighlightBounds;
    
    1. I added a new function to calculate the rectangle sizes, and call it from CDialog::OnInitDialog:
    void CFieldServiceGroupManagerDlg::CalculateSizesWithBorderHilight()
    {
        auto getInflatedClientRect = [this](CWnd& wnd) -> CRect {
            CRect rect;
            wnd.GetWindowRect(&rect);
            ScreenToClient(&rect);
            rect.InflateRect(2, 2);
            return rect;
        };
    
        m_rcOverseerHighlightBounds = getInflatedClientRect(m_cbGroupOverseer);
        m_rcAssistantHighlightBounds = getInflatedClientRect(m_cbGroupAssistant);
    }
    
    1. I simplified the CDialog::OnPaint handler:

    I can't find any official CDialog documentation for this function on the Microsoft website. 😵

    void CFieldServiceGroupManagerDlg::OnPaint()
    {
        CPaintDC dc(this); // Device context for painting
    
        // Lambda for drawing a red rectangle
        auto drawRedRect = [&](const CRect& rect)
            {
                CPen redPen(PS_INSIDEFRAME, 2, RGB(255, 0, 0));
                CPen* pOldPen = dc.SelectObject(&redPen);
                CBrush* pOldBrush = static_cast<CBrush*>(dc.SelectStockObject(NULL_BRUSH));
                dc.Rectangle(&rect);
                dc.SelectObject(pOldPen);
                dc.SelectObject(pOldBrush);
            };
    
        if (m_cbGroupOverseer.GetCurSel() == -1)
            drawRedRect(m_rcOverseerHighlightBounds);
    
        if (m_cbGroupAssistant.GetCurSel() == -1)
            drawRedRect(m_rcAssistantHighlightBounds);
    }
    
    1. I adjusted both of my ON_CBN_SELCHANGE handlers. Example:
    InvalidateRect(m_rcAssistantHighlightBounds, m_cbGroupAssistant.GetCurSel() != CB_ERR);
    

    Screenshots


    Here are a couple of examples showing both combos with the red rectangle when my application is in dark-mode:

    Visual warning - Missing overseer

    Group in critical condition status