Create CBitmap from CDC?

867 views Asked by At

Using C++/MFC and GDI (not GDI+), the overall goal is to create an patterned HBRUSH, which will be used in OnCtlColor to outline an edit control in red, with the ability to turn the outline on and off. To do this, you attach a bitmap to an HBRUSH using CreatePatternBrush. Here is the code for doing that, using a stored bitmap resource:

CDialog::OnInitDialog();
BOOL ok = redBoxBitmap.LoadBitmap(MAKEINTRESOURCE(IDB_mespe_EditBox_Red));
ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);

and in OnCtlColor

HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    HBRUSH hbr;
    int ctrlID=pWnd->GetDlgCtrlID();
    if(ctrlID==IDC_MyEditControl)
        hbr=(HBRUSH) redBoxBrush;
    else
        hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);

    return hbr;
}

The above code all works as desired. However, it depends on the bitmap being sized to the edit control. What I need now is the ability to create the bitmap within the C++ program, sized to the client area of the control, which depends on both the design size of the control (in the dialog editor), and the user's setting of text size in Windows 10 settings.

I cannot find a straightforward way of either constructing a bitmap, or, better, creating an empty one of the proper size (can do), selecting it into a CDC (can do), drawing the red box into it (can do), then extracting the update bitmap from the CDC (how to do?).

Can anyone suggest either how to create the bitmap programmatically, or suggest a better method of getting an edit control boxed in red when the program calls for that?

Added in response to @Constantine Georgiou's answer of 3/9:

New code:

CBitmap redBoxBitmap; // member variables of class CModelEditorSpecies
CBrush redBoxBrush;

BOOL CModelEditorSpecies::OnInitDialog()
{
    CDialog::OnInitDialog();
    BOOL ok;
    CRect r; defaultSpecies1Ctrl.GetClientRect(&r);
    xx(r.Width(), r.Height()/*, redBoxBrush*/);
    ok = redBoxBrush.CreatePatternBrush(&redBoxBitmap);
    //...
}

void CModelEditorSpecies::xx(const int w, const int h)
{
    CDC *pDC=GetDC();
    redBoxBitmap.CreateCompatibleBitmap(pDC, w, h);
    
    // Create a red pen
    CPen redPen;
    redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    
    // Draw the bitmap - red pen & default background brush
    CBitmap *pOldBitmap=pDC->SelectObject(&redBoxBitmap);
    pDC->SelectObject(&redPen);
    CBrush editBoxBrush;
    editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);
    pDC->SelectObject(&editBoxBrush);
    pDC->Rectangle(0, 0, w, h);
        
    pDC->SelectObject(pOldBitmap);
    
    // Create the edit-control custom brush
    redBoxBrush.CreatePatternBrush(&redBoxBitmap);
    return;
}

This code produces an all-black edit control, as if the bitmap being used were monochrome. That would be expected if drawing in the dc does not affect the bitmap, or, if drawing in a dc-compatible bitmap does not use the colors in redPen and editBoxBrush, as suggested by @IInspectable.

1

There are 1 answers

1
Constantine Georgiou On

Here's how to create the brush - used the Win32 functions instead of their MFC wrappers, but you can figure it out.

BOOL CModelEditorSpecies::OnInitDialog()
{
    CDialogEx::OnInitDialog();

    // Get edit-control's size
    RECT rc;
    GetDlgItem(IDC_MyEditControl)->GetClientRect(&rc);

    // Create the bitmap and a memory-DC
    HDC hDC = ::GetDC(HWND_DESKTOP);
    HDC mDC = CreateCompatibleDC(hDC);
    HBITMAP hBmp = CreateCompatibleBitmap(hDC, rc.right, rc.bottom);
    ::ReleaseDC(HWND_DESKTOP, hDC);

    // Create a red pen
    HPEN hPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));

    // Draw the bitmap - red pen & default background brush
    HBITMAP hOldBmp = (HBITMAP) SelectObject(mDC, hBmp);
    HPEN hOldPen = (HPEN) SelectObject(mDC, hPen);
    HBRUSH hOldBr = (HBRUSH) SelectObject(mDC, GetSysColorBrush(COLOR_WINDOW));
    Rectangle(mDC, rc.left, rc.top, rc.right, rc.bottom);
    SelectObject(mDC, hOldBr);
    SelectObject(mDC, hOldPen);
    SelectObject(mDC, hOldBmp);

    // Create the edit-control custom brush
    redBoxBrush = CreatePatternBrush(hBmp);

    // Clean-up - the SysColorBrush doesn't need to be deleted
    DeleteObject(hPen);
    DeleteObject(hBmp);
    DeleteDC(mDC);

    return TRUE;
}

HBRUSH CModelEditorSpecies::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
    return (nCtlColor == CTLCOLOR_EDIT && pWnd->GetDlgCtrlID() == IDC_MyEditControl) ?
        redBoxBrush : CDialogEx::OnCtlColor(pDC, pWnd, nCtlColor);
}

Also check the alternatives I suggested in the comments. This one will be drawing into the edit-control's client area.


EDIT:

The code I posted above does work, and after it runs all left is the HBRUSH handle (pattern-brush for your edit box). I can't see why you insist using the MFC wrappers instead. They are "higher-level" thin wrappers, so... "thin" actually, that you still have to perform almost exactly the same operations (create, select into a DC, perfrom some drawing operation, select out of the DC) as the GDI ones. The only operation you don't need to perform is delete the resource (the object's destructor will call DeleteObject() for you).

Anyways, if you prefer MFC over GDI, let's see what's wrong with your code. It has quite a few issues:

  • First and foremost, you need a memory DC to draw on a bitmap, the window DC you get by calling GetDC() draws on a window's surface.
  • The DC you get by calling GetDC() must be returned to the system (ReleaseDC()), because pDC is just a pointer, and the compiler won't call the destructor.
  • The objects you select into a DC must be selected out before it is destroyed, otherwise memory-leaks may be occurring.

So your code should be changed as shown below:

void CModelEditorSpecies::xx()
{
    CRect r;
    GetDlgItem(IDC_MyEditControl)->GetClientRect(&r);
    
    // Create the bitmap and a memory-DC
    CBitmap redBoxBitmap;
    CDC mDC, *pDC = GetDC();
    redBoxBitmap.CreateCompatibleBitmap(pDC, r.Width(), r.Height());
    mDC.CreateCompatibleDC(pDC);
    ReleaseDC(pDC);

    // Create a red pen and get the default background brush
    CPen redPen;
    redPen.CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
    CBrush editBoxBrush;
    editBoxBrush.CreateSysColorBrush(COLOR_WINDOW);

    // Draw the bitmap - red pen & default background brush
    CBitmap *pOldBitmap = mDC.SelectObject(&redBoxBitmap);
    CPen *pOldPen = mDC.SelectObject(&redPen);
    CBrush *pOldBrush =mDC.SelectObject(&editBoxBrush);
    mDC.Rectangle(r);
    mDC.SelectObject(pOldBrush);
    mDC.SelectObject(pOldPen);
    mDC.SelectObject(pOldBitmap);

    // Create the edit-control custom brush
    redBoxBrush.CreatePatternBrush(&redBoxBitmap);
}

It's basically the same as the GDI version.