Buttons sperren

Fragen zu Tkinter.
Antworten
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

Hallo Forum,

nach langer Delphi-Pause nun zurück zu Python.

Bei folgendem Programm habe ich ein wahrscheinlich tiefes Veständnisproblem:

Code: Alles auswählen

#-*- coding: cp1252 -*-
#Grundgerüst für allgemeingültige GUI

import Tkinter
import tkMessageBox
import math
import random
import thread
import time

#Konstanten definieren
Schleifenzahl=1000
xAnfang=20
yAnfang=200
Versatz=0.5
Amplitude=100
Groesse=50
Teiler=10.0    

#allgemeine Funktionen
def Buttonlogik_Aus():
    btnStart['state']="disabled"
    btnEnde['state']="disabled"
    print "disabled"
def Buttonlogik_An():
    btnStart['state']="normal"
    btnEnde['state']="normal"
    print "normal"
def Zufallsfarbe():            #gibt 16-bit Zufallszahlenfarbe aus
    Zufallsfarbe=""
    for i in range(0,12):
        Zufall=hex(int(random.random()*255)).lstrip("0x")
        if len(Zufall)==0:
            Zufall="00"+Zufall #falls nullziffrig wird verlängert
        if len(Zufall)==1:
            Zufall="0"+Zufall  #falls einziffrig wird verlängert
        Zufallsfarbe=Zufallsfarbe+Zufall
    Zufallsfarbe="#"+Zufallsfarbe
    return Zufallsfarbe
#Ende allgemeine Funktionen

#Hier die Testfunktion(en)!
def Zeichnen():
    for i in range(Schleifenzahl):
            x1=xAnfang+i*Versatz
            x2=x1+Groesse
            y1=yAnfang+math.sin(i/Teiler)*Amplitude
            y2=y1+Groesse
            Farbe=Zufallsfarbe()
            cnvGrafik.create_oval(x1,y1,x2,y2, fill=Farbe, outline=Farbe)

#Funktionsdefinitionen für die Widget-Commands
def btnStart_Click():        #Das ist die zu testende Funktion nach Druck auf "Start"
    Buttonlogik_Aus()
    Zeichnen()
    Buttonlogik_An()
    
    # in einem Thread starten:
    #thread.start_new_thread(Zeichnen,())
    
def btnEnde_Click():
    Antwort=tkMessageBox.askokcancel("Programm beenden", "Sind Sie sicher?")
    if Antwort==True:
        Haupt.quit()

#GUI
 #Hauptfenster installieren
Haupt=Tkinter.Tk()
Haupt.title("GUI für Tests 0.1") 
Haupt.geometry("800x600")
 #die Widgets erzeugen
 #Zeichenfläche, eingebettet in einen Rahmen
fraRahmen=Tkinter.Frame(Haupt, relief="raised", borderwidth="1")
cnvGrafik=Tkinter.Canvas(fraRahmen, width="640", height="480", bg="white")
 #leeres Label zum Positionieren (blinde Leerzeile)
lblBlind=Tkinter.Label(Haupt)
 #zwei Schalter zum interagieren
btnStart=Tkinter.Button(Haupt, text="Start", command=btnStart_Click)
btnEnde=Tkinter.Button(Haupt, text="Ende", command=btnEnde_Click)
 #Widgets anzeigen
fraRahmen.grid(column="1", row="0", columnspan="5", sticky="ew")
cnvGrafik.grid()
lblBlind.grid(column="0", row="1", columnspan="7")
btnStart.grid(column="2", row="2", sticky="ew") 
btnEnde.grid(column="4", row="2", sticky="ew") 
#Ende GUI

#Start Hauptschleife Tk-Fenster    
Haupt.mainloop()
Was hier nicht läuft: Eigentlich dachte ich, dass während des Zeichnens die Buttons "Start" und "Ende" augegraut (deaktiviert) sind - dem ist aber nicht so, man kann lustig weiter "drauf drücken".

Wo ist mein Fehler?
Muss ich der GUI "Prozessorzeit" geben, so wie bei Delphi mit

Code: Alles auswählen

Application.ProcessMessages;
?


Viele Grüße & Danke im Voraus,

Tom
BlackJack

Man darf auf Tkinter-GUIs nur von dem Thread aus zugreifen, in dem die `mainloop()` läuft. Alles andere kann zu "lustigen" Effekten bis hin zu Abstürzen führen. Schau Dir mal die `after()`-Methode auf Widgets an.

Dann solltest Du Dir die Delphi-Namenskonventionen bei Python abgewöhnen. Sieht ja furchtbar aus. :-)

