Button wird nicht richtig angezeigt und hängt sich auf

Fragen zu Tkinter.
Antworten
Astraioz

Hallo erstmal :),
ich bin noch ziemlich neu in Python und brauche ein paar Ratschläge.

Ich bin grade dabei ein Programm mit Tkinter zu schreiben indem ich in einer bestimmten Zeit
etwas eingeben kann, und z.B. nach 5 Sekunden zählt, wieviele Zeichen der eingebene Text hat.
Mein Problem dabei ist, dass time.sleep nicht hilft, da es das ganze GUI vereist.

Bis jetzt sieht es so aus:

Code: Alles auswählen

from Tkinter import *
import time
root = Tk()

score = 0

print 'Startet in'
time.sleep(1)
print '3'
time.sleep(1)
print '2'
time.sleep(1)
print '1'
time.sleep(1)
print 'GO'

e1 = Entry(root)
e1.pack()

time.sleep(5)
score = len(e1.get())

print score

root.mainloop()
Alternativ wär es auch schön wenn dies ohne Tkinter gehen würde und einfach mit input in Python Shell funktionieren würde. Das wär noch viel besser aber, ich habe keine Ahnung wie das gehen sollte, da raw_input das Programm komplett stoppt.

Großes Dankeschön im Voraus :)
LG
Astraioz
Zuletzt geändert von Astraioz am Sonntag 10. März 2013, 18:21, insgesamt 2-mal geändert.
BlackJack

@Astraioz: Die `after()`-Methode auf `Tkinter`-Widgets ist dazu da nach einer angegebenen Zeit eine Funktion oder allgemeiner ein aufrufbares Objekt aufzurufen. Damit liesse sich ein Countdown realisieren.

Den Sternchen-Import solltest Du Dir gar nicht erst angewöhnen. Damit holt man sich im Fall von `Tkinter` an die 200 Namen in den Modulnamensraum. Die braucht man da nicht annähernd. Und wenn man das bei mehreren Modulen macht, können Namenskollisionen zu Problemen führen und man verliert den Überblick was eigentlich woher kommt. Damit führt man das Konzept von Modulen ad absurdum. Bei `Tkinter` ist es üblich ``import Tkinter as tk`` zu schreiben und die Objekte aus dem Modul dann über `tk` anzusprechen.
Astraioz

BlackJack hat geschrieben:@Astraioz: Die `after()`-Methode auf `Tkinter`-Widgets ist dazu da nach einer angegebenen Zeit eine Funktion oder allgemeiner ein aufrufbares Objekt aufzurufen.
Danke,
könntest du vielleicht ein Beispiel machen?
Ich verstehe nicht ganz was du meinst
LG
Astraioz

Ok ich habe es...
Trotzdem kriege ich eine Sache noch nicht hin...
Im Moment sieht mein Code so aus:

Code: Alles auswählen

score = 0
def play():
    e1 = Button(root, text='schnell', command= up)
    e1.pack() 
    root.after(5000)
    print 'Deine Punktzahl beträgt: '+str(score)+'\n\n'
def up():
    score += 1
            
print 'Startet in'
root.after(1000)
print '3'
root.after(1000)
print '2'
root.after(1000)
print '1'
root.after(1000)
print 'GO'
play()
Nun ist das Problem, dass sich das Tk Fenster aufhängt und ich den Button nicht drücken kann.
Außerdem ist der Button garnicht zusehen. Nur wenn ich den Code umschreibe, dass dieser von Anfang da ist hängt dieser sich auch auf.
LG
BlackJack

@Astraioz: Du übergibst der `after()`-Methode ja gar keine Funktion. Dann passiert da überhaupt nichts. Solange die `mainloop()` nicht aufgerufen wird auch nicht. Und GUI-Programmierung funktioniert nicht so linear wie Du Dir das anscheinend vorstellst. GUI-Programmierung ist Ereignisorientiert, dass heisst Du sagst dem GUI-Toolkit was bei welchem Ereignis aufgerufen werden soll und gibst dann die Kontrolle an das Toolkit ab. Das ruft dann bei Eintreten der registrierten Ereignisse, wie beispielsweise wenn eine Schaltfläche gedrückt wird, die vorher übergebene Funktion oder Methode auf. Mit `after()` kann man Ereignisse generieren die nach der angegebenen Verzögerung eintreten und die Funktion aufrufen.

