Win32 MFC OnPaint Resizing and Redrawing handling - c++

how to handle this problem everytime i resize my drawing seems to not draw right.
i think i need to call Invalidate() everytime i resize the window but doesnt WM_PAINT automatically called every time i move or resize the window?
CPaintDC dc(this); // device context for painting
CRect rect;
GetClientRect(rect);
if (IsIconic())
{
//CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
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;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
for(int ndx(0); ndx < rect.Width() - 150; ndx += 10)
{
dc.MoveTo( rect.left + 50, rect.bottom / 2 );
dc.LineTo( rect.left + 50 + ndx, rect.bottom / 2 );
}
CBrush mybrush(RGB(30,30,30));
dc.FillRect( CRect(rect.left + 10, rect.top + 10, rect.Width() / 4, rect.Height() / 4),&mybrush );
CDialogEx::OnPaint();
}
Before Resizing:
After Resizing:

When you start painting and use CPaintDC it doesn't make sense to call the base class that tries this again and may again erase the background...
What happens.
Your CPaintDC ist created
BeginPaint is called and WM_ERASEBKGND is sent.
You paint your stuff.
You call the base class anbd a new CPaintDC calles BeginPaint.
Because EndPaint isn't called the paint area isn't validated. So BeginPaint is executed and the WM_ERASEBKGND is called again.
Finally the CPaintDC's destructors are called and the client area is validated.
Never call the OnPaint baseclass function if you start using CPaintDC/BeginPaint!

I do not know if this is the optimal solution.
In your paint code, before painting, fill the entire client area with the background color:
dc.FillSolidRect(&rect, ...);
Remove CDialogEx::OnPaint().
Add an OnEraseBkgnd handler:
BEGIN_MESSAGE_MAP(CMFCApplication1Dlg, CDialogEx)
//...
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
//...
BOOL OnEraseBkgnd(CDC * pDC)
{
RedrawWindow();
return TRUE;
}
If the dialog is flickering, use MFC's CMemDC (undocumented).

this is fixes my problem
step 1: BeginPaint()
// If you add a minimize button to your dialog, you will need the code below
// to draw the icon. For MFC applications using the document/view model,
// this is automatically done for you by the framework.
void CTestDrawDlg::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect rect;
GetClientRect(rect);
// draw background manually
EraseBkgnd(&dc);
if (IsIconic())
{
//CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
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;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
for(int ndx(0); ndx < rect.Width() - 150; ndx += 10)
{
dc.MoveTo( rect.left + 50, rect.bottom / 2 );
dc.LineTo( rect.left + 50 + ndx, rect.bottom / 2 );
}
CBrush mybrush(RGB(30,30,30));
dc.FillRect( CRect(rect.left + 10, rect.top + 10, rect.Width() / 4, rect.Height() / 4),&mybrush );
}
}
STEP: 2 RedrawWindow() on WM_ERASEBACKGND
BOOL CTestDrawDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
RedrawWindow();
return TRUE;
}
and Manually draw the background
void CTestDrawDlg::EraseBkgnd( CDC * pDC )
{
CRect rect;
GetClientRect(rect);
pDC->FillSolidRect(rect, RGB(255,255,255));
// paint whatever you want in the background
}
STEP 3: EndPaint()

Related

C++ MFC overriden OnPaint() not painting on another computer

