Redraw shapes in MFC - c++

I'm trying to develop a painter app using MFC with C++.
So I set up a view in which I do the actual painting. However, when I open the color button I get "leftovers" of the menu on my canvas view.. I have no idea how to erase them.. I tried using SaveDC and RestoreDC to restore it to the previous state but no luck there. As I understand it is meant to restore properties of the device context such as pen & brush but it has no use for me...
I also need this feature so when I put up a rectangle, I could present a preview for it, but the "previews` are again presented like leftovers.
My view's OnEraseBkgbd;
BOOL CanvasView::OnEraseBkgnd(CDC* pDC)
{
if (!isBackgroundInit)
{
CRect rect;
GetClientRect(&rect);
CBrush myBrush(RGB(255, 255, 255)); // dialog background color
CBrush* pOld = pDC->SelectObject(&myBrush);
BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOld); // restore old brush
isBackgroundInit = true;
return bRes;
}
return 0;
}
My OnPaint:
void CanvasView::OnPaint()
{
CDialogEx::OnPaint();
//UpdateData(true);
CRect rect;
GetClientRect(&rect);
if (dc == nullptr)
{
dc = new CClientDC(this);
//dc->CreateCompatibleDC(dc);
HDC hdc = CreateCompatibleDC(*dc);
this->hdc = &hdc;
} else
{
BitBlt(*dc, 0, 0, (int)rect.Width(), (int)rect.Height(), *hdc, 0, 0, SRCCOPY);
}
}
How it looked at first:
How it looks when opening a color button:
How it looks after closing the color button menu(leftovers marked with red arrows):
When trying to put up a rectangle:
Any idea how to fix this and really restore drawing?

I am not sure I understand what you are trying to do.
CView has a virtual method called OnDraw. This is the method you must override:
void CMyView::OnDraw(CDC* pDC)
{
CMyDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// first erase the entire client rectangle
CRect cr;
GetClientRect( &cr );
pDC->FillSolidRect( cr, RGB( 255, 255, 255 ) );
// your actual drawing goes here
pDC->Rectangle( 0, 0, 100, 100 );
}
You will also have to write OnEraseBkgnd:
BOOL CMyView::OnEraseBkgnd( CDC* /*pDC*/ )
{
return TRUE; // we fill the client rectangle in OnDraw
}
Also, it looks like you are trying to implement an ad-hoc memory DC. There is already one: CMemDC.

Related

How to get a 32bpp bitmap/image from a GDI Device Context?

