Spazieren in der GUI

Fragen zu Tkinter.
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

@Alfons Mittelmeyer: Danke! Hat jetzt geklappt.
Gruss wuf :wink:
Take it easy Mates!
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

@Alfons Mittelmeyer: noch ein Grund, warum man sich an die Namenskonvention halten sollte, bei "go_in" gibt es keine Mißverständnisse.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

jens hat geschrieben:Du bist ein echter fan von 'eval',was? :twisted:
Er ist halt der Rebell des Forums, quasi ein Python Punk, der aus Prinzip alles anders macht, alle Regeln bricht und Ratschläge konsequent ignoriert... :roll:
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
Benutzeravatar
jens
Python-Forum Veteran
Beiträge: 8502
Registriert: Dienstag 10. August 2004, 09:40
Wohnort: duisburg
Kontaktdaten:

Python Punk
:mrgreen:

GitHub | Open HUB | Xing | Linked in
Bitcoins to: 1JEgSQepxGjdprNedC9tXQWLpS424AL8cd
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: noch ein Grund, warum man sich an die Namenskonvention halten sollte, bei "go_in" gibt es keine Mißverständnisse.
Sorry hatte nicht gewußt, dass bei Eurer Normalschriftart groß i genauso wie klein L aussieht. Wenn man eintippt, hat man eine andere Schriftart bei der der Unterschied deutlich ist. Aber was Ihr dann ausgebt unterscheidet sich.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Sirius3 hat geschrieben:@Alfons Mittelmeyer: leider tut Dein Spaziergang bei mir nicht:

Code: Alles auswählen

> ls()
=> \.
   Frame
> Tcl_WaitForEvent: Notifier not initialized
Abort trap: 6
Ich gehe mal davon aus, dass es daran liegt, dass Du versuchst, von einem Thread aus auf die GUI zuzugreifen.
Kann sein, dass das mit dem Thread unter Windows nicht klappt. Bei mir unter Ubuntu tut es. Daher habe ich es anderes implementiert. Allerdings hatte ich gemerkt, dass ich bei spazieren.py den Toplevel vergessen hatte: Bitte noch hinzufügen zu spazieren.py:

Code: Alles auswählen

class Toplevel(GuiElement,StatTkInter.Toplevel):
	def __init__(self,myname="Toplevel",**kwargs):
		_initGuiElement(kwargs,StatTkInter.Toplevel,self,myname,"Toplevel",True)
Dann habe ich buttons.py geändert und statt dem Thread das Modul GuiWalk in der vorletzten Zeile eingebunden:

Code: Alles auswählen

from spazieren import *

class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print("Tkinter is easy to use!")

root = Tk()
app = App(root)

import GuiWalk
root.mainloop()
Und das ist dann das Modul GuiWalk.py:

Code: Alles auswählen

from spazieren import *

goApp()

Toplevel()
goIn()

this().title('GuiWalk')

Entry('Entry')
Label('Label',text="""Your Input:""")

widget('Label').pack(side='left')
widget('Entry').pack(side='left')

### Code ======================================

def function(entry):
    try: eval(compile(entry.get(),'<string>','exec'))
    except:pass
    entry.delete(0,END)
    ls()

widget('Entry').bind('<Return>',lambda event, entry=widget('Entry'), func=function: func(entry))
del function

### ===========================================

goApp()
ls()
Bitte testen, ob das bei Euch funktioniert.

PS: natürlich kann man solche Befehle wie hier auch eingeben, während das Programm läuft - bei entsprechender Eingabemöglichkeit oder Dazuimport.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Alfons Mittelmeyer hat geschrieben:
Sirius3 hat geschrieben:Ich gehe mal davon aus, dass es daran liegt, dass Du versuchst, von einem Thread aus auf die GUI zuzugreifen.
Kann sein, dass das mit dem Thread unter Windows nicht klappt. Bei mir unter Ubuntu tut es.
Das hat nur begrenzt etwas mit den Betriebssystemen zu tun. Kein (verbreitetes) GUI Framework ist threadsafe, greifst du aus einem anderen Thread aus dem Hauptthread auf die GUI-Elemente zu, ist das Verhalten nicht definiert.
Soll heissen: Wenn es wie gewuenscht funktioniert, ist das ein gluecklicher Zufall.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

