Flackern / Performanceproblem beim Zeichnen
-
Hallo!
Ich bin relativ neu in der C++ Welt, "darf" aber im Rahmen eines Uniprjektes eine C++ Applikation bauen.
Das ganze soll eine Messkarte auslesen und die Messwerte graphisch darstellen. Die Werte aus der Karte bekomme ich, bzw ich hab eine Verkettete Liste, die Dummydaten enthaelt.
Ich habe mit dem Visual Studio 2005 ein neues "Visual C++ -> CLR -> Windows Forms Anwendung" Projekt erzeugt und mir erst einmal eine grobe Gui zusammengeklickt, auf der auch ein Panel ist, in dem ich gerne zeichnen moechte.
(Ich hoff, ich bin im richtigen Forum hier )
Eine der Anforderungen an mein Programm ist, dass die neuen Messwerte immer am rechten Rand erscheinen sollen und die alten nach links wegscrollen sollen...
Daher lese ich meine Liste von hinten aus und zeichne so das ganze Panel neu. Das dauert allerdings relativ lange, flackert und frisst 100% der Systemrescourcen...private: System::Void panel1_Paint(System::Object^ sender, System::Windows::Forms::PaintEventArgs^ e) { koordinatenSys(); graphZeichnen(); } private: System::Void koordinatenSys(){ //Darstellungsflaeche zeichnen panel1->BackColor = System::Drawing::Color::White; panel1->BorderStyle = System::Windows::Forms::BorderStyle::Fixed3D; Graphics^ graph = panel1->CreateGraphics(); Pen^ pen = gcnew Pen(Color::Black); //10 Pixel seitenabstand int seitenAbstand = 10; int panelHeight = panel1->Size.Height; int panelWidth = panel1->Size.Width; //Achse(n) zeichnen graph->DrawLine(pen, seitenAbstand, panelHeight/2, panelWidth-seitenAbstand, panelHeight/2); graph->DrawLine(pen, panelWidth/2, seitenAbstand, panelWidth/2, panelHeight-seitenAbstand); delete pen; delete graph; } // erst einmal nur Punkte, spaeter option fuer linien, dazu muessen dann zwei Messwerte geholt werden private: System::Void graphZeichnen(){ Graphics^ graph = panel1->CreateGraphics(); int panelMitte = panel1->Size.Height/2; int panelWidth = panel1->Size.Width; int yKoord = 0; int xVal = panelWidth; Pen^ penRed = gcnew Pen(Color::Red); Pen^ penBlue = gcnew Pen(Color::Blue); Pen^ penGreen = gcnew Pen(Color::Green); Pen^ penPink= gcnew Pen(Color::Pink); for (int i = 0; i < panelWidth; i++) { //Daten aus Liste holen CMesswert* messung = werteListe->getMessungXvorEnde(i*40); //Kanal 1 zeichnen if (kanal1->Checked) { //y-Wert berechnen yKoord = panelMitte + (int)((messung->getKanal1())*30); //Pixel zeichnen graph->DrawRectangle(penRed,xVal,yKoord,1,1); } //Kanal 2 zeichnen if (kanal2->Checked) { //y-Wert berechnen yKoord = panelMitte + (int)((messung->getKanal2())*30); //Pixel zeichnen graph->DrawRectangle(penBlue,xVal,yKoord,1,1); } //Kanal 3 zeichnen if (kanal3->Checked) { //y-Wert berechnen yKoord = panelMitte + (int)((messung->getKanal3())*30); //Pixel zeichnen graph->DrawLine(penGreen,xVal,yKoord,xVal,yKoord); } //Kanal 4 zeichnen if (kanal4->Checked) { //y-Wert berechnen yKoord = panelMitte + (int)((messung->getKanal4())*30); //Pixel zeichnen graph->DrawRectangle(penPink,xVal,yKoord,1,1); } xVal--; } delete penRed; delete penBlue; delete penGreen; delete penPink; delete graph; } private: System::Void startButton_Click(System::Object^ sender, System::EventArgs^ e) { if (!timer1->Enabled) { startButton->Text = "Stop"; timer1->Interval = 100; timer1->Start(); } else { startButton->Text = "Start"; timer1->Stop(); } } //TimerEvent private: System::Void redraw(System::Object^ sender, System::EventArgs^ e) { //Karte auslesen und in Speicherstruktur schreiben... //hier jetzt nicht drin da ich DummyDaten benutze panel1->Invalidate(); }
Die Frage ist nun, gibt es eine Moeglichkeit, das zeichnen zu beschleunigen oder das Flackern abzuschlaten? (Doublebuffered laesst sich beim Panel irgendwie nicht setzen). Ich denke mal die zwei flaschenhaelse sind einmal das durchiterieren der Pixel in z-Richtung und meine Liste (auch wenn ein Vector auch nicht schneller ist)
Genial waere es natuerlich, wenn ich einfach die "alten" Messwerte nicht neu zeichnen muesste sondern es einfach nach rechts verschieben koennte.Danke schonmal fuer Tipps und Hinweise
driddePS: Bitte nicht wegen des Codes den Kopf schuetteln, ich sag ja, ich hab von C++ nicht wirklich eine Ahnung, geschweige denn von Oberflaechen zusammenbasteln. Solltet ihr den ganzen Projektcode brauchen, kann ich den auch nachreichen.
-
Ich würde auf jeden Fall erst mal die Berechnungen und die Objekterzeugung am Anfang der Funktion in den Konstruktor oder ins Init verschieben und die lokalen Variablen in die Klasse packen.
Ist zwar nicht der Flaschenhals aber tut auch nicht weh es anders zu machen.
-
Ich komm von der VCL-Front, also kann ich nichts konkretes sagen, aber ich würde versuchen in ein temporäres (unsichtbares) Objekt zu zeichnen und dann einfach die Zeichenfläche kopieren. In der VCL würde ich ein TImage zur Darstellung nehmen und ein TBitmap zur Pufferung.
-
templäd schrieb:
Ich würde auf jeden Fall erst mal die Berechnungen und die Objekterzeugung am Anfang der Funktion in den Konstruktor oder ins Init verschieben und die lokalen Variablen in die Klasse packen.
Okay, ich habe jetzt die Variablen als Klassenvariablen deklariert und initialisiere sie im Konstruktor. In wiefern meinst du, die Berechnungen raushauen? Dann muesste ich die Werte ja wieder irgendwo speichern und beim Zeichnen durchiterieren... ich koennte zwar meine Messungsklasse so erweitern dass jeder Kanal sich seine y-Koordinate speichert und die beim hinzufuegen in die Liste setzen.
Dabei gibt es aber das Problem, dass man die Kurven sowohl auf der X als auch auf der Y-Achse verschieben koennen soll um die uebereinanderlegen zu koennen. dann muesste ich, wenn jemand an den Einstellungen rumfummelt alle y-Werte neu berechnen. Und durch meine verkettete Kiste muesste ich trotzdem iterieren, was ziemlich lange dauert. Werde ich aber nachher mal versuchenJoe_M. schrieb:
Ich komm von der VCL-Front, also kann ich nichts konkretes sagen, aber ich würde versuchen in ein temporäres (unsichtbares) Objekt zu zeichnen und dann einfach die Zeichenfläche kopieren. In der VCL würde ich ein TImage zur Darstellung nehmen und ein TBitmap zur Pufferung.
Den Ansatz hab ich vorhin einmal probiert, ich habe aber nur ein System::Drawing::Bitmap und eine System::Forms::PictureBox in die ich das Bitmap reinwerfen kann. Das Bitmap hat aber kein graphics-Objekt bzw ich kann da nicht mit graphics drauf rummalen. setPixel gibt es... ich werd das mal ausprobieren, kann mir ehrlich gesagt aber nicht vorstellen, dass das schneller ist
Danke fuer eure Denkanstoesse
-
Ich habe die Berechnungen der y-Koordinate jetzt in meine Funktion zur Dummydaten-Generierung verlagert und hole in der panel1_paint() nur noch die y-koordinaten aus der Struktur... das hat für die Zeichengeschwindigkeit allerdings wenig gebracht, ich kann der Kurve immer noch beim gemalt werden zusehen... daran lag es wohl nicht wirklich...
Die zweite Sache mit den Bild ist noch weniger performant... ich habe aber durch ein bisschen googlen und auch Suche hier im Forum habe ich die geschichte mit dem eingebauten "OptimiezedDoubleBuffer" in .Net 2.0 gefunden....
Ich hab mir also ein BufferedPanel vom Panel abgeleitet und da im Konstruktor mit this->SetStyle(ControlStyles.OptimizedDoubleBuffer); den Buffer eingeschaltet. In meinem Code hab ich das Panel durch mein BufferedPanel ersetzt, aber jetzt funktioniert die Entwurfsansicht für das Formular nicht mehr und ich hab trotzdem noch komische Effekte, wenn zeichne... das Zeichnen mache ich immernoch in der panel1_paint()-Methode, aber wenn ich jetzt das Fenster verschiebe so das es sich neu zeichen muss malt er die Graphen, danach macht er das Panel aber weiss...
Ich hab also noch 2 Probleme... einmal das mit den Double Buffer, dass er das weiss überzeichnet und das andere ist, wie ich mein BufferedPanel dazu bekomme, dass es in der Entwurfsansicht funktioniert... also quasi in der ToolBox mit den Komponenten rechts drin ist.
Hat da jemand eine Idee wie ich das hinbekomme?Danke schonmal,
dridde
-
Du musst eine *eigene* Klasse z.B. von Panel ableiten. Darin dann im Konstruktor das DoubleBufering aktivieren (SetStyle) und in OnPaint zeichnen.
Ersetze das das "Panel1" duch Dein eigenes...
-
ich würde nicht von panel ableiten, sondern direkt nen CustomUserControl dafür anlegen... dort kannst du WMIPaint, DoubleBuffered, etc. anschalten wie du willst.
Wegen dem zeichnen: überschreibe das onPaint event bzw subscribe das event und nimm das HDC das von dem Event kommt... (dieses hdc hat iwie bessere Renderer etc... frag mich nach sonnenschein).
Fazit:
control.CreateGraphics() weglassen
Wegen der Systemresourcenfresserei (was ein Wort oO) die Pens, Brushes, etc. disposen...hoffe das hilft
-
Hallo, da bin ich wieder... so wirklich klappen tut das alles noch nicht...
Jochen Kalmbach schrieb:
Du musst eine *eigene* Klasse z.B. von Panel ableiten. Darin dann im Konstruktor das DoubleBufering aktivieren (SetStyle) und in OnPaint zeichnen.
Ersetze das das "Panel1" durch Dein eigenes...Das hatte ich schon so gemacht, bin hatte da aber das Problem dass ich trotzdem noch mit der "panel1_paint" darauf malen wollte und nicht in der OnPaint.
majin schrieb:
ich würde nicht von Panel ableiten, sondern direkt nen CustomUserControl dafür anlegen... dort kannst du WMIPaint, DoubleBuffered, etc. anschalten wie du willst.
[...]
control.CreateGraphics() weglassen
Wegen der Systemresourcenfresserei (was ein Wort oO) die Pens, Brushes, etc. disposen...Das mache ich jetzt so, ich hab mir eine Klasse erstellt, die cpp dazu ist leer bis auf die #includes
#pragma once using namespace System; using namespace System::ComponentModel; using namespace System::Collections; using namespace System::Windows::Forms; using namespace System::Data; using namespace System::Drawing; namespace Schwingung { /// <summary> /// Zusammenfassung für BufferedPanel /// </summary> public ref class BufferedPanel : public System::Windows::Forms::UserControl { public: BufferedPanel() { InitializeComponent(); // //TODO: Konstruktorcode hier hinzufügen. // this->SetStyle( ControlStyles::UserPaint | ControlStyles::AllPaintingInWmPaint | ControlStyles::DoubleBuffer, true); } protected: /// <summary> /// Verwendete Ressourcen bereinigen. /// </summary> ~BufferedPanel() { if (components) { delete components; } } private: /// <summary> /// Erforderliche Designervariable. /// </summary> System::ComponentModel::Container ^components; #pragma region Windows Form Designer generated code /// <summary> /// Erforderliche Methode für die Designerunterstützung. /// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden. /// </summary> void InitializeComponent(void) { this->SuspendLayout(); // // BufferedPanel // this->Name = L"BufferedPanel"; this->Size = System::Drawing::Size(720, 506); this->ResumeLayout(false); this->BackColor = System::Drawing::Color::White; } #pragma endregion protected: virtual void OnPaint(PaintEventArgs ^e) override { koordinatenSys(e->Graphics); graphZeichnen(e->Graphics); } private: System::Void koordinatenSys(System::Drawing::Graphics ^graph){ //Achse(n) zeichnen System::Drawing::Pen ^penBlack = gcnew Pen(System::Drawing::Color::Black); int seitenAbstand = 10; graph->DrawLine(penBlack, seitenAbstand, this->Height/2, this->Width-seitenAbstand, this->Height/2); graph->DrawLine(penBlack, this->Width/2, seitenAbstand, this->Width/2, this->Height-seitenAbstand); delete(penBlack); } private: System::Void graphZeichnen(System::Drawing::Graphics ^graph){ System::Drawing::Pen ^penRed = gcnew Pen(Color::Red); System::Drawing::Pen ^penBlue = gcnew Pen(Color::Blue); System::Drawing::Pen ^penGreen = gcnew Pen(Color::Green); System::Drawing::Pen ^penPink= gcnew Pen(Color::Pink); int yKoord = 0; int xVal = this->Width; for (int i = 0; i < this->Width; i++) { CMesswert* messung = new CMesswert(); //werteListe->getMessungXvorEnde(i*40); //Kanal 1 zeichnen //if (form->kanal1->Checked) { yKoord = this->Height/2 + (int)((messung->getKanal1())*30); graph->DrawRectangle(penRed,xVal,yKoord,1,1); } //Kanal 2 zeichnen //if (form->kanal2->Checked) { yKoord = this->Height/2 + (int)((messung->getKanal2())*30); graph->DrawRectangle(penBlue,xVal,yKoord,1,1); } //Kanal 3 zeichnen //if (form->kanal3->Checked) { yKoord = this->Height/2 + (int)((messung->getKanal3())*30); graph->DrawRectangle(penGreen,xVal,yKoord,1,1); } //Kanal 4 zeichnen //if (form->kanal4->Checked) { yKoord = this->Height/2 + (int)((messung->getKanal4())*30); graph->DrawRectangle(penPink,xVal,yKoord,1,1); } xVal--; } delete(penRed); delete(penBlue); delete(penGreen); delete(penPink); } }; }
Die tut auch soweit was sie soll, ich muss sie aber per Hand in meine Form1 hinzufügen, da sie in der Toolbox nicht auftaucht... funktioniert beim ausführen der Applikation zwar, aber der "Forms Designer" ist dann tot und meldet sich mit dieser Meldung.
Ich kann ihn also so nicht verwenden. Woher kommt das? Die Komponente funktioniert ja soweit erst einmal und befindet sich doch auch in den Namensraum.
Ich hab vorsichtshalber auch mal das ganze Projekt gepackt hochgeladen. (~2Mb wegen der Intellisense DB)---------
Damit ich nicht was tue, was man nicht tun soll - mehrere Probleme in einem Thread ansprechen und mischen - setz ich mal noch einen Link auf den zweiten Thread den ich gleich aufmachen werde...Gleiches Projekt, anderes Problem -> anderer Thread
Danke, dass ihr euch mit meinem Mist beschäftigt.
dridde