Label friert ein

Python und das Qt-Toolkit, erstellen von GUIs mittels des Qt-Designers.
Antworten
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

Hallo,

wir haben mithilfe von pyqt4 eine ui-File erstellt, auf dem sich unter anderem auch ein Label befindet (erstellt mit QLabel)"
Nun haben wir ein Pythonprogramm, in dem wir das ui-File laden, um darauf Daten zu visualisieren.
In besagtem Label wollen wir eine Anzeige für Gradzahlen machen dies machen wir in der Funktion:
wobei self._widget das UI-File ist

Code: Alles auswählen

def setLabel(self, d):
    str = "Degree: %s"%d
    self._widget.degree_label.setText(str)
die Gradzahl wird etwa alle 0.1 Sekunden neu gesetzt

das Problem:
nach einer gewissen (unregelmäßigen) Zeit (5 - 45 Minuten) friert das Label ein
und die Anzeige verändert sich nicht mehr

woran könnte dies liegen?

Vielen Dank im Voraus =)
Zuletzt geändert von Anonymous am Samstag 9. Juni 2012, 17:05, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Code-Tags gesetzt.
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo,

setzt ihr den Wert des Labels aus einem anderen Thread? Ohne entrspchende Maßnahmen kann es da zu solchen Problemen führen.

Sebastian
Das Leben ist wie ein Tennisball.
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

Ja, dass ist ein guter Hinweis die Grad-Änderungen werden mit einem eigenen Thread beobachtet,
der bei Änderungen diese Funktion aufruft.

Wir werden uns daran setzten dieses Problem zu fixen und anschliessend noch Rückmeldung geben ob es funktioniert hat.

Vielen Dank =)
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Du musst die Threading Technologien von Qt nutzen. Hier ein Beispiel dazu.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

Vielen dank, dass scheint das Problem behoben zu haben
wir machen den Labelupdate jetzt ueber einen QTimer

Code: Alles auswählen

# In der Init-Methode einen QTimer erstellen:
        # Der Text, der in das Label geschrieben werden soll:
        self.degree_text = "no data"
        # Einen Timer erstellen, der automatisch alle 0.1 sekunden das Label updated:
        self.timer = QTimer(self)
        self.connect(self.timer, SIGNAL('timeout()'), self.update_label)
        # den Timer mit der Verzögerung von 100 ms starten
        self.timer.start(100)
(...)
    # Die Labelbeschriftung setzen
    def setLabel(self, d):
        self.degree_text = "Degree: %s"%d

    # Die Labelbeschriftung in das Label schreiben
    def update_label(self):
        self._widget.degree_label.setText(self.degree_text)

Vielen Dank fuer die schnelle Hilfe und die Tipps

// Edit:
jetzt ohne global,
sollte man es so machen?
Zuletzt geändert von tron46 am Sonntag 10. Juni 2012, 13:29, insgesamt 1-mal geändert.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

Noch ein Tipp: Vergesst `global`! Das ist in 99,9% aller Fälle böse.
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
lunar

@tron64: Wieso verwendet ihr nicht "QThread", und löst ein Signal aus, um das Label zu aktualisieren? Das ist eleganter und effizienter als Polling mit "QTimer".
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

lunar hat geschrieben:@tron64: Wieso verwendet ihr nicht "QThread", und löst ein Signal aus, um das Label zu aktualisieren? Das ist eleganter und effizienter als Polling mit "QTimer".
Die Degreeänderungen generieren wir nicht selbst, sondern wir bekommen sie übergeben
(eine Funktion wird automatisch aufgerufen wenn sich die Daten ändern)

Ich bin mir nicht sicher wie QThread funktioniert, kann man in QThread einfach ein Signal erstellen
und es immer passend aufrufen oder muss man mit einer Endlosschleife arbeiten


Ich würde die QThread Klasse jetzt so schreiben:

Code: Alles auswählen

class update_thread(QThread):
    text = "no data"
    def __init__(self, degree_label, parent=None):
        QThread.__init__(self, parent)
        self.label = degree_label
        self._run_semaphore = QSemaphore(1)
        
    def run(self):
        while self._run_semaphore.available() > 0:
            self.msleep(100)
            self.label.setText(self.text)

    def setText(self, degree_text):
        self.text = degree_text

    def stop(self):
        self._run_semaphore.acquire(1)
Und dann eben immer setText mit dem richtigen Text aufrufen,
allerdings scheint mir diese Variante nicht effizienter als mit QTimer
ich würde ja das gleiche tun es nur in einer Extra Thread Klasse auslagern

übersehe ich eine Möglichkeit die QThread bietet?
deets

Zuerstmal ist das glaube ich schlicht genauso falsch wie der urspruengliche timer-lose Ansatz. Wenn musst du in deinem Hintergrund-Thread ein *Signal* schicken an den GUI-Thread. Da sorgt dann Qt dafuer, dass das ohne Probleme passiert.