cofi hat geschrieben:
Alfons Mittelmeyer hat geschrieben:
Sirius3 hat geschrieben:Ich gehe mal davon aus, dass es daran liegt, dass Du versuchst, von einem Thread aus auf die GUI zuzugreifen.
Kann sein, dass das mit dem Thread unter Windows nicht klappt. Bei mir unter Ubuntu tut es.
Das hat nur begrenzt etwas mit den Betriebssystemen zu tun. Kein (verbreitetes) GUI Framework ist threadsafe, greifst du aus einem anderen Thread aus dem Hauptthread auf die GUI-Elemente zu, ist das Verhalten nicht definiert.
Soll heissen: Wenn es wie gewuenscht funktioniert, ist das ein gluecklicher Zufall.
Naja hatte bei mir unter Linux Ubuntu einwandfrei funktioniert und beim User wuf hatte es unter Linux Ubuntu auch einwandfrei funktioniert. Dafür ist ja so ein Forum gut, damit man sieht, ob es woanders auch funktioniert. Für Windows .Net gibt es einen Backgroundworker, mit dem man dann von einer Task in die GUI einen event melden kann. Bei Python hatte ich so etwas nicht gefunden. Aber da ist dann eine in die GUI integrierte Eingabe in einem Toplevel Window die richtige Lösung. So etwas kann man überall machen, etwa auch in Java.

Allerdings bei .Net Windows GUI Applikationen muß man ein wenig tricksen. Die haben nämlich keine Konsoleneingabe oder eine über input. Aber wenn man Konsoleneingaben auf ein Programm umleitet, das nichts anderes tut, als die über Standard Input empfangenen Zeichen wieder über Standard Output auszugeben, dann kann man die Standardausgabe dieses Programmes über eine Pipe mit der Standardeingabe der Windows GUI Applikation verbinden: Und schon hat man eine Konsoleneingabe für Windows GUI Applikationen.
BlackJack

@Alfons Mittelmeyer: Ein Forum ist aber auch dazu gut das einem gesagt wird das Tk(inter) nicht thread-safe ist und man deshalb nur aus dem Thread in dem die `mainloop()` läuft Veränderungen machen darf und aus anderen nicht. Auch wenn das scheinbar beim eigenen Rechner noch keine Probleme gemacht hat die man bemerkt hat, das ist dann halt Zufall.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Ein Forum ist aber auch dazu gut das einem gesagt wird das Tk(inter) nicht thread-safe ist und man deshalb nur aus dem Thread in dem die `mainloop()` läuft Veränderungen machen darf und aus anderen nicht. Auch wenn das scheinbar beim eigenen Rechner noch keine Probleme gemacht hat die man bemerkt hat, das ist dann halt Zufall.
Hatte eben funktioniert. Aber für einen anderen Thread gibt es selbstverständlich auch Lösungen, etwa Eingabe und Compilierung im Thread. Abholen und Ausführen über after von der GUI. Machbar ist alles. Aber von der GUI aus ohne Thread, finde ich am Besten, sofern man nicht Scripte über Standardinput für die GUI reinlesen will. Für händisch eingeben würde die Timer Lösung reichen, aber für reinrasselnde Scripte über Standardinput müßte man sich eine bessere Lösung ausdenken, bzw. man macht es nicht über Standardinput. Dass man in der GUI von einem Thread aus einen event auslösen könnte, darüber habe ich für tkinter noch nichts gefunden. Aber da fällt mir ein, dass es gar nicht für tkinter sein müßte. Es müßte reichen von einem Thread in die Haupttask zu kommen. Aber dieses Thema möchte ich nicht weiter vertiefen. Ich fang jetzt dann lieber an, meinen GuiDesigner umzuschreiben. Und doch da gäbe es auch eine Lösung - meine Message Queue.
Zuletzt geändert von Alfons Mittelmeyer am Sonntag 16. August 2015, 23:36, insgesamt 1-mal geändert.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nur der Main-Thread darf Aenderungen an der GUI machen, versucht ein anderer Thread das fallen alle Garantien weg.

Der funktionierende Weg den du nicht gefunden hast ist die `after` bzw `after_idle` Methode.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

cofi hat geschrieben:Nur der Main-Thread darf Aenderungen an der GUI machen, versucht ein anderer Thread das fallen alle Garantien weg.

Der funktionierende Weg den du nicht gefunden hast ist die `after` bzw `after_idle` Methode.
Die hatte ich schon gefunden, ich hatte es nur nicht so schnell geschrieben. Ach was sehe ich, ich war doch schneller und noch vor Dir dran: 'Abholen und Ausführen über after von der GUI.' Aber after_idle kannte ich noch nicht. Das wäre dann anscheinend die Lösung für Scripts.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