Die Zufallsfarbe wird reichlich umständlich ermittelt. Das fängt schon damit an, dass das `random`-Modul eine Funktionen bietet um eine ganze Zahl zwischen einer gegebenen Unter- und Obergrenze zu ermitteln. Und dann könntest Du auch gleich die ganze Zahl mit *einem* Aufruf dieser Funktion erzeugen.

Und es wird auch keine 16-Bit-"Farbe" erzeugt, sondern eine 96-Bit-"Farbe". Das ist ein klein wenig zu viel wenn Du mich fragst. Und 16 wäre wohl ein Byte zu wenig.

Code: Alles auswählen

In [23]: Zufallsfarbe()
Out[23]: '#6b5c4009df81bf23932c5547'
Die Funktion würde ich so schreiben:

Code: Alles auswählen

def random_color():
    """Creates random 24 bit color as Tk color string."""
    return '#%06x' % random.randrange(2**24)
Last but not least: Mir liegt da zu viel auf Modulebene -- das gehört IMHO in eine Klasse verpackt.
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

Danke für die zahlreichen Tipps! :D
BlackJack hat geschrieben:Man darf auf Tkinter-GUIs nur von dem Thread aus zugreifen, in dem die `mainloop()` läuft. Alles andere kann zu "lustigen" Effekten bis hin zu Abstürzen führen. Schau Dir mal die `after()`-Methode auf Widgets an.
*schluck*
OMGs, das wird noch heftig...
BlackJack hat geschrieben:Dann solltest Du Dir die Delphi-Namenskonventionen bei Python abgewöhnen. Sieht ja furchtbar aus. :-)
:wink:
BlackJack hat geschrieben: Die Zufallsfarbe wird reichlich umständlich ermittelt[...]
Sehr peinlich :oops:, aber habe ich verstanden...
BlackJack hat geschrieben:Last but not least: Mir liegt da zu viel auf Modulebene -- das gehört IMHO in eine Klasse verpackt.


Ich weiß, ich weiß - objektorientiert vs. prozedural; ich oute mich hier gern als Chobo - ich kann OOP nicht ab, sorry... :shock:

Viele Grüße, herzlichen Dank,


Tom

:shock:
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

Ich geb's auf... :(
WIE genau könnte die Methode after (die ja zeitorientiert ist) meine Konzeption retten?

Viele Grüße,
Tom :(
BlackJack

Ups, ich dachte da wird im Thread gezeichnet. Ohne Syntax-Highlighting habe ich übersehen, dass der Aufruf auskommentiert ist.

Ich habe es jetzt einmal ausprobiert: Die Buttons werden deaktiviert und dann steht das Programm weil auf der Konsole eine Fehlermeldung wegen der "Farbe" kommt.

Code: Alles auswählen

bj@s8n:~$ python test2.py
disabled
Exception in Tkinter callback
Traceback (most recent call last):
  File "lib-tk/Tkinter.py", line 1406, in __call__
    return self.func(*args)
  File "test2.py", line 55, in btnStart_Click
    Zeichnen()
  File "test2.py", line 50, in Zeichnen
    cnvGrafik.create_oval(x1,y1,x2,y2, fill=Farbe, outline=Farbe)
  File "lib-tk/Tkinter.py", line 2163, in create_oval
    return self._create('oval', args, kw)
  File "lib-tk/Tkinter.py", line 2148, in _create
    *(args + self._options(cnf, kw))))
TclError: invalid color name "#0f2345b898a9a122968ed70c"
Benutzeravatar
numerix
User
Beiträge: 2696
Registriert: Montag 11. Juni 2007, 15:09

tomdest hat geschrieben:Ich geb's auf... :(
WIE genau könnte die Methode after (die ja zeitorientiert ist) meine Konzeption retten?
So schnell gibt man doch nicht auf ... :wink:

Meine Empfehlung wäre zunächst einmal die Lektüre von Thinking in Tkinter.

Diese Darstellung leistet genau das, was sie im Titel verspricht: Man lernt, in Tkinter zu denken. Das Layout dieses "Tutorials" ist zwar echt sch***, aber inhaltlich ist es überwiegend richtig gut.
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

@BlackJack:
Sowohl mit meiner als auch mit Deiner Farbroutine läuft das Programm bei mir ohne Fehler durch (WingIde, WinXP, Python 2.5.2; geht auch in IDLE).
Woher kommt a) der Fehler und b) der Unterschied (bei mir ist nie etwas "disabled")?

@numerix: Tutorial zeihe ich mir rein, Danke!


Viele Grüße,

Tom
BlackJack