I added code to OnPaint() and it paints correctly on my Windows 10 laptop, but the painting is not shown on another computer (Windows 8). I'm a novice at painting and I probably did something wrong - maybe with invalidate and updatewindow. Regardless, here is my code:
I am painting on a dialog window; the code for OnPaint() is mostly created by Visual Studios - I simply added DrawValveImage() at the end. Also, here is picture that shows what DrawValveImage() draws. I don't think you need to look at all the code for DrawValveImage() to solve the problem; I think my error is my placement of the calling of DrawValveImage(). Maybe OnPaint() isn't the right event for custom painting.
void CCleaningAndScreeningDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
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;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
DrawValveImage();
}
void CCleaningAndScreeningDlg::DrawValveImage()
{
//my own drawing
CClientDC* pDC = new CClientDC(this);
pDC->SelectStockObject(NULL_BRUSH);
COLORREF blueBorder = RGB(67, 99, 155);
COLORREF blueFill = RGB(218, 227, 243);
int x1 = 1050;
int y1 = 50;
int width = 300;
int x2 = x1 + width;
int y2 = y1 + width;
CPen pen;
CBrush brush;
pen.CreatePen(PS_SOLID, 5, blueBorder);
brush.CreateSolidBrush(blueFill);
// select brush and pen
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
if (valveImageDrawn == FALSE)
pDC->Ellipse(x1, y1, x2, y2);
DeleteObject(brush);
DeleteObject(pen);
DeleteObject(&brush);
DeleteObject(&pen);
int heightWidth = width;
int smallHeightWidth = heightWidth / 7;
int radiusFromOriginSmallCircle = heightWidth / 2.75;
for (int circleIndex = 0; circleIndex < 10; circleIndex++) {
POINT centerSmallCircle = FindPointOnCircle(POINT{ heightWidth / 2, heightWidth / 2 }, radiusFromOriginSmallCircle, (circleIndex * 360 / 10) - 90);
int smallCircleX = centerSmallCircle.x - smallHeightWidth / 2 + x1;
int smallCircleY = centerSmallCircle.y - smallHeightWidth / 2 + y1;
rectangle smallCircleRect = { smallCircleX, smallCircleY, smallCircleX + smallHeightWidth, smallCircleY + smallHeightWidth };
rectsSmallCircles[circleIndex] = smallCircleRect;
bool greenFilled = false;
if (valvePosition - 1 == circleIndex) {
CPen pen;
CBrush brush;
COLORREF greenFill = RGB(181, 230, 29);
pen.CreatePen(PS_SOLID, 5, blueBorder);
brush.CreateSolidBrush(greenFill);
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
pDC->Ellipse(smallCircleRect.x1, smallCircleRect.y1, smallCircleRect.x2, smallCircleRect.y2);
DeleteObject(brush);
DeleteObject(pen);
DeleteObject(&brush);
DeleteObject(&pen);
greenFilled = true; //if mouse clicked and green color is painted on the valve image
}
else {
CPen pen;
CBrush brush;
pen.CreatePen(PS_SOLID, 5, blueBorder);
brush.CreateSolidBrush(blueFill);
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
pDC->Ellipse(smallCircleRect.x1, smallCircleRect.y1, smallCircleRect.x2, smallCircleRect.y2);
DeleteObject(brush);
DeleteObject(pen);
DeleteObject(&brush);
DeleteObject(&pen);
}
CString circleText;
int CircleNumber = circleIndex + 1;
circleText.Format(_T("%d"), CircleNumber);
CRect testRect = { smallCircleRect.x1,
smallCircleRect.y1, smallCircleRect.x2, smallCircleRect.y2 };
pDC->SetBkMode(TRANSPARENT);
CClientDC dc(this);
CFont font;
VERIFY(font.CreateFont(
30, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_BOLD, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("Arial"))); // lpszFacename
CFont* def_font = pDC->SelectObject(&font);
pDC->DrawText(circleText, testRect, DT_SINGLELINE | DT_VCENTER | DT_CENTER);
pDC->SelectObject(def_font);
font.DeleteObject();
}
//redraw the combobox infront of the valve-image
UpdateData(TRUE);
comboPorts.Invalidate();
comboPorts.UpdateWindow();
valveImageDrawn = true;
}
OnPaint() is the right method to do custom painting. When you override OnPaint(), you should not call the OnPaint() method of the base class. You are responsible to draw all of the content of the window on your own (except child windows).
You only need this code:
void CCleaningAndScreeningDlg::OnPaint()
{
CPaintDC dc(this); // constructor of CPaintDC calls ::BeginPaint()
DrawValveImage( dc ); // pass device context to the drawing function
// Destructor of CPaintDC automatically calls ::EndPaint()!
}
You can see that I have added a parameter to DrawValveImage. The declaration would look like this:
void DrawValveImage( CDC& dc );
Make sure that in your drawing function, you only use the dc parameter for drawing.
This is wrong:
CClientDC* pDC = new CClientDC(this);
You should not create any additional device contexts.
Example of another mistake:
pDC->SelectObject(&pen);
pDC->SelectObject(&brush);
if (valveImageDrawn == FALSE)
pDC->Ellipse(x1, y1, x2, y2);
DeleteObject(brush);
DeleteObject(pen);
DeleteObject(&brush);
DeleteObject(&pen);
First, you don't restore the original state of the device context before you delete objects selected into it. When an object is still selected into a device context, it cannot be deleted correctly. To fix that, store the return value of SelectObject() and SelectStockObject(), which is a pointer to the object previously selected in the device context (if you didn't select one, there is a default object). Before you delete the object, call SelectObject(), passing the stored pointer.
Second, why do you delete objects twice? Either let the object destroy itself automatically when it leaves the current scope or call the DeleteObject() member function (rarely needed).
Corrected code:
auto pOldPen = dc.SelectObject(&pen);
auto pOldBrush = dc.SelectObject(&brush);
if (valveImageDrawn == FALSE)
dc.Ellipse(x1, y1, x2, y2);
if(pOldPen)
dc.SelectObject(pOldPen);
if(pOldBrush)
dc.SelectObject(pOldBrush );
// No need for DeleteObject(), the destructor of each object will delete it at the
// end of the current scope (before the function returns). But if you actually need it
// (say you want to reuse the variable), it would look like this:
// pen.DeleteObject();
// brush.DeleteObject();
This code is unnecessary:
//redraw the combobox infront of the valve-image
UpdateData(TRUE);
comboPorts.Invalidate();
comboPorts.UpdateWindow();
Just set the WS_CLIPCHILDREN style of the dialog window (using the dialog editor) to prevent the painting code from drawing over the combo box.

