Simples Popup erzeugen

Plattformunabhängige GUIs mit wxWidgets.
Antworten
Twix
User
Beiträge: 4
Registriert: Freitag 22. Juni 2007, 12:04

Hallo,

ich versuche nun seit Stunden mit der ToasterBox-Klasse ein Popup zu erzeugen, was soweit auch prima klappt. Nur kann ich das ganze insgesamt nicht in mein Programm integrieren.
Eigentlich läuft die gesamte "GUI" über curses und es wird ein Netzwerkprotokoll ausgelesen und ich möchte nur, dass bei bestimmten Situationen über wxPython ein grafisches Popup (eben eine Toasterbox) erscheint. Probleme stellt offenbar jedoch der wx.Timer dar, denn ohne die MainLoop() von wx.PySimpleApp() läuft dieser nicht. Ich kann jedoch die MainLoop() nicht sinnvoll integrieren, da ich den Code des Programms nicht hauptlastig auf wxPython legen möchte.

Ich habe ebenso versucht, mit Threads einfach die ToasterBox aufzurufen und nach einem Timer (time.sleep(...)) mit box.GetToasterBoxWindow().ScrollDown() die Box zu schließen. Aber auch das führte zu wenig Erfolg seitens ScrollDown().

Das nächste Problem ist, dass durchaus mehrere ToasterBox'es erzeugt werden können, je nach Daten, die empfangen werden.

Daher meine Fragen:

Kann ich irgendwie die MainLoop() in einem Thread laufen lassen und erst danach(!) die ToasterBoxen initialisieren? (Dafür bräuchte ich aber ein nicht sichtbares Window, das ständig präsent ist, oder? Sonst kommt die MainLoop ja sofort zurück....)
In meinen simplen Versuchen ging's nicht (das nachträgliche Initialisieren: Keine Box zu sehen) und die Xlib verabschiedete sich beim Threading auch mit einem Xlib: unexpected async reply

Wie kann ich das überhaupt sinnvoll umsetzen? Ich will nicht mehr als diese Popups, für alles andere möchte und will ich kein wxPython verwenden. Gibt es vielleicht Alternativen, mit denen mach auch grafisch anspruchsvolle Popups machen kann?


Bitte befruchtet mich gedanklich :D

Grüße
Benutzeravatar
veers
User
Beiträge: 1219
Registriert: Mittwoch 28. Februar 2007, 20:01
Wohnort: Zürich (CH)
Kontaktdaten:

Vorschlag:
Knall den wx mainloop in einen eigenen Thread, häng den wx mainloop an eine Queue.
Wenn immer du ein Popup brauchst fügst du ein neues Element in die Queue.

Gruss,
Jonas
Twix
User
Beiträge: 4
Registriert: Freitag 22. Juni 2007, 12:04

Hallo,
veers hat geschrieben: Knall den wx mainloop in einen eigenen Thread, häng den wx mainloop an eine Queue.
Wenn immer du ein Popup brauchst fügst du ein neues Element in die Queue.
hört sich gut an, aber ich habe mit wxPython leider bisher wenig Erfahrung.
Den Thread über das threading oder das thread-Modul? Und wie erzeuge ich die Queue?

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

Twix hat geschrieben:Eigentlich läuft die gesamte "GUI" über curses
[...]
ich möchte nur, dass bei bestimmten Situationen über wxPython ein grafisches Popup (eben eine Toasterbox) erscheint.
[...]
Ich habe ebenso versucht, mit Threads einfach die ToasterBox aufzurufen und nach einem Timer (time.sleep(...)) mit box.GetToasterBoxWindow().ScrollDown() die Box zu schließen.
[...]
Das nächste Problem ist, dass durchaus mehrere ToasterBox'es erzeugt werden können, je nach Daten, die empfangen werden.
[...]
Kann ich irgendwie die MainLoop() in einem Thread laufen lassen und erst danach(!) die ToasterBoxen initialisieren?
[...]
Wie kann ich das überhaupt sinnvoll umsetzen? Ich will nicht mehr als diese Popups, für alles andere möchte und will ich kein wxPython verwenden.
Hallo Twix!

Das ist schwieriger als man glaubt. Es ist wichtig, dass wxPython nur in **einem** Thread läuft. Also muss das Initialisieren der wxPySimpleApp im gleichen Thread laufen wie später die MainLoop. An der MainLoop blockiert wxPython so lange, bis ein wxPython-Event ausgelöst wird.

Das bedeutet, du brauchst einen Hauptthread (das normale Programm) für dein Curses-Programm und einen Thread für die wxPython-GUI

Der Hauptthread gibt dann die Anweisung über ``wx.CallAfter()`` (damit wird die Threadtrennung garantiert) an den wxPython-Thread weiter. Und dieser wxPython-Thread kann dann unabhängig vom Hauptthread arbeiten und ein paar Sekunden lang eine ToasterBox einblenden.

Ich glaube nicht, dass du den Umweg über eine Queue brauchst, aber ich werde mich mal ein wenig spielen. Vielleicht finde ich etwas raus.

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:

Ich habe mich ein wenig gespielt und das ist dabei raus gekommen:

Code: Alles auswählen

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

import wx
import ToasterBox as TB # http://xoomer.alice.it/infinity77/eng/freeware.html#toasterbox
import threading
import time

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