cofi hat geschrieben:Nur der Main-Thread darf Aenderungen an der GUI machen, versucht ein anderer Thread das fallen alle Garantien weg.
Fallen bei jeder GUI Änderung alle Garantien weg, oder gibt es einige Kommandos, bei denen die Garantien nicht wegfallen? Dass die GUI zyklisch über ein after nachschaut, müßte nicht sein, wenn man von einem Thread aus ein after triggern dürfte. Wie sieht das mit after triggern aus? Weißt Du etwas darüber, oder dafür auch keine Garantien?
BlackJack

@Alfons Mittelmeyer: Events sind auch thread-safe, allerdings benutzt das kaum jemand weil die Implementierung in der `Tkinter`-Anbindung nicht vollständig ist. Man kann nur Ereignisse ohne zusätzlich Daten auslösen, also wirklich nur ein Event mit einem eigenen Namen ohne das man da weitere Informationen mitgeben kann. Wenn einem das reicht, kann man das natürlich so verwenden. In der Praxis habe ich das aber nur *sehr* selten gesehen, in der Regel wird einem immer eine `Queue.Queue` und `after()`/`after_idle()` nahegelegt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Events sind auch thread-safe, allerdings benutzt das kaum jemand weil die Implementierung in der `Tkinter`-Anbindung nicht vollständig ist. Man kann nur Ereignisse ohne zusätzlich Daten auslösen, also wirklich nur ein Event mit einem eigenen Namen ohne das man da weitere Informationen mitgeben kann. Wenn einem das reicht, kann man das natürlich so verwenden. In der Praxis habe ich das aber nur *sehr* selten gesehen, in der Regel wird einem immer eine `Queue.Queue` und `after()`/`after_idle()` nahegelegt.
Man könnte natürlich Mausklicks, Buttonklicks in einen Eventbuffer schreiben. Aber das ginge mir zu sehr ins Eingemachte. Könnte auch Betriebssystemabhängig sein, sofern tkinter das nicht nochmal in ein anderes Format bringt. Und Python events bringen auch nicht viel. Mit einem Lock würde ich ja die GUI sperren. Eine Endlosschleife in der GUI geht auch nicht. Und wenn ich das triggern will, lande ich wieder bei after. Und eine Queue habe ich selber. Wenn die nicht idle ist und gerade Messages verarbeitet, ist es kein Problem von einem anderen Thread aus zusätzliche Messages in der Queue abzulegen. Wenn sie aber alles abgearbeitet hat, arbeitet sie nicht mehr. Und wenn dann eine Message kommt, führt sie sie gleich aus. Das aber darf sie nicht, wenn die Message von einem anderen Thread kommt. Wenn sie von einem anderen Thread kommt, dann kann man das so machen, dass man dann die Messages nur in der Queue ablegt. Aber weil das Message System nicht läuft, muß man es aufrufen und dannn sind wir wieder beim after.

Allerdings ein event auslösen ohne zusätzliche Daten, das klingt jetzt sehr interessant. Denn damit könnte man die Abarbeitung der Messagequeue anstoßen. Darüber würde ich gerne mehr erfahren. Heißt das ich kann einfach so etwas machen:

self.bind("irgendetwas", mycallback)

Und kann es dann von außen her triggern?
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

Ach hab es doch gefunden:

Code: Alles auswählen

from tkinter import *

def doFoo(*args):
    print("Hello, world")

root = Tk()
root.bind("<<Foo>>", doFoo)

# some time later, inject the "<<Foo>>" virtual event at the
# tail of the event queue
root.event_generate("<<Foo>>", when="tail")
Quelle: http://stackoverflow.com/questions/2706 ... -main-loop

So etwas ist natürlich toll. Dann braucht doFoo in meinem Fall nur aufzurufen: send("voelligwurscht_aber_nicht_bereits_sonst_definiert") und die Abarbeitung läuft.

Noch eine Frage: wie ist das mit Python Threads. Kommt es zu einem Threadwechsel nur nach Ausführung vollständiger Python Befehle oder auch mittendrin?
Wenn ich etwa von einer anderen Task aus einen append mache, bevor in der Maintask ein pop kommt, ist kein Problem.
Wenn ich von einer anderen Task aus einen append mache, nachdem in der Maintask ein pop ausgeführt wurde, ist auch kein Problem.
Aber wenn ich einen append mache, während eines pops, das dürfte ein Problem sein, sofern man da nicht lockt. Also ist da ein lock erforderlich?

Oder anders ausgedrückt: laufen die Threadwechsel Pythongesteuert ab oder auf Prozessorebene?
BlackJack