Dabei dürfen die Funktionen die auf die Ereignisse reagieren nur relativ kurz laufen und nicht längerfristig blockieren, denn solange läuft die GUI-Hauptschleife nicht und die GUI reagiert in dieser Zeit nicht mehr auf den Benutzer.

Die `up()`-Funktion funktioniert so nicht, weil `score` innerhalb der Funktion erhöht werden soll, aber vorher gar kein Wert an diesen *lokalen* Namen gebunden wurde, also gar nicht klar ist wozu eigentlich 1 dazu addiert werden soll. An dieser Stelle sollte man objektorientierte Programmierung (OOP) einsetzen. Ist IMHO eine Voraussetzung für GUI-Programmierung.
Astraioz

@BlackJack
es tut mir Leid aber ich kann mir dazu nicht viel Vorstellen..
Es wäre super nett von dir wenn du dir kurz Zeit nimmst und dieses einfache Programm oder die paar Zeilen von dem Programm ins Forum schreibst ich habe keine Ahnung wie du dir das Vorstellst :(. Die Tkinter Teile habe ich bewusst weggelassen, damit der Code dort klar wird.
BlackJack

@Astraioz: Beispiel für eine Schaltfläche die erst nach einem Countdown aktiviert wird:

Code: Alles auswählen

#!/usr/bin/env python
import Tkinter as tk


class CountdownUI(object):
    def __init__(self, master):
        self.master = master
        self.counter = None
        self.countdown_label = tk.Label(master, text='-')
        self.countdown_label.pack(side=tk.TOP)
        self.close_button = tk.Button(
            master, text='Quit', state=tk.DISABLED, command=master.quit
        )
        self.close_button.pack(side=tk.TOP)

    def _tick(self):
        self.counter -= 1
        self.countdown_label['text'] = self.counter
        if self.counter == 0:
            self.close_button['state'] = tk.NORMAL
        else:
            self.master.after(1000, self._tick)

    def start_countdown(self, seconds):
        self.counter = seconds
        self.master.after(1000, self._tick)



def main():
    root = tk.Tk()
    countdown_ui = CountdownUI(root)
    countdown_ui.start_countdown(10)
    root.mainloop()


if __name__ == '__main__':
    main()
Astraioz

@BlackJack: Vielen Dank, das hat mir sehr geholfen =)

Eine Letzte Frage habe ich noch ;)
Bei mir hängt sich das Tkinter Fenster immer auf wenn ich in IDLE F5 benutze und damit es ausführe. Wenn ich es aber mit einem Doppelklick auf die Datei versuche, dann klappt es problemlos
Wo liegt das Problem?
Ich benutze Python 2.7.2
Und das hier ist der Code:

PS: Viele Dinge wie z.B. from Tkinter import * konnte ich nicht zu import Tkinter as tk machen da und dann nurnoch komische Error Meldungen bekommen hab wie z.B. "name 'Button' is not defined". Nicht irritieren von vielen Fehlern bin neu in der Programmierung ^^ Außerdem gehört vieles daraus nicht zu der Frage aber vielleicht liegt da auch das Problem

Code: Alles auswählen

import time
import sqlite3 as sql
from Tkinter import *

root = Tk()
connection = sql.connect('datenbank.db')
cursor = connection.cursor()

def query(cmd):
    cursor.execute(cmd)

query('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY,name CHAR[30],password CHAR[30],score TEXT)')

def up():
    global score
    score += 1

