Flackernder Hintergrund mit AlphaBlend in OnPaint



  • Hallo Leute.

    Ich hätte da mal wieder gern ein Problem. Ich weiß, dass es schon einige Beiträge gibt, die sich mit dem Flackern beschäftigen und hab auch ein paar Ansätze verfolgt. Leider hat keiner zur Lösung geführt. 😡
    Ich möchte in OnPaint zunächst ein Hintergrund für meinen Dialog festlegen, da ich diesen zur Laufzeit des Programms auch noch ändern möchte. 🙄
    Darüber soll ein weiteres Bild gelegt werden. Wobei dieses dann aber Transparent sein soll. 🙄
    So weit so gut. Das klappt auch wie ich es möchte. Nur flackert nun das transparente Bild. Der Hintergrund an sich ist stabil und gefällt mir soweit.

    Hier der relevante Codeausschnitt:

    void CTest3Dlg::OnPaint() 
    {
    
    	.
            .
            .
    		CClientDC dcScreen(this);
    
    		SetWindowPos(NULL, m_winX, m_winY, m_winW, m_winH, NULL);
    
    		CRect rectDlg;
    		GetWindowRect(rectDlg);
    
    		// Einen Gerätekontext erzeugen
    		CDC dcMem;
    		// Neuen Gerätekontext zum originalen DC kompatibel machen
    		dcMem.CreateCompatibleDC(&dcScreen);
    
    		// Hintergrundbild laden
    		CBitmap BkBmp;
    		// Die Bitmap-Datei laden
    		HBITMAP hBitmap = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
    		"D:\\...", IMAGE_BITMAP, 0, 0,
    		LR_LOADFROMFILE | LR_CREATEDIBSECTION);
    		// Das geladene Bild dem CBitmap-Objekt zuweisen
    		BkBmp.Attach(hBitmap);
    		BITMAP bmpBk;
    		BkBmp.GetBitmap(&bmpBk);
    		// Bitmap in den neuen DC selektieren
    		CBitmap *pBk = dcMem.SelectObject(&BkBmp);
    
    		// Bitmap in den Anzeige-DC kopieren und dabei in der Größe ändern
    		dcScreen.StretchBlt(0, 0, (rectDlg.Width() - 5),
    		(rectDlg.Height() - 30), &dcMem, 0, 0,
    		bmpBk.bmWidth, bmpBk.bmHeight, SRCCOPY);
    
    		// Zweites transparentes Bild laden
    		CBitmap bitmap;
    		bitmap.CreateBitmap(m_winW, m_winH, 4, 8, m_imgBuf);
    		BITMAP bmp;
    		bitmap.GetBitmap(&bmp);
    		CBitmap *pSec = dcMem.SelectObject(&bitmap);
    
    		BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    		dcScreen.AlphaBlend(0, 0, rectDlg.Width(), rectDlg.Height(), &dcMem, 0, 0, bmp.bmWidth,
    			bmp.bmHeight, blendPixelFunction);
    
    		dcMem.SelectObject(pBk);
    		dcMem.SelectObject(pSec);
    		bitmap.DeleteObject();
    		BkBmp.DeleteObject();
    		dcMem.DeleteDC();
    }
    

    Kann einer einen möglichen Fehler entdecken oder mir einen anderen Ansatz nennen für mein Problem? 😕

    Danke euch im Voraus. 😉 :schland:


  • Mod

    Du verwendest keinen CPaintDC. Wie soll jemals der Bereich validiert werden?



  • Du verwendest keinen CPaintDC. Wie soll jemals der Bereich validiert werden?

    Es waren ja noch ein paar Punkte vor meinem Codefetzen. So besser?

    void CTest3Dlg::OnPaint() 
    {
    	if (IsIconic())
    	{
    		CPaintDC dc(this); // device context for painting
    		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
    		// Center icon in client rectangle
    		int cxIcon = GetSystemMetrics(SM_CXICON);
    		int cyIcon = GetSystemMetrics(SM_CYICON);
    		CRect rect;
    		GetClientRect(&rect);
    		int x = (rect.Width() - cxIcon + 1) / 2;
    		int y = (rect.Height() - cyIcon + 1) / 2;
    		// Draw the icon
    		dc.DrawIcon(x, y, m_hIcon);
    	}
    	else
    	{
    		CDialog::OnPaint();
    	}		
    	if (!m_imgBuf)
    		return;
    	if (m_isV==1)
    	{
    		// Ich werde nicht den gesamten Code posten ;-)
    	}
    	else
    	{
    		HDC dc= ::GetDC(0);
    		::ReleaseDC(0,dc);
    		CClientDC dcScreen(this);
    
    		SetWindowPos(NULL, m_winX, m_winY, m_winW, m_winH, NULL);
    
    		CRect rectDlg;
    		GetWindowRect(rectDlg);
    
    		// Einen Gerätekontext erzeugen
    		CDC dcMem;
    		// Neuen Gerätekontext zum originalen DC kompatibel machen
    		dcMem.CreateCompatibleDC(&dcScreen);
    
    		// Hintergrundbild laden
    		CBitmap BkBmp;
    		// Die Bitmap-Datei laden
    		HBITMAP hBitmap = (HBITMAP)::LoadImage(AfxGetInstanceHandle(),
    		"D:\\..", IMAGE_BITMAP, 0, 0,
    		LR_LOADFROMFILE | LR_CREATEDIBSECTION);
    		// Das geladene Bild dem CBitmap-Objekt zuweisen
    		BkBmp.Attach(hBitmap);
    		BITMAP bmpBk;
    		BkBmp.GetBitmap(&bmpBk);
    		// Bitmap in den neuen DC selektieren
    		CBitmap *pBk = dcMem.SelectObject(&BkBmp);
    
    		// Bitmap in den Anzeige-DC kopieren und dabei in der Größe ändern
    		dcScreen.StretchBlt(0, 0, (rectDlg.Width() - 5),
    		(rectDlg.Height() - 30), &dcMem, 0, 0,
    		bmpBk.bmWidth, bmpBk.bmHeight, SRCCOPY);
    
    		// Zweites Bild (transparent)
    		CBitmap bitmap;
    		bitmap.CreateBitmap(m_winW, m_winH, 4, 8, m_imgBuf);
    		BITMAP bmp;
    		bitmap.GetBitmap(&bmp);
    		CBitmap *pSec = dcMem.SelectObject(&bitmap);
    
    		BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    		dcScreen.AlphaBlend(0, 0, rectDlg.Width(), rectDlg.Height(), &dcMem, 0, 0, bmp.bmWidth,
    			bmp.bmHeight, blendPixelFunction);
    
    		dcMem.SelectObject(pBk);
    		dcMem.SelectObject(pSec);
    		bitmap.DeleteObject();
    		BkBmp.DeleteObject();
    		dcMem.DeleteDC();
    	}
    }
    

    Jetzt eine Idee oder mach ich doch was falsch? 😕



  • Hab mit dem genannten Beispiel noch etwas herumgespielt und festgestellt, dass ohne das Hintergrundbild kein Flackern entsteht. Allerdings wird dann das dargestellte transparente Bild dauerhaft gespeichert. Wenn dieses also verschoben wird kann man alle alten Positionen des Bildes sehen. 😮
    Könnte das eine mögliche Ursache für das Flackern sein? Und wie könnte man die Dialogbox "cleanen"? 😕

    Ich weiß, viele dumme Fragen. Bin aber leider recht unerfahren mit MFC. 😞

    Bin also über jede Hilfe dankbar.


  • Mod

    Du musst für jeden Fall in OnPaint den CPaintDC verwenden. im letzten else case ist kein CPaintDC verwendet!



  • Okay da hast du allerdings recht. 😃 Und ohne CPaintDC gibt es keine automatischen Aufrufe der API-Funktionen BeginPaint und EndPaint. Nur was muss ich tun bzw. wie muss ich CPaintDC einbauen um mein dummes Flackern zu verhindern?
    Ein einfaches

    CPaintDC dc(this);
    

    hilft mir ja nicht viel weiter.

    Oder sollte das eher eine allgemeine Feststellung eines Programmierfehlers sein? :p


  • Mod

    ZZZ schrieb:

    Ein einfaches

    CPaintDC dc(this);
    

    hilft mir ja nicht viel weiter.

    1. Warum hilft es ncht weiter?
    2. Durch ein fehlendes OnPaint wird permament neu gezeichnet, was auch Flackern auslösen kann!
    3. Versuch mal den grundsätzlichen Ansatz von CMemDC (siehe CodeProjct), dazu muss aber auch ein entsprechender WM_ERASEBKGND Handler vorhanden sein, den ich bisher von Dir nicht gesehen habe.



  • 1. Warum hilft es ncht weiter?

    Damit meinte ich, wenn ich es einfach so einbaue macht es das Flackern auch nicht besser. 😞

    2. Durch ein fehlendes OnPaint wird permament neu gezeichnet, was auch Flackern auslösen kann!

    Ich geh davon aus, dass du CPaintDC und nicht OnPaint gemeint hast. Soll also heißen, wenn ich

    CPaintDC dc(this);
    

    einbaue wird nur beim Aufruf von OnPaint gezeichnet? 😕

    3. Versuch mal den grundsätzlichen Ansatz von CMemDC (siehe CodeProjct), dazu muss aber auch ein entsprechender WM_ERASEBKGND Handler vorhanden sein, den ich bisher von Dir nicht gesehen habe.

    Das mit CMemDC schaue ich mir noch genauer an, aber hab ich bis jetzt so verstanden, dass der Hintergrund statisch ist. Und mein Hintergrundbild soll sich ja auch noch ändern. Hab es aber wie gesagt erst überflogen.
    Und was meintest du mit WM_ERASEBKGND Handler?
    Das hier? 😕

    BOOL CTest3Dlg::OnEraseBkgnd(CDC* pDC)
    {
      return FALSE;
    }
    

    Was so mein Flackern auch nicht beseitigen konnte. 😞


  • Mod

    Also vermutlich wird Dein erstes statisches Bild zuerst gezeichnetund das zweite transparente Bild eben etwas später. Dadurch das "vermeintliche" Flackern.

    Du musst beide über den CMemDC ausgeben und mit einem Schlag in die Ausgabe kopieren!



  • Also vermutlich wird Dein erstes statisches Bild zuerst gezeichnetund das zweite transparente Bild eben etwas später. Dadurch das "vermeintliche" Flackern.

    Diese Vermutung hatte ich auch. Darum habe ich das ganze etwas verkleinert um das heraus zu finden.
    Sieht dann wie folgt aus:

    else
    	{
    		CPaintDC dc(this); // device context for painting
    		CClientDC dcScreen(this);
    		SendMessage(WM_ERASEBKGND, (WPARAM) dcScreen.GetSafeHdc(), 0);
    
    		CRect rectDlg;
    		GetWindowRect(rectDlg);
    
    		// Einen Gerätekontext erzeugen
    		CDC dcMem;
    		// Neuen Gerätekontext zum originalen DC kompatibel machen
    		dcMem.CreateCompatibleDC(&dcScreen);
    
    		// Hintergrundbild laden wurde weggelassen
    
    		// Das transparente Bild
    		CBitmap bitmap;
    		bitmap.CreateBitmap(m_winW, m_winH, 4, 8, m_imgBuf);
    		BITMAP bmp;
    		bitmap.GetBitmap(&bmp);
    		CBitmap *pSec = dcMem.SelectObject(&bitmap);
    
    		// Blendfunktion festlegen
    		BLENDFUNCTION blendPixelFunction= { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    		dcScreen.AlphaBlend(0, 0, rectDlg.Width(), rectDlg.Height(), &dcMem, 0, 0, bmp.bmWidth,
    			bmp.bmHeight, blendPixelFunction);
    
    		dcMem.SelectObject(pSec);
    		bitmap.DeleteObject();
    		dcMem.DeleteDC();
    	}
    

    Da ich immer vergessen hatte

    SendMessage(WM_ERASEBKGND, (WPARAM) dcScreen.GetSafeHdc(), 0);
    

    einzubauen wurden die alten Bilder gespeichert. Was ich ganz an Anfang noch als Problem genannt hatte. 🙂

    Allerdings ist immer noch ein Flackern da, was mich so langsam zur Verzweiflung bringt. 😡

    Noch irgendwelche Einwände / Ideen / Tipps oder der gleichen? 😃


  • Mod

    WM_ERASEBKGND ist keine Nachricht, die Du versenden darfst!



  • Und was muss ich dann tun damit der Hintergrund gelöscht wird? 😕



  • Normalerweise passiert das mit InvalidateRect.



  • Normalerweise passiert das mit InvalidateRect.

    Das hab ich schon so versucht:

    CRect rectDlg;
    GetWindowRect(rectDlg);
    InvalidateRect(rectDlg, TRUE);
    

    Dann kommt aber gar kein Bild mehr. 😮



  • Also wenn ich jetzt statt direkt

    OnPaint();
    

    aufzurufen

    Invalidate(TRUE);
    

    verwende löscht es mir zwar meinen Hintergrund, aber das Flackern ist nach wie vor da. 😡



  • 1. CTest3Dlg::OnEraseBkgnd muss TRUE zurückgeben, damit das Fenster seinen Standardhintergrund nicht selbst zeichnet.

    2. Der Hintergrund muss dann selbst in OnPaint gezeichnet werden.

    3. Hintergrund, Hintergrundbild und Vordergrund werden nacheinander in ein CMemDC gezeichnet, anschließend wird dessen Inhalt mit einem Rutsch in den PaintDC übertragen.



  • 1. CTest3Dlg::OnEraseBkgnd muss TRUE zurückgeben, damit das Fenster seinen Standardhintergrund nicht selbst zeichnet.

    2. Der Hintergrund muss dann selbst in OnPaint gezeichnet werden.

    3. Hintergrund, Hintergrundbild und Vordergrund werden nacheinander in ein CMemDC gezeichnet, anschließend wird dessen Inhalt mit einem Rutsch in den PaintDC übertragen.

    Was meinst du mit Hintergrund und Hintergrundbild? Wenn das Bild so groß ist wie der gesamte Hintergrund ist es doch das selbe oder? Eben nur wenn ich ein kleineres Hintergrundbild verwende ist das nötig. 😕
    Und was macht es für einen unterschied die abgeleitete Klasse CMemDC statt einem normalen CDC zu verwenden? Ist diese Klasse überhaupt in einem Standard-Header enthalten? Laut MFC wohl in afxglobals.h. Diesen Header kann ich aber auf meinem System nicht finden (VS 2005). 😞

    Und noch eine Grundlegende Frage zur Methode OnEraseBkgnd. Wann wird diese aufgerufen?



  • ZZZ schrieb:

    Was meinst du mit Hintergrund und Hintergrundbild? Wenn das Bild so groß ist wie der gesamte Hintergrund ist es doch das selbe oder? Eben nur wenn ich ein kleineres Hintergrundbild verwende ist das nötig.

    Das ist korrekt.

    ZZZ schrieb:

    Und was macht es für einen unterschied die abgeleitete Klasse CMemDC statt einem normalen CDC zu verwenden? Ist diese Klasse überhaupt in einem Standard-Header enthalten? Laut MFC wohl in afxglobals.h. Diesen Header kann ich aber auf meinem System nicht finden (VS 2005). 😞

    Damit ist diese Klasse gemeint. Einfach herunterladen und die Headerdatei einbinden.

    ZZZ schrieb:

    Und noch eine Grundlegende Frage zur Methode OnEraseBkgnd. Wann wird diese aufgerufen?

    In der Regel vor dem Aufruf von OnPaint. In OnEraseBkgnd wird der Fensterhintergrund gezeichnet, in OnPaint der Vordergrund. Wenn Dein Fenster also z.B. standardmäßig einen weißen Hintergrund hat, dann sieht die Reihenfolge so aus:

    1. OnEraseBkgnd zeichnet für das ganze Fenster einen weißen Hintergrund
    2. Dein OnPaint zeichnet ein eigenes Hintergrundbild und den Vordergrund

    Wenn das Fenster nun vergrößert wird, dann wird immer erst der weiße Hintergrund gezeichnet und anschließend sofort Dein Hintergrundbild samt Vordergrund. Das führt zum Flickern, da unser Auge das erkennt.

    Aus diesem Grund muss man also beide Aktionen kombinieren (entweder in OnEraseBkgnd oder in OnPaint). Damit OnEraseBkgnd keinen eigenen Hintergrund zeichnet, muss man TRUE zurückgeben. Bei Dir findet dann alles in OnPaint statt.



  • Damit ist diese Klasse gemeint. Einfach herunterladen und die Headerdatei einbinden.

    Ich hab diese verwendet UND ES GEHT!!!! 😃 :schland: JUBEL

    Klasse Tipp mit CMemDC Jungs. *bedank*

    Jetzt aber doch noch eine Frage wegen dem Hintergrund. Ich mach ihn im Moment mit

    CMemDC.BitBlt(0, 0, rectDlg.Width(), rectDlg.Height(), NULL,
    			rectDlg.Width(), rectDlg.Height(), WHITENESS);
    

    einfach nur weiß. Gefällt mir aber nicht so wie das langweilige grau von Windows. Wie könnte ich den Standardhintergrund hinbekommen?

    Und noch was ganz anderes: Letztendlich will ich das Hintergrundbild drehen, zoomen und scrollen wie eine Art Imageviewer. Kennt einer von euch ein Beispiel / Tutorial etc. das sowas in der Art behandelt?


Log in to reply