Thread verschlingt Frame

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Shiroi
User
Beiträge: 7
Registriert: Samstag 23. Juni 2007, 09:50

Mittwoch 8. Oktober 2008, 16:05

Hallo,

ich habe da ein Problem mit meinem Thread.
Wenn man auf den Button "Modales Fenster anzeigen" klickt, öffnet sich für einen kurzen Moment der Frame, bevor er irgendwo in den Hintergrund verschwindet und nicht mehr sichtbar ist.

(Das hier hab ich mir aus Beispielen aus dem Forum zusammen gesucht, mein orginal Programm wäre zu goß und das kleine Beispiel tut es ja auch schon)

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx
import threading

wx.SetDefaultPyEncoding("iso-8859-15")

class MyModalFrame(wx.Frame):
   
    def __init__(self, parent, title = "Modal Frame", size = wx.Size(500, 600)):
        wx.Frame.__init__(self, parent, -1, title, size = size)
        panel = wx.Panel(self)
        panel.SetBackgroundColour("green")
        self.MakeModal(True)
        self.Bind(wx.EVT_CLOSE, self.on_close)
   
   
    def on_close(self, event = None):
        self.MakeModal(False)
        event.Skip()
        
class MyThread(threading.Thread):
    
    def __init__(self, parent):
        threading.Thread.__init__(self)
        self.__parent = parent
     
        
    def run(self):
        self.modal_frame = MyModalFrame(self.__parent)
        self.modal_frame.Show()
        
class MyMainFrame(wx.Frame):
   
    def __init__(
        self, parent = None, title = "Example", size = wx.Size(350, 220)
    ):
        wx.Frame.__init__(self, parent, -1, title, size = size)
       
        panel = wx.Panel(self)
       
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
       
        vbox_main.AddStretchSpacer()
       
        btn_modalframe = wx.Button(panel, label = u"Modales Frame anzeigen")
        btn_modalframe.Bind(wx.EVT_BUTTON, self.show_modal_frame)
        vbox_main.Add(btn_modalframe, 0, wx.CENTER)
       
        vbox_main.AddStretchSpacer()
   
   
    def show_modal_frame(self, event = None):
        t = MyThread(self)
        t.start()


def main():
    """Testing"""
    app = wx.PySimpleApp()
    f = MyMainFrame()
    f.Center()
    f.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()
Kann mir einer erklären was da genau schief läuft und wie man es beheben kann?
Flano
User
Beiträge: 43
Registriert: Sonntag 5. September 2004, 14:13

Mittwoch 8. Oktober 2008, 18:36

Hallo Shiroi,
habe das ganze nur überflogen. Bei mir funktioniert es wenn ich folgendes ändere:

Code: Alles auswählen

def show_modal_frame(self, event = None):
        t = MyThread(self)
        t.run()  # statt t.start()
Gruss Flano
Leonidas
Administrator
Beiträge: 16024
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Mittwoch 8. Oktober 2008, 20:05

Flano hat geschrieben:Bei mir funktioniert es wenn ich folgendes ändere:
Natürlich funktioniert das, weil du damit keinen neuen Thread startest, sondern den Code der im neuen Thread laufen sollte, im aktuellen Thread ausführst. In ``run()`` wird der auszuführende Code reingeschrieben, mit ``start()`` wird dieser Code in einem neuen Thread gestartet.
My god, it's full of CARs! | Leonidasvoice vs Modvoice
Shiroi
User
Beiträge: 7
Registriert: Samstag 23. Juni 2007, 09:50

Donnerstag 9. Oktober 2008, 08:18

Das mag wx leider auch nicht...

Code: Alles auswählen

class MyModalFrame(wx.Frame):
   
    def __init__(self, parent, title = "Modal Frame", size = wx.Size(500, 600)):
        wx.Frame.__init__(self, parent, -1, title, size = size)
        panel = wx.Panel(self)
        panel.SetBackgroundColour("green")
        self.MakeModal(True)
        self.Bind(wx.EVT_CLOSE, self.on_close)
   
    def on_close(self, event = None):
        self.MakeModal(False)
        event.Skip()


class MyApp(wx.PySimpleApp):

    def OnInit(self):
        # MyFrame anzeigen
        self.myframe = MyModalFrame(None)
        self.myframe.Center()
        self.myframe.Show()

        # MainLoop
        self.MainLoop()
        return True
        
        
class MyThread(threading.Thread):
    
    def __init__(self, parent):
        threading.Thread.__init__(self)
        self.__parent = parent
        
    def run(self):
        app = MyApp()

... 
Meine Idee war, da ja der Frame in einem eigenen Thread läuft, dass der Thread eine eigene wx.App benötigt. Aber anscheinend mag das wx nicht, wenn man 2 Applications am laufen hat und eine davon ihren eigenen Thread hat... denn wenn ich die zweite Application im selben Thread starte funktioniert es.

Code: Alles auswählen

...

    def show_modal_frame(self, event = None):
#        t = MyThread(None)
#        t.start()
        app = MyApp()

...
Mir gehen jetzt echt die Ideen aus.... weiß jemand Rat?
lunar

Donnerstag 9. Oktober 2008, 08:26

Auf Threads verzichten? Kein GUI-Toolkit verträgt Zugriffe auf GUI-Elemente außerhalb des Hauptthreads, wx ist da keine Ausnahme.
Shiroi
User
Beiträge: 7
Registriert: Samstag 23. Juni 2007, 09:50

Donnerstag 9. Oktober 2008, 08:45

Das kann ich leider nicht, da im Thread etwas automatisch abgearbeitet wird, aber ab und zu Benutzerinteraktionen benötigt (der Frame wird dann durch ein Dialog ersetzt)
Wie kann man so etwas mit wx realisieren?
lunar

