Forum: PC-Programmierung Qt Widget teilweise neu zeichnen


von Markus (Gast)


Lesenswert?

Hallo zusammen,

ich habe mir mit Qt eine GUI Applikation geschrieben, die auf einem 
Industriedisplay läuft. Es gibt eine Übersichtsseite, auf der mehrere 
physikalischen Größen dargestellt werden. Teilweise nur als Zahlenwert, 
teilweise mit Diagrammen und verschiedenen Icons/grafiken. Zusätzlich 
gibt es noch ein paar Softkeys, um Dinge ein/ausschalten zu können. 
Außerdem gibt es noch ein Menü, in dem man beispielsweise die Sprache 
umstellen kann. Insgesamt sind es 8 Seiten und diese sind als Widget in 
einem Stackedwidget abgelegt. Ein Neuzeichnen der Seite erfolgt über 
update(), wodurch paintEvent() aufgerufen wird. Dies erfolgt alle 50 ms. 
Bis hier her läuft alles prima.

Nun kam aber die Anforderung auf, dass ein Neuzeichnen alle 20 ms 
erfolgen soll( das Projekt läuft im Rahmen des Studiums ). Ziel ist es 
nun, dass nur gewisse Teile eines Widgets neu gezeichnet werden.
Und da fängt mein Problem an. Sobald paintEvent() aufgerufen wird, wird 
ja der komplette Screen gelöscht. Ist mein bisheriger Ansatz überhaupt 
ok mit dem Stackedwidget oder muss ich das Konzept total überarbeiten?

Über einen kleinen Denkanstoß wäre ich sehr dankbar.

Gruß Markus

von Guest (Gast)


Lesenswert?

Das QPaintEvent in paintEvent auswerten und nur diesen Teil neu 
zeichnen.
QPainter::clipRect wäre ein erster Schritt.

Dann mit einem QTimer von dem Fenster update(rect) aufrufen wobei rect 
das entsprechende Rechteck ist. Dieses Rechteck kommt dann bei 
paintEvent im QPaintEvent an.

von Markus (Gast)


Lesenswert?

Guest schrieb:
> Das QPaintEvent in paintEvent auswerten und nur diesen Teil neu
> zeichnen.
> QPainter::clipRect wäre ein erster Schritt.
>
> Dann mit einem QTimer von dem Fenster update(rect) aufrufen wobei rect
> das entsprechende Rechteck ist. Dieses Rechteck kommt dann bei
> paintEvent im QPaintEvent an.

Mir kam gerade ein anderer Ansatz. Aktuell ist es ja so, dass jede Seite 
ein eigenes Widget ist. Auf einer Seite sind halt mehrere Texte, Icosn, 
Diagramme aber an sich in verschiedene Bereiche oder Container 
aufgeteilt. Wäre es ein Ansatz nicht jede Seite als Widget zu speichern, 
sondern jeden Bereich/Container?

Aber hier habe ich doch auch wieder das Problem, genau wie bei deinem 
Vorgehen, dass durch den Aufruf von paintEvent() alles gelöscht wird?!

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Markus schrieb:
> Nun kam aber die Anforderung auf, dass ein Neuzeichnen alle 20 ms
> erfolgen soll

Und was soll das bringen? Wer soll das noch visuell erfassen können? 
Ändern sich die dargestellten Daten so hektisch, oder geht es nur um 
einen reinen Performance-Test, wie schnell die Zeichenroutinen sind?

Üblicherweise werden Displays mit 60 Hz Auffrischungsrate angesteuert - 
es gibt Ausnahmen (alte Röhrenkübel arbeiten humanerweise mit höheren 
Bildwechselfrequenzen, da sie sonst flackern, und in manchen 
Kinderzimmern finden sich schnellere Displays am Spiel-PC), aber ca. 60 
Hz sind sehr weit verbreitet. Was also soll eine Änderung der 
Bildschirminhalte mit 50 Hz bringen?

Sinnvollerweise lässt man Bildelemente sich erst dann neu zeichnen, wenn 
es nötig ist, also wenn sich ihr Inhalt geändert hat.