def main():
    global score
    while True:    
        inp = raw_input('Bitte eingeben: \n[i]nsert\n[d]elete\n[u]pdate\n[s]how\n[q]uit\n[p]lay: ')

        if inp == 'p':
            score = 0
            def result():
                e1['state'] = DISABLED
                print 'Deine Punktzahl beträgt: '+str(score)+''
                query('SELECT score FROM users WHERE name = "'+benutzername+'"')
                oldscore = cursor.fetchone()[0]
                if oldscore < score:                    
                    query('UPDATE users SET score = "'+str(score)+'" WHERE name = "'+benutzername+'" \n\n')
                else:
                    print 'Du warst mal besser...\n\n'
            
            
            benutzername = raw_input('Benutzername: ')
            print 'Startet in'
            time.sleep(1)
            print '3'
            time.sleep(1)
            print '2'
            time.sleep(1)
            print '1'
            time.sleep(1)
            print 'GO'
            e1['state'] = NORMAL 
            root.after(5000, result)
            root.after(6000, main)
            break
            
            
        if inp == 'sql':
            sql = raw_input('Sql Befehl: ')
            query(sql)
        
        if inp == 'i':
            ok = True
            user = raw_input('Benutzername: ')
            if len(user) < 3 or len(user) > 25:           
                print 'Der Benutzername muss zwischen 3 und 25 Zeichen lang sein.'
                ok = False
            password = raw_input('Passwort: ')
            if len(password) < 3 or len(password) > 25:
                print 'Das Passwort muss zwischen 3 und 25 Zeichen lang sein.'
                ok = False
            score = 0    
            if ok:
                query('INSERT INTO users VALUES (null,"'+user+'","'+password+'","'+str(score)+'")')
                print 'Der Benutzer '+user+' wurde erfolgreich erstellt'
                
        elif inp == 'd':
            user = raw_input('Benutzername: ')
            query('DELETE FROM users WHERE name = "'+user+'"')
            print 'Benutzer '+user+' wurde gelöscht.'
                
        elif inp == 'u':
            ucmd = raw_input('Was möchten Sie ändern? [[b]enutzername/[p]assword ')            
            if ucmd == 'b':
                username = raw_input('Aktueller Benutzername: ')
                newusername = raw_input('Neuer Benutzername: ')
                query('UPDATE users SET name = "'+newusername+'" WHERE name = "'+username+'"')
                print 'Benutzernamen erfolgreich geändert'                
            if ucmd == 'p':
                username = raw_input('Aktueller Benutzername: ')
                newpasswort = raw_input('Neues Passwort: ')
                query('UPDATE users SET password = "'+newpasswort+'"[WHERE name = "'+username+'"]')
                print 'Passwort erfolgreich geändert'            
            else:
                print 'Syntax Error'
                connection.commit()
                print
                
        elif inp == 's':
            query('SELECT * FROM users')
            users = cursor.fetchall()
            if len(users) == 0:
                print 'Keine Benutzer registriert.'
                print
            else:
                tabs = ' '*20
                headlines = ['ID', 'Name', 'Score', 'Passwort']
                for string in headlines:
                    print string + (' '*(30-len(string))),
                print
                print '-'*120 
                for user in users:
                    data = [str(user[0]), user[1], user[3], ('*'*len(user[2]))]
                    for string in data:
                        print str(string) + (' '*(30-len(str(string)))),
                        
                    print
                print
        elif inp == 'q':
                break
        else:
                print 'Syntax Error'
                connection.commit()
                print

e1 = Button(root, text='SCHNELL', command= up, state=DISABLED)
e1.pack(ipadx=50, ipady=50)


main()
root.mainloop()
connection.close()
Zuletzt geändert von Astraioz am Sonntag 10. März 2013, 20:14, insgesamt 1-mal geändert.
BlackJack

@Astraioz: IDLE ist selbst mit `Tkinter` geschrieben und verträgt sich manchmal (oder immer?) nicht mit GUI-Programmen die daraus gestartet werden.

Wie gesagt so linear funktioniert das mit GUIs nicht. Du kannst keine länger laufenden Schleifen schreiben die mit dem Benutzer interagieren, und dann auch noch auf der Konsole. Du rufst `main()` vor der `mainloop()` von `Tkinter` auf. Und wenn Du *die* aufrufst, dann kann nicht gleichzeitig die ``while``-Schleife in der `main()`-Funktion laufen. Das schliesst sich gegenseitig aus.

Ansonsten fällt noch das ``global`` negativ auf — saubere Programme sollten dieses Schlüsselwort nicht verwenden, zumindest nicht wenn die bessere Lösung eine Klasse ist. Und das ist sie fast immer. Werte, ausser Konstanten, sollten eine Funktion als Argumente betreten und nicht einfach so aus dem „nichts” verwendet werden. Denn nur dann kann eine Funktion ihre Funktion als in sich abgeschlossene Einheit mit einer klaren Schnittstelle erfüllen. Wenn man Funktionen „mit Zustand” braucht, dann sind dafür Klassen von der Sprache vorgesehen.

Und man sollte niemals Benutzereingaben einfach so mit einfachen Zeichenkettenoperationen in eine SQL-Anweisung hinein basteln. Das ist gefährlich weil der Benutzer dann SQL-Injection-Angriffe machen kann. Der Benutzer könnte als Name ja beispielsweise ``'; DELETE TABLE users; --`` eingeben und schon ist die Tabelle gelöscht wenn Du den Punktestand abfragen willst.

Die Zeichenkettenformatierung sieht insgesamt „unpythonisch” aus. `str()` und ``+`` ist eher BASIC. Python hat eine `format()`-Methode auf Zeichenketten und als etwas ältere Variante den ``%``-Operator. Mit beiden kann man auch gewisse Breiten vorgeben und rechts- oder linksbündig innerhalb dieses Platzes formatieren. Selbst ohne ginge es einfacher als ``' '*(30-len(string))``, denn Zeichenketten haben für so etwas auch eine Methode.

Die `commit()`-Aufrufe scheinen mir teilweise an der falschen Stelle im Programmablauf zu sein.

Und die Funktion ist viel zu voll und damit zu lang und unübersichtlich.
Astraioz

@BlackJack: Das ist schön und gut wie du mir weiterhilfst.
Aber mit diesen Informationen komm ich nicht grade viel weiter und hat mein Problem auch nicht gelöst..

Hast du irgendwelche guten Seiten auf Lager wo ich dies gut Lernen und verstehen kann?
Google hat mir da nicht grade viel geholfen.
LG
BlackJack

@Astraioz: Ups, bei der SQL-Injection hatte ich ganz vergessen zu erwähnen, dass man dagegen Platzhalter im SQL schreibt und die Werte als Sequenz als zweites Argument bei `execute()` angibt. Dann sorgt das Datenbankmodul dafür, dass der Benutzer eingeben kann was er will, ohne dass das von der Datenbank ausgeführt wird, sondern wirklich alles nur als Wert behandelt wird.

Für Anfänger wird oft Learn Python The Hard Way empfohlen. Und das Python-Tutorial in der Python-Dokumentation sollte man mal durchgearbeitet haben.

Bei der Benutzerinteraktion musst Du Dich zwischen Konsole und GUI entscheiden. Beides so zu mischen macht keinen Sinn und bringt zusätzliche Probleme. Konsole ist vielleicht erst einmal einfacher. Eventuell könnte man das `cmd`-Modul benutzen.
Astraioz

@Blackjack: Das mit dem SQL Befehl das war mir klar, dass man damit alles machen kann, genau das war der Sinn. Aber nicht so wichtig :D.
Trotzdem dickes Dankeschön vielleicht sieht man sich ja mal wieder :D
BlackJack

@Astraioz: Ich denke nicht, dass Dir das klar war oder auch nicht ist. Ich spreche nicht von dem Fall das `inp` = 'sql' ist, sondern wo der Benutzer seinen Namen eingibt, kann er auch beliebigen SQL-Code einschleusen.
Antworten