GUI in Programm integrieren

Fragen zu Tkinter.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Hi,

Ich hab ein Problem mit Tkinter. Ich weiß nicht wie ich es hinkriegen soll, diese Funktion

Code: Alles auswählen

def frage(data):
    question, solve, stat = data
    solution = solve.split(", ")
    answer = raw_input(question)
    if answer in solution:
        print "Richtig!"
        data[2] = data[2] + 2
        return 1
    else:
        print "Falsch! Richtig ist: ", solve
        data[2] = data[2] + 1
        return 0
und diese Klasse:

Code: Alles auswählen

class Iface:
    def __init__(self, master):        
        frame = Frame(master, width=300, height=200)
        frame.pack()
        self.label = Label(frame, text="Start", pady=10)
        self.label.pack()
        self.entry = Entry(frame)
        self.entry.bind("<Return>", self.get_entry)
        self.entry.pack()
        self.solve=Button(frame, text="Start")
        self.solve.pack()

    def get_entry(self, event): 
        x = self.entry.get()
        return x
miteinander zu kombinieren.
Dabei soll dann:
1. "question" dem Label als Text zugewiesen werden.

2. Der Benutzer gibt dann seine Antwort ind das Feld ein und drückt Enter, wodurch die Funktion entry_get() seine Eingabe zurückgibt. Diese soll dann answer zugewiesen werden.

3. Dann soll dem Button entweder "Richtig!" bzw. "Falsch ..." zugewiesen werden. Wenn der Benutzer auf den button klickt, bzw. nochmal Enter drückt soll die nächste Frage gestellt werden.

Es würde mich freuen, wenn mir jemand helfen könnte. Ich hab bis jetzt noch keine Erfahrungen mit Tkinter und weiß daher überhaupt nicht, wie ich das umsetzen soll.
BlackJack

Die beiden Quelltextfragmente wirst Du nicht kombinieren können. Es sind beides "GUI"s für unterschiedliche "Toolkits". Eine "GUI" ist eben eine Textoberfläche.
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

Stimmt, er könnte aber die Funktionalität von ``frage()`` in ``Iface`` einbinden. Also das ``raw_input`` und die ganzen print's loswerden.
Kurz: Ein rewrite.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Auch damit hätte ich kein Problem, hauptsache ich schaff es, das ich durch irgendeinen Befehl (vorzugsweise Funktionsaufruf) den gewünschten Effekt erziele und das Programm das macht, was es soll, nämlich aus der Liste ["Frage", "Antwort", 0] die richtige Anzeige.

Ich kann ja mal versuchen, die Funktion irgedwie in die Klasse zu integrieren, allerdings hab ich dann immernoch das Problem, wie ich der Klasse sagen soll, dass sie ne Frage stellen soll.

Es wäre allerdings nett, wenn irgendjemand mal nen Lösungsansatz posten könnte, weil ich hab nämlich bis jetzt keine Ahnung, wie ich das umsetzen soll und ich wollte eigentlich bis zum Schuljahresende fertig sein (Ab da hab ich dann nämlich voraussichtlich kein Latein mehr :wink: ).
BlackJack

Du musst wohl etwas umdenken müssen. GUIs sind in der Regel ereignisbasiert, d.h. Du rufst keine Funktion auf die alles steuert, sondern du baust die GUI auf und reagierst auf entsprechende Ereignisse. Also zum Beispiel das ein Button gedrückt wurde.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Dann muss ich einen Weg finden (falls es einen geben sollte), wie ich es schaffe, in dem Programm ein Ereignis zu erzeugen, auf welches dei GUI dann reagiert.
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

klaus hat geschrieben:Dann muss ich einen Weg finden (falls es einen geben sollte), wie ich es schaffe, in dem Programm ein Ereignis zu erzeugen, auf welches dei GUI dann reagiert.
Vorausgesetzt du hast die tuts durchgearbeitet, ist das doch relativ simpel.

