Re: MFC Flicker and the Application Framework
On Tue, 13 Mar 2007 01:49:18 -0700, C Hill MBSC
<CHillMBSC@discussions.microsoft.com> wrote:
Dear All!
I have just written a piece of MFC Code to output a counter value every
second to the screen. Could anyone tell me where I have gone wrong, because
the text occasionally flickers with white lines through it! It only happens
occasionally on my machine but is unprofessional! I would like to know
whether there is a problem with MFC, or my code.
P.S. Most of the code was created with AppWizard and message map functions
have been added along with initialisation.
Thank you for your assistance in this matter.
Hi,
first to say: there are people much more experienced and authoritative
than me on this newsgroup; the following are just my "2 cents"...
// FlickeringCounterView.cpp : implementation of the CFlickeringCounterView
class
//
#include "stdafx.h"
#include "FlickeringCounter.h"
Why #include the main app header here?
Does the view class need it?
If not, better not including it...
I tend to minimize inclusion dependencies.
#include "FlickeringCounterDoc.h"
#include "FlickeringCounterView.h"
#include ".\flickeringcounterview.h"
The above #include ".\flick...." is IMHO a useless line, and
duplicated from the #include "Flick...."
Which version of Visual Studio are you using?
Do Visual Studio guys fixed these *bugs* in VS2005?
(As Joe - one of the "gurus" here - wrote, they think about
"coolness", produce huge software, but avoid more important stuff like
this! [Maybe the focus/hype is now on C#??] What a pity.)
I read in your code that you re-create the same font in OnDraw(), at
each function call.
Do you have flicker? So, try to do as less things as possible in the
drawing handler.
The font is constant, so build it once. I would build this constant
font once in the constructor, so you may add a data member in your
class:
// View class header
// ...
private:
CFont MyFont;
and init in constructor:
CFlickeringCounterView::CFlickeringCounterView()
{
// TODO: add construction code here
Counter = 0;
MyFont.CreatePointFont(240, _T("Arial"));
Note that I decorate strings with _T(...).
It helps a lot in Unicode builds (there are very interesting posts on
this newsgroup about Unicode.)
Moreover, note that I'm using your coding convention for MyFont; but I
prefer using an m_ prefix for the class data members, so I would much
prefer calling your MyFont object "m_myFont" (m_ --> reminds the
reader that it is a member variable).
Moreover, I read in your code that you are doing drawing in the
OnTimer handler.
I would not do this. In the OnTimer, I would do the *update*
operations like "Counter++;", and then request a repaint.
But I would put the drawing routines in OnDraw (or in a separate
member function, to be called in OnDraw).
void CFlickeringCounterView::OnDraw(CDC* pDC)
{
CFlickeringCounterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
CFont MyFont;
MyFont.CreatePointFont(240, "Arial");
pDC->SelectObject(&MyFont);
pDC->TextOut(0, 0, String);
}
To avoid flickering, use the very good CMemDC class - if I recall
correctly, Tom wrote also the CodeProject URL; however, here it is:
http://www.codeproject.com/gdi/flickerfree.asp
Build a CMemDC in OnDraw, and do the drawing onto CMemDC instance.
The class will do the double-buffering for you.
Don't forget also to add an OnEraseBackground handler and return
FALSE, to prevent the flickering.
void CFlickeringCounterView::OnDraw(CDC* pDC)
{
// Memory DC, to prevent flickering
CMemDC dc(pDC);
CFlickeringCounterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// *** Print timer value ***
m_strCounter.Format(_T("Counter = %d"), m_nCounter);
CFont * pOldFont = dc.SelectObject(&m_font);
dc.TextOut(0, 0, m_strCounter);
dc.SelectObject(pOldFont);
}
void CFlickeringCounterView::OnTimer(UINT nIDEvent)
{
// TODO: Add your message handler code here and/or call default
Counter++;
Stop here: just updating.
And request a repaint:
void CFlickeringCounterView::OnTimer(UINT nIDEvent)
{
// Increase counter value
m_nCounter++;
// Ask repaint
Invalidate();
UpdateWindow();
CView::OnTimer(nIDEvent);
}
int CFlickeringCounterView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: Add your specialized creation code here
SetTimer(1, 1000, NULL); // 1Hz Counter Timer
I prefer identifing timers with mnemonics, not with raw numbers.
So I put a TimerID (= 1) constant in the class, and manage the timer
ID via this constant number, rather than raw "1".
Moreover, you have to kill the timer.
Maybe OnDestroy is a good place to do it:
void CFlickeringCounterView::OnDestroy()
{
CView::OnDestroy();
// Delete the timer
KillTimer(TimerID);
}
You might want to consider the following code. I tested it, and there
was no flickering... (both in debug and release build).
Here you can find three source files: the view class .cpp, the view
class .h, and MemDC.h (they are separated by the *---....---* line).
<CODE>
*---------------------------------------------------------*
///////////////////////////////////////////////////////////
// FlickeringCounterView.cpp :
// implementation of the CFlickeringCounterView class
///////////////////////////////////////////////////////////
#include "stdafx.h"
#include "FlickeringCounterDoc.h"
#include "FlickeringCounterView.h"
#include "MemDC.h" // CMemDC - prevent flickering
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CFlickeringCounterView
IMPLEMENT_DYNCREATE(CFlickeringCounterView, CView)
BEGIN_MESSAGE_MAP(CFlickeringCounterView, CView)
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
ON_WM_ERASEBKGND()
ON_WM_DESTROY()
ON_WM_CREATE()
ON_WM_TIMER()
END_MESSAGE_MAP()
// CFlickeringCounterView construction/destruction
CFlickeringCounterView::CFlickeringCounterView()
: m_nCounter(0)
{
// Build the font
VERIFY( m_font.CreatePointFont(240, _T("Arial")) );
}
CFlickeringCounterView::~CFlickeringCounterView()
{
}
BOOL CFlickeringCounterView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
// CFlickeringCounterView drawing
void CFlickeringCounterView::OnDraw(CDC* pDC)
{
// Memory DC, to prevent flickering
CMemDC dc(pDC);
CFlickeringCounterDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// *** Print timer value ***
m_strCounter.Format(_T("Counter = %d"), m_nCounter);
CFont * pOldFont = dc.SelectObject(&m_font);
dc.TextOut(0, 0, m_strCounter);
dc.SelectObject(pOldFont);
}
// CFlickeringCounterView printing
BOOL CFlickeringCounterView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}
void CFlickeringCounterView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo*
/*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CFlickeringCounterView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo*
/*pInfo*/)
{
// TODO: add cleanup after printing
}
// CFlickeringCounterView diagnostics
#ifdef _DEBUG
void CFlickeringCounterView::AssertValid() const
{
CView::AssertValid();
}
void CFlickeringCounterView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CFlickeringCounterDoc* CFlickeringCounterView::GetDocument() const //
non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CFlickeringCounterDoc)));
return (CFlickeringCounterDoc*)m_pDocument;
}
#endif //_DEBUG
// CFlickeringCounterView message handlers
BOOL CFlickeringCounterView::OnEraseBkgnd(CDC* pDC)
{
// Prevent flickering
return FALSE;
}
void CFlickeringCounterView::OnDestroy()
{
CView::OnDestroy();
// Delete the timer
KillTimer(TimerID);
}
int CFlickeringCounterView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// Create the timer (1 Hz)
SetTimer(TimerID, 1000, NULL);
return 0;
}
void CFlickeringCounterView::OnTimer(UINT nIDEvent)
{
// Increase counter value
m_nCounter++;
// Ask repaint
Invalidate();
UpdateWindow();
CView::OnTimer(nIDEvent);
}
///////////////////////////////////////////////////////////
*---------------------------------------------------------*
///////////////////////////////////////////////////////////
// FlickeringCounterView.h :
// interface of the CFlickeringCounterView class
///////////////////////////////////////////////////////////
#pragma once
class CFlickeringCounterView : public CView
{
protected: // create from serialization only
CFlickeringCounterView();
DECLARE_DYNCREATE(CFlickeringCounterView)
// Attributes
public:
CFlickeringCounterDoc* GetDocument() const;
// Operations
public:
// Overrides
public:
virtual void OnDraw(CDC* pDC); // overridden to draw this view
virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);
// Implementation
public:
virtual ~CFlickeringCounterView();
#ifdef _DEBUG
virtual void AssertValid() const;
virtual void Dump(CDumpContext& dc) const;
#endif
// *** DATA MEMBERS ***
private:
int m_nCounter; // The counter value
CString m_strCounter; // Counter formatted text
CFont m_font; // Drawing font
enum { TimerID = 1 };
protected:
// Generated message map functions
protected:
DECLARE_MESSAGE_MAP()
public:
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
afx_msg void OnDestroy();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnTimer(UINT nIDEvent);
};
#ifndef _DEBUG // debug version in FlickeringCounterView.cpp
inline CFlickeringCounterDoc* CFlickeringCounterView::GetDocument()
const
{ return reinterpret_cast<CFlickeringCounterDoc*>(m_pDocument); }
#endif
///////////////////////////////////////////////////////////
*---------------------------------------------------------*
///////////////////////////////////////////////////////////
// MemDC.h :
// Memory DC class
//
// From: http://www.codeproject.com/gdi/flickerfree.asp
///////////////////////////////////////////////////////////
#ifndef _MEMDC_H_
#define _MEMDC_H_
//////////////////////////////////////////////////
// CMemDC - memory DC
//
// Author: Keith Rule
// Email: keithr@europa.com
// Copyright 1996-2002, Keith Rule
//
// You may freely use or modify this code provided this
// Copyright is included in all derived versions.
//
// History - 10/3/97 Fixed scrolling bug.
// Added print support. - KR
//
// 11/3/99 Fixed most common complaint. Added
// background color fill. - KR
//
// 11/3/99 Added support for mapping modes other than
// MM_TEXT as suggested by Lee Sang Hun. - KR
//
// 02/11/02 Added support for CScrollView as supplied
// by Gary Kirkham. - KR
//
// This class implements a memory Device Context which allows
// flicker free drawing.
class CMemDC : public CDC {
private:
CBitmap m_bitmap; // Offscreen bitmap
CBitmap* m_oldBitmap; // bitmap originally found in CMemDC
CDC* m_pDC; // Saves CDC passed in constructor
CRect m_rect; // Rectangle of drawing area.
BOOL m_bMemDC; // TRUE if CDC really is a Memory DC.
public:
CMemDC(CDC* pDC, const CRect* pRect = NULL) : CDC()
{
ASSERT(pDC != NULL);
// Some initialization
m_pDC = pDC;
m_oldBitmap = NULL;
m_bMemDC = !pDC->IsPrinting();
// Get the rectangle to draw
if (pRect == NULL) {
pDC->GetClipBox(&m_rect);
} else {
m_rect = *pRect;
}
if (m_bMemDC) {
// Create a Memory DC
CreateCompatibleDC(pDC);
pDC->LPtoDP(&m_rect);
m_bitmap.CreateCompatibleBitmap(pDC, m_rect.Width(),
m_rect.Height());
m_oldBitmap = SelectObject(&m_bitmap);
SetMapMode(pDC->GetMapMode());
SetWindowExt(pDC->GetWindowExt());
SetViewportExt(pDC->GetViewportExt());
pDC->DPtoLP(&m_rect);
SetWindowOrg(m_rect.left, m_rect.top);
} else {
// Make a copy of the relevent parts of the current
// DC for printing
m_bPrinting = pDC->m_bPrinting;
m_hDC = pDC->m_hDC;
m_hAttribDC = pDC->m_hAttribDC;
}
// Fill background
FillSolidRect(m_rect, pDC->GetBkColor());
}
~CMemDC()
{
if (m_bMemDC) {
// Copy the offscreen bitmap onto the screen.
m_pDC->BitBlt(m_rect.left, m_rect.top,
m_rect.Width(), m_rect.Height(),
this, m_rect.left, m_rect.top, SRCCOPY);
//Swap back the original bitmap.
SelectObject(m_oldBitmap);
} else {
// All we need to do is replace the DC with an illegal
// value, this keeps us from accidentally deleting the
// handles associated with the CDC that was passed to
// the constructor.
m_hDC = m_hAttribDC = NULL;
}
}
// Allow usage as a pointer
CMemDC* operator->()
{
return this;
}
// Allow usage as a pointer
operator CMemDC*()
{
return this;
}
};
#endif
///////////////////////////////////////////////////////////
*---------------------------------------------------------*
</CODE>
MrAsm