MFC: flickering issue with GDI+ - mfc

I create a sample dialog application which has a circle drawn. Also on mouse move the circle will be re-drawn. I am providing my code below. Its also compilable.
I tried using double buffering and erasebackground, i was not getting the flickering issue, but i observed that the drawining is not erased properly. So to erase, in OnPaint i wrote the erasing code. Again i am facing the flickering issue.
void CPOCDlg::OnPaint()
{
CPaintDC dc(this);
GetClientRect(&clientRect);
circle = clientRect;
circle.DeflateRect(100,100);
dc.SelectStockObject(NULL_BRUSH);
dc.SelectStockObject(NULL_PEN);
dc.FillSolidRect(circle, ::GetSysColor(COLOR_BTNFACE));
Bitmap buffer(circle.right, circle.bottom);
Graphics graphicsbuf(&buffer);
Graphics graphics(dc.m_hDC);
graphicsbuf.SetSmoothingMode(SmoothingModeHighQuality);
SolidBrush brush(Color(255,71,71,71));
Pen bluePen(Color(255, 0, 0, 255),1);
graphicsbuf.DrawEllipse(&bluePen,Rect(circle.left,circle.top,circle.Width(),circle.Height()));
graphicsbuf.SetSmoothingMode(SmoothingModeHighQuality);
graphics.DrawImage(&buffer, 0, 0);
}
void CPOCDlg::OnMouseMove(UINT nFlags, CPoint point)
{
m_point = point;
InvalidateRect(circle,FALSE);
CDialogEx::OnMouseMove(nFlags, point);
}
BOOL CPOCDlg::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
Please let me know if i am doing any mistake.

You need to use so called double buffer technique to prevent flickering:
// create Mem DC
dcMemory = new CDC;
dcMemory->CreateCompatibleDC(pDC);
pDC->SetMapMode(MM_TEXT);
dcMemory->SetMapMode(MM_TEXT);
// TODO: draw to memDC here
//switch back to paint dc
pDC->BitBlt(rectDirty.left, rectDirty.top,
rectDirty.Width(), rectDirty.Height(),
dcMemory,
rectDirty.left,rectDirty.top,SRCCOPY);
dcMemory->DeleteDC();
delete dcMemory;
dcMemory = NULL;

Related

Make CRectTracker object top-most

I have an MFC dialog application where the user can upload a photo and it will be displayed within the dialog. After they have uploaded an image they will be able select a region of this image and for this I am using the CRectTracker class. My problem however is that the tracker is currently being displayed behind the image.
What I have tried so far is:
Googling how to set the z-order of the CRectTracker object, but cannot seem to find any documentation on it.
Set the z-order of the image to the bottom using SetWindowPos(&wndBottom,...) but the tracker still appears behind the image.
I am using a Picture Control (type Bitmap) to display the image.
EDIT:
I draw the tracker in the OnPaint() method of my dialog class like this:
CPaintDC dc(this);
m_tracker.Draw(&dc);
which is being called by this->Invalidate(); in OnLButtonDown and OnMouseMove whenever I need to refresh the tracker:
void CTAB3::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_tackerFlag)
{
int nHitTest;
nHitTest = m_tracker.HitTest(point);
if (nHitTest < 0) // if the mouse down point is outside of the rect tracker
{
m_start = point; // Record the start drawing point
m_bDraw = TRUE; // set m_bDraw (in handle funtion WM_MOUSEMOVE will test this to decide whether to draw)
}
else // if the mouse down point is inside of the rect tracker
{
m_tracker.Track(this, point, FALSE); // start drag the rect tracker
this->Invalidate(); // make the window paint to refresh the track
}
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CTAB3::OnMouseMove(UINT nFlags, CPoint point)
{
// m_bDraw is set to true in funtion OnLButtonDown and set to false in funtion OnLButtonDown
// m_bDraw is use for testing if the mouse is moved along with the left button is pressed.
if (m_bDraw)
{
m_tracker.m_rect.SetRect(m_start, point); // set the rect of rect tracker
this->Invalidate(); // make the window paint to refresh the rect
}
CDialogEx::OnMouseMove(nFlags, point);
}
EDIT2:
Based on #ConstantineGeorgiou comment I decided to keep trying to implement the tracker with the resize handles using Track() as this would be the preferred solution for my project. I finally managed to draw the tracker on top of the image, but I am not particularly happy with the solution I ended up with. Basically I moved the Draw() function to the mouse move handler, so that the tracker is drawn after the OnPaint() has drawn everything else on the dialog. If anyone has any suggestion on better ways to do this, I would be very grateful for any feedback.
This is how I implemented it with the Draw() in the OnMouseMove() handler:
//In the header file:
BOOL m_bDraw = FALSE
void CTAB3::OnLButtonDown(UINT nFlags, CPoint point)
{
if (m_tackerFlag)
{
m_tracker.Track(this, point, FALSE); // start drag the rect tracker
this->Invalidate();
m_bDraw = TRUE;
}
CDialogEx::OnLButtonDown(nFlags, point);
}
void CTAB3::OnMouseMove(UINT nFlags, CPoint point)
{
if (m_bDraw)
{
//Draw tracker
CDC* dc = GetDC();
m_tracker.Draw(dc);
}
CDialogEx::OnMouseMove(nFlags, point);
}
BOOL CTAB3::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if (m_tackerFlag)
{
if (pWnd == this && m_tracker.SetCursor(this, nHitTest))
{
return TRUE;
}
}
return CDialogEx::OnSetCursor(pWnd, nHitTest, message);
}
void CTAB3::OnPaint()
{
CPaintDC dc(this); // device context for painting
//m_tracker.Draw(&dc);
}