I am using code from this Project http://www.codeproject.com/Articles/9064/Yet-Another-Transparent-Static-Control in order to draw transparent button images from my subclassed Button control onto my CDialogEx.
This code is meant for legacy 24bpp GDI functions:
BOOL CTransparentStatic2::OnEraseBkgnd(CDC* pDC)
{
if (m_Bmp.GetSafeHandle() == NULL)
{
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect); //convert our corrdinates to our parents
//copy what's on the parents at this point
CDC *pDC = pParent->GetDC();
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
m_Bmp.CreateCompatibleBitmap(pDC,Rect.Width(),Rect.Height());
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),pDC,Rect.left,Rect.top,SRCCOPY);
MemDC.SelectObject(pOldBmp);
pParent->ReleaseDC(pDC);
}
else //copy what we copied off the parent the first time back onto the parent
{
CRect Rect;
GetClientRect(Rect);
CDC MemDC;
MemDC.CreateCompatibleDC(pDC);
CBitmap *pOldBmp = MemDC.SelectObject(&m_Bmp);
pDC->BitBlt(0,0,Rect.Width(),Rect.Height(),&MemDC,0,0,SRCCOPY);
MemDC.SelectObject(pOldBmp);
}
return TRUE;
}
However the background of my CDialogEx is being drawn with GDI+ 32bpp rendering like this:
BOOL CParentDialogEx::OnEraseBkgnd(CDC* pDC)
{
// Get GDI+ Graphics for the current Device Context
Graphics gr(*pDC);
// Get the client area
CRect clientRect;
GetClientRect(&clientRect);
// Draw the dialog background
// PLEASE NOTE: m_imgDlgBkgnd is a Gdiplus::Image with PNG format ==> 32bpp Image
gr.DrawImage(m_imgDlgBkgnd, 0, 0, clientRect.Width(), clientRect.Height());
}
This causes the first code snippet to make a backup of a black rectangle instead of the 32bpp drawn content to it. This again causes my button control to always have a black background.
To make my problem clear, please see the pictures below:
Button images are being drawn onto the CDialogEx background (normally):
Button images are being drawn with the first code snippet
As you can see GDI 24bpp cannot see the dialog background. It just assumes plain black background. Only GDI+ could see it. However I was not able to find a way to get a bitmap from a Gdiplus::Graphics object.
How can I get a 32bpp background backup in order to draw my transparent images correctly?
Using no backup images at all causes the alpha blending of GDI+ to blur the background more and more on every draw.
After 3 days of figuring around I found something by testing. This can actually be done way easier and with 32bpp rendering!
// Get the client area on the parent
CRect Rect;
GetWindowRect(&Rect);
CWnd *pParent = GetParent();
ASSERT(pParent);
pParent->ScreenToClient(&Rect);
// Get the Parent's DC
CDC *parentDC = pParent->GetDC();
// GDI Code (only 24bpp support)
//CDC MemDC;
//CBitmap bmp;
//MemDC.CreateCompatibleDC(parentDC);
//bmp.CreateCompatibleBitmap(parentDC,Rect.Width(),Rect.Height());
//CBitmap *pOldBmp = MemDC.SelectObject(&bmp);
//MemDC.BitBlt(0,0,Rect.Width(),Rect.Height(),parentDC,Rect.left,Rect.top,SRCCOPY);
//MemDC.SelectObject(pOldBmp);
// GDI+ Code with 32 bpp support (here comes the important change)
Bitmap* bmp = new Bitmap(Rect.Width(), Rect.Height());
Graphics bmpGraphics(bmp);
HDC hBmpDC = bmpGraphics.GetHDC();
BitBlt(hBmpDC, 0, 0, Rect.Width(), Rect.Height(), parentDC->GetSafeHdc(), Rect.left, Rect.top, SRCCOPY); // BitBlt is actually capable of doing 32bpp blts
// Release DCs
bmpGraphics.ReleaseDC(hBmpDC);
pParent->ReleaseDC(parentDC);
Here you go! 4 lines of code and you get a 32bpp Gdiplus::Bitmap! Which you can draw later on in OnEraseBkgnd:
// Get client Area
CRect rect;
GetClientRect(&rect);
// Draw copied background back onto the parent
Graphics gr(*pDC);
gr.DrawImage(bmp, 0, 0, rect.Width(), rect.Height());
Please note that the Bitmap should be a member variable for performance increase. It also has to be freed on the controls destruction.
Instead of Gdiplus::Image, use Gdiplus::Bitmap which has GetHBITMAP
In this example the background is set to red for png image with transparent background:
Gdiplus::Bitmap gdi_bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
gdi_bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
CDC memdc;
memdc.CreateCompatibleDC(pDC);
memdc.SelectObject(&bmp);
pDC->StretchBlt(0, 0, clientRect.Width(), clientRect.Height(), &memdc, 0, 0,
gdi_bitmap.GetWidth(), gdi_bitmap.GetHeight(), SRCCOPY);
Alternatively, you can use a brush to handle the painting:
CBrush m_Brush;
BOOL CMyDialog::OnInitDialog()
{
CDialogEx::OnInitDialog();
Gdiplus::Bitmap bitmap(L"c:\\test\\filename.png");
HBITMAP hbitmap = NULL;
bitmap.GetHBITMAP(Gdiplus::Color(128, 0, 0), &hbitmap);
CBitmap bmp;
bmp.Attach(hbitmap);
m_Brush.CreatePatternBrush(&bmp);
return 1;
}
HBRUSH CMyDialog::OnCtlColor(CDC* pDC, CWnd* wnd, UINT nCtlColor)
{
//uncomment these two lines if you only want to change dialog background
//if (wnd != this)
// return CDialogEx::OnCtlColor(pDC, wnd, nCtlColor);
if (nCtlColor == CTLCOLOR_STATIC)
{
pDC->SetTextColor(RGB(0, 128, 128));
pDC->SetBkMode(TRANSPARENT);
}
CPoint pt(0, 0);
if (wnd != this)
MapWindowPoints(wnd, &pt, 1);
pDC->SetBrushOrg(pt);
return m_Brush;
}
BOOL CMyDialog::OnEraseBkgnd(CDC* pDC)
{//don't do anything
return CDialogEx::OnEraseBkgnd(pDC);
}