OnDraw not getting called

I'm trying to display an image in CScrollView-derived class:
C++ CScrollView, how to scroll an image?
So I want to override OnDraw, to move the code from OnPaint to OnDraw. But I can't. Every time I call Invalidate() only OnPaint is getting called.
void CCardioAppView::OnDraw(CDC* pDC)
{
}
void CCardioAppView::OnPaint()
{
if (theApp.ImageFolderPath == _T("")) return;
//---------------------метод № 2 с CPictureHolder-------------------------------
CPaintDC dc(this);
CBitmap bmp;
BITMAP b;
HBITMAP hbitmap;
CRect rect;
auto bmp_iter = theApp.FullBmpMap.find(m_iCurrentImage);
if (bmp_iter == theApp.FullBmpMap.end()) return;
hbitmap = bmp_iter->second;
bmp.Attach((*bmp_iter).second);
bmp.GetObject(sizeof(BITMAP), &b);
GetClientRect(&rect);
scaleRect = rect;
OriginalWidth = b.bmWidth;
OriginalHeight = b.bmHeight;
if (rect.Height() <= b.bmHeight)
scaleRect.right = rect.left + ((b.bmWidth*rect.Height()) / b.bmHeight);
else if (rect.Height() > b.bmHeight)
{
scaleRect.right = b.bmWidth;
scaleRect.bottom = b.bmHeight;
}
scaleRect.right = scaleRect.right + scale_koef_g;
scaleRect.bottom = scaleRect.bottom + scale_koef_v;
pic.CreateFromBitmap(hbitmap);
pic.Render(&dc, scaleRect, rect);
(*bmp_iter).second.Detach();
(*bmp_iter).second.Attach(bmp);
bmp.Detach();
int isclWidth = scaleRect.Width();
int isclHeight = scaleRect.Height();
int irHeight = rect.Height();
int irWidth = rect.Width();
if ((isclWidth> irWidth)||(isclHeight > irHeight))
{
SetScrollSizes(MM_TEXT, CSize(isclWidth, isclHeight));
}
else SetScrollSizes(MM_TEXT, CSize(irWidth, irHeight));
}
Of course it doesn't call OnDraw(). When you call Invalidate() it ends up with a WM_PAINT message for the CView derived class. The default implementation of CView::OnPaint() gets a paint DC and then it later calls CView::OnDraw(). You are overriding OnPaint() and you never call OnDraw() in your OnPaint() handler.
You can move some of your OnPaint() code into OnDraw() except for obvious stuff like CPaintDC dc(this);
After that you can delete your OnPaint() declaration and implementation. Then, delete your ON_WM_PAINT() message map entry. I can't vouch either way for your drawing code.

