Funktionsplotter
-
Du Windows nicht verstanden. OnPaint wirdimmer automatisch aufgerufen und nicht erst wenn Du irgendwas machst.
Wenn Du nihct willst das gezeichnet wird, bevor irgendwas passiert ist, dann musst Du eben entsprechende Flags setzen und berücksichtigen.
-
OnPaint darfst man nicht direkt aufrufen!
Das machst du indirekt indem du Invalidate(); und UpdateWindow(); aufrufst.
Mit Invalidate sagst du dass der Anzeigebereich deines Programmfensters ncht mehr gültig ist und mit UpdateWindow veranlasst du ein Neuzeichnen -> Indirektes Aufrufen von OnPaint.Versuch's doch mal so:
(Es könnten Fehler enthalten sein, weil ich dass aus dem Gedächtnis geschrieben habe)//stdafx.h #include "afxstr.h" #include "atlimage.h"
//MyDlg.h CImage cDisplayImg;
//MyDlg.cpp void CMyDlg::OnButton() { // Wenn schon ein Bild geladen ist -> Speichern und zerstöhren if(!cDisplayImg.IsNull()) { cDisplayImg.Save(_T("C:\\Bild.png"));// Bild spechern cDisplayImg.Destroy();// Bild zerstöhren } // Bild neu erstellen // X = 600 Pixel // Y = 550 Pixel // Farbtiefe = 24 Bit cDisplayImg.Create(600,550,24); ZeichneDasZeug(); // Anzeigebereich für ungültig erklähren Invalidate(); // Alles neu zeichnen UpdateWindow(); } void CMyDlg::ZeichneDasZeug() { // Überprüfen ob ein Bild erzeugt wurde if(cDisplayImg.IsNull()) { // Akustisch Bescheid geben dass etwas nicht stimmt MessageBeep(MB_ICONERROR); return; } // DC zum malen holen. CDC* dc = CDC::FromHandle(cDisplayImg.GetDC()); // Pinsel deffinieren CBrush cBrush; // Die Farbe Weiß wählen cBrush.CreateSolidBrush(COLORREF(0xFFFFFF)); CBrush* cOldBrush = dc->SelectObject(&cBrush); // Den DC-Bereich weiß malen, da sonst alles schwarz ist dc->Rectangle(0,0,cDisplayImg.GetWidth(),cDisplayImg.GetHeight()); CPen Pen; Pen.CreatePen(PS_SOLID,1,RGB(0,0,0)); CPen* ptrPen=dc->SelectObject(&Pen); // dc->SetViewportOrg(180,-125); // Hier wird lustig gemalt -> Aufpassen dass nicht außerhalb des // Zeichenbereichs gemalt wird dc->MoveTo(0,200); dc->LineTo(0,300); dc->MoveTo(0,300); dc->LineTo(480,300); int x= 0; int y= 0; for (int j = 0; j < 9; j++) { y = rand() %2; x = j; if(y == 0) { dc->LineTo((x*50),300); dc->LineTo((x*50)+50,300); } else { dc->LineTo(x*50,250); dc->LineTo((x*50)+50,250); } } // Zum Schluss immer Aufräumen dc->SelectObject(&cOldBrush); dc->SelectObject(&cptrPen); // DC der CImage Klasse freigeben, sonst kracht's cDisplayImg.ReleaseDC(); } void CMyDlg::OnPaint() { CPaintDC dc(this); // device context for painting // Überprüfen ob die CImageklasse bereit zum Malen ist if(!cDisplayImg.IsNull()) { // Das Bild/die Linien auf den Bildbereich klatschen cDisplayImg.Draw(dc.GetSafeHdc(),0,0); } /////////////////////////////////////////////////////////////////// // Kein Aufruf von CDialog::OnPaint() für Zeichnungsnachrichten // /////////////////////////////////////////////////////////////////// } BOOL CMyDlg::OnEraseBkgnd() { // Damit verhindert man das Flickern beim Neuzeichnen return TRUE; }
-
Hallo Bernhard,
danke auf jeden Fall schonmal für die tolle Antwort. Ich versuche mich da jetzt schon seit ein paar Stunden dran, allerdings ein bisschen anders als du es beschrieben hast. Ich bekomme bei meinem Code aber immer einen Fehler. Hier mal ein paar kurze Ausschnitte meines Codes:
void CRandomDlg::OnButton() { GraphPaint(); Invalidate(); UpdateWindow(); }
void CRandomDlg::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Code für die Behandlungsroutine für Nachrichten hier einfügen CPen Pen; // Erzeugen eines neuen Stift Pen.CreatePen(PS_SOLID,1,RGB(0,0,0)); // Verbinden Stift und Geraet dc.SelectObject(&Pen); dc.SetTextColor(RGB(140,70,0)); // Koordinatenkreuz zeichnen dc.SetViewportOrg(180,-125); //Ursprung des Koordinatenkreuzes versetzen // x- und y-Achse dc.MoveTo(0,230); dc.LineTo(0,300); dc.MoveTo(0,300); dc.LineTo(930,300); // Hier zeichne ich einfach ein paar Linien die immer da sein sollten // Kein Aufruf von CDialog::OnPaint() für Zeichnungsnachrichten }
void CRandomDlg::GraphPaint() { CPaintDC dc(this); CDC dcgraph; CPen pena; dcgraph.SetViewportOrg(180,-125); pena.CreatePen(PS_SOLID,1,RGB(255,0,0)); dcgraph.SelectObject(&pena); dcgraph.MoveTo(0,300); int x[9] = {0}; int y[9]; CString strData[9]; for (int j = 0; j < 9; j++) { y[j] = rand() %2; x[j] = j; strData[j].Format("%d", y[j]); strRandData += strData[j]; m_strRandomData.SetFocus(); m_strRandomData.SetSel(strRandData.GetLength()); m_strRandomData.ReplaceSel(strRandData, false); if(y[j] == 0) { dcgraph.LineTo((x[j]*3),300); dcgraph.LineTo((x[j]*3)+3,300); } else { dcgraph.LineTo(x[j]*3,250); dcgraph.LineTo((x[j]*3)+3,250); } Sleep(50); } }
Tja, ich dachte so müsste es klappen. Ich möchte auch direkt in meinem Dialog zeichnen. Wenn ich alles in der OnPaint aufrufe klappt es auch. Hier hat er irgendwie ein Problem wenn ich "dcgraph.xxx" aufrufe. Sobald ich das in meinem Code drin habe gibt es einen Assert Fehler wenn ich auf meinen Button klicke. Also das Problem muss ja irgendwie an "CDC dcgraph;" liegen. Hast du da eine Idee was ich falsch mache.
Vielen Dank und viele Grüße
Laura
-
1. CPaintDC darf nur in OnPaint Nachrichten verwendet werden.
2. Dein CDC dcgraph; ist NULL, d.h. es ist kein KOntext erzeugt.
3. Wenn Du tatsächlich außerhalb von OnPaint zeichnen musst, dann solltest Du den entsprechenden DC als Zeiger an die Funktion übergeben, wie CView::OnDraw das tut.Ansonsten finde ich den Konstrukt etwa unsinnig. in OnButton zu zeichnen. Es muss doch genügen einfach einen Invalidate und UpdateWindow auszuführen. Für was bitte vorher der GraphPaint?
-
Ich hab die GraphPaint() grad mal noch ein bisserl abgeändert. Jetzt kommt kein Fehler mehr, aber es wird auch nichts gezeichnet. Hab auch schon versucht per Invalidate() und UpdateWindow() an diversen Stellen das Zeichnen zu veranlassen, aber das hat auch nicht geklappt.
void CRandomDlg::GraphPaint() { CPaintDC dc(this); CPen pena; dc.SetViewportOrg(180,-125); pena.CreatePen(PS_SOLID,1,RGB(255,0,0)); dc.SelectObject(&pena); dc.MoveTo(0,300); int x[9] = {0}; int y[9]; CString strData[9]; for (int j = 0; j < 9; j++) { y[j] = rand() %2; x[j] = j; strData[j].Format("%d", y[j]); strRandData += strData[j]; m_strRandomData.SetFocus(); m_strRandomData.SetSel(strRandData.GetLength()); m_strRandomData.ReplaceSel(strRandData, false); if(y[j] == 0) { dc.LineTo((x[j]*3),300); dc.LineTo((x[j]*3)+3,300); } else { dc.LineTo(x[j]*3,250); dc.LineTo((x[j]*3)+3,250); } Sleep(50); } }
Edit: @Martin Richter: haben gleichzeitig gepostet ich werd mir deine Tips also erstmal anschauen
-
Martin Richter schrieb:
Ansonsten finde ich den Konstrukt etwa unsinnig. in OnButton zu zeichnen. Es muss doch genügen einfach einen Invalidate und UpdateWindow auszuführen. Für was bitte vorher der GraphPaint?
Also wenn ich das Zeichnen komplett in OnDraw erledige, wird der Graph schon immer gezeichnet wenn ich den Dialog aufrufe. Ich möchte allerdings den Dialog aufrufen, in dem mein Button zu sehen ist und das Koordinatenkreuz und dann wenn man den Button betätigt soll der Graph gezeichnet werden und die Daten in ein Editfeld geschrieben werden.
Ich habe mir die OnDraw Methode mal angeschaut, allerdings werde ich nicht so ganz schlau draus, was ich dann beim Aufruf übergeben muss. Also einen Zeiger auf irgendwas, aber auf was? Sorry wenn ich mich blöd anstelle, aber ich programmier noch nicht so lange mit MFC.
Hier noch mal mein neuer Code:
void CRandomDlg::OnButton() { OnDraw(); Invalidate(); UpdateWindow(); } void CRandomDlg::OnDraw(CDC *pDC) { CPen pena; pDC->SetViewportOrg(180,-125); pena.CreatePen(PS_SOLID,1,RGB(255,0,0)); pDC->SelectObject(&pena); pDC->MoveTo(0,300); int x[9] = {0}; int y[9]; CString strData[9]; for (int j = 0; j < 9; j++) { y[j] = rand() %2; x[j] = j; strData[j].Format("%d", y[j]); strRandData += strData[j]; m_strRandomData.SetFocus(); m_strRandomData.SetSel(strRandData.GetLength()); m_strRandomData.ReplaceSel(strRandData, false); if(y[j] == 0) { pDC->LineTo((x[j]*3),300); pDC->LineTo((x[j]*3)+3,300); } else { pDC->LineTo(x[j]*3,250); pDC->LineTo((x[j]*3)+3,250); } Sleep(50); } }
-
Ich hatte es Dir schon einmal geschrieben:
1. Verwende ein Flag.
2. Zeichne nur in OnPaint.
3. Das Flag setzt Du um, wenn der Button geklickt wird und dann führst Du einen Invalidate/UpdateWindow durch.Zu Deinem Code:
Deine Fehler haben gar nichts mit der MFC zu tun!
Wieso schreibst Du eine Funktion OnDraw mit einem Argument CDC* und rufst diese Funktion ohne CDC auf?
Wieso rufst Du überhaupt diese Zeichenfunktion in OnButton auf, wenn Dir schon mehrfach gesagt wurde, dass man nur in OnPaint zeichnet unddass diese Aktion sowieso ungültig wird durch die nächsten Statements?
-
Ich weis einfach nicht wie ich ein Flag setzten kann bzw. wie ich das hier anwenden kann. Wie ich schon geschrieben habe, ich programmiere noch nicht so lange mit MFC und insgesamt auch noch nicht so lange. Ich würde mich sehr freuen wenn du mir ein Beispiel für ein Flag geben könntest. Ich habe schon ein bisschen gegoogelt, aber nichts verständliches gefunden.
-
Also ich hab es jetzt endlich geschafft. Das mit den Flags setzen geht ja eigentlich total einfach, ich hatte es halt einfach nicht unter diesem Namen gekannt. Vielen Dank für die guten Tips.
-
Zeichne nur in OnPaint.
Warum?
Ich zeichne immer außerhalb von OnPaint in eine eigene CDC bzw. CImage und kopiere diese dann in OnPaint auf den Anzeigebereich.
Dadurch fallen Mehrfachberechnungen weg wenn die Ansicht oft neu gezeichnet wird. Und bei langsameren PC's verhindet man damit dass die Linien langsam Stück für Stück einzeln auf den Bildschirm gezeichnet werden.Darf ich fragen was ich bei meiner Methode falsch gemacht habe?
Durch CImage ist der Speicher leicht handzuhaben und das Speichern als Bild benötigt nur eine zusätzliche Zeile.
Die Positionen der Punkte und Linien werden nur einmal berechnet und beim Zeichnen/Anzeigen gibt es fast keinen zusätzlichen Rechenaufwand.
Durch Abwürgen des Löschen des Hintergrundes gibt es kein Flickern.
Man kann alles sehr einfach ein einer Klasse verkapseln und hat beim Wiederverwenden fast keinen Aufwand beim Anpassen.
Den einzigen Nachteil den ich sehe, ist der etwas höhere Speicherverbrauch.
-
Bernhard. schrieb:
Zeichne nur in OnPaint.
Darf ich fragen was ich bei meiner Methode falsch gemacht habe?
Damit zeichnest Du in den "realen" DC nur im OnPaint und damit wird der DC valid. Damit entsprichst Du erstmal der Grundforderung, die ich hier mal aufstelle.
Dennoch! Warum sollte ich irgendwo bei irgend einer Aktion in einen fiktiven DC zeichnen, wenn mir OnPaint detailiert sagt, welchen Auschnitt er benötigt. Wenn dieser nicht aktualisiert werden muss, kann ich evtl. die gecachten Daten wiedergeben. Was Du vermutlich damit auch erreichst...
Nochmal: Warum sollte ich den Code, der etwas zeichnet auseinanderreißenund mal hier und mal da in einen DC Zeichnen? Das machst Du vermtulich auch nicht.
Es geht darum, dass man nicht in irgendwelchen Handlern irgendwas auf den Bildschirm DC schreibt. Das ist sowieso weg, wenn der nächste OnPaint kommt...
Also nochmal: Zeichne nur in OnPaint... OK?
-
Ich habe noch ein kleines Problem. Ich zeichne nur in OnPaint. Wenn ich jetzt meinen Graphen etwas langsamer zeichnen lassen möchte (also z.B. mit Sleep(1); ) dann wird so lange der Graph gezeichnet wird nur der Graph selbst gezeichnet und der Rest des Fensters ist nicht sichtbar. Ich denke das ist das Flickern von dem ihr immer sprecht, nur das es bei mir ziemlich lange dauert weil das Zeichnen des Graphen eben ein paar Sekunden dauert. Kann ich irgendwie sagen dass nur der Graph gezeichnet werden soll und der Rest einfach so bleibt wie er ist? Ich hatte es mit OnEraseBkgnd() versucht, aber dann wird das Fenster immer durchsichtig und das dauerhaft, das möchte ich natürlich auch nicht. Irgend jemand eine Idee?
-
Ich habe noch ein kleines Problem. Ich zeichne nur in OnPaint. Wenn ich jetzt meinen Graphen etwas langsamer zeichnen lassen möchte (also z.B. mit Sleep(1); ) dann wird so lange der Graph gezeichnet wird nur der Graph selbst gezeichnet und der Rest des Fensters ist nicht sichtbar.
Sleep finktioniert normalerweise erst ab 10ms und aufwärts.
Wenn man einen niedrigeren Wert eingiebt, wird trotzdem 10ms gewartet.
Wenn das Zeichnen / die Animation länger dauert, würde ich mit Doppelpuffer oder mit einem seperaten "Arbeitsthread" arbeiten.Ich denke das ist das Flickern von dem ihr immer sprecht, nur das es bei mir ziemlich lange dauert weil das Zeichnen des Graphen eben ein paar Sekunden dauert.
Das Flickern ist die Zeitdauer zwischen dem der Anzeigebereich gelöscht und neu gezeichnet wird.
Im Normalfall nimmt man dass als "Blinken" wahr.Kann ich irgendwie sagen dass nur der Graph gezeichnet werden soll und der Rest einfach so bleibt wie er ist?
Schau dir mal http://msdn.microsoft.com/en-us/library/2f3csed3(VS.80).aspx an.
Du kannst aber auch in eine CImage Klasse malen und das Bild dann in ein CStatic Element kopieren.
Dass übernimmt dann die ganze Aktualisierungsarbeit in OnPaint.
(So mache ich dass meißtens)
Dass könnte so ausschaun://MyDlg.cpp bool CMyDlg::ZeichneDasZeug() { CImage cDisplayImg; // Bild neu erstellen // X = 600 Pixel // Y = 550 Pixel // Farbtiefe = 24 Bit if(!cDisplayImg.Create(600,550,24)) { return false; } // DC zum malen holen. CDC* dc = CDC::FromHandle(cDisplayImg.GetDC()); // Pinsel deffinieren CBrush cBrush; // Die Farbe Weiß wählen cBrush.CreateSolidBrush(COLORREF(0xFFFFFF)); CBrush* cOldBrush = dc->SelectObject(&cBrush); // Den DC-Bereich weiß malen, da sonst alles schwarz ist dc->Rectangle(0,0,cDisplayImg.GetWidth(),cDisplayImg.GetHeight()); CPen Pen; Pen.CreatePen(PS_SOLID,1,RGB(0,0,0)); CPen* ptrPen=dc->SelectObject(&Pen); // dc->SetViewportOrg(180,-125); // Hier wird lustig gemalt -> Aufpassen dass nicht außerhalb des // Zeichenbereichs gemalt wird dc->MoveTo(0,200); dc->LineTo(0,300); dc->MoveTo(0,300); dc->LineTo(480,300); int x= 0; int y= 0; for (int j = 0; j < 9; j++) { y = rand() %2; x = j; if(y == 0) { dc->LineTo((x*50),300); dc->LineTo((x*50)+50,300); } else { dc->LineTo(x*50,250); dc->LineTo((x*50)+50,250); } } // Zum Schluss immer Aufräumen dc->SelectObject(&cOldBrush); dc->SelectObject(&cptrPen); // DC der CImage Klasse freigeben, sonst kracht's cDisplayImg.ReleaseDC(); //m_cStatic ist die Variable des Static Anzeigeelement's. // Das Bild wird in das Anzeigeelement kopiert m_cStatic.SetBitmap(cDisplayImg.Detach()); }
Ich hatte es mit OnEraseBkgnd() versucht, aber dann wird das Fenster immer durchsichtig und das dauerhaft, das möchte ich natürlich auch nicht.
Dass funktioniert nur, wenn man über den gesammten Bildbereich malt.
Wenn man OnEraseBkgnd abwürgt um das Flickern/Blinken zu verhindern, muss man sich selbst um das Malen des Hintergrunds kümmern.@Martin Richter
"Live" zu berechnen und zeichnen funktioniert nur, wenn man nicht viel zeichnen muss und man genug Rechnerleistung zur Verfügung hat.Ansonsten ist das Arbeiten mit Doppelpuffer bei denenen man das Bild "vorbereitet" und erst bei Bedarf auf den Bildbereich kopiert die bessere Lösung. Vorallem das Zoomen, Verschieben,... mit einem Doppelpuffer wesentlich einfacher.
Schiebe doch einfach einmal ein Programmfenster über den Anzeigebereich wenn eine Plod-Datei mit >1000 Zeilen angezeigt wird.
Einmal mit Doppelbuffer und einmal mit "Live" Zeichnen.