MFC how to save and reuse the current device context CDC

Well simply what I need to do is to create a grid of rectangle and fill any selected case of that grid at the user click. thus I save the parameters (color, position..). The problem is when I try to fill the case at the click event I can't any changes maybe because the device context is changed even if I use GetDC() method. So is there any way to save the current created on OnDraw() method for exmaple and use it somewhere in another function, I tried to use SaveDC() and RestoreDC() but in vain.
Here's some of my code:
void CXThiefManView::OnDraw(CDC* pDC)
{
CXThiefManDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
CRect rcClient;
GetClientRect(&rcClient);
// Get the background color of the board
COLORREF clr = pDoc->GetBoardSpace(-1, -1);
// Draw the background first
pDC->FillSolidRect(&rcClient, clr);
// Create the brush for drawing
CBrush br;
br.CreateStockObject(HOLLOW_BRUSH);
CBrush* pbrOld = pDC->SelectObject(&br);
// Draw the squares
for (int row = 0; row < pDoc->GetRows(); row++)
{
for (int col = 0; col < pDoc->GetColumns(); col++)
{
// Get the color for this board space
clr = pDoc->GetBoardSpace(row, col);
// Calculate the size and position of this space
CRect rcBlock;
rcBlock.top = row * pDoc->GetHeight();
rcBlock.left = col * pDoc->GetWidth();
rcBlock.right = rcBlock.left + pDoc->GetWidth();
rcBlock.bottom = rcBlock.top + pDoc->GetHeight();
// Fill in the block with the correct color
pDC->FillSolidRect(&rcBlock, clr);
// Draw the block outline
pDC->Rectangle(&rcBlock);
}
}
saveState = pDC->SaveDC();
DrawItem(pDC, pDoc->GetThiefRow(), pDoc->GetThiefCol(), pDoc->GetThiefColor());
}
void CXThiefManView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CView::OnLButtonDown(nFlags, point);
CXThiefManDoc* pDoc = GetDocument();
// the draw item here to fill the case
DrawItem(GetDC(), 5, 5, RGB(0, 0, 0));
}
Underneath all those MFC function, the WINAPI functions of the same name are being called. In WINAPI, GetDC returns a new device context. SaveDC & RestoreDC allow you to save and restore the state (brush, font etc...) of a specific DC handle, but that doesn't cross to other handles.
There's nothing MFC can do about it, nor can you. You have to reconfigure the new DC handle for your needs.
Handle from GetDC has to be released by ReleaseDC. Please see GetDC
Or use CClientDC dc(this) which has automatic cleanup, it's usage is similar, for example Draw(&dc,...);.
As to your main question, you can save the drawing on a bitmap. The bitmap can be saved.
define:
CBitmap m_bitmap;
CDC m_memdc;
initialize the bitmap and dc:
CClientDC dc(this);
m_bitmap.CreateCompatibleBitmap(&dc, 2000, 1200);
//this creates a bitmap as big as the screen...
m_memdc.CreateCompatibleDC(&dc);
m_memdc.SelectObject(m_bitmap);
m_memdc.FillSolidRect(0, 0, 2000, 1200, RGB(255, 255, 255));
//draw on m_memdc, then draw memdc on to dc
Usage:
void CMyWindow::OnPaint()
{
CWnd::OnPaint();
CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &m_memdc, 0, 0, SRCCOPY);
}
void CMyWindow::OnMouseMove(UINT k, CPoint p)
{
CClientDC dc(this);
CRect rc;
GetClientRect(&rc);
if (k & MK_LBUTTON)
{
//draw on m_memdc, (instead of drawing on dc)
m_memdc.SetPixel(p.x, p.y, RGB(0,0,0));
m_memdc.SetPixel(p.x, p.y+1, RGB(0,0,255));
//when finished drawing, draw m_memdc on to dc
dc.BitBlt(0, 0, rc.Width(), rc.Height(), &m_memdc, 0, 0, SRCCOPY);
}
}

Issue in drawing color on propertysheet footer?