Why CDC::LineTo() doesn't draw in Visual C++ 2015 MFC Dialog?

I'm learning MFC and I'm trying to draw some lines on a MFC Dialog-based application main window, it shall be a rather simple task but while running I see no lines drawing on the dialog.
Following is the method I wrote:
// draw corner of a rectangle on specified device context
void CTestDrawCornerDlg::DrawCorner(
CDC* pDC,
const CornerType& type,
const CPoint& position,
const unsigned int& size
)
{
CPen pen(PS_SOLID, 5, RGB(0, 0, 0));
CPen* pOldPen = pDC->SelectObject(&pen);
CPoint pH, pV;
// I could make following lines simply with a 2-lines block,
// but I'd leave it as it was to make it easier to understand.
switch (type)
{
case LEFT_TOP:
pH.x = position.x + size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y + size;
break;
case LEFT_BOTTOM:
pH.x = position.x - size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y + size;
break;
case RIGHT_TOP:
pH.x = position.x + size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y - size;
break;
case RIGHT_BOTTOM:
pH.x = position.x - size;
pH.y = position.y;
pV.x = position.x;
pV.y = position.y - size;
break;
default: break;
}
pDC->MoveTo(position);
pDC->LineTo(pH);
pDC->MoveTo(position);
pDC->LineTo(pV);
pDC->SelectObject(pOldPen);
}
And I called this method in OnPaint method of Dialog class:
void CTestDrawCornerDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting
// lines generated automatically when creating
// MFC project are truncated for brevity
}
else
{
CDialogEx::OnPaint();
}
CPaintDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
I guess it's a newbie mistake but I just don't know what the mistake is. Thanks for help!
P.S. please download from following link the MFC project to re-create this problem:
https://www.dropbox.com/s/exeehci9kopvgsn/TestDrawCorner.zip?dl=0
You can change your code to use CDialogEx::OnPaint() + CClientDC as follows:
void CTestDrawCornerDlg::OnPaint()
{
CDialogEx::OnPaint();
CClientDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
or just use CPaintDC:
void CTestDrawCornerDlg::OnPaint()
{
CPaintDC pDC(this);
DrawCorner(&pDC, LEFT_TOP, CPoint(50, 50), 50);
}
But don't use OnPaint + CPaintDC
To see the problem, note how OnPaint and CPaintDC are defined in MFC:
void CDialog::OnPaint()
{
CPaintDC dc(this);
if (PaintWindowlessControls(&dc))
return;
Default();
}
CPaintDC::CPaintDC(CWnd* pWnd)
{
if (!Attach(::BeginPaint(m_hWnd = pWnd->m_hWnd, &m_ps)))
AfxThrowResourceException();
}
::BeginPaint is a core WinAPI function. It should only be called once in response to WM_PAINT, and it can't be used anywhere else.
CClientDC on the other hand uses ::GetDC which can be used pretty much anywhere, as long as window handle is available.

MFC: Draw dialog border using GDI+

I modified my dialog to a polygon region dialog. Then i am trying to frame/draw the border.Using device context the CRgn::FrameRgn, i am bale to draw the border around the dialog. But i want to achieve this using Gdi+. I did as below, but the border is appearing only on left and top of the dialog.
Can someone please help on this.
CPoint vertex[4];
BOOL CPolygonDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
ModifyStyle(WS_CAPTION,0);
ModifyStyle(WS_BORDER,0);
CRect rect(400,200,900,700);
CRect wr = rect;
AdjustWindowRect( wr, 0, FALSE );
MoveWindow(wr);
GetClientRect( rect );
CRect csr = rect;
ClientToScreen( csr );
vertex[0] = CPoint(rect.left,rect.top);
vertex[1] = CPoint(rect.right,rect.top);
vertex[2] = CPoint(rect.right,rect.bottom);
vertex[3] = CPoint(rect.left,rect.bottom);
m_rgnShape.CreatePolygonRgn( vertex, 4, ALTERNATE );
m_rgnShape.OffsetRgn( CPoint( csr.TopLeft() - wr.TopLeft() ) );
SetWindowRgn( (HRGN)m_rgnShape.Detach(), TRUE );
m_rgnShape.CreatePolygonRgn( vertex, 4, ALTERNATE );
return TRUE; // return TRUE unless you set the focus to a control
}
void CPolygonDlg::OnPaint()
{
Graphics graphics(dc.m_hDC);
CRect rect;
GetClientRect(rect);
GraphicsPath gp;
Point point[4];
point[0] = Point(rect.left,rect.top);
point[1] = Point(rect.right,rect.top);
point[2] = Point(rect.right,rect.bottom);
point[3] = Point(rect.left,rect.bottom);
gp.AddPolygon(point,4);
Pen pen(Color(255, 255, 0, 0));
graphics.DrawPath(&pen, &gp);
}
Thanks
When you call GetClientRect(), it returns the size of the client area of the window - the part you can easily draw on, and the part that is controlled by the device context when you do CPaintDC dc(this); in your OnPaint() method.
The problem you are facing is that your dialog window has a border and you need to handle WM_NCPAINT in order to draw on border area.