CClientDC and DC not Drawing on ChtmlEditCtrl

Hi all I am working with a CHtmlEditCtrl in MFC. I want to draw some random rectangles and lines inside a function handling right click event.
The ChtmlEditCtrl control is created from static using this snippet:
bool CHtmlEditCtrlEx::CreateFromStatic( UINT nID, CWnd* pParent ) {
CStatic wndStatic;
if ( !wndStatic.SubclassDlgItem(nID, pParent)) {
return false;
}
CRect rc;
wndStatic.GetWindowRect( &rc );
pParent->ScreenToClient( &rc );
if (Create( 0, (WS_CHILD | WS_VISIBLE), rc, pParent, nID, 0 )) {
...
}
Then I override the CWnd::pretranslate() function as thus:
CClientDC dcc(this);
switch (pMsg->message) {
case WM_RBUTTONUP: // Right-click
// Just some dummy values
DrawSquigly(dcc, 600, 240, 20);
break;
}
the DrawSquigly() function is defined as thus:
void CHtmlEditCtrlEx::DrawSquigly(CDC &dcc, int iLeftX, int iWidth, int iY)
{
CAMTrace trace;
trace.Trace("Drawing Squiggly");
//dcc.TextOut(10, 10, CString(_T("I used a client DC!")));
CPen * oldPen;
CBrush * oldBrush;
oldPen = (CPen *) dc.SelectStockObject(WHITE_PEN);
dcc.MoveTo(5,10);
dcc.LineTo(80, 10);
dcc.SelectObject(oldPen);
//GDI 002_2: Create custom pen with different Line thickness.
CPen thick_pen(PS_SOLID, 3, RGB(0,255,0));
oldPen = dc.SelectObject(&thick_pen);
dcc.MoveTo(5, 20);
dcc.LineTo(80,20);
dcc.SelectObject(oldPen);
//GDI 002_3: Create a Rectangle now
dcc.Draw3dRect(5,30,80,70, RGB(25,25,255), RGB(120,120,120));
//GDI 002_4: Create a Brush that we can use for filling the
// closed surfaces
CBrush brush(RGB(255,0,255));
oldBrush = dc.SelectObject(&brush);
dcc.Rectangle(5,110,80,140);
dcc.SelectObject(oldBrush);
//GDI 002_5: Hatch Brush is useful to apply a pattern in stead
//of solid fill color
CBrush* hatBrush = new CBrush();
hatBrush->CreateHatchBrush(HS_CROSS, RGB(255,0,255));
oldBrush = dc.SelectObject(hatBrush);
dcc.FillRect(new CRect(5,160,80,190), hatBrush);
dcc.SelectObject(oldBrush);
}
but no drawing happens when I right click. I think I am missing something especially because I am new to MFC.
I have added a trace to the top of the event handler to be sure that the function is getting called and it is.
Can anyone please point me the right direction?
There are actually 2 device context in your code: one you pass as parameter in the call (we don't know where it comes from) and the other created locally in the drawing function.
Normally, when the systems gives you a DC it expects you draw something in it, not that you draw into something else.
If the window you are working on is layered, the system gives you a memory context you draw in that is - upon clearing - blit-ted onto the window itself with some window manager effect.
My suspect is that -by allocating a second dc- your drawing are ovewritten when the first one (you left blank) is cleared upon returning from the message handler.

Clipping region for OnEraseBkgnd differs from clipping region for OnPaint

I'm writing an mfc application.
I've a simple CWnd with OnEraseBkgnd and OnPaint. I'm experiencing some problems when another window covers partially my window.
So When the covering window is being moved out my CWnd gets WM_ERASEBKGND. I'm cleaning up dirty area and I return TRUE. What I can see here is that CDC I get has clipping box set and I use it so only a covered part is being erased. That's good.
But then WM_PAINT comes. CDC I get with GetDC does not have any clipping box so the whole window area is being repainted. This is a problem because in my paint event I use CDC::DrawText with a transparent background (CDC::SetBkMode(TRANSPARENT)) and painting the same text in the same not-erased place causes that text becomes 'bold'. Simply painting text over and over in the same place without wiping out background makes it look ugly.
Is it a normal behavior? Is my approach ok?
EDIT:
Here I attach more inforamtion about issue.
SSCCE:
class Foo : public CFrameWnd
{
public:
BOOL OnEraseBkgnd(CDC* pDC)
{
CRect rect;
pDC->GetClipBox(rect);
HBRUSH brush = ::GetSysColorBrush(COLOR_WINDOW);
HGDIOBJ pOld = pDC->SelectObject(brush);
const BOOL result = pDC->PatBlt(rect.left, rect.top, rect.Width(), rect.Height(), PATCOPY);
pDC->SelectObject(pOld);
return result;
}
void OnPaint()
{
CWnd::OnPaint();
CDC *dc = GetDC();
CRect clipBox;
dc->GetClipBox(clipBox);
CRect rect;
GetClientRect(rect);
CFont *font = &globalFont; // in my app here is the font I use but it doesn't matter
HFONT hFont = static_cast<HFONT>(font->GetSafeHandle());
auto oldFont = dc->SelectObject(hFont);
const int bkMode = dc->SetBkMode(TRANSPARENT);
dc->DrawText("AAAAAAAAA", -1, rect, 0);
dc->SetBkMode(bkMode);
dc->SelectObject(oldFont);
}
DECLARE_MESSAGE_MAP()
};
Creation:
Foo* f = new Foo;
f->Create( 0, "test", WS_VISIBLE| WS_OVERLAPPEDWINDOW);
Below how does the window look normally:
And below after moving the window so half of the text was out of monitor and then moved back:
So the part of window which was invisible was erased and then text was placed again. For the visible part of the window text was not erased and in OnPaint was redrawn again causing 'bold'.
You should be using CPaintDC, not just because it controls resources, as Barmak pointed out, but also because it retrieves clipping data. GetDC does not do that. (Barmak also mentioned PAINTSTRUCT, but it may not be clear that is the key to the clipping issue.)
This is unrelated issue, but GetDC is causing GDI resource leak in above code. ReleaseDC must be called before exiting the function:
void OnPaint()
{
CWnd::OnPaint();
CDC *dc = GetDC();
dc.DrawText(...);
...
ReleaseDC(dc);
}
Better yet, MFC has automatic cleanup with CClientDC
void myWnd::foo()
{
CClientDC dc(this);
dc.DrawText(...);
}
OnPaint can use special CPaintDC class which corresponds to PAINTSTRUCT:
void myWnd::OnPaint()
{
CPaintDC dc(this); //don't call CWnd::OnPaint
dc.DrawText(...);
}
Back to the problem:
It looks like part of background is not repainted, but part of the text is repainted. This makes it look ugly specially with clear type fonts (it looks like bold but it isn't).
You can fix the problem with this:
dc.SetBkMode(OPAQUE);
dc.SetBkColor(GetSysColor(COLOR_WINDOW));
dc.DrawText(L"AAAAAAAAA", -1, rect, 0);
Another option: override OnEraseBkgnd and force it to do nothing:
BOOL OnEraseBkgnd(CDC*)
{
return TRUE;
}
Do all of the painting in OnPaint()
void myWnd::OnPaint()
{
CPaintDC dc(this);
CRect rect;
GetClientRect(rect);
dc.FillSolidRect(rect, ::GetSysColor(COLOR_WINDOW) );
CFont *font = &globalFont;
auto oldFont = dc.SelectObject(font->GetSafeHandle());
dc.SetBkMode(TRANSPARENT);
dc.DrawText(L"AAAAAAAAA", -1, rect, 0);
dc.SelectObject(oldFont);
}

Clear GDI shape inside the loop

I have a program which draw a Rectangle under mouse cursor and show the pixel color, but I can't manage it to clear the shape inside the while loop, if I use 'InvalidateRect()' it clear rectangle too fast and flickering, if not use 'InvalidateRect()' then Rectangle keep duplicating like THIS, how to fix that?
HWND hwnd;
POINT p;
unsigned short R=0, G=0, B=0;
void drawRect()
{
GetCursorPos(&p);
HDC hdc = GetDC(NULL);
HPEN border = CreatePen(PS_SOLID, 2, RGB(0, 0, 0));
HBRUSH background = CreateSolidBrush(RGB(R, G, B));
SelectObject(hdc, border);
SelectObject(hdc, background);
Rectangle(hdc, p.x+10, p.y+10, p.x+40, p.y+40);
DeleteObject(border);
DeleteObject(background);
}
void init()
{
while (GetAsyncKeyState(VK_RBUTTON) & 0x8000)
{
grabPixel(); //get RGB color from cursor coordination
drawRect(); //draw preview rectangle under cursor
InvalidateRect(hwnd, NULL, true);
}
}
Note: it doesn't have WinMain() or WndProc()
There are all sorts of things wrong with this. What are you actually trying to do?
From the fact that you're using GetDC(NULL), it looks like this is supposed to be drawing a rectangle on the entire screen.
Where is the hwnd value coming from? If that window does have a message loop (and it probably does), then that's the window being invalidated and redrawing itself.
A note: InvalidateRect merely marks the rectangle as needing-to-be-painted the next time that that application's (actually thread's, more-or-less) message queue is empty. UpdateWindow will cause a WM_PAINT message to be sent immediately.
drawRect isn't cleaning up properly, either. It should call ReleaseDC when it's finished, and it ought to restore the previous drawing objects after it's finished (and most definitely before it deletes them) as well:
HBRUSH oldBackground = SelectObject(hDC, background);
// ...
SelectObject(hDC, oldBackground);
What you probably want to do is, when selection starts, create a window the size of the screen and copy the existing screen into it. Then you can draw all over that intelligently.
The DrawDragRect function (see my blog) is designed for this sort of thing.

Displaying image in MFC method that is not ONPaint

I am trying to display an image in a dialog dynamically, it works no problem if I put the code in the on paint method and use the dc from there, I can't do this though I need to display after the window is shown, the code I am using is as follows, I am getting the dc of the client window creating the bitmap from a resource and "trying" to display it in the window but nothing displays, Any suggestions what might be wrong?
void CProcessSteps::OnShowWindow(BOOL bShow, UINT nStatus)
{
CDialog::OnShowWindow(bShow, nStatus);
SetupInstructions();<-----------------Call To Method
}
void CProcessSteps::OnPaint()
{
CPaintDC dc(this);
}
void CProcessSteps::SetupInstructions()
{
CDC *pDC = new CDC();<------------------------------Problem starts here
CFontUtil cfu;
cfu.SetFont(&LineFont,30);
CDC memDC;
memDC.CreateCompatibleDC(pDC);
int stepTop = 10;
int stepEnd = 230;
int imageLeft = 30;
STEP_STRUCT* step;
CBitmap iconImage;
iconImage.LoadBitmap ( IDB_TID_CHECK );
memDC.SelectObject(&iconImage);
CRect iconRect;
BITMAP bmInfo;
iconImage.GetObject ( sizeof ( bmInfo ), &bmInfo );
iconRect.SetRect ( imageLeft, stepTop, imageLeft+bmInfo.bmWidth, stepTop+bmInfo.bmHeight );
pDC = this->GetDC();
pDC->BitBlt(imageLeft, stepTop, imageLeft+bmInfo.bmWidth, stepTop+bmInfo.bmHeight, &memDC, 0, 0, SRCCOPY);
//RedrawWindow();<-------- tried this here no luck
int stepCount = m_pageStructure->PageSteps.GetCount();<----------------------------Bellow this works correctly
POSITION pos = m_pageStructure->PageSteps.GetHeadPosition();
while (pos)
{
step = m_pageStructure->PageSteps.GetNext(pos);
CStatic *label = new CStatic;
label->Create(_T( step->StepInstruction ),WS_CHILD | WS_VISIBLE, CRect(80, stepTop, 480, stepEnd), this);
label->SetFont(&LineFont, true);
label->GetWindowRect(rect);
ScreenToClient(rect);
pDC = label->GetDC();
pDC->SelectObject(&LineFont);
pDC->DrawText(step->StepInstruction, &rect, DT_CALCRECT|DT_WORDBREAK);
label->ReleaseDC(pDC);
label->MoveWindow(rect);
stepTop += rect.Height();
stepTop += 30;
stepEnd += rect.Height();
}
}
Reasons why you can't use OnPaint() are not clear.
The usual strategy when one needs to redraw all or part of a window upon some event is to call InvalidateRect().
Windows will in turn send WM_PAINT (handled by your OnPaint() method) to your app, specifying which part of the window should be redrawn.
I think there's more in the BeginPaint-function than just giving you the CDC. And BeginPaint can only be called from the OnPaint-method.
To solve your problem, use the Invalidate-functions to force a repaint from your "SetupInstructions" method. Then do the drawing inside the OnPaint function.
I suppose CProcessSteps derives from CWnd, perhaps a CDialog?
If you want to draw in the client area of a CWnd derived class you have to get the DC using the CWnd GetDC method. I don't understand why you create your own CDC, you should get the CWnd DC at the beginning of SetupInstructions and use this DC everywhere, also to create your memDC.
Also you should be careful when you allocate memory (new CStatic) if you don't call delete for this variables you will have memory leaks. If you really need to create this CStatics dynamically you will have to keep a pointer to all of them in order to delete them before closing the dialog/view.
As people suggested, I don't think you are following the right way by drawing using OnShowWindow. You should use OnPaint to make your drawing stuff, if you don't want to draw the image until the window is fully initialized you should use a member variable of the window (for instance a bool) initialized to false in the constructor and set it to true when you are ready to draw the image. Then calling Invalidate will draw the image. Something like:
In the .h:
class CProcessSteps : CDialog
{
...
private:
bool m_bReadyToDraw;
};
In the .cpp:
CProcessSteps::CProcessSteps() : CDialog()
{
m_bReadyToDraw = false;
}
BOOL CProcessSteps::OnInitDialog()
{
CDialog:OnInitDialog();
m_bReadyToDraw = true;
return TRUE;
}
void CProcessSteps::OnPaint()
{
CPaintDC dc(this);
if(m_bReadyToDraw)
{
CFontUtil cfu;
cfu.SetFont(&LineFont,30);
CDC memDC;
memDC.CreateCompatibleDC(&dc);
...
}
}
Hope it helps.
Javier