I designed a property sheet and painted its footer to some gradient in the OnPaint() event.
The footer looks like as below.Observe the button area circled in red colour.
In the OnPaint I was doing as follows,
//CMySheet is derived from CPropertySheet.
void CMySheet::OnPaint()
{
if(IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND,reinterpret_cast<WPARAM>(dc.GetSafeHdc()),0);
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1)/2;
int y = (rect.Height() - cyIcon + 1)/2;
}
else
{
CPaintDC dc(this);
UpdateData(false);
CRect Clientrect;
GetClientRect(&Clientrect);
LONG RectDifference = ((Clientrect.bottom - m_PageRectBottom)-2);//m_pageRectBottom is of page bottom rect
CRect rectFooter(Clientrect.top,(Clientrect.bottom - RectDifference),Clientrect.right,Clientrect.bottom);//638//520
//CRect rectFooter(0,390,640,445);
FillGradation(&dc,rectFooter,RGB(150,150,150),RGB(0,0,0),true);
}
}
}
void CMySheet::OnPaint(CDC* pDC, CRect rc, COLORREF colBegin, COLORREF colEnd, bool bV)
{
TRIVERTEX av[2] = {rc.left,rc.top,GetRValue(colBegin) << 8,GetGValue(colBegin) << 8,GetBValue(colBegin) << 8 ,0xff00,
rc.right,rc.bottom,GetRValue(colEnd) << 8 ,GetGValue(colEnd) << 8,GetBValue(colEnd) << 8,0xff00,};
GRADIENT_RECT gr = {0,1};
ULONG ulMode;
if(bV){
ulMode = GRADIENT_FILL_RECT_V;
}
else{
ulMode = GRADIENT_FILL_RECT_H;
}
GradientFill(pDC->GetSafeHdc(),av,2,&gr,1,ulMode);
}
The buttons are not transparent in the above image ,but actually the background of the button should look like as in below image.
The wizard buttons background or the footer area should look like the above image.But if you can have a look at the first image in that there is some white colour around the Back button ,Next and cancel buttons.
HBRUSH CMySheet::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
HBRUSH hbr = CPropertySheet::OnCtlColor(pDC, pWnd, nCtlColor);
if((pWnd->GetDlgCtrlID() == ID_WIZBACK) || (pWnd->GetDlgCtrlID() == ID_WIZNEXT) ||
(pWnd->GetDlgCtrlID() == ID_WIZFINISH) || (pWnd->GetDlgCtrlID() == IDCANCEL))
{
return CreateSolidBrush(RGB(130,130,130));
}
return hbr;
}
If I am doing like this ,the Image is as follows with gray colour .But that colour should be gradient right,I am not able to create a Gradient brush.
I tried returning NULL in CtlColor but I could not see any difference.
Derived my own classes from CPropertySheet and CButton ,
//Overrided the DrawItem and PreSubclassWindow
void CPropSheetButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect = lpDrawItemStruct->rcItem;
UINT state = lpDrawItemStruct->itemState;
if (state & ODS_SELECTED)
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
else
pDC->DrawFrameControl(rect, DFC_BUTTON, DFCS_BUTTONPUSH);
UINT uStyle = DFCS_BUTTONPUSH;
HTHEME hTheme = OpenThemeData(m_hWnd, L"BUTTON");
HRESULT hr = DrawThemeBackground(hTheme, lpDrawItemStruct->hDC, BP_PUSHBUTTON, PBS_DEFAULTED, &lpDrawItemStruct->rcItem, NULL);
// Get the button's text.
CString strText;
GetWindowText(strText);
CloseThemeData(hTheme);
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
int nMode = pDC->SetBkMode(TRANSPARENT);
pDC->SetBkMode(nMode);
}
void CPropSheetButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
ModifyStyle(0, BS_OWNERDRAW); // make the button owner drawn
}
//In the Sheet derived class OnInitDialog ,
BOOL CMySheetWizard::OnInitDialog()
{
CPropertySheet::OnInitDialog();
CMyButton backbutton;
BOOL bRet = backbutton.SubclassDlgItem(ID_WIZBACK,this);
}
Can anyone please let me know how I can remove the border around those buttons.
Using your painting code to render the background and some extra classes, I was able to achieve this...
I think this was what you were trying to achieve. I was able to accomplish this by doing the following:
Derive my own CPropertySheet and CButton classes.
Subclass the property sheet buttons and make them owner drawn.
And here's the code that draws the buttons from the button class.
void SheetButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
UINT uStyle = DFCS_BUTTONPUSH;
HTHEME hTheme = OpenThemeData(m_hWnd, L"BUTTON");
DrawThemeBackground(hTheme, lpDrawItemStruct->hDC, BP_PUSHBUTTON, PBS_DEFAULTED, &lpDrawItemStruct->rcItem, NULL);
// Get the button's text.
CString strText;
GetWindowText(strText);
// Draw the button text using the text color red.
COLORREF crOldColor = ::SetTextColor(lpDrawItemStruct->hDC, RGB(255, 0, 0));
::DrawText(lpDrawItemStruct->hDC, strText, strText.GetLength(),
&lpDrawItemStruct->rcItem, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
::SetTextColor(lpDrawItemStruct->hDC, crOldColor);
CloseThemeData(hTheme);
}
BTW...you still need to add code to return a null brush for the buttons. I did not, however, account for the different states of the buttons in the drawing code. I left that up to you as an exercise.