Du baust dir eine GUI in dem du eine Eingabezeile. Bei jedem eintragen der Antwort erzeugt das ``Eingabezeile``-Widget ein Event. Danach schreibst du eine Methode (Event-Handler) die an das Event gebindet wird. Immer wenn dann das Event ausgelöst wurde, wird dann diese Methode gestartet in den dann dein Code ausgeführt wird.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Ich bin jetzt schon mal so weit gekommen:

Code: Alles auswählen

from Tkinter import *

data = ["abc", "a, b, c", 0]
question, solve, stat = data
solution = solve.split(", ")

class Iface:
    def __init__(self, master):
        frame = Frame(master, width=300, height=200)
        frame.pack()
        self.label = Label(frame, text=question, pady=10)
        self.label.pack()
        self.entry = Entry(frame)
        self.entry.bind("<Return>", self.correct)
        self.entry.pack()
        self.solve=Button(frame, text="-------")
        self.solve.bind("<1>", self.next)
        self.solve.pack()

    def correct(self, event): 
        x = self.entry.get()
        print x
        if x in solution:
            respond = "Richtig!"
        else:
            respond = "Falsch! Richtig ist "+ solve
        self.solve.config(text=respond)

    def next(self, event):
        pass

root=Tk()
iface=Iface(root)
root.mainloop()
Ich muss es jetzt nur noch hinkriegen, mit next() die nächste Frage aufzurufen, und des ganze überhaupt erstmal zu starten.
BlackJack

Aktionen für Buttons gibt man in der Regel als `command`-Argument beim Erzeugen des Buttons an. Wenn Du die Maustaste an den Button bindest, verhält sich das ganze nicht so, wie man das von Buttons sonst gewohnt ist.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Wenn ich das mache, ist in der Funktion next die Variable self nicht bekannt. Ich habs nicht geschafft des zu umgehen.
Ich hab jetzt mal wieder ein paar Sachen in Angriff genommen, z.B. dass das Programm nicht mehr reagiert, wenn man 2 Mal hintereinander auf Enter bzw. den Button klickt. Dafür hab ich allerdings ein paar globale Variablen in Kauf nehmen müssen, was ich nicht so toll finde. Ich bin für jeden Verbesserungsvorschlag offen. Hier ist mal Code:

Code: Alles auswählen

from Tkinter import *

index = [["1", "2", 0], ["2", "3", 0], ["3", "5", 0], ["4", "7", 0]]
counter, switch = 0, 0

class Iface:
    def __init__(self, master):
        global solve
        question, solve, stat = index[0]

        frame = Frame(master, width=300, height=200)
        frame.pack()
        self.label = Label(frame, text=question, pady=10)
        self.label.pack()
        self.entry = Entry(frame)
        self.entry.bind("<Return>", self.correct)
        self.entry.pack()
        self.button=Button(frame, text="-------")
        self.button.bind("<1>", self.next)
        self.button.pack()

    def correct(self, event):
        global switch, counter
        if switch:
            pass
        else:
            counter = counter + 1
            x = self.entry.get()
            if x in solve.split(", "):
                respond = "Richtig!"
            else:
                respond = "Falsch! Richtig ist "+ solve
            self.button.config(text=respond)
            switch = 1 - switch
        
    def next(self, event):
        global switch, question, solve, stat
        if not switch:
            pass
        else:
            print "Button pressed"
            question, solve, stat = index[counter]
            self.label.config(text=question)
            self.entry.config(text="")
            self.button.config(text="-------")
            switch = 1 - switch
        

root=Tk()
iface=Iface(root)
root.mainloop()
Ich würde allerdings noch gerne wissen, ob man ein Entry-Feld reseten kann, damit es wieder leer wird. Ich habe das schon mit
self.entry.config(text="")
probiert, aber es hat nicht geklappt.

Außerdem, wieso funktionieren width und height im Frame (Z 11) nicht?
BlackJack