Der Thread sollte dann auch einfach auf die Semaphore blockierend warten, dann kommst du ohne diesen Verzug von 100ms daher. Stellt sich natuerlich die Frage wie die eigentlich gesetzt wird.
lunar

@tron46: Es wird also ein Callback aufgerufen, wenn sich die Gradzahl ändert? Diese nicht ganz unwichtige Information hätte im ersten Beitrag stehen sollen…

Ihr braucht in diesem Fall nämlich weder Thread noch Timer. Stattdessen reicht es, einfach im Callback ein Signal auszulösen. Dazu müsst ihr eine Klasse erstellen, die von "QObject" erbt und ein Signal für die Aktualisierung der Gradzahl hat. Als Callback verwendet ihr dann eine Methode dieser Klasse an. In dieser Methode löst ihr das Signal mit dem neuen Wert für die Gradzahl aus. Dieses Signal verbindet ihr mit einer Methode, die den Zahlenwert als Text formatiert und dem Label zuweist.

Das ist eigentlich die einfachste und offensichtlichste Lösung.
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

@lunar tut mir leid ich bin neu in Python und QT

wir verwenden ROS

http://www.ros.org/wiki/

und haben einen Subscriber geschrieben, der wie du richtig erkannt hast einen Callback aufruft,
wenn sich die entsprechenden Daten ändern

habe ich das richtig verstanden, dass es durch die Signale automatisch richtig syncronisiert wird?

Code: Alles auswählen

# die updater Klasse, die die Callback methode hat und ein Signal mit der
# Gradzahl erstellt
class degreeLabelUpdater(QObject):
    dataUpdated = pyqtSignal(object)
    def _updateWindDirection(self, data):
        self.dataUpdated.emit(data.angle)


# in meiner HauptKlasse:
    def __init__(self, context):
    (...)
# In der Init methode erstelle eine ein Instanz dieser Callback-Klasse
        self.degreeLabelUpdater = degreeLabelUpdater()
# verbinde die Callback(_updateWindDirection) Methode so das sie aufgerufen wird, wenn sie die Daten ändern
        self._windDirectionSubscriber = rospy.Subscriber('wind', Winddirection, self.degreeLabelUpdater._updateWindDirection)
# dann verbinde ich das Signal aus der Callbackmethode mit der setlabel Methode
        self.degreeLabelUpdater.dataUpdated.connect(self._updateDegreeLabel)

#und schließlich die set label methode, die das label setzt
    def _updateDegreeLabel(self, degree):
        text = "Degree: %s"%degree
        self._widget.degree_label.setText(text)
Funktioniert das so oder ist wieder ein Fehler drin?

Vielen Dank für die ausdauernde Hilfe =)

// Edit:
bessere Variabeln Benennung und Anpassung mit Lunar's Hinweisen
Zuletzt geändert von tron46 am Sonntag 10. Juni 2012, 23:36, insgesamt 2-mal geändert.
lunar

@tron46: Theoretisch funktioniert das so, praktisch musst Du das schon selbst ausprobieren :)

Ein paar Anmerkungen zum Quelltext: Nutze die moderen API für Signale und Slots, um schwierig zu findende Fehler (durch Tippfehler in Signalnamen usw.) zu vermeiden. Statt einen "updater" und einen "subscriber" zu definieren, würde ich persönlich eher eine Klasse "WindObserver" (oder so) definieren, die den "subscriber" verwaltet und das Signal entsprechend auslöst.

Oder – wenn an mehreren Stellen ein "Signal"-Aufsatz für einen Subscriber benötigt wird – einen "QtSubscriber" von "rospy.Subscriber" ableiten, der automatisch einen "callback" verwendet, der dann ein Signal auslöst. Dazu ein unvollständiges und ungetestetes Beispiel, Klassen und Modulnamen musst Du natürlich entsprechend anpassen.

Achte beim Programmieren auch ein bisschen auf die Namen, die Du so verwendest. "set_label", "subscriber" und "updater" sagen beispielsweise nichts darüber aus, welches Label mit welchem Text gesetzt wird, oder was "updater" eigentlich aktualisiert. Wenn Du Namen verwendest, die etwas über die eigentliche Funktion des Objekts aussagen, also beispielsweise "windSubscriber" oder "updateWindAngle" (statt "set_text"), dann erleichterst Du sowohl Dir selbst als auch Deinen Mitarbeiter die Arbeit ungemein.
tron46
User
Beiträge: 9
Registriert: Sonntag 27. Mai 2012, 18:34

Vielen Dank für die vielen hilfreichen Tipps und sehr schnellen Antworten

das hat uns sehr geholfen :)
Antworten