Nun der Fehler kommt eben dadurch, dass die Zeichenkette bei Deiner alten Funktion keine gültige Farbe darstellt. Was Tk unter Windows da macht weiss ich nicht.

Wenn ich meine Funktion benutze, dann sind die Buttons auch nicht deaktiviert. Das Zeichnen geht einfach zu schnell. Die `update()`-Methode auf Widgets erwirkt die sofortige Aktualisierung.

Ohne OOP wirst Du gerade bei GUIs auf Dauer keine Freude haben.
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

Die Methode .update war's - mille grazie :D

Hab das schon mal versucht aber nicht mit dem Widget (btnStart.update()) sondern via Haupt.update()... :oops:

Heissen Dank,

Tom
BlackJack

Auf welchem Widget die Methode aufgerufen wird, sollte eigentlich egal sein, bei mir ging's im Test mit ``Haupt.update()``.
tomdest
User
Beiträge: 12
Registriert: Donnerstag 3. Januar 2008, 19:49

Stimmt - Haupt.update() geht auch.

Wahrscheinlich war die Dauer zu kurz (vor der Änderung der Iterationen) um von mir bemerkt zu werden!

Hier der Code, mit dem ich nun alle möglichen Sachen weitermachen kann -herzlichen Dank!

Code: Alles auswählen

#-*- coding: cp1252 -*-
#Grundgerüst für allgemeingültige GUI

import Tkinter
import tkMessageBox
import math
import random
import thread
import time

#Konstanten definieren
Schleifenzahl=10000
xAnfang=20
yAnfang=200
Versatz=0.5
Amplitude=100
Groesse=50
Teiler=10.0    

#allgemeine Funktionen
def Buttonlogik_Aus():
    btnStart['state']="disabled"
    btnEnde['state']="disabled"
    Haupt.update()
def Buttonlogik_An():
    btnStart['state']="normal"
    btnEnde['state']="normal"
    Haupt.update()
def Zufallsfarbe():
    #Creates random 24 bit color as Tk color string => aus www.python-forum.de, Autor: BlackJack
    return '#%06x' % random.randrange(2**24)
#Ende allgemeine Funktionen

#Hier die Testfunktion(en)!
def Zeichnen():
    for i in range(Schleifenzahl):
            x1=xAnfang+i*Versatz
            x2=x1+Groesse
            y1=yAnfang+math.sin(i/Teiler)*Amplitude
            y2=y1+Groesse
            Farbe=Zufallsfarbe()
            cnvGrafik.create_oval(x1,y1,x2,y2, fill=Farbe, outline=Farbe)

#Funktionsdefinitionen für die Widget-Commands
def btnStart_Click():        #Das ist die zu testende Funktion nach Druck auf "Start"
    Buttonlogik_Aus()
    Zeichnen()
    Buttonlogik_An()
    
    # in einem Thread starten:
    #thread.start_new_thread(Zeichnen,())
    
def btnEnde_Click():
    Antwort=tkMessageBox.askokcancel("Programm beenden", "Sind Sie sicher?")
    if Antwort==True:
        Haupt.quit()

#GUI
 #Hauptfenster installieren
Haupt=Tkinter.Tk()
Haupt.title("GUI für Tests 0.1") 
Haupt.geometry("800x600")
 #die Widgets erzeugen
 #Zeichenfläche, eingebettet in einen Rahmen
fraRahmen=Tkinter.Frame(Haupt, relief="raised", borderwidth="1")
cnvGrafik=Tkinter.Canvas(fraRahmen, width="640", height="480", bg="white")
 #leeres Label zum Positionieren (blinde Leerzeile)
lblBlind=Tkinter.Label(Haupt)
 #zwei Schalter zum interagieren
btnStart=Tkinter.Button(Haupt, text="Start", command=btnStart_Click)
btnEnde=Tkinter.Button(Haupt, text="Ende", command=btnEnde_Click)
 #Widgets anzeigen
fraRahmen.grid(column="1", row="0", columnspan="5", sticky="ew")
cnvGrafik.grid()
lblBlind.grid(column="0", row="1", columnspan="7")
btnStart.grid(column="2", row="2", sticky="ew") 
btnEnde.grid(column="4", row="2", sticky="ew") 
#Ende GUI

#Start Hauptschleife Tk-Fenster    
Haupt.mainloop()

Schleifenanzahl mit Absicht zu groß gewählt, um Effekt des Disablens darzustellen!

Interessanterweise akzeptiert die GUI trotzdem einen zweiten Tastendruck auf den ausgegrauten Button!

Viele Grüße,

Tom
Antworten