ProgressDialog beenden

Plattformunabhängige GUIs mit wxWidgets.
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Hi,

ich versuche mich gerade am ProgressDialog mit folgendem Code:

Code: Alles auswählen

import wx
from threading import Thread

class Worker(Thread):
    def __init__(self, progressDialog):
        Thread.__init__(self)
        self.progressDialog = progressDialog
    def run(self):
        for i in range(0, 100):
            for j in range(0, 100000):
                pass
            self.progressDialog.Update(i+1, str(i+1))
        self.progressDialog.Destroy(True)

app = wx.PySimpleApp()
progressDialog = wx.ProgressDialog("Progress", "Message", 100, None, wx.PD_APP_MODAL)
worker = Worker(progressDialog)
worker.start()
app.MainLoop()
Der Balken baut sich wunschgemaess auf, aber am Ende kann man nicht auf X klicken um den Dialog zu beenden.

Was ist die einfachste Moeglichkeit, den Dialog entweder automatisch destroyen zu lassen oder dem Benutzer die Moeglichkeit zu geben, das fenster zu schliessen ?
Im Moment hangt sich das Fenster am Ende kompett auf :(

vielen Dank

ciao Daniel
lunar

Du greifst aus einem anderen Thread auf GUI-Elemente zu, die sich im Hauptthread befinden. Kein Toolkit, welches ich kenne, hat Thread-sichere GUI-Widgets. Ergo bringt jeder Zugriff auf solche Widgets, der **nicht** aus dem Hauptthread erfolgt, die Nachrichtenschleife durcheinander, was sich bei dir wahrscheinlich im Einfrieren des Fensters äußert.

Merke: Zugriffe auf ein GUI-Elemente sollten **immer** aus dem Thread erfolgen, in dem diese GUI-Elemente erzeugt wurden.

Lösungsansätze bietet die wxPython Dokumentation.
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Wenn ich da so mache, dass nur der Erzeuger Thread darauf zugreift, funktioniert das aber auch nicht...

Code: Alles auswählen

import wx
from threading import Thread

class Worker(Thread):
    def __init__(self):
        Thread.__init__(self)
        self.progressDialog = wx.ProgressDialog("Progress", "Message", 100, None, wx.PD_APP_MODAL)
    def run(self):
        for i in range(0, 100):
            for j in range(0, 100000):
                pass
            self.progressDialog.Update(i+1, str(i+1))
        self.progressDialog.Close(True)
        self.progressDialog.Destroy(True)

app = wx.PySimpleApp()
worker = Worker()
worker.start()
app.MainLoop()
BlackJack

Der Hauptthread enthält die `MainLoop` und auch das Widget wird dort erstellt, aber die `run()` läuft in einem anderen Thread. Also wieder Zugriff auf die GUI von einem anderen Thread als dem, in dem die Hauptschleife des GUI-Toolkits läuft -> Chaos.
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Hm, wie ist dass dann denn zu loesen ?

so gehts:

Code: Alles auswählen

import wx

app = wx.PySimpleApp()
progressDialog = wx.ProgressDialog("Progress", "Message", 100, None, wx.PD_APP_MODAL)
for i in range(0, 100):
    for j in range(0, 100000):
        pass
    progressDialog.Update(i+1, str(i+1))
app.MainLoop()
so nicht mehr

Code: Alles auswählen

import wx
from threading import Thread

class Main():
    def __init__(self):
        self.progressDialog = wx.ProgressDialog("Progress", "Message", 100, None, wx.PD_APP_MODAL)
        self.worker = Worker(self)
        self.worker.start()
    def Update(self, value):
        self.progressDialog.Update(value, str(value))


class Worker(Thread):
    def __init__(self, listener):
        Thread.__init__(self)
        self.listener = listener
    def run(self):
        for i in range(0, 100):
            for j in range(0, 100000):
                pass
            self.listener.Update(i+1)
        print "finished"

app = wx.PySimpleApp()
main = Main()
app.MainLoop()

wie mach ich dass dann denn mit threads ?

danke
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Wie? Wo? Was? ProgressDialog nur mit Threads-handlebar?

Ich flehe Euch an .... schaut Euch den Beispiel-Code zum ProgressDialog aus den "wx Python Docs Demos and Tools" an.

(Downloadbar in der Version 2.8.7.1 hier -> http://downloads.sourceforge.net/wxpyth ... .8.7.1.exe).

Code: Alles auswählen

wx.ProgressDialog(...,  style = wx.PD_CAN_ABORT, ...
... lautet das Zauberwort. Ohne Threads, ohne Events und ohne eventuell sogar Kills :shock:

>>Masaru<<
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Ich benoetige aber zwingend Threads.

Und wx.PD_CAN_ABORT hilft auch nicht weiter...
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Zuviele Bugs in ProgressDialog

werd wohl selber was schreiben muessen...
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

So gehts:

Code: Alles auswählen

import wx
from threading import Thread

class ProgressFrame(wx.Frame):
    def __init__(self, parent, message, heading):
        wx.Frame.__init__(self, parent, wx.ID_ANY, heading, size=(400, 200))
        self.EnableCloseButton(False)
        self.panel = wx.Panel(self, wx.ID_ANY )
        self.messageText = wx.StaticText(self.panel, wx.ID_ANY, message, pos=(50, 50))
        self.gauge = wx.Gauge(self.panel, wx.ID_ANY, 100, pos=(50, 100), size=(300, 30))
        self.Show(True)
    def update(self, value):
        self.gauge.SetValue(value)
    def OnFinish(self):
        self.EnableCloseButton(True)

class Worker(Thread):
    def __init__(self, pd):
        Thread.__init__(self)
        self.pd = pd
    def run(self):
        
        for i in range(0, 99):
            for j in range(0, 1000000):
                pass
            self.pd.update(i)
            print str(i)
        print "finished"
        self.pd.OnFinish()

app = wx.PySimpleApp()
progressFrame = ProgressFrame(None, "Inserting...", "Progress")
worker = Worker(progressFrame)
worker.start()
app.MainLoop()
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Du hast Dir nicht die Demo genau genug angeschaut ... :?

Ich habe Dir hier mal einen abbrechbaren Dialog gebaut + die Update-Funktionalität in einen Thread noch geschoben (was man sich aber auch schenken könnte *seufz*.

Code: Alles auswählen

import threading
import time
import wx


class MainFrame(wx.Frame):
    def __init__(self, parent, ID, title, pos=wx.DefaultPosition,
                 size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE):

        size = wx.Size(150,150)
        wx.Frame.__init__(self, parent, ID, title, pos, size, style)
        panel = wx.Panel(self, -1)

        button = wx.Button(panel, 1003, "Start")
        button.SetPosition((15, 15))
        self.Bind(wx.EVT_BUTTON, self.__start_a_progress, button)

    def __start_a_progress(self, event):
        def do_update(_progress):
            pbar_max = 100
            pbar_curr = 0
            keep_going = True
            while keep_going and pbar_curr < pbar_max:
                (keep_going, skip) = _progress.Update(pbar_curr)
                wx.MilliSleep(10)
                pbar_curr += 1
            _progress.Destroy()

        progress = wx.ProgressDialog(
                "Progress",
                "Message",
                maximum=100,
                parent=self,
                style=wx.PD_CAN_ABORT|wx.PD_APP_MODAL)

        pthread = threading.Thread(target=do_update, args=[progress])
        pthread.start()


class TestApp(wx.App):
    def OnInit(self):
        win = MainFrame(None, -1, "wxProgressDialog Tester")
        win.Show(True)
        self.SetTopWindow(win)
        return True

if __name__ == '__main__':
    app = TestApp()
    app.MainLoop()
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Dein Programm laeuft bis sich der Button auf Close aendert und danach ist es nicht mehr responsive.

Habe es 1:1 kopiert und laufen lassen

Python 2.5, wxPython aktuelle Version und Windows XP SP2
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

Ah ... Fehler behoben, kopiers nochmal bitte.

Die "Close" Funktionalität darf bei dem Dialog nicht erreicht werden, sofern die Update-Schleife in einem Thread ausgeführt wird.

Bevor der Button also auf "Close" (wie gesagt: NUR im Thread-Szenario) wechselt (nach 99%) wird im Beispiel nun der Dialog `destroy`t.

Generell solltest Du Dir nochmal die "ProgressBar.Update" Beschreibung anschauen. Es werden die Werte "keep_going" und "skip" nämlich zurückgeliefert, die für die eigene Update-Steuerung von enormer Wichtigkeit sind sofern man "Cancel" nutzen möchte ;).
maxip
User
Beiträge: 61
Registriert: Dienstag 11. März 2008, 09:43

Ich verstehe es einfach nicht, warum funktioniert denn dieser zum obigen Postigng analoger Code nicht ?

Code: Alles auswählen

from threading import Thread
import wx

class MainFrame(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, wx.ID_ANY, "Just a simple ProgressDialog", size=(400, 300))
        self.Show(True)
    def startWork(self):

        def runWork(_pd):
            keep_going = True
            for i in range(0, 100):
                for j in range(0, 100):
                    print i, j
                    pass
                (keep_going, skip) = _pd.Update(i)
                wx.MilliSleep(10)
                if not keep_going:
                    print "cancelled!"
                    break
            print "finished"
            _pd.Destroy()

        progress = wx.ProgressDialog(
                "Progress",
                "Message",
                maximum=100,
                parent=self,
                style=wx.PD_CAN_ABORT|wx.PD_APP_MODAL)
        worker = Thread(target=runWork, args=[progress])
        worker.start()


app = wx.PySimpleApp()
mainFrame = MainFrame()
mainFrame.startWork()
app.MainLoop()
Abbrechen funktioniert, laeuft der ProgressDialog aber bis zum ende, haengt sich das programm auf :(
sechsrad
User
Beiträge: 173
Registriert: Montag 31. März 2008, 17:09

Abbrechen funktioniert, laeuft der ProgressDialog aber bis zum ende, haengt sich das programm auf

spielst du "hangmann"
Mephisto
User
Beiträge: 28
Registriert: Mittwoch 17. Januar 2007, 15:52

Kann den nicht mal jemand abschalten? :(
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

Also bei mir funktioniert es.

Code: Alles auswählen

Das ist meine Umgebung:
=======================
sys.version: 2.4.4 (#71, Oct 18 2006, 08:34:43) [MSC v.1310 32 bit (Intel)]
sys.platform: win32
sys.winver: 2.4
wx.VERSION_STRING: 2.8.6.0
Ich glaube aber, dass (wie bereits anfänglich gesagt) die unterschiedlichen Threads schon von Bedeutung sind. Vielleicht sollte der Worker-Thread doch seinen Arbeitsfortschritt über eine "Variable" an den Hauptthread mitteilen, der diese Variable regelmäßig über einen wxPython-Timer abfragt und den Fortschrittsbalken anpasst.
MfG
HWK
lunar

Mephisto hat geschrieben:Kann den nicht mal jemand abschalten? :(
Ach, füttern ihn doch einfach nicht...
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

lunar hat geschrieben:
Mephisto hat geschrieben:Kann den nicht mal jemand abschalten? :(
Ach, füttern ihn doch einfach nicht...
Einfach kurz an die Freundin denken und ignorieren. :wink:
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Benutzeravatar
Masaru
User
Beiträge: 425
Registriert: Mittwoch 4. August 2004, 22:17

maxip hat geschrieben:..., warum funktioniert denn dieser zum obigen Postigng analoger Code nicht ?

...

Abbrechen funktioniert, laeuft der ProgressDialog aber bis zum ende, haengt sich das programm auf :(
Wie ich schon erwähnt habe, darf der ProzessDialog !!! sofern er von einem Thread aus befütter/geupdated wird !!! nicht bis 100% durchlaufen.

Wird das bei der ProgressDialog-Initialisierung gesetzte "maximum" (in unseren Beispielen mit dem Wert 100) erreicht, switcht automatisch der "Cancel"-Button auf "Close" um.

Was genau im Hintergrund der wxWidgets passiert kann ich zum aktuellen Zeitpunkt nicht sagen ... was ich nur weiss ist, dass dieser switch in einem Thread ausgeführt dafür sorgt, dass sich das ganze aufhängt.

Deswegen darf auch das Updaten nur bis zum Punkt 99% erfolgen, gefolgt von einem .Destroy().

Bei HWK scheint es scheinbar auch mit 100% zu funktionieren, ich habs mit Python2.3 getestet, und es hat nicht geklappt.

Unter Verwendung einer Threadgesteuerten Auffüllung des Balkens, war ein destroyen bevor die 100 erreicht war - damit sich das ganze nicht aufhängt - unumgänglich :(

>>Masaru<<
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Hallo!

Es ist sehr schwierig, ein gutes Beispiel für den ProgressDialog zu erstellen. Ich habe es jetzt schon mehrmals versucht, so ein Beispiel dafür zu erstellen, aber irgendwie wird nichts mit dem "einen", einfachen Beispiel. Man kann diesen Dialog nämlich auf viele verschiedene Arten verwenden. Und jede Art fügt sich in die eigene Art zu programmieren ein. Ein Beispiel, das für meine Art zu programmieren ideal ist, kann für andere Programmierer wieder komplett unbrauchbar sein.
Aber eines bleibt immer gleich:
NIEMALS von einem anderen Thread aus, direkt auf wxPython-GUI-Elemente zugreifen.
Auch wenn es auf einem Computer funktioniert, kann es sein, dass es auf dem gleichen Computer mit einem anderen Servicepack oder einer anderen wxPython-Version schon nicht mehr funktioniert. Dann gibt es noch die Unterschiede zwischen Windows, Linux, Gnome, KDE und Mac. Nur wenn man nicht von einem Thread auf den anderen Thread zugreift, kann man halbwegs sicher sein, dass das Programm sinngemäß funktioniert.

Zur Trennung der Threads gibt es verschiedene Wege. Ein sehr einfacher Weg ist der, dass man threadübergreifende Befehle über ``wx.CallAfter()`` aufruft.

Ein anderer Weg ist der, dass man gezielt kapselt, Events auslöst und Handlerfunktionen an die Events bindet. ``wx.CallAfter()`` macht es im Hintergrund so ähnlich.

Und noch etwas: Ob der Dialog bei 100 verschwindet, hat nichts mit den Threads zu tun. Es gibt eine Einstellung (``wx.PD_AUTO_HIDE``) die genau dieses Verhalten erzwingt. Siehe: http://docs.wxwidgets.org/stable/wx_wxp ... ialog.html
Wenn der Dialog dann trotzdem nicht verschwindet, dann wurde falsch programmiert. Und das hat dann vielleicht etwas mit Threading zu tun.

Ich versuche jetzt zwei Beispiele für den ProgressDialog mit Worker-Thread zusammen zu bringen. Wenn ich es schaffe, dann schmeiße ich sie in das Codesnippets-Forum.

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Antworten