Hi all,
The question is: How can I get flicker-free drawing in a
CListView (NOT CScrollView)?
The rest of this is fluff. Read on at your own
peril.
I hope I'm not beating the subject into the ground, but I have
a problem that is somewhat similar to the many posts here related to
double-buffering and flicker-free drawing in a CView. My particular
problem is a little different. I think I've read some of these
same details in another post or two, so this one will hopefully give
more detail. (Oh yeah, VC++ 6.0 SP5 on XP Pro with all the latest updates
and SDK; target is WinXP Pro/2000 Pro/Win98 (if the thing will still run
there when it's done).)
I'm writing an SDI app with real Doc/View support and three
Views of the same Document. I started off with the AppWizard default SDI
app with a splitter window, and added another splitter. The first
view (top-left pane) is derived from CTreeView. The second view
(top-right pane) and third view (bottom pane) are derived from CListView
and are normally in LVS_REPORT mode. (As an aside, there's also lots of
unrelated goodies like multi-line tooltips, a ReBar with two ToolBars, password
security, ADO connection to Access 2000, FairCom CTree database code,
dynamically-created threads and child processes, several timers,
minimize-to-systray, Wizards, PropertySheets, Critical Sections, a
single-instance mutex, MAPI and TAPI support, socket comm code, grid
controls in PropertySheet dialogs with three child windows and their own
splitters, etc.)
The CDocument-derived class is used as the data source for all
three views, and I've been keeping pretty close to what's recommended for
PreCreateWindow(), OnInitialUpdate(), OnUpdate(), and related methods. I'm
using MFC and its Doc/View architecture as the rule, and dropping back to
the API only when necessary.
The program has a "countdown-to-child-process-kickoff"
timer firing off once a second that causes an update to one or
more ListView items in the second view. When each ListView's countdown
timer reaches zero, the program creates a new thread and writes some "journal"
information about having done so to the bottom view. It then proceeds to
journalize and display details about that thread's execution as it runs.
(It's a multi-threaded "task manager" kind of program.)
My problem in a nutshell is that as the program runs, it
flickers like mad! This is the problem I'm trying to solve.
I've added Keith Rule's latest iteration of CMemDC from http://www.codeproject.com/gdi/flickerfree.asp
to not much avail. (BTW he shows CMyListView::OnEraseBkgnd()
returning FALSE though I've tried TRUE as well, with no visible difference
in flicker.)
Keith's CMemDC class works very well in CView-derived
classes where OnPaint() calls OnDraw(), but the kicker is that none of the MFC
classes derived from CCtrlView (which is in turn derived from CView) seem to
support either OnDraw() or OnPaint() directly. I say that because when I
override OnDraw() in my CListView-derived classes, it never gets
called(!). An inspection of a CView-derived class shows CView::OnPaint()
calls CView::OnDraw(). As another aside, why does Mr. Rule get a pointer
to the Document and ASSERT its validity (then not do anything with it) in all
his sample code?
Anyway, the pertinent MFC code from ~\MFC\SRC\ViewCore.cpp is
as follows:
void
CCtrlView::OnDraw(CDC*)
{
ASSERT(FALSE);
}
void
CCtrlView::OnPaint()
{
// this is done to avoid CView::OnPaint
Default();
}
Uh... Yikes!
I've read here something along the lines of "views based on
controls are responsible for painting themselves", and I'd have to agree that's
the way it sure seems. Well... since that very internal painting
flickers so badly with the sometimes-fairly-rapid updating I need to
do, where can I jump in there and interrupt the process in order to
add some double-buffering?
I've now added my own WM_PAINT handler that DOES get
called, and from there I call my own OnDraw() method. Obviously,
neither one of them call any parent class methods of the same name. It is
in my own OnDraw() that I'm creating a CMemDC object. This all
works fine, as long as the ListCtrl area COMPLETELY fills in the ListView
window and there are no scrollbars. However, the two CListView-derived
windows I'm using are not always FULLY populated, and besides, my users can
drag splitter windows to enlarge those views. Doing so leaves an area
of the window that is not repainted.
The offending line of code in CMemDC that I can't figure
out how to work around is this:
pDC->GetClipBox(&m_rect);
This method gets the "dimensions of the tightest bounding
rectangle around the current clipping boundary", which is necessarily smaller
than the size of my entire CListView-derived window. As a result, only the
area of the populated ListCtrl lines gets redrawn (but at least it's fairly
rock-solid). The remaining area of the ListView window is NOT
redrawn, leaving "droppings" all over the bottom and right side of both of these
views.
To Keith's defense, I *can* pass a 'const CRect *' to his
CMemDC constructor. When I do so, the line:
pDC->LPtoDP(&m_rect);
blows up with a "Debug assertion failed" on:
_AFXWIN_INLINE void
CDC::LPtoDP(LPRECT lpRect) const
{m_hAttribDC != NULL); VERIFY(::LPtoDP(m_hAttribDC,
(LPPOINT)lpRect, 2));}
... whether I pass the CRect from GetClientRect(),
GetListCtrl().GetClientRect(), GetWindowRect(), or
GetListCtrl().GetWindowRect().
So <whew> here's the question: How can I get
flicker-free drawing of View objects derived from CCtrlView, especially
CListView?
Thanks,
Jeff Elliott
C Guru, Inc.