Donnerstag 9. Oktober 2008, 08:52

GUI-Zugriffe dürfen nun mal nur aus dem Hauptthread heraus erfolgen. Wenn du also einen Worker-Thread hast, der Interaktion benötigt, dann musst du Haupt- und Worker-Thread entweder in irgendeiner Weise kommunizieren lassen, so dass der Hauptthread die GUI-Interaktion abwickelt, oder du unterteilst den Worker in mehrere kleine Schritte, so dass er die Zwischenergebnisse wieder an den Hauptthread zurückgibt, dieser die Interaktion durchführt, und dann einen neuen Thread mit dem alten Zwischenergebnis und den neuen Parametern startet.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 9. Oktober 2008, 09:00

Hallo Shiroi!

Also, ich möchte das noch ein wenig erklären. Eine wxPython-Anwendung läuft in einem Thread -- meistens im Hauptthread. In diesem Thread wird im Hintergrund die "MainLoop" abgearbeitet. Diese kümmert sich darum, dass Events abgearbeitet werden und das Programm grafisch neu aufgebaut wird, wenn es mal kurz von einem anderen Fenster überdeckt wurde.

Innerhalb dieses Threads darfst du direkt auf die GUI-Elemente wie z.B. ein Frame oder einen Button zugreifen. Was du nicht machen darfst, ist von einem anderen Thread aus, **direkt** auf diese GUI-Elemente zuzugreifen. Das bedeutet auch, dass du nicht einfach die APP auseinanderreißen und einen Teil davon in einem anderen Thread laufen lassen kannst.

Normalerweise lässt man wxPython, also die GUI, im Hauptthread laufen. Und wenn "Arbeit" zu erledigen ist, dann lagert man diese in einen anderen Thread aus. Mit Arbeit meine ich jetzt z.B. das Abarbeiten von Listen oder das Warten auf Daten und nicht die Anzeige von Daten. Das Anzeigen muss im GUI-Thread erfolgen.

Wenn du von einem anderen Thread aus auf die GUI-Elemente des anderen Threads zugreifen möchtest, dann musst du das **threadsicher** erledigen. Du kannst z.B. einen wxPython-Event auslösen, oder du verwendest den Befehl wx.CallAfter, der das für dich im Hintergrund erledigt.

http://www.python-forum.de/topic-5757.html

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 9. Oktober 2008, 09:05

lunar hat geschrieben:dann musst du Haupt- und Worker-Thread entweder in irgendeiner Weise kommunizieren lassen, so dass der Hauptthread die GUI-Interaktion abwickelt
Genau! Und für diese Kommunikation zwischen den Threads kannst du wx.CallAfter verwenden. Aber was du nicht machen kannst, ist im Worker-Thread auf das Ergebnis warten. ``dialog.ShowModal`` wartet zwar im GUI-Thread, aber nicht im Worker-Thread. Deshalb musst du z.B. mit ``threading.Event`` arbeiten, um den Worker-Thread an der Stelle warten zu lassen, bis im GUI-Thread der Dialog wieder geschlossen wurde.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Donnerstag 9. Oktober 2008, 09:23

Beispiel:

Code: Alles auswählen

#!/usr/bin/env python
# -*- coding: iso-8859-15 -*-

import wx
import threading
import time

wx.SetDefaultPyEncoding("iso-8859-15")


class MyWorker(threading.Thread):
    
    def __init__(self, dialog_function):
        threading.Thread.__init__(self)
        self.cancel = threading.Event()
        self.dialog_finished = threading.Event()
        self.dialog_function = dialog_function
        self.dialog_retval = None
    
    
    def run(self):
        while True:
            if self.cancel.isSet():
                break
            time.sleep(5) # arbeiten
            if self.cancel.isSet():
                break
            self.dialog_finished.clear()
            wx.CallAfter(self.dialog_function, "Hallo Welt")
            self.dialog_finished.wait()
            print self.dialog_retval
        print "Thread beendet"
    
    
    def stop(self):
        self.dialog_finished.set()
        self.cancel.set()


class MyFrame(wx.Frame):
    
    def __init__(
        self, parent = None, title = "Example", size = wx.Size(550, 420)
    ):
        wx.Frame.__init__(self, parent, -1, title, size = size)
        
        panel = wx.Panel(self)
        
        vbox_main = wx.BoxSizer(wx.VERTICAL)
        panel.SetSizer(vbox_main)
        
        self.worker = MyWorker(self.show_irgendeinen_dialog)
        self.worker.start()
        
        self.Bind(wx.EVT_CLOSE, self.on_frame_close)
    
    
    def show_irgendeinen_dialog(self, nachricht):
        diag = wx.MessageDialog(self, message = nachricht)
        retval = diag.ShowModal()
        self.worker.dialog_retval = retval
        self.worker.dialog_finished.set()
    
    
    def on_frame_close(self, event):
        event.Skip()
        self.worker.stop()
        busy_info = wx.BusyInfo("Thread wird gestoppt")
        self.worker.join()
        busy_info.Destroy()


def main():
    """Testing"""
    app = wx.PySimpleApp()
    f = MyFrame()
    f.Center()
    f.Show()
    app.MainLoop()


if __name__ == "__main__":
    main()
mfg
Gerold
:-)
Zuletzt geändert von gerold am Donnerstag 9. Oktober 2008, 10:47, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Shiroi
User
Beiträge: 7
Registriert: Samstag 23. Juni 2007, 09:50

Donnerstag 9. Oktober 2008, 10:26

Ah vielen Dank für die gute Erklärung! Mein Tag ist gerettet :D
Dann werde ich mein Programm mal umbauen :>
Antworten