C++ MFC Bitmap displays incorrectly

I am trying to get a background picture working in a MFC Dialog.
protected:
CBitmap m_Background;
CBrush m_BrushHold;
CSize m_BitmapSize;
BOOL CNameDlg::OnInitDialog()
{
//[...]
int i = m_Background.LoadBitmapW (30996);
//int y = GetLastError();
BITMAP bm;
m_Background.GetBitmap (&bm);
m_BitmapSize = CSize (bm.bmWidth, bm.bmHeight);
Invalidate(1);
//[...]
return TRUE; // return TRUE unless you set the focus to a control
}
m_BitmapSize is WAY to low. (e.g. 33, 11 while the real size is 428, 368)
BOOL CLotteryDlg::OnEraseBkgnd(CDC* pDC)
{
CDC dcMemory;
dcMemory.CreateCompatibleDC (pDC);
CBitmap* pOldbitmap = dcMemory.SelectObject (&m_Background);
CRect rcClient;
GetClientRect (&rcClient);
const CSize &sbitmap = m_BitmapSize;
pDC->BitBlt (0, 0, sbitmap.cx, sbitmap.cy, &dcMemory, 0, 0, SRCCOPY);
dcMemory.SelectObject (pOldbitmap);
return TRUE;
}
After painting it I will get either a small square in the top left corner or it displays
How can I get it to display correctly ?

Custom CTreeCtrl - How to modify text / selection color

First of all, i would customize - among other things - color of text and selection color(text background).
For example, text color should be blue; color of text background should be transparent.
So, I have overriden OnPaint() method;
I call SetTextColor() and SetBkColor() functions, but unfortunately I always get invalid colors, or I get an annoying "infinite loop flash" effect.
Here you can see his complete implementation.
void CustomTree::OnPaint()
{
CPaintDC dc(this);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CRect rcClip, rcClient;
dc.GetClipBox( &rcClip );
GetClientRect(&rcClient);
CBitmap bitmap;
bitmap.CreateCompatibleBitmap( &dc, rcClient.Width(), rcClient.Height() );
memDC.SelectObject( &bitmap );
CRgn rgn;
rgn.CreateRectRgnIndirect( &rcClip );
memDC.SelectClipRgn(&rgn);
rgn.DeleteObject();
/* WHAT IS the correct usage of SetText/Bk Color? */
// ::SetTextColor(memDC, RGB(0, 0, 255));
// ::SetBkColor(memDC, RGB(0, 0, 255));
// COLORREF col = SetTextColor(RGB(0,0,255));
// COLORREF co2 = memDC.SetTextColor(RGB(0,0,255));
// First let the control do its default drawing.
CWnd::DefWindowProc(WM_PAINT, (WPARAM)memDC.m_hDC, 0);
// do some others stuffs...
dc.BitBlt(rcClip.left, rcClip.top, rcClip.Width(), rcClip.Height(), &memDC,
rcClip.left, rcClip.top, SRCCOPY);
memDC.DeleteDC();
}
Where is the error?
Thanks
IT
If you want to change color and font for a treeview, you have to catch and respond to NM_CUSTOMDRAW. Simply setting the properties before calling the default is insufficient.