@Alfons Mittelmeyer: Das mit dem Kontextwechsel bei Threads hatte ich gerade im dedizierten Thema dazu beantwortet, darum hier nur kurz: Es werden native Threads verwendet soweit auf dem System vorhanden, man muss also allgemein davon ausgehen das der Wechsel auf Prozessorebene stattfindet wenn man threadsicher programmieren will. `Queue.Queue` aus der Standardbibliothek ist aber beispielsweise von der Dokumentation her garantiert threadsicher und `collections.deque` auch. So etwas muss man sich also nicht aus einer Liste und Sperrobjekten selber basteln.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Das mit dem Kontextwechsel bei Threads hatte ich gerade im dedizierten Thema dazu beantwortet, darum hier nur kurz: Es werden native Threads verwendet soweit auf dem System vorhanden, man muss also allgemein davon ausgehen das der Wechsel auf Prozessorebene stattfindet wenn man threadsicher programmieren will. `Queue.Queue` aus der Standardbibliothek ist aber beispielsweise von der Dokumentation her garantiert threadsicher und `collections.deque` auch. So etwas muss man sich also nicht aus einer Liste und Sperrobjekten selber basteln.
Hatte im anderen Thread jetzt auch dazu geschrieben warum es so gemacht wird. Denn durch ein atomares Objekt, wie Fileschreiben, das länger dauert, käme der andere Thread ansonsten in der Zwischenzeit nicht dran.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:@Alfons Mittelmeyer: Das mit dem Kontextwechsel bei Threads hatte ich gerade im dedizierten Thema dazu beantwortet, darum hier nur kurz: Es werden native Threads verwendet soweit auf dem System vorhanden, man muss also allgemein davon ausgehen das der Wechsel auf Prozessorebene stattfindet wenn man threadsicher programmieren will. `Queue.Queue` aus der Standardbibliothek ist aber beispielsweise von der Dokumentation her garantiert threadsicher und `collections.deque` auch. So etwas muss man sich also nicht aus einer Liste und Sperrobjekten selber basteln.
Also hab es mal mit queue (Python3) gemacht. Hier das Modul execute_gui.py:

Code: Alles auswählen

from spazieren import *
import queue

_queue = queue.Queue()
_root = None

def _execute(*args):
    try:   _queue.get()()
    except: print("execute error")
	
def init(root):
    global _root
    _root = root
    root.bind("<<EXEC>>",_execute)

def execute(command): 
    _queue.put(command)
    _root.event_generate("<<EXEC>>", when="tail")
Und dazu das angepasste buttons.py:

Code: Alles auswählen

from spazieren import *
import execute_gui
import threading

class App:
  def __init__(self, master):
    frame = Frame(master)
    frame.pack()
    self.button = Button(frame, 
                         text="QUIT", fg="red",
                         command=frame.quit)
    self.button.pack(side=LEFT)
    self.slogan = Button(frame,
                         text="Hello",
                         command=self.write_slogan)
    self.slogan.pack(side=LEFT)
  def write_slogan(self):
    print("Tkinter is easy to use!")

root = Tk()
app = App(root)
ls()

execute_gui.init(root)

class MyThread(threading.Thread):
    def run(self):
        while True:
            a = input("> ")
            try: 
                command = lambda cmd=compile(a+"\nls()",'<string>', 'exec'): eval(cmd)
                execute_gui.execute(command)
            except: print("Error:",a)
 
mythread = MyThread()
mythread.daemon = True
mythread.start()

root.mainloop()
Hoffe, dass das jetzt auch unter Windows geht.

Außerdem hattest Du geschrieben:
BlackJack hat geschrieben:@Alfons Mittelmeyer: Events sind auch thread-safe, allerdings benutzt das kaum jemand weil die Implementierung in der `Tkinter`-Anbindung nicht vollständig ist. Man kann nur Ereignisse ohne zusätzlich Daten auslösen, also wirklich nur ein Event mit einem eigenen Namen ohne das man da weitere Informationen mitgeben kann. Wenn einem das reicht, kann man das natürlich so verwenden. In der Praxis habe ich das aber nur *sehr* selten gesehen, in der Regel wird einem immer eine `Queue.Queue` und `after()`/`after_idle()` nahegelegt.
Stimmt natürlich nicht ganz, denn den command kann man auch mit Parametern machen. Sozusagen Funktionsaufruf von außen in Gui Callback verwandelt.
Alfons Mittelmeyer
User
Beiträge: 1715
Registriert: Freitag 31. Juli 2015, 13:34

BlackJack hat geschrieben:In der Praxis habe ich das aber nur *sehr* selten gesehen, in der Regel wird einem immer eine `Queue.Queue` und `after()`/`after_idle()` nahegelegt.
Tut mir leid BlackJack, wenn man jetzt die gängige Praxis und die Empfehlungen wohl ändern müssen wird.
Gesperrt