QAbstractSlider u.a.: wie Loops vermeiden?

Verschiedenes zu Qt
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Woher weiß ich (bzw. weißt Du) denn, dass eine Qt-Eventloop eine Singlethreadapplikation ist?
Source/Doku/Debugger :wink: Wobei ich erst vorher gemerkt habe, dass du Python programmierst. Obwohl ich vermute, dass das nur ein Wrapper ist und daher der Programmfluss gleich ist, wäre es sinnvoll, dies gleich am Anfang zu erwähnen...
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.
Was sollen wir mit einem Beispiel, welche andere Events (resize) behandelt als in diesem Thread angesprochen (QSlider). Bei einem Problem beginnt man beim kleinsten funktionierenden Codeschnipsel. In deinem Fall wäre es also sinnvoll, einfach mal zu prüfen, ob die "valueChanged(..)" bei einem QSlider in der richtigen Reihenfolge ankommen... ohne irgendwelche andere Operationen.

Das Hauptproblem bleibt, dass die Scrollbars/Sliders auch Signale versenden, wenn sie programmseitig angesprochen werden.
Die Lösung dazu ist in diesem Thread schon lange gefallen :wink:
olebole
Beiträge: 38
Registriert: 20. März 2009 16:39

Beitrag von olebole »

solarix hat geschrieben: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.
Kann ich, ist aber wieder Python. Wäre das OK?
Was sollen wir mit einem Beispiel, welche andere Events (resize) behandelt als in diesem Thread angesprochen (QSlider).
Es zeigt einfach, dass die Annahme "Events werden in genau der Reihenfolge verarbeitet, in der sie kommen" falsch ist. In dem erwähnten Thread ist ja auch seit meinem Code-Posting Stille, sodass ich vermute, dass da keiner eine Idee hat. Und warum sollten resize-Events anders behandelt werden als Mousewheel-Events?
In deinem Fall wäre es also sinnvoll, einfach mal zu prüfen, ob die "valueChanged(..)" bei einem QSlider in der richtigen Reihenfolge ankommen... ohne irgendwelche andere Operationen.
Nur ein QSlider liefert natürlich die Events in der richtigen Reihenfolge.
Aber wenn die Events auch noch mein Diagramm anpassen und eine Scrollbar anpassen sollen, dann kommen sie teilweise tatsächlich nicht mehr in der gleichen Reihenfolge an.
Das Hauptproblem bleibt, dass die Scrollbars/Sliders auch Signale versenden, wenn sie programmseitig angesprochen werden.
Die Lösung dazu ist in diesem Thread schon lange gefallen :wink:
Huch? Habe ich das überlesen? (Vetos habe ich getestet, die gehen nicht zuverlässig -- wegen der erwähnten Asynchronität)
olebole
Beiträge: 38
Registriert: 20. März 2009 16:39

Beitrag von olebole »

OK, hier ist das Beispiel (in Python, aber Python ist nur ein Wrapper):

Code: Alles auswählen

import math
import sys

from PyQt4 import QtGui, QtCore
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure

class DiagramWidget(QtGui.QWidget):
    def __init__(self, parent):
        QtGui.QWidget.__init__(self, parent)
        layout = QtGui.QVBoxLayout(self)
        self.setLayout(layout)
        self.diagram = InnerDiagramWidget(self)
        self.slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        self.connect(self.slider, QtCore.SIGNAL('valueChanged(int)'),
                     self.slider_changed_event)
        layout.addWidget(self.diagram)
        layout.addWidget(self.slider)
            
    def slider_changed_event(self, x):
        print '**1**: slider event for', x 
        self.diagram.draw()

class InnerDiagramWidget(FigureCanvas):
    def __init__(self, parent):
        self.zoom = 1.0
        FigureCanvas.__init__(self, Figure())
        self.setParent(parent)

    def zoom_in(self, x):
        self.zoom *= x
        if (self.zoom > 1): self.zoom = 1
        new_slider_pos = int(-20 * math.log(self.zoom))
        print '**2**: set slider to', new_slider_pos
        self.parent().slider.setValue(new_slider_pos)
        print '**3**: slider done for', new_slider_pos
        self.draw()

    def wheelEvent(self, event):
        self.zoom_in(math.exp(event.delta()/(15.*80)))

a = QtGui.QApplication(sys.argv)
w = QtGui.QMainWindow()
w.setCentralWidget(DiagramWidget(w))
w.show()
a.exec_()
Wenn man das ablaufen lässt und dann schnell am Mausrad dreht, passiert folgendes:

Code: Alles auswählen

