Fehler bei Double-Buffering



  • Um eine Reihe von Linien und Rechtecken per GDI+ als Teil einer Drag-and-Drop-Operation immer wieder neu zu zeichnen, wenn der User die Maus bewegt, habe ich folgendes Konstrukt geschrieben.

    Im Konstruktor einer Klasse, der mein Custom Control verwaltet, in dem alles abläuft, werden eine Reihe von notwendigen Membervariablen definiert:

    EventView::EventView(HWND hwnd_new) {
        /*diverse Kommandos*/
    
    	RECT rect;
    	GetClientRect(hwnd_new, &rect);
    	bitmap = new Gdiplus::Bitmap(rect.right, rect.bottom);
    	graphics = Graphics::FromImage(bitmap);
    	main_graphics = new Graphics(hwnd_new, 0);
    
        /*weitere Kommandos*/
    }
    

    In der Methode, die dann zeichnen soll, läuft folgendes ab:

    void EventView::EventView_Paint() {
        /*viele Zeichenkommandos, die über graphics auf die Bitmap geschrieben werden*/
    
        main_graphics->DrawImage(bitmap, 0, 0);
    }
    

    Bei jeder Mausbewegung wird folgende weitere Methode aufgerufen:

    void EventView::EventView_Draw(LPARAM lParam) {
    	if (draw == true) {
        /*diverse Kommandos*/
    
    	RECT rect;
    	GetClientRect(hwnd, &rect);
    
    	delete bitmap;
    	bitmap = new Gdiplus::Bitmap(rect.right, rect.bottom);
    	delete graphics;
    	graphics = Graphics::FromImage(bitmap);
    
    	InvalidateRect(hwnd, &rect, true);
    	UpdateWindow(hwnd);
    	}
    }
    

    Hier werden die Bitmap und das darauf schreibende Graphics-Objekt neu erstellt, um nicht den Inhalt des letzten Frames wieder zu schreiben. Dann wird das ganze Fenster per UpdateWindow erneut zum Zeichnen bestimmt. Bei dieser Methode bin ich mir aber am wenigsten sicher, dass alles richtig ist.

    Diese Technik wurde mir hier schon empfohlen, ich habe sie auch in der MSDN-Dokumentation schon gesehen und entsprechend umgesetzt. Eigentlich sollte ja jedes Kommando á la graphics->DrawXXX/FillXXX auf die Bitmap schreiben und die dann auf einen Schlag ausgegeben werden, und somit das Flimmern verhindern.

    Dem ist aber nicht so. Die Objekte bauen sich trotz allem eins nach dem anderen auf. Lasse ich aber in der Zeichen-Methode den abschließenden Befehl, der die Bitmap ausgibt, weg, so erscheint gar nichts mehr.

    Ich benutze diese Technik zum ersten Mal, also ist es warscheinlich irgendein ganz trivialer Fehler.



  • Hat niemand eine Idee?



  • Hmm wann rufst du welche Funktion auf? Am einfachsten gehst du anderes vor:

    EventView::EventView(HWND hWnd) : m_hWnd(hWnd)
    {
        ::RECT rect;
        ::GetClientRect(hWnd, &rect);
        m_pBitmap = new Gdiplus::Bitmap(rect.right - rect.left, rect.bottom - rect.top);
        m_pMemGraphics = Gdiplus::Graphics::FromImage(m_pBitmap);
    }
    

    ... hmm aber warum übergibst du nen hWnd? Normal kapselt man sowas alles in ne Klasse so das du nur doch die create-Methode deiner Klasse aufrufen musst und das Control dann da ist 😉

    void EventView::paint(HDC hDC) 
    {
        Gdiplus::Graphics graphic(hDC);
        graphic.DrawImage(m_pBitmap, 0, 0);
    }
    
    void EventView::resize() 
    {
        ::RECT rect;
        ::GetClientRect(m_hWnd, &rect);
    
        delete m_pBitmap;
        delete m_pMemGraphics;
    
        m_pBitmap = new Gdiplus::Bitmap(rect.right - rect.left, rect.bottom - rect.top);
        m_pMemGraphics = Gdiplus::Graphics::FromImage(m_pBitmap);
        // Jetzt zeichnest du das was du haben willst auf die Bitmap :)
        m_pMemGraphics->DrawLine(...)
    }
    


  • Wann jede Funktion aufgerufen wird?

    • EventView_Paint wird immer aufgerufen, wenn das Control eine WM_PAINT-Message bekommt.
    • EventView_Draw (war ein Tippfehler, heißt mittlerweile EventView_Drag wird immer aufgerufen, wenn der Drag-and-Drop-Modus an ist und der User die Maus bewegt. Da wird dann zuerst die Position eines Objektes aktualisiert und dann wie gesagt das Control neu gezeichnet. Übrigens sind Zeilen 8 bis 16 des dritten Codeschnipsels eigentlich eine abgekapselte Update-Methode, aber das habe ich hier mal rausgenommen, um es einfach zu halten.
    • Wie man sehen kann, wird durch UpdateWindow() in EventView_Draw wieder EventView_Paint aufgerufen, denn das setzt ja eine WM_PAINT-Message ab.

    Da also die Drag-Methode die Paint-Methode aufruft, komme ich auf so ziemlich das gleiche, wie du. Wenn der User die Maus zieht, wird Drag aufgerufen. Hier lösche ich die Bitmap und das Graphics-Objekt, dass sich darauf bezieht, erstelle die beiden neu, und dann wird über UpdateWindow() die Paint-Methode eingeleitet, in der dann auf die Bitmap gezeichnet wird und an deren Ende die Bitmap gezeichnet wird. Der einzige größere Unterschied ist, dass du die Bitmap schon in der Drag-Methode, (bei dir resize) bemalst. Aber das sollte ja nicht den Unterschied machen. Oder doch?

    Achja: In der Paint-Methode wird ganz zu Beginn BeginPaint und ganz zum Schluss EndPaint aufgerufen. Da ich ja außerhalb dieser zwei Funktionen aber ohnehin nichts zeichne, sollte das auch kein Problem sein, oder?

    Könnte es vielleicht nicht auch sein, dass es eine Verzögerung beim Darstellen der Bitmap gibt, die groß genug ist, als dass man sie bei den häufigen Aktualisierungen während des Draggens bemerken würde?



  • Nein kann nicht. Du musst den HDC den du durch BeginPaint erhälst nutzen um dein Graphics Objekt zu erzeugen ... sonnst schick mir dein Projekt ... dann korrigier ich es dir.



  • Das kann eigentlich nicht sein. Ich habe auch schonmal meine Paint-Methode so umgeschrieben, dass BeginPaint einen hdc zurückgibt, habe daraus ein graphics gemacht, und habe dann versucht, die Grafik mit dem statt mit dem bisher im Konstruktor per hwnd erzeugtem graphics zu zeichnen. Das hatte keine Auswirkungen.

    Ich schicke dir aber gern die entsprechenden zwei Dateien, wenn du mir irgendwie deine Mail-Adresse zukommen lässt.

    Das Problem kommt übrigens auch bei "Einzelbildern" vor, viel mir gerade auf. Wenn ich nämlich während des Ziehens die Aktualisierung weglasse, dann gibt es zwar ganz üble Rückstände der vorherigen Frames, aber es gibt dieses Flimmern nur an Anfang und Ende jeweils einmal, und das ist, wenn halt jeweils einmal die Aktualisierung durchgeführt wird.

    Ich habe aber auch mittlerweise ein bisschen die Vermutung, dass die Message Queue da irgendwie eine Rotte spielt, bei diesem Phänomen. Denn bei laufendem Ziehen müssten sich ja die von UpdateWindow abgesetzten WM_PAINT-Nachrichten und die eingehenden WM_MOUSEMOVE-Nachrichten in die Quere kommen. Aber das ist wie gesagt auch nur eine Vermutung.



  • devil (dot) contact (at) googlemail (dot) com ist die eMail-Adresse ...



  • Die Mail ist vor einer ganzen Weile rausgegangen.


Log in to reply