SetPixel crashes after an amount of pixels

Im new in MFC/C++ an Im trying to fill my windows with pixels.I found out that there is a function which is called :
SetPixel(X,Y,RGB(,,));
After I tried to put use it in my loop I found out that this function stops after an amount of pixels.So It don't give me the result I actually want to reach.
Here is my code :
void PIXELPROG::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this);
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;
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
CStatic * XText = (CStatic *)GetDlgItem(IDC_X);
CStatic * YText = (CStatic *)GetDlgItem(IDC_YWERT);
CString XYWert;
for (int x=0,y=0;;)
{
GetDC()->SetPixelV(x, y, RGB(y,x,y));
XYWert.Format(L"%d",y);
XText->SetWindowTextW(XYWert);
++x;
if (x == 500)
{
++y;
x = 0;
}
if (y == 100)
{
break;
}
}
}
I also don't get any errors.It is just stopping.
I also tried it with
SetPixelV()
But didn't helped neither.
Someone got an idea ?
From the documentation for CWnd::GetDC:
Unless the device context belongs to a window class, the ReleaseDC member function must be called to release the context after painting.
Since you're not assigning the return value from GetDC to anything, there's no way for you to call ReleaseDC. Since they're not released, they build up - there's a limit to the total number of GDI objects your application can use, see GDI Objects. Once you hit that limit, things go bad very fast (Don't ask me how I know).
If this is in response to a WM_PAINT message, you shouldn't be calling GetDC in the first place. You should be using the CPaintDC object that you create. As a general rule, don't call the parent OnPaint method in your own OnPaint handler, because you can only generate a single CPaintDC.
I found the solution. You have to initialize the DC before. So don't use GetDC()->SetPixelV(x, y, RGB(y,x,y));. Make an object before f.x CDC *pDC = GetDC(); an then use pDC->SetPixelV(x, y, RGB(y,x,y));.This is it ! Now it works like a charm :-)
Hopefully this will help someone !