**2**: set slider to 2
**1**: slider event for 2
**2**: set slider to 4
**1**: slider event for 4
**2**: set slider to 10
**1**: slider event for 10     (*************)
**3**: slider done for 10
**3**: slider done for 4
**3**: slider done for 2
das heißt: das Drehem am Scrollrad erzeugt einen neuen Zoom, der dazu führt, dass der Slider auf "2" gesetzt wird. Während dieses Event noch verarbeitet wird (also noch bevor der entsprechende "slider done"-Printout aus Zeile **3** erfolgte, kommt bereits das nächste Mausrad-Event, in dessen Verlauf der Slider auf "4" gesetzt wird. Auch das wird noch verarbeitet, während schon das dritte Mausrad-Event eintrudelt, welches den Slider auf "10" setzt. Erst nachdem dieses komplett verarbeitet wurde, erfolgt der Rest der Verarbeitung für "4", und erst ganz zum Schluss wird die Verarbeitung des allerersten Events abgeschlossen.

Ich würde das "asynchrone Eventverarbeitung" nennen. An der im Printout markierten Stelle (********) müssen wenigsten 3 Threads am Wirken sein; für jedes der Events eines.

Und diese Asynchronität macht eben die Möglichkeit kaputt, einfach ein Veto einzuführen.
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Also so habe ich diese loop nicht:

Code: Alles auswählen

#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    for (int i = 0; i <= 100; i++)
        ui->comboBox->addItem(QString::number(i));

    connect(ui->horizontalSlider, SIGNAL(valueChanged(int)), this, SLOT(sliderevent(int)));
    connect(ui->comboBox,         SIGNAL(currentIndexChanged(QString)), this, SLOT(comboboxevent(QString)));
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::sliderevent(int newValue)
{
    ui->comboBox->blockSignals(true);
    ui->comboBox->setCurrentIndex(ui->comboBox->findText(QString::number(newValue)));
    ui->comboBox->blockSignals(false);
}

void MainWindow::comboboxevent(QString newValue)
{
    ui->horizontalSlider->blockSignals(true);
    ui->horizontalSlider->setValue(newValue.toInt());
    ui->horizontalSlider->blockSignals(false);
}
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Ich würde das "asynchrone Eventverarbeitung" nennen. An der im Printout markierten Stelle (********) müssen wenigsten 3 Threads am Wirken sein; für jedes der Events eines.
Das sind keine Threads, sondern das ist (wie bereits erwähnt) indirekt rekursiv. Ist doch schön zu erkennen: 2 - 4 - 10 - 10 - 4 - 2

einfach zu verhindern, wie Aue und ich erwähnt haben...
olebole
Beiträge: 38
Registriert: 20. März 2009 16:39

Beitrag von olebole »

solarix hat geschrieben:Das sind keine Threads, sondern das ist (wie bereits erwähnt) indirekt rekursiv. Ist doch schön zu erkennen: 2 - 4 - 10 - 10 - 4 - 2
Ich glaube, dass es hier ein Mißverständnis gab: ich meine mit "asynchron", dass die Eventauslösung asynchron erfolgt, dass also ein neues Event bereits verarbeitet wird, während das alte noch nicht fertig verarbeitet ist.

Du meintest dagegen (oder?), dass die Eventverarbeitung synchron ist, dass also weitere Events, die im Zuge eines Events ausgelöst werden, im gleichen Thread behandelt werden.

Was letztlich tatsächlich geholfen hat, ist ein "blockSignals()", was ich noch nicht kannte. So ganz definitiv korrekt ist es zwar immer noch nicht: wenn ein zweites Event in die Verarbeitung plauzt, kann sie das durcheinanderbringen:
  • 1. Event: widget1->blockSignals(true);
  • 1. Event: widget1->setValue(actualValue);
  • jetzt kommt das 2. Event (wenn es schnell genug war):
  • 2. Event: widget1->blockSignals(true);
  • inzwischen kommt wieder das erste an die Reihe...
  • 1. Event: widget1->blockSignals(false);
  • ... und das 1. Event ist verarbeitet. Beim zweiten jetzt:
  • 2. Event: widget1->setValue(actualValue); hier wird ein Signal ausgelöst, da blockSignal durch das 1. Event auf "false" gesetzt wurde.
  • 2. Event: widget1->blockSignals(false);
Vermutlich ist dieser Fall aber recht akademisch.

Danke auf jeden Fall für die Hilfe.
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

der ablauf ist doch anders den ich beschrieben hab!

Signal1 wird ausgelöst => dann Sperre ich Signale von2, setze den Wert von 2 und danach gebe ich diese wieder frei. Somit kann 2 in dem Fall kein Signal raushauen!
olebole
Beiträge: 38
Registriert: 20. März 2009 16:39

Beitrag von olebole »

AuE hat geschrieben:Signal1 wird ausgelöst => dann Sperre ich Signale von2, [...]
Was ich meine: Event1 und Event2 sind beide vom gleichen Typ, d.h. beides (z.B.) MouseWheelEvents. Und da kann es durchaus (theoretisch) passieren, dass das zweite die Blockade setzt, dann das erste die Blockade aufhebt und dann das zweite setValue() auf das dann nicht blockierte Widget ausführt.
iso8859-1
Beiträge: 25
Registriert: 8. März 2009 11:02

Beitrag von iso8859-1 »

hm - könnte da nicht einfach das mvc-paradigma weiterhelfen?

eine Model-Klasse mit einer setValue Methode. Diese Überprüft ob der neue Wert ungleich dem alten ist, wenn ja ändern und signal, sonst ist nix zu tun. Die Controls (V und C) reagieren auf das signal, passen ihre Darstellung an und da sie auch Controls sind, werden sie die setValue Methode noch einmal aufrufen, aber da der Wert schon gesetzt ist wird die Loop durch die Überprüfung gebrochen.
So haben alle Controls den richtigen Wert und man kann von allen Controls ändern ohne Loop.
upsala
Beiträge: 3946
Registriert: 5. Februar 2006 20:52
Wohnort: Landshut
Kontaktdaten:

Beitrag von upsala »

Wurde schon mal vorgeschlagen, will er nicht.
olebole
Beiträge: 38
Registriert: 20. März 2009 16:39

Beitrag von olebole »

upsala hat geschrieben:Wurde schon mal vorgeschlagen, will er nicht.
Geht nicht, da die QSlider intern eine Integer-Darstellung haben und dadurch Rundungsfehler auftreten.

Hinzu kommt, dass bereits während der Verarbeitung eines Signals ein neues eintreffen kann. Daher kann es durchaus sein, dass der QSlider das setValue() des Control erst aufruft, nachdem diesen bereits einen neuen Wert erhalten hat und ihn dann wieder zurücksetzt. Daher ist dadurch im Vergleich zur blockSignals()-Lösung nichts gewonnen.

blockSignals() funktioniert, mit Ausnahme des gezeigten (theoretischen) Falls.
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

olebole hat geschrieben:
solarix hat geschrieben:Das sind keine Threads, sondern das ist (wie bereits erwähnt) indirekt rekursiv. Ist doch schön zu erkennen: 2 - 4 - 10 - 10 - 4 - 2
Ich glaube, dass es hier ein Mißverständnis gab: ich meine mit "asynchron", dass die Eventauslösung asynchron erfolgt, dass also ein neues Event bereits verarbeitet wird, während das alte noch nicht fertig verarbeitet ist.

Du meintest dagegen (oder?), dass die Eventverarbeitung synchron ist, dass also weitere Events, die im Zuge eines Events ausgelöst werden, im gleichen Thread behandelt werden.
OK. Wenn du so davon überzeugt bist, dass es sich um Threads handelt, dann bitte block deine SLOTS mit ner QMutex. Wenn sich an dem Verhalten etwas ändert, weißt du dass es Threads sind, wenn nicht sind es keine Threads.
Geht nicht, da die QSlider intern eine Integer-Darstellung haben und dadurch Rundungsfehler auftreten.
Dann hast du ein Designproblem, wenn du Kontrollelemente mit verschiedenen Typen zulässt.
Hinzu kommt, dass bereits während der Verarbeitung eines Signals ein neues eintreffen kann. Daher kann es durchaus sein, dass der QSlider das setValue() des Control erst aufruft, nachdem diesen bereits einen neuen Wert erhalten hat und ihn dann wieder zurücksetzt. Daher ist dadurch im Vergleich zur blockSignals()-Lösung nichts gewonnen.
Dann hast du eine Programmierblockade :D
DU hast es in der Hand, dein GraphicsView zu koordinieren. DU hast die Connects in der Hand.

Ich würde dir als Design vorschlagen:
Gib doch deinem GV zwei SIGNALS:
scrolled(int x-val, int y-val);
zoomed(int val);

Jetzt connectest du jedes Control nur mit dem GV, und nicht die Controls auch noch untereinander.
Ein Control wird bedient. SLOT im GV wird aufgerufen. GV wird gezoomt/gescrollt. GV emittiert Signale entsprechend, alle Controls werden angepasst. Controls emittieren SIGNALs, werden aber abgewiesen, da die Werte denen des GV entsprechen. Da alles auf int basiert gibts auch keine Rundungsfehler...

Und um die ganze Sache noch abzurunden, packst du das ganze in ein eigenes Widget, damit du nicht bei jedem erstellen diesen ganzen HokusPokus betreiben musst. Dann ergibt sich eine vollkommen neue Option! Dein übergeordnetes Widget managed mit eigenen Signals/Slots die Connections, die GV und Controls sind nicht miteinander verbunden!
Tada, dann kannst du in einem eigenen SLOT die Slots der Controls und des GV direkt aufrufen (ohne connection), dann funktioniert ein bloskSignal vorher gar wunderbar.
Antworten