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.
Related
I try to make some easy application which scrolling ECG singal which is drawing on bitmap grid. Environment which I use is Visual Studio 2013 with C++ MFC.
My problem is with transfer gdi object like LineTo or Rectangle() function from dcMemory to my main device context (cdc). Before I make similar application using WinAPI and all go well. I spent a lot time with studying msdn and looking answer with google, and I have no idea why only bitmap from Bitmap.LoadBitmapW(IDB_BITMAP2) is printing. Can anybody help me?
Message when button from menu was calling:
void CToradex_MFC_BitmapView::OnBitmapDraw()
{
Bitmap.LoadBitmapW(IDB_BITMAP2);
cdc.CreateDC(L"DISPLAY", NULL, NULL, NULL);
dcMemory.CreateCompatibleDC(&cdc);
LoadData();
GetObject(Bitmap, sizeof(bmpInfo), &bmpInfo);
dcMemory.SelectObject(&Pen);
dcMemory.Rectangle(100, 100, 200, 200);
dcMemory.LineTo(100, 300);
dcMemory.SelectObject(&Bitmap);
druk.DrawECG(&dcMemory, pointer, 3, SIGN_LEN);
}
Message on mouse button down:
void CToradex_MFC_BitmapView::OnLButtonDown(UINT,CPoint)
{
CToradex_MFC_BitmapDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
POINT p;
GetCursorPos(&p);
x_start = p.x;
y_start = p.y;
}
Message on mouse move:
void CToradex_MFC_BitmapView::OnMouseMove(UINT, CPoint)
{
POINT d;
//CDC * pDC = this->GetDC();
//this->GetClientRect(&rect);
//rect = CRect(rect.left, rect.top, rect.right, rect.bottom);
if (GetCursorPos(&d))
{
move_x = d.x - x_start;
move_y = d.y - y_start;
cdc.BitBlt(move_x, move_y, bmpInfo.bmWidth, bmpInfo.bmHeight, &dcMemory, 0, 0, SRCCOPY);
x_start = d.x;
y_start = d.y;
//Invalidate();
Sleep(10);
}
Below link for all .cpp file:
https://pastebin.com/h7hcLJbz
You need to select a bitmap into your DC first, then draw on top of it:
dcMemory.SelectObject(&Bitmap);
dcMemory.SelectObject(&Pen);
dcMemory.Rectangle(100, 100, 200, 200);
dcMemory.LineTo(100, 300);
druk.DrawECG(&dcMemory, pointer, 3, SIGN_LEN);
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()
Hello and happy new year, (it is acceptable to say it until Thursday)
I am trying to change the color of the tabs in the CTabCtrl class. I am trying to create my own ReskinCTablCtrl so that I can just call it in separate classes and easily use it throughout my program.
Currently I am able to change the background color of the CTabCtrl but I cannot modify the tab's themselves.
I used ON_WM_ERASEBKGND() for painting the background and it worked without a problem:
BOOL ReskinCTabCtrl::OnEraseBkgnd(CDC* pDC)
{
CRect rect;
GetClientRect(&rect);
CBrush myBrush(RGB(51, 51, 51)); // dialog background color
BOOL bRes = pDC->PatBlt(0, 0, rect.Width(), rect.Height(), PATCOPY);
pDC->SetBkColor(RGB(51, 51, 51));
pDC->FillRect(&rect, &myBrush);
return bRes;
}
However, I have been unsuccesfull at changing the tab colors themselves. They are still the default MFC colors. I have tried to implement ON_WM_PAINT() and ON_WM_DRAWITEM() without any success. I think I can get to the specific tab rect with using both OnDraw and DrawItem similar to the second link example that I have posted in the end of this question.
void ReskinCTabCtrl::OnPaint() {
...
// paint the tabs first and then the borders
int nTab = GetItemCount();
int nSel = GetCurSel();
if (!nTab) // no pages added
return;
while (nTab--)
{
if (nTab != nSel)
{
dis.itemID = nTab;
dis.itemState = 0;
VERIFY(GetItemRect(nTab, &dis.rcItem));
dis.rcItem.bottom -= 2;
DrawItem(&dis);
DrawItemBorder(&dis);
}
}
...
}
I would really appreciate at least some direction to go about this problem, perhaps some more examples or what methods I should focus on using. I don't need the tabs to be different colors, maybe there is an easy way of doing this?
I've been trying to follow some examples like the following links but I still couldn't figure out the right way to do it.
https://support.microsoft.com/en-us/help/179909/how-to-change-the-background-color-of-a-tab-control
https://www.codeproject.com/Articles/1786/Ownerdraw-Tab-Controls-Borders-and-All
Enable OwnerDraw for tab control, either in resource editor, or set TCS_OWNERDRAWFIXED in OnInitDialog
CTabCtrl has message reflection for WM_DRAWITEM therefore we don't want to override WM_DRAWITEM/OnDrawItem from parent class. Instead override in CTabCtrl::DrawItem(LPDRAWITEMSTRUCT).
Unfortunately the result is rather ugly. It's sort of like overriding DrawItem in a button.
If Visual Style is available and enabled, then you can override CTabCtrl::OnPaint and draw everything manually. Example:
void CMyTabCtrl::OnPaint()
{
CPaintDC dc(this);
dc.SelectObject(GetFont());
CPen pen, pen_active;
COLORREF color_off = RGB(240, 240, 240);
COLORREF color_active = RGB(200, 240, 240);
CBrush brush_off, brush_active;
brush_off.CreateSolidBrush(color_off);
brush_active.CreateSolidBrush(color_active);
pen.CreatePen(PS_SOLID, 1, RGB(200, 200, 200));
pen_active.CreatePen(PS_SOLID, 1, color_active);
CRect rcitem;
GetItemRect(0, &rcitem);
CRect rc;
GetClientRect(&rc);
rc.bottom = rcitem.bottom;
dc.FillSolidRect(&rc, GetSysColor(COLOR_3DFACE));
GetClientRect(&rc);
rc.top = rcitem.bottom - 1;
dc.SelectObject(&pen);
dc.SelectObject(&brush_active);
dc.Rectangle(&rc);
for(int i = 0; i < GetItemCount(); i++)
{
dc.SelectObject(&pen);
if(i == GetCurSel())
{
dc.SelectObject(&brush_active);
dc.SetBkColor(color_active);
}
else
{
dc.SelectObject(&brush_off);
dc.SetBkColor(color_off);
}
GetItemRect(i, &rcitem);
rcitem.right++;
dc.Rectangle(&rcitem);
if(i == GetCurSel())
{
dc.SelectObject(pen_active);
dc.MoveTo(rcitem.left+1, rcitem.bottom - 1);
dc.LineTo(rcitem.right, rcitem.bottom - 1);
}
TCITEM item = { 0 };
wchar_t buf[32];
item.pszText = buf;
item.cchTextMax = 32;
item.mask = TCIF_TEXT;
GetItem(i, &item);
dc.DrawText(buf, &rcitem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
}
BOOL CMyTabCtrl::OnEraseBkgnd(CDC*)
{
return TRUE;
}
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.
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.