: Bearbeitet durch User
von Markus (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Und was soll das bringen? Wer soll das noch visuell erfassen können?

Wie gesagt, es handelt sich um ein Projekt an der Uni. Ziel ist es halt, 
nur Teilbereiche neu zu zeichnen und nicht immer den kompletten Screen.

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Markus schrieb:
> Wie gesagt, es handelt sich um ein Projekt an der Uni.

Und dann ist es nicht nötig, über den Sinn von Vorgaben nachzudenken?

Früher haben wir anders studiert.

von temp (Gast)


Lesenswert?

Eventuell stört hier jemanden das Geflackere auf dem Schirm wenn er 20 
mal pro Sekunde zuerst gelöscht und dann neu beschrieben wird und dieser 
eine denkt, wenn er das alle 20ms macht ist das Flackern weg...

Selbst mit dieser brachialen Methode ist das trotzdem relativ 
flackerfrei hinzukriegen, allerding nur mit Doppelbufferung.

von Markus (Gast)


Lesenswert?

Rufus Τ. F. schrieb:
> Und dann ist es nicht nötig, über den Sinn von Vorgaben nachzudenken?

Ich habe hier ein 5" Display liegen und mit dem aktuellen Konzept klappt 
das auch prima. Nur soll mein Konzept später auf ein deutlich größeres 
Display übertragen werden, wo auch deutlich mehr Werte und Grafiken 
angezeigt werden. Daher ist bei dem neuem Display auch sinnvoll nur 
teilbereiche neu zu zeichnen. Das ist der Hintergrund.

von Sven B. (scummos)


Lesenswert?

temp schrieb:
> Eventuell stört hier jemanden das Geflackere auf dem Schirm wenn er 20
> mal pro Sekunde zuerst gelöscht und dann neu beschrieben wird und dieser
> eine denkt, wenn er das alle 20ms macht ist das Flackern weg...
>
> Selbst mit dieser brachialen Methode ist das trotzdem relativ
> flackerfrei hinzukriegen, allerding nur mit Doppelbufferung.

Das Qt Malsystem funktioniert nicht so, dass das flackern könnte. Da 
musst du schon was sehr komisches machen um das überhaupt zu erreichen.

Eine konstante Repaint-Rate ist unsinnig. Repaints sollten immer nur für 
Teilbereiche passieren wenn die tatsächlich neue Daten darstellen 
müssen. Du kannst an update() ein rect übergeben und nur das neu 
zeichnen in deinem paintEvent().

: Bearbeitet durch User
von schotter (Gast)


Lesenswert?

Sven B. schrieb:
> Du kannst an update() ein rect übergeben und nur das neu
> zeichnen in deinem paintEvent().

Scheint nur ein Vorschlag zu sein.

http://stackoverflow.com/questions/11707768/trying-to-update-only-a-rectangle-in-a-widget-in-qt-but-the-entire-widgets-are

von Sven B. (scummos)


Lesenswert?

schotter schrieb:
> Sven B. schrieb:
>> Du kannst an update() ein rect übergeben und nur das neu
>> zeichnen in deinem paintEvent().
>
> Scheint nur ein Vorschlag zu sein.
>
> 
http://stackoverflow.com/questions/11707768/trying-to-update-only-a-rectangle-in-a-widget-in-qt-but-the-entire-widgets-are

Wie da im Kommentar beschrieben m.E. ein Bug an der Stelle. Es ist nur 
ein Hinweis, ja, aber wenn du das paintEvent() auch selber 
implementierst muss es möglich sein deinem eigenen Hinweis zu folgen.

von Mark B. (markbrandis)


Lesenswert?

Markus schrieb:
> Daher ist bei dem neuem Display auch sinnvoll nur teilbereiche neu zu
> zeichnen. Das ist der Hintergrund.

Es ist immer sinnvoll, nur das neu zu zeichnen was sich auch geändert 
hat. Daraus ergibt sich aber noch nicht die Notwendigkeit dies alle 20 
Millisekunden zu tun.

von Markus (Gast)


Lesenswert?

Mark B. schrieb:
> Es ist immer sinnvoll, nur das neu zu zeichnen was sich auch geändert
> hat. Daraus ergibt sich aber noch nicht die Notwendigkeit dies alle 20
> Millisekunden zu tun.

Ok dann war die Zeitangabe quatsch. Wie gesagt es geht darum nur 
Teilbereiche neu zu zeichnen und nicht immer die ganze Seite.

Schon einmal vielen dank für die bisherigen Hinweise!

von Markus (Gast)


Lesenswert?

Vielen Dank für eure Beiträge. Das mit dem teilweise Neuzeichnen klappt 
echt gut, aber jetzt muss ich mir noch ein gescheites Handling 
überlegen. Aktuelle vergleiche ich den gezeichneten Wert mit dem neuen 
Wert und wenn ein Unterschied vorliegt, dann wird neu gezeichnet. Recht 
primitiv und die Region, die neu gezeichnet werden soll gebe ich noch 
händisch im Code an. Mal schauen, wie ich das automatisiere.

von PittyJ (Gast)


Lesenswert?

Alle 20ms neuzeichnen? Das ist doch ein geflacker und ein Anwender sieht 
nichts.
Einmal die Sekunde, das reicht für Menschen vollkommen aus.

Weiterhin könnte man schauen, was sich geändert hat, und dann nur die 
geänderten Informationen neu Ausgeben. Das spart Rechenzeit.

von Sven B. (scummos)


Lesenswert?

PittyJ schrieb:
> Alle 20ms neuzeichnen? Das ist doch ein geflacker und ein Anwender sieht
> nichts.
> Einmal die Sekunde, das reicht für Menschen vollkommen aus.

Das ist doch für einen Graphen nun wirklich unsinnig. Dein Oszilloskop 
zeichnet doch seinen Bildschirm auch nicht nur einmal die Sekunde neu, 
da würde man ja wahnsinnig werden.

von Rolf M. (rmagnus)


Lesenswert?

Markus schrieb:
> Vielen Dank für eure Beiträge. Das mit dem teilweise Neuzeichnen klappt
> echt gut, aber jetzt muss ich mir noch ein gescheites Handling
> überlegen. Aktuelle vergleiche ich den gezeichneten Wert mit dem neuen
> Wert und wenn ein Unterschied vorliegt, dann wird neu gezeichnet. Recht
> primitiv und die Region, die neu gezeichnet werden soll gebe ich noch
> händisch im Code an. Mal schauen, wie ich das automatisiere.

Ich würde es für sinnvoller erachten, die einzelnen Elemente zu eigenen 
Widgets zu machen. Das ist dann eine sauberere Aufteilung. Und wenn sich 
ein Wert geändert hat, kriegt das Widget ein Signal mit dem neuen Wert 
und zeichnet sich daraufhin neu.

Sven B. schrieb:
> PittyJ schrieb:
>> Alle 20ms neuzeichnen? Das ist doch ein geflacker und ein Anwender sieht
>> nichts.
>> Einmal die Sekunde, das reicht für Menschen vollkommen aus.
>
> Das ist doch für einen Graphen nun wirklich unsinnig. Dein Oszilloskop
> zeichnet doch seinen Bildschirm auch nicht nur einmal die Sekunde neu,
> da würde man ja wahnsinnig werden.

Und auch der Zeiger vom Tacho ändert die Position der Nadel mehr als 
einmal pro Sekunde, auch wenn er auf einem Display dargestellt wird.

von Bernd K. (prof7bit)


Lesenswert?

Es gibt doch garantiert auch für Qt eine empfohlene Vorgehensweise und 
Codebeispiele wie man owner-drawn Controls implementiert, was wann 
welche Signale auslöst und welche Methoden man implementieren oder auf 
welche Signale man reagieren muss (und wie man das macht).

Üblicherweise (bei allen GUI Toolkits die ich kenne) ist das doch so daß 
solange sich nichts ändert wird auch kein Paint Event ausgelöst und wenn 
sich was ändert wird irgendwo tief im Framework ein Flag gesetzt bzw. 
ein rechteckiger Bereich für ungültig erklärt. Wenn dann die Event-Loop 
das nächste Mal Zeit hat ruft diese die Paint-methode auf und die darf 
dann anhand der Rechteckkoordinaten entscheiden was neu gemalt werden 
muss und das dann neu malen.

Und damit man nicht jedesmal mühevoll aus den Rechteckkoordinaten 
entscheiden muss welcher Bereich das sein soll implementiert man am 
besten jeden unabhängig zu ändernden Teil als eigenes Widget mit 
eigener Paint Methode, dann wird nur das geänderte Widget invalidiert, 
die anderen nicht, und nur die jeweils zuständige Paint methode wird 
aufgerufen und man spart sich das Rumgefummel mit den Rechtecken.

Aber es ist stets die Eventloop die entscheidet wann das geschieht, 
niemals die Anwendung. Die Anwendung kann nur InvalidateRect() - je nach 
Tooolkit auch andere Namen, bei Qt heißt sie update() - aufrufen, das 
kann sie auch tausend Mal in der Sekunde machen wenn sie lustig ist, 
jedoch neu gezeichnet wird nur dann wenn die Event-Loop Zeit hat.

Ich bin mir sicher daß das bei Qt im wesentlichen im gleichen Sinne 
funktioniert.

https://doc.qt.io/qt-5/qwidget.html#update

Für das Beispiel mit dem Tacho würdest Du also den neuen Wert in eine 
Variable schreiben und dann das ganze Tacho-Widget mit update() 
invalidieren. Das malt noch nichts und kehrt sofort zurück, das kannst 
Du zigtausend Mal in der Sekunde machen, das setzt nur ein Flag. Die 
Eventloop sorgt dann dafür dass bei der nächtstmöglichen Gelegenheit die 
paintEvent() methode des Tachos aufgerufen wird, in der sollst Du dann 
den Zeiger neu malen, basierend auf dem Wert der gerade in der 
entsprechenden Variablen steht.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.