CMemDC aus dem Artikel Flicker free drawing with MFC ist wohl der Klassiker für Doublebuffering und ist wohl jedem MFC Entwickler bekannt.
Er macht was er soll und ich habe ihn selbst über Jahre hinweg unverändert verwendet. Solange man einfache Sachen macht und ausschließlich den MM_TEXT Mappingmode verwendet ist alles OK.
Hat man sich aber was spezielles – zoomfähiges – zusammengebaut mit MM_ANISOTROPIC dann funktioniert das Ganze nicht mehr, weil Keith an den Window Origin Koordinaten dreht und nicht am Viewport.
Meine abgewandelte Variante trägt einer entsprechende Verwendung auch für andere Mappingmodes Rechnung. Es müsste IMHO für jeden funktionieren. Intensiv getestet habe ich es mit MM_ANISOTROPIC.
Will/muss man auch an den Viewport Koordinaten drehen, dann sollten die alten Viewport Koordinaten gelesen werden und dann entsprechend durch ein Offset geändert werden.
Alles anderen Werte wie die Extents können beliebig verändert werden.
Bei der Verwendung sollte MM_TEXT eingeschaltet sein (entsprechende ASSERTs habe ich ergänzt). MM_TEXT ist normalerweise gesetzt, wenn CMemDC direkt nach Instanzierung des CPaintDC zum Einsatz kommt. Der Destruktor setzt dann den Mappingmode auf MM_TEXT zurück.
Dieser neue, modifizierte CMemDC kann einfach die bisherige Implementierung ersetzen.
Have fun & Happy coding 🙂
class CMemDC : public CDC
{
public:
// constructor sets up the memory DC
CMemDC(CDC* pDC)
: CDC()
, m_pDC(pDC)
, m_pOldBitmap(NULL)
, m_bMemDC(!m_pDC->IsPrinting())
{
ASSERT(pDC != NULL);
if (m_bMemDC)
{
// Create a Memory DC.
// At this moment we should have mapping mode text
ASSERT(m_pDC->GetMapMode()==MM_TEXT);
ASSERT(m_pDC->GetWindowOrg()==CPoint(0,0));
ASSERT(m_pDC->GetViewportOrg()==CPoint(0,0));
// Get the clip box and create bitmap for this size
m_pDC->GetClipBox(&m_rect);
CreateCompatibleDC(m_pDC);
m_bitmap.CreateCompatibleBitmap(m_pDC,
m_rect.Width(), m_rect.Height());
m_pOldBitmap = SelectObject(&m_bitmap);
// Adjust the view port so that we hit the clip rect.
SetViewportOrg(-m_rect.left, -m_rect.top);
IntersectClipRect(m_rect);
// Fill background in case the user has overridden
// WM_ERASEBKGND. We end up with garbage otherwise.
FillSolidRect(m_rect, m_pDC->GetBkColor());
}
else
{
// Make a copy of the relevant parts of the current DC for printing
m_bPrinting = m_pDC->m_bPrinting;
m_hDC = m_pDC->m_hDC;
m_hAttribDC = m_pDC->m_hAttribDC;
}
}
// Destructor copies the contents of the mem DC to the original DC
~CMemDC()
{
if (m_bMemDC)
{
// The mapping mode might effect the BitBlt. So we need to return to
// MM_TEXT. This makes m_rect.left and m_rect.top again the
// coordinates of our clipped rectangle. And to make sure that the
// coordinates are really used we just clear the ViewportOrg and the
// WindowOrg
SetMapMode(MM_TEXT);
SetViewportOrg(0,0);
SetWindowOrg(0,0);
// Copy the off screen bitmap onto the screen.
// For this we just make sure that we really target the rectangle of
// our temporal bitmap.
m_pDC->BitBlt(m_rect.left, m_rect.top,
m_rect.Width(), m_rect.Height(),
this, 0, 0, SRCCOPY);
//Swap back the original bitmap.
SelectObject(m_pOldBitmap);
}
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; }
private:
CBitmap m_bitmap; // Offscreen bitmap
CBitmap* m_pOldBitmap; // 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.
};
Dein CMemDC beschränkt die Zeichenfläche prinzipiell auf die Clipping Region. s. Konstruktor
// Get the clip box and create bitmap for this size
m_pDC->GetClipBox(&m_rect);
Das wird zum Problem, wenn Windows unvermutet eine Clipbox setzt. Das passiert z.B. wenn man ein anderes Fenster über dem Zeichenfenster (schnell) bewegt. CMemDC kopiert (BitBlt im Destruktor) dann immer in den Bereich, der unter dem sich bewegenden Fenster frei wird, dessen Koordinaten mit der eigentlichen Zeichnung nichts zu tun haben.
Vielleicht ist die alte Lösung von Keith besser, welche im Konstruktor den Zeichenbereich übernehmen kann.
CMemDC(CDC* pDC, const CRect* pRect = NULL) : CDC()
{
// …
// Get the rectangle to draw
if (pRect == NULL) {
pDC->GetClipBox(&m_rect);
} else {
m_rect = *pRect;
}
// …
}
So kann man im Notfall selber festlegen, wohin das Kunstwerk am Ende kopiert wird.