Update GUI mit Threads Problematik

Programmierung für GNOME und GTK+, GUI-Erstellung mit Glade.
Antworten
baer999
User
Beiträge: 3
Registriert: Sonntag 2. Februar 2014, 00:00

Hallo,

ich habe zurzeit ein kleines Problem beim Entwickeln einer GUI.

Sie soll nicht einfrieren, weshalb ich einen extra Thread erstellt habe. Dieser ist in einer eigenen Klassen und soll per EventHandling mit anderen Klassen kommunizieren können

Code: Alles auswählen

class myThread(multiprocessing.Process)

        def connect(self, event_name, callback):
                if self.callbacks is None:
                        self.callbacks = {}

                if event_name not in self.callbacks:
                        self.callbacks[event_name] = [callback]
                else:
                        self.callbacks[event_name].append(callback)

        def fire(self, event_name, data=None):
                if self.callbacks is not None and data in self.callbacks:
                        for callback in self.callbacks[data]:
                                callback(self)

        def run(self):
                       self.fire(self, "go")

,

Die Thread Klasse rufe ich aus dem der Klasse "dimm_gui" auf und binde das Ereignis "go" an die Funktion do_change_pbar => dort soll meine GTK ProgressBar mit einem neuen Wert aus einer Variable gefüllt werden (öffentliche Variable per sharedctypes):

Code: Alles auswählen

                myt = myThread()
                myt.connect("go", self.do_change_pbar)
                myt.start()

        def do_change_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
Mein Problem:
Das Update der Progressbar funktioniert nicht, warum kann ich mir nicht erklären, denn die Variable ist korrekt gefüllt und das "set_value" wird sauber ausgeführt. Aber die Oberfläche wird nicht entsprechend gäendert...

Kann sich das jemand erklären und kann mir einen Tipp geben?

Vielen Dank!
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

@baer999: so wie Du die Callbacks implementiert hast, werden sie im zweiten Thread ausgeführt. Das funktioniert bei GUIs aber so nicht. Dann hättest Du im Thread ja gleich set_value direkt aufrufen können.
Einrücktiefe sind 4 Leerzeichen.
Warum initialisierst Du »self.callbacks« erst in der »connect«-Methode? Ein »defaultdict« würde Dir das Leben deutlich einfacher machen (aber noch nicht funktionsfähig, dazu brauchst Du Queues):

Code: Alles auswählen

class myThread(multiprocessing.Process)
    def __init__(self):
        self.callbacks = defaultdict(list)

    def connect(self, event_name, callback):
        self.callbacks[event_name].append(callback)
 
    def fire(self, event_name, *data):
        for callback in self.callbacks.get(event_name, ()):
            callback(self, *data)
 
    def run(self):
        self.fire(self, "go")
BlackJack

@baer999: Ich würde an einer passenden Stelle statt eines direkten Aufrufs den Aufruf per `GObject.idle_add()` in die Gtk-Hauptschleife verlagern.
baer999
User
Beiträge: 3
Registriert: Sonntag 2. Februar 2014, 00:00

@Sirius3: Was muss ich da ändern bzgl. Queues - ich verstehe, dass der Code durch defaultdict deutlich einfacher und lesbarer wird, aber ich bekomme das nicht hin (Fehler: "first argument must be callable")

Code: Alles auswählen

                self.keys = {}
                self.callbacks = defaultdict(self.keys)
@BlackJack: Wie setze ich das mit dem idle_add um? Ich habe hier mein Bsp. aber es wird irgendwie nicht aufgerufen?

Code: Alles auswählen

        def update_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
                print "update_pbar"

        def do_change_pbar(self, data):
                self.pbar.set_value(int(self.s.value))
                gobject.idle_add(self.update_pbar, ())
Danke euch für die Hilfe, bin noch Pyhton Neuling, also nicht über die Unfähigkeit wundern ;-)
BlackJack

@baer999: `defaultdict` will als Argument ein aufrufbares Objekt welches immer dann aufgerufen wird wenn man einen Schlüssel abfragt für den es noch keinen Wert gibt und diesen Wert erzeugt. Das sollte doch so in der Dokumentation stehen. Was willst Du mit dem `self.keys` erreichen? Das kommt doch in dem Beispiel von Sirius3 auch gar nicht vor.

Beim zweiten Beispiel dürfte Zeile 6 ein Problem sein wenn das nicht vom GUI-Thread abgearbeitet wird.
baer999
User
Beiträge: 3
Registriert: Sonntag 2. Februar 2014, 00:00

Ja richtig, Zeile 6 ist Schwachsinn, aber mir geht es da um Zeile 7...

Wird per gobject.idle_add() nicht dem GUI Thread dieser Aufruf übergeben, damit auch tatsächlich die Progressbar einen Update erhält?

Klappt leider so bisher nicht...
BlackJack

@baer999: Die Frage ist ob Zeile 7 überhaupt ausgeführt wird solange es davor die Zeile 6 gibt.
Benutzeravatar
diesch
User
Beiträge: 80
Registriert: Dienstag 14. April 2009, 13:36
Wohnort: Brandenburg a.d. Havel
Kontaktdaten:

Ein einfaches Beispiel, wie du Process, Queue und GLib.idle_add zusammenbringen kannst:

Code: Alles auswählen

#!/usr/bin/env python3
#-*- coding: utf-8-*-

from gi.repository import Gtk, GLib
import multiprocessing, time

def do_something(queue):
    for i in range(100):
        queue.put(i)
        time.sleep(0.1)

class MyApp(object):
    def __init__(self):
        win = Gtk.Window()
        self.pbar = Gtk.ProgressBar()
        win.add(self.pbar)
        
        q = multiprocessing.Queue()
        process = multiprocessing.Process(target=do_something, args=(q,))
        GLib.idle_add(lambda *args: self.update_pbar(q.get()) or True)
        
        win.show_all()
        process.start()

    def update_pbar(self, i):
        f = i/100
        self.pbar.set_fraction(f)

app = MyApp()
Gtk.main()
http://www.florian-diesch.de
Antworten