QAbstractSlider u.a.: wie Loops vermeiden?
QAbstractSlider u.a.: wie Loops vermeiden?
Hallo,
ich habe folgendes Problem: in meinem Programm gibt es verschiedene Möglichkeiten, eine Darstellung zu zoomen:
* ComboBox
* QSlider
die natürlich auch immer den aktuellen Zustand anzeigen sollen.
Die entsprechenden Signale (valueChanged(int) beim QSlider) sind verbunden mit Funktionen, die das andere Inputwidget updaten und dann den Zoom ausführen.
Das Problem hier ist: Wenn ich z.B. in der ComboBox einen Wert eintrage, dann wird das mittels Signal an diese Funktion übertragen, und diese passt den QSlider an. Das führt dann aber dazu, dass der QSlider ein valueChanged()-Signal emittiert und somit die damit verbundene Funktion ausführt. Diese trägt einen neuen Wert in die ComboBox ein (neu, weil er sich ja wegen der internen Darstellung als "int" im QSlider nicht exakt speicherbar war) -- was einerseits dazu führt, dass da ein anderer Wert drinsteht, als ursprünglich ausgewählt. Andererseits emittiert jetzt die QComboBox wieder ein "changed"-Signal, sodass wir hier eine potentielle Endlosschleife erhalten.
Irgendwie muss sieht das doch nach einem prinzipiellen Mißverständnis von Qt bei mir aus? Welchen Denkfehler habe ich hier? Wie kann ich das gesagte vermeiden?
Viele Grüße
olebole
ich habe folgendes Problem: in meinem Programm gibt es verschiedene Möglichkeiten, eine Darstellung zu zoomen:
* ComboBox
* QSlider
die natürlich auch immer den aktuellen Zustand anzeigen sollen.
Die entsprechenden Signale (valueChanged(int) beim QSlider) sind verbunden mit Funktionen, die das andere Inputwidget updaten und dann den Zoom ausführen.
Das Problem hier ist: Wenn ich z.B. in der ComboBox einen Wert eintrage, dann wird das mittels Signal an diese Funktion übertragen, und diese passt den QSlider an. Das führt dann aber dazu, dass der QSlider ein valueChanged()-Signal emittiert und somit die damit verbundene Funktion ausführt. Diese trägt einen neuen Wert in die ComboBox ein (neu, weil er sich ja wegen der internen Darstellung als "int" im QSlider nicht exakt speicherbar war) -- was einerseits dazu führt, dass da ein anderer Wert drinsteht, als ursprünglich ausgewählt. Andererseits emittiert jetzt die QComboBox wieder ein "changed"-Signal, sodass wir hier eine potentielle Endlosschleife erhalten.
Irgendwie muss sieht das doch nach einem prinzipiellen Mißverständnis von Qt bei mir aus? Welchen Denkfehler habe ich hier? Wie kann ich das gesagte vermeiden?
Viele Grüße
olebole
Schön wär's, aber das geht ebenfalls nicht: das sieht zwar sehr schön aus, wenn man den Slider direkt verschiebt, aber sobald man mit den Slider mittels Scrollrad verändert, verschiebt sich zwar der Slider, aber es kommt kein Event an.upsala hat geschrieben:Falsches Signal für deinen Einsatz, du suchst sliderMoved(int)
Auch wenn man die Position des Sliders durch Klicken links bzw. rechts davon verändere, kommt kein Event an.
Soo einfach ist das eben nicht.
Wie mache ich das möglichst robust?AuE hat geschrieben:alternativ kannst du auch signale blockieren!
Ein Problem ist ja, dass beim schnellen Verschieben möglicherweise mehrere Signale hintereinander kommen: während das erste valueChanged() noch verarbeitet wird, trudelt bereits das nächste ein. Die kann man nicht einfach ignorieren, weil dann eventuell die Endposition des Sliders nicht korrekt gesetzt wird.
Ergänzung: Komplizierter wird das noch dadurch, dass ich gerne um das Zentrum zoomen will. Das heißt, dass ich bei einem eingeblendetem Scrollbar während des Zoomens auch noch die Größe und die Position (!) des Scrollbars anpassen muss. Das führt zusätzlich zu (völlig unnötigen) Scroll-Events, die gemeinsam mit den Zoomevents die Anwendung recht zähflüssig machen.
Irgendwie habe ich das Gefühl, dass ich hier einen Designfehler habe. Aber wie löst man derartiges "richtig", also mit der Verarbeitung nur der nötigen Events? Ich bin doch sicher nicht der einzige, der das Problem hat, dass die programmseitige Anpassung von UI-Elementen zu unerwünschten Signalen führt?
Du hast recht, ich habe/hatte das gleiche Problem. Scrollen soll mittels Mausrad möglich sein, ebenso wie die direkte Betätigung des Sliders.olebole hat geschrieben: Irgendwie habe ich das Gefühl, dass ich hier einen Designfehler habe. Aber wie löst man derartiges "richtig", also mit der Verarbeitung nur der nötigen Events? Ich bin doch sicher nicht der einzige, der das Problem hat, dass die programmseitige Anpassung von UI-Elementen zu unerwünschten Signalen führt?
In dem einen Fall habe ich das so gelöst (bitte um allf. Nachsicht, ich rekonstruiere das jetzt aus dem Gedächtnis):
Zuerst das Mausrad einfangen:
Code: Alles auswählen
void wheelEvent(...)
{
...
/* bool */ ignoreZoomValueChanged = true;
slider->setValue(newValue);
...
}
Code: Alles auswählen
if (ignoreZoomValueChanged) {
ignoreZoomValueChanged = false;
return;
}
lg,
Christian
Naja, das Problem hier ist, dass das Mausrad schneller Events produzieren kann, als sie vom Slider verarbeitet werden, dass man also zuerst zwei Events vom Mausrad hat, und erst danach die beiden verarbeiteten Events vom Slider ankommen. Dann wird zwar das erste verschluckt, das zweite aber schon nicht mehr.der.grisu hat geschrieben:Zuerst das Mausrad einfangen: [...] und beim Abfangen des valueChanged-Signals:
Genauso kann es sein, dass der Slider eben doch kein Event produziert (weil es keine Veränderung im integer-Wert gab). Dann bleibt der Zustand auf "ignoreZoomValueChanged = true;" und das nächste Slider-Event wird verschluckt.
Hat keiner eine "saubere" Lösung dafür?
Blöde Frage: wie mache ich das?upsala hat geschrieben:Alle Signale auf einen Slot zusammenführen.
Anhang vom sender() ermitteln wer gesendet hat
Wirklich helfen wird das wohl nicht, da ja die Scrollbars wirklich einen etwas anderen Wert haben (ansonsten würden sie ja keine valueChanged()-Events aussenden, wenn man per setValue() einen setzt).dessen Wert auslesen. An alle diesen Wert verteilen, die einen anderen Wert haben.
Danke. Aber, wie gesagt, das hilft nicht recht weiter. Denn dem neuen Signal vom Slider ist ja nicht anzusehen, dass es aufgrund eines alten (z.B. vom Mausrad) erzeugt wurde.upsala hat geschrieben:QObject::sender()
Hat hier wirklich niemand eine Idee?
Das Problem wird inzwischen noch dadurch verschärft, dass Panning unterstützt werden soll, d.h. dass man durch Draggen mit der Maus direkt in die "Content Area" verschieben (scrollen) möchte.
d.h. fürs Scrolling sollten folgende Möglichkeiten vorhanden sein:
Sind meine Wünsche wirklich so exotisch?
Das Problem wird inzwischen noch dadurch verschärft, dass Panning unterstützt werden soll, d.h. dass man durch Draggen mit der Maus direkt in die "Content Area" verschieben (scrollen) möchte.
d.h. fürs Scrolling sollten folgende Möglichkeiten vorhanden sein:
- Scrolling per Scrollbar mit allen Möglichkeiten: direktes Verschieben, Tastatur, Klick links bzw. rechts neben dem Scrollbalken und auf die Pfeiltasten
- Scrolling per Mausrad über der Content Area
- Panning mit der Maus
- zusätzlich ändert zoom in/out die Breite der QScrollBar und die Lage des linken Randes (da das Zentrum konstant bleibt), das darf das kein Scrolling durch die ScrollBar verursachen
- Tasten für zoom-in und zoom-out
- Slider
- Ctrl + Mausrad
- Ctrl + Markieren eines Bereiches (zoom in markierten Bereich nach Loslassen)
Sind meine Wünsche wirklich so exotisch?
Falls ich dich richtig verstanden habe, hast du irgend einen aktuellen Wert (Zoomfaktor oder sonstwas) und einfach nur ein paar verschiedene Widgets, die diesen liefern können und auch anzeigen sollen. Worin besteht denn das Problem? Ich meine, wenn du einfach nur reagierst, wenn sich der Wert ändert, kann es dir doch egal sein, wie oft solche Signals kommen:olebole hat geschrieben: Sind meine Wünsche wirklich so exotisch?
Code: Alles auswählen
void foo::habeNeuenWert(int val)
{
if (actualValue != val) {
actualValue = val;
machWas(actualValue);
widget1->setValue(actualValue);
widget2->setValue(actualValue);
widget3->setValue(actualValue);
}
}
GUIs sind singelthreaded.. während der Ausführung eines Slots können keine weiteren Signals generiert werden.. Es können sehr viele Events in eine Queue gespeichert werden.. das ja, aber es können sich keine Events "überholen" oder so.Ein Problem ist ja, dass beim schnellen Verschieben möglicherweise mehrere Signale hintereinander kommen: während das erste valueChanged() noch verarbeitet wird
Was Aue dachte ist, dass du vor einem "setValue()" die Signals dieses Widgets blockiest, und daher keine weiteren emitiert werden:
Code: Alles auswählen
widget1->blockSignals(true);
widget1->setValue(actualValue);
widget1->blockSignals(false);
Das Problem ist, dass die einzelnen Widgets (insbesondere die QScrollBar) auch dann ein Signal schicken, wenn man den Wert programmseitig ändert. Und da die Scrollbars intern nur eine Integer-Darstellung der Position verwenden, stimmen die gesendeten Werte der Signale i.allg. auch nicht überein, sondern differieren ein wenig (aber sichtbar).
Konkret verschickt die QScrollBar auch dann ein valueChanged-Event, wenn ich setValue() mit einem neuen Wert aufrufe:
Edit: auch in dem hier gezeigten Fall kommen die Events asynchron. Wenn ich (wie von Dir vorgeschlagen) ein Veto einbaue und z.B. den Zoom-Slider schnell bewege, dann kommt das letzte Zoom-Event des Sliders eventuell schon an, während er noch an der Verarbeitung eines vorherigen Events ist, was dazu führt, dass ein falscher Zoom angezeigt wird. Das kann man letztlich auch deutlich sehen, wenn man sich (wie in meinem anderen Beitrag gezeigt) die einzelnen Signale ausprinten lässt.
Konkret verschickt die QScrollBar auch dann ein valueChanged-Event, wenn ich setValue() mit einem neuen Wert aufrufe:
- Man dreht am Mausrad mit gedrückter Ctrl-Taste
- es werden Zoom-Events generiert, die die Darstellung anpassen (OK)
- diese ändern u.a. auch Größe und Position des Scrollbalkens (*).
- und sie ändern die Position des Zoom-Sliders (#)
- die Änderung der Position des Scrollbalkens (*) löst ein valueChanged()-Event aus, der eine erneute Änderung der Darstellung zur Folge hat (schlecht)
- sie Änderung der Position des Zoom-Sliders (#) löst ein weiteres Zoom-Event aus, welches eine erneute Änderung der Darstellung zur Folge hat (schlecht)
- er führt außerdem zu einer Anpassung von Position und Größe des Scrollbalkens (**)
- die erneute Änderung der Position des Scrollbalkens (**) löst ein valueChanged()-Event aus, der wiederum eine Änderung der Darstellung zur Folge hat (schlecht)
Ist diese Eigenschaft Teil der API? In http://www.qtforum.de/forum/viewtopic.php?t=9196 hatte ich ja beschrieben, wie bei [Py]Qt4 die Reihenfolge durchaus durcheinanderkommt und mit dort das Resizing von Fenstern kaputt macht...GUIs sind singelthreaded.. während der Ausführung eines Slots können keine weiteren Signals generiert werden..
Edit: auch in dem hier gezeigten Fall kommen die Events asynchron. Wenn ich (wie von Dir vorgeschlagen) ein Veto einbaue und z.B. den Zoom-Slider schnell bewege, dann kommt das letzte Zoom-Event des Sliders eventuell schon an, während er noch an der Verarbeitung eines vorherigen Events ist, was dazu führt, dass ein falscher Zoom angezeigt wird. Das kann man letztlich auch deutlich sehen, wenn man sich (wie in meinem anderen Beitrag gezeigt) die einzelnen Signale ausprinten lässt.
Nein... das ist eine Eigenschaft einer Singlethreadapplikation. Wenn eine Applikation was tut, kann sie nichts anderes tun. Events koennen (vor der Ausfuehrung eines Slots) gesammelt werden, das schon.. Methoden koennen auch rekursiv aufgerufen werden (bei Qt z.B. bei einer DirectConnection). Aber _nicht_ "asynchron".Ist diese Eigenschaft Teil der API?
Code: Alles auswählen
Das kann man letztlich auch deutlich sehen, wenn man sich (wie in meinem anderen Beitrag gezeigt) die einzelnen Signale ausprinten lässt.
Woher weiß ich (bzw. weißt Du) denn, dass eine Qt-Eventloop eine Singlethreadapplikation ist?solarix hat geschrieben:Nein... das ist eine Eigenschaft einer Singlethreadapplikation.Ist diese Eigenschaft Teil der API?
Dem widerspricht doch aber das gezeigte Beispiel: Layoutmanager, zwei Widgets (1. Widget: langsames Diagram; 2. Widget: QScrollBar). Resize-Event, ausgelöst duch Größenänderung mit der Maus. Reihenfolge:Aber _nicht_ "asynchron".
- 1. Resize-Event an 1. Widget
- 2. Resize-Event an 1. Widget
- 2. Resize-Event an 2. Widget
- 1. Resize-Event an 2. Widget
Das ist eindeutig nicht synchron. Und es ist nur zu erklären, wenn man annimmt, dass es mehr als einen eventauslösenden Thread gibt.
Ich habe einfach [Python] "print" verwendet. Und da kommt es vor, dass diese Reihenfolge nicht stimmt. Das hängt aber davon ab, ob die anderen Widgets (insbesondere das Update des Diagramms) "langsam genug" sind, dass sich Events "überholen" können.Versuch mal, im Slot des Sliders einfach nur den aktuellen Wert mittels qDebug() auszugeben. Ohne irgendwelchen weiteren Code. Bekommst du da wirklich Zahlenfolgen, welche _nicht_ 100% auf- oder absteigend (also z.B. 0-1-2-3-10-5-20) sind?
Wie schon geschrieben: das kürzeste (und IMO instruktivste) Beispiel ist incl. Code in dem anderen Thread zu sehen; ich kann ihn ja noch mit dem Printout der Eventreihenfolge versehen. Hier ist das ein Nebenproblem, welches aber beachtet werden möchte, wenn ich keine Darstellungsfehler will.
Das Hauptproblem bleibt, dass die Scrollbars/Sliders auch Signale versenden, wenn sie programmseitig angesprochen werden.