class MyCursesGui(object):
    
    def __init__(self, wx_thread):
        while True:
            self.show_menu()
            retval = raw_input()
            if retval == "Q":
                break
            else:
                wx_thread.show_toasterbox(retval)
        
        # Fertig
        print "Das Programm wird beendet."
    
    
    def show_menu(self):
        print
        print "Q - Beendet das Programm"
        print
        print "Gib etwas ein und druecke Enter. Dann wird es in der ToasterBox angezeigt."


class MyWxPythonGui(threading.Thread):
    
    def run(self):
        """
        Hier läuft der Thread (hier und in allen von hier aufgerufenen Methoden).
        Wird mit ``self.start()`` auf gerufen.
        """
        
        self.app = wx.PySimpleApp()
        self.wait_frame = wx.Frame(None)
        
        self.app.MainLoop()
        print "MainLoop fertig"
    
    
    def stop(self):
        # wx.CallAfter ist wichtig für die Threadtrennung. Ein Kommando darf niemals
        # direkt von einem Thread an einen anderen Thread gegeben werden.
        wx.CallAfter(self.wait_frame.Close)
        time.sleep(0.5)
        wx.CallAfter(self.app.ExitMainLoop)
    
    
    def show_toasterbox(self, text):
        wx.CallAfter(self._show_toasterbox_threadsafe, text)
    
    
    def _show_toasterbox_threadsafe(self, text):
        tb = TB.ToasterBox(self.wait_frame)
        tb.SetPopupPosition((800, 600))
        tb.SetPopupText(text)
        tb.SetPopupPauseTime(4000)
        tb.Play()


def main():
    wx_thread = MyWxPythonGui()
    wx_thread.start()
    
    MyCursesGui(wx_thread)
    
    wx_thread.stop()
    wx_thread.join() # warten bis die MainLoop fertig ist


if __name__ == "__main__":
    main()
Ich hoffe, dass dieser Code auch unter Linux funktioniert. Das wollte ich jetzt nicht testen.

mfg
Gerold
:-)
Zuletzt geändert von gerold am Freitag 22. Juni 2007, 14:16, insgesamt 1-mal geändert.
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
Twix
User
Beiträge: 4
Registriert: Freitag 22. Juni 2007, 12:04

Hallo!

Erst mal danke für die schnelle Hilfe und Mühe :)
gerold hat geschrieben: Das ist schwieriger als man glaubt.
Das kann man laut sagen ;)
Es ist wichtig, dass wxPython nur in **einem** Thread läuft. Also muss das Initialisieren der wxPySimpleApp im gleichen Thread laufen wie später die MainLoop. An der MainLoop blockiert wxPython so lange, bis ein wxPython-Event ausgelöst wird.
Kann ich durchaus verstehen, aber mir fehlt der Zwischenschritt zwischen
app = wx.PySimpleApp() und
app.MainLoop()

Wenn ich das wie oben aufrufe, beendet das Programm natürlich sofort, da die MainLoop ja nichts "zu tun" hat.
Der Hauptthread gibt dann die Anweisung über ``wx.CallAfter()`` (damit wird die Threadtrennung garantiert) an den wxPython-Thread weiter.
Wärst du so nett und könntest mir hier ein ganz simples Beispiel zeigen? Mir fehlt dafür gerade etwas die Vorstellungskraft :-/

Viele Grüße
Benutzeravatar
gerold
Python-Forum Veteran
Beiträge: 5555
Registriert: Samstag 28. Februar 2004, 22:04
Wohnort: Oberhofen im Inntal (Tirol)
Kontaktdaten:

Twix hat geschrieben:Wärst du so nett und könntest mir hier ein ganz simples Beispiel zeigen?
Hallo Twix!

...ist einen Beitrag ober dir. ;-)

mfg
Gerold
:-)
http://halvar.at | Kleiner Bascom AVR Kurs
Wissen hat eine wunderbare Eigenschaft: Es verdoppelt sich, wenn man es teilt.
midan23
User
Beiträge: 147
Registriert: Sonntag 21. Mai 2006, 21:41
Wohnort: Müchen
Kontaktdaten:

(Sorry, das ich mal kurz meinen Senf dazu gebe ... in der Hoffnung, das nicht zu viel Unsinn dabei ist ...)
Twix hat geschrieben: app = wx.PySimpleApp() und
app.MainLoop()

Wenn ich das wie oben aufrufe, beendet das Programm natürlich sofort, da die MainLoop ja nichts "zu tun" hat.
So weit ich weiss, ist dieses Verhalten normal ...

Zwischen

Code: Alles auswählen

app = wx.PySimpleApp()
und

Code: Alles auswählen

app.MainLoop()
gehört genau der Code, der ein Fenster erzeugt und anzeigt. Die MainLoop() übernimmt dann die Verarbeitung der Ereignisse.
Sobald das Fenster geschlossen wird, endet auch die MainLoop().
Twix
User
Beiträge: 4
Registriert: Freitag 22. Juni 2007, 12:04

gerold hat geschrieben:Ich habe mich ein wenig gespielt und das ist dabei raus gekommen:
[...]
Ich hoffe, dass dieser Code auch unter Linux funktioniert. Das wollte ich jetzt nicht testen.
Hallo Gerold,

vielen Danke für deine schnelle und umfassende Bemühung! Es funktioniert wunderbar und genauso, wie ich es mir vorgestellt hatte!

Besten Dank!
Antworten