Das kann nicht sein das `self` nicht bekannt ist, das ist das erste Argument von `next()`.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Ja, wenn ich next() über .bind aufruf. Wenn ich es über command aufruf (wie du es vorgeschlagen hast) wird ihm nur das Event als Argument übergeben, aber nicht self.
Es wird ja dann mit command = next aufgerufen (nicht mit self.next).
Die Parameter lauten dann next(event) und nicht next(self, event).
Deswegen muss ich auch mit "<1>" arbeiten. Allerdings weicht das Verhalten gegenüber einem "gewöhnlich" erstellten Button mit command nur sehr gefingfügig ab, was ich wahrscheinlich in Kauf nehmen muss.

Ich bin aber für jede bessere Lösung offen, und wäre froh, wenn mir jemand einen Tipp geben könnte, wie ich index (die Vokabelliste) jetzt noch durch den Benutzer einstellen lassen kann, bevor die Abfrage startet. Bis hierher hab ich mich nämlich schon mehr schlecht als recht durchgewurschtel, und da Tkinter für mich absolutes Neuland ist, gehen mir allmählich die Umsetzungsideen aus.
Zuletzt geändert von klaus am Montag 24. September 2007, 19:05, insgesamt 1-mal geändert.
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

klaus hat geschrieben:
Die Parameter lauten dann next(event) und nicht next(self, event).
Hi versuch es doch mit einem lambda:

Code: Alles auswählen

self.button.bind("<1>", lambda event: self.next(self, event))
Sollte funktionieren.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

@ poker: bei .bind klappt es doch. Nur BlackJack hat gesagt, ich soll es mit command umsetzen und da hat die Funktion kein Parameter self. Außerdem weiß ich nicht mal im Ansatz, was lambda bewirkt.
poker
User
Beiträge: 146
Registriert: Donnerstag 20. September 2007, 21:44

klaus hat geschrieben:@ poker: bei .bind klappt es doch. Nur BlackJack hat gesagt, ich soll es mit command umsetzen und da hat die Funktion kein Parameter self. Außerdem weiß ich nicht mal im Ansatz, was lambda bewirkt.
Argh sorry, hab das ganze falsch verstanden.

Labmbda macht eine anonyme Funktion. ``f = lambda x: x`` macht das gleiche wie ``def f(x): return x`` mit dem unterschied das man lambda auch in ausdrücken lokal definieren kann, halt anonym. Damit kann man dann z.B. dirket in einem Funktionsaufruf, der als Parameter eine Funktion erwartet, eine Funktion definieren was mit ``def`` nicht geht.
BlackJack

@klaus: Die Funktion hat immer ein Argument `self`, das steht da als erstes in der Argumentliste. Das innerhalb der Methode `self` nicht definiert ist, ist einfach nicht möglich. Und `self` wird auch übergeben wenn man die Funktion von der Instanz holt und dabei eine gebundene Methode bekommt. Da ist absolut kein Unterschied ob man das nun bei `bind` oder beim Button als `command` übergibt.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Oh, ich hab meinen Fehler gemerkt. Ich dachte die Funktion erhält als Argument das Event, aber das wird gar nicht übergeben wenn man sie mit command aufruft. :oops:
Habs schon umgeschrieben und es funktioniert.

Übrigens, seh ich des richig, das ich Variablen in der Klasse zwar lesen kann, wenn sie außerhalb definiert sind, wenn ich sie aber ändern will, muss ich sie als global deklarieren?
BlackJack

Ja, aber Du willst sie nicht ändern. Oder zumindest solltest Du das nicht wollen.
Benutzeravatar
klaus
User
Beiträge: 88
Registriert: Samstag 23. Juni 2007, 09:33
Wohnort: Kaufbeuren
Kontaktdaten:

Wie meinst du das? Ich ändere doch z.B. switch und counter.

edit: Ich glaub ich mach für heute Schluss. Ich programmier erst morgen weiter.
http://klausweidinger.kl.funpic.de
BlackJack

Ich meine man will keine "globalen" Variablen ändern, das ist unsauber. Du könntest sie zum Beispiel an das Objekt binden.
Antworten