Einen Button mit klick selbst zerstören lassen?

Fragen zu Tkinter.
Antworten
Herr Mo
User
Beiträge: 3
Registriert: Freitag 31. Januar 2020, 17:08

Hallo zusammen,
aktuell sitze ich an meinem ersten eigenständigen Python-Projekt; Hangman.
nun habe ich kleine Buttons für jeden Buchstaben + Umlaute in einer for Schleife. Den Inhalt (also A, B, C,..) und den Namen (hab ich das?) entnehme ich dementsprechend einer Liste.
Nun möchte ich dass sich diese Buttons bei einem Klick selbst zerstören. Ich verstehe leider die ganze Geschichte mit dem self.* nicht ganz..
Mein aktueller Code (das eigentliche Fenster öffnet sich beim Bestätigen des eingegebenen Wortes) ist folgender:

Code: Alles auswählen

from tkinter import *
def guess_destroy():
    #????????????
    print("???????????")
#okay button -> neues fenster mit strichen anstelle von buchstaben
def submit():
    #abspeichern des wortes in guess_word
    guess_word=entry_word.get()
    #game fenster
    game_word=entry_word.get()
    game = Tk(className="Hangman V1 - The Game")
    game.geometry("900x200")
    #eingabefeld für versuche
    entry_guess=Entry(game)
    entry_guess.focus()
    entry_guess.place(relx=0.5,rely=0.5,height=30,width=150)
    #button um versuch zu bestätigen
    button_enter_guess=Button(game,text="Raten!")
    button_enter_guess.place(relx=0.5,rely=0.6)
    #zählen der anzahl von zeichen des wortes und erstellen der unterstriche
    counter_underscore=len(entry_word.get())
    label_underscore=Label(game,text="__  "*counter_underscore)
    label_underscore.config(font=(75))
    label_underscore.place(relx=0.5,rely=0.1)
    #suchbuchstaben in einzelne label packen
    x_c=10
    loop=28
    while loop>0:
        letters=["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","Ä","Ö","Ü","ß"]
        for letter in letters:
            letter=letter_button=Button(game,text=letter,command=guess_destroy)
            letter_button.place(x=x_c,rely=0.8,height=30,width=20)
            x_c=x_c+25
            loop=loop-1
    #rauslöschen von inhalt aus eingabefeld in den settings
    entry_word.delete(first=0,last=999)
#settings fenster
root = Tk(className="Hangman V1 - Settings")
root.geometry("500x100")
#schriftzug wort eingeben
label_eingabe=Label(root,text="Gib das zu suchende Wort ein!")
label_eingabe.place(x=10,y=10)
#eingabefeld gesuchtes wort
entry_word=Entry(root)
entry_word.focus()
entry_word.place(x=10,y=40,height=30,width=150)
#okay button mit neuem fenster als verknüpfung
bsubmit=Button(root,text="Okay!",command=submit)
bsubmit.place(x=170,y=40,height=30,width=80)
#quit button
bclose=Button(root,text="Schließen",command=root.destroy)
bclose.place(x=410,y=40,height=30,width=80)

mainloop()
Vielen Dank im Voraus für jegliche Hilfe :)
Benutzeravatar
__blackjack__
User
Beiträge: 14092
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Herr Mo: Das mit dem `self.*` solltest Du aber lernen, weil man für jede nicht-triviale GUI-Anwendung Objektorientierung benötigt. Und das sollte man IMHO am besten verstanden haben *bevor* man mit GUI-Programmierung anfängt, denn sonst muss man objektorientierte Programmierung (OOP) *und* die ereingnisbasierte Programmierung die GUIs mit sich bringen *gleichzeitig* lernen. Es ist IMHO einfacher diese beiden Konzepte nacheinander zu lernen.

Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` hunderte von Namen ins Modul von denen nur ein Bruchteil tatsächlich benötigt werden. Auch Namen die gar nicht im `tkinter`-Modul definiert werden, sondern von diesem Modul seinerseits für eigene Zwecke importiert werden.

Das ist alles sehr ”gequetscht” geschrieben. Zwischen Funktionsdefinitionen gehören Leerzeilen und nach Kommas, um binäre Operatoren, und um Gleicheitszeichen bei Zuweisungen ausserhalb von Argumentlisten erhöhen Leerzeichen die lesbarkeit.

Auf Modulebene gehört nur Code der Konstanten, Funktionen, und Klassen definiert. Das Hauptprogramm steht üblicherweise in einer Funktion die `main()` heisst.

Das `className`-Argument von `tkinter.Tk` ist nicht dazu da den Fenstertitel zu setzen. Das ist nur ein Nebeneffekt davon den Tk-Widget-Klassennamen auf etwas anderes als "Tk" zu setzen. Zum setzen des Fenstertitels ist die `title()`-Methode da.

Namen sollten dem Leser ohne raten zu müssen und irgendwelchen kryptischen Abkürzungen vermitteln was der Wert dahinter bedeutet. Wenn man also `submit_button` meint, sollte man nicht `bsubmit` schreiben. Die Reihenfolge ist bei sehr vielen Namen in dem Programm auch ”yodamässig”. `label_eingabe` müsste eingentlich `eingabelabel` heissen, `entry_word` müsste `word_entry` heissen, und so weiter. Man sollte nicht „denglisch“ benennen, also nicht `eingabelabel` sondern `eingabebeschriftung` oder `entry_label`. Insgesamt macht englisch mehr Sinn weil auch die Schlüsselworte von Python und die ganzen Funktionen und Module englische Bezeichner verwenden.

`place()` sollte man nicht verwenden. Mit absoluten Positions- und Grössenangaben schon mal gar nicht, aber auch relative Angaben führen zu kaputten, unschönen Anordnungen:
Bild

Funktionen und Methoden sollten alles was sie ausser Konstanten benötigen als Argument(e) übergeben bekommen. Da auf Modulebene keine Variablen sind – das ist ja alles in der `main()`-Funktion verschwunden — muss man `word_entry` als Argument an `submit()` übergeben.

`submit()` ist ein zu generischer Name für die Funktion. `start_game()` wäre beispielsweise viel aussagekräftiger.

Weder `guess_word` noch `game_word` werden in der Funktion irgendwo noch mal benutzt. Andererseits sollte man das vielleicht machen statt das Entry aus dem Hauptfenster mehrfach abzufragen obwohl das immer den gleichen Wert liefert.

Um den Inhalt eines Entry zu löschen hofft man nicht das 999 wohl gross genug ist, sondern verwendet `tkinter.END`.

`Tk` ist *das* Hauptfenster. Davon darf es immer nur ein Exemplar geben. Zusätzliche Fenster erstellt man mit `tkinter.Toplevel`.

Ein Fenster sollte besser `window` als `game` heissen.

`config()` direkt nach dem erstellen eines Widget-Objekts ist nur nötig wenn man in der/den Option(en) das Widget-Objekt selbst benötigt. Sonst kann und sollte man die Option(en) direkt beim erstellen schon setzen. Klammern um eine Zahl zu setzen macht auch keinen Sinn.

Die ``while``-Schleife macht so überhaupt gar keinen Sinn weil die immer *genau* *einmal* durchlaufen wird. Schleifen sind dazu da Code mehrfach zu durchlaufen, wenn das immer nur genau einmal passiert, dann ist das keine Schleife. `loop` verschwindet dann auch, weil damit sowieso nichts sinnvolles gemacht wird.

Statt die Buchstaben alle einzeln in einer Liste anzugeben, wäre es weniger Tipparbeit eine Zeichenkette mit allen Buchstaben anzulegen. Und da kann man sich dann auch noch mal fehleranfällige Tipparbeit sparen weil es die Grossbuchstaben von A bis Z im `string`-Modul als Konstante gibt.

`letter` wird durch die ``for``-Schleife an eine Zeichenkette mit einem Buchstaben gebunden und *in* der ``for``-Schleife bindest Du den Namen dann an einen `Button`. Das macht keinen Sinn.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial
from string import ascii_uppercase

ALPHABET = ascii_uppercase + "ÄÖÜß"


def start_game(word_entry):
    secret_word = word_entry.get()
    word_entry.delete(0, tk.END)

    window = tk.Toplevel()
    window.title("Hangman V1 - The Game")

    underscore_label = tk.Label(
        window, text="__  " * len(secret_word), font=75
    )
    underscore_label.pack(side=tk.TOP)

    guess_entry = tk.Entry(window)
    guess_entry.focus()
    guess_entry.pack(side=tk.TOP)

    tk.Button(window, text="Raten!").pack(side=tk.TOP)

    letter_frame = tk.Frame(window)
    for letter in ALPHABET:
        letter_button = tk.Button(letter_frame, text=letter)
        letter_button["command"] = letter_button.destroy
        letter_button.pack(side=tk.LEFT)
    letter_frame.pack(side=tk.TOP)


def main():
    root = tk.Tk()
    root.title("Hangman V1 - Settings")

    tk.Label(root, text="Gib das zu suchende Wort ein!").pack(side=tk.TOP)

    frame = tk.Frame(root)

    word_entry = tk.Entry(frame, width=15)
    word_entry.focus()
    word_entry.pack(side=tk.LEFT)

    tk.Button(
        frame, text="Okay!", command=partial(start_game, word_entry)
    ).pack(side=tk.LEFT)

    tk.Button(frame, text="Schließen", command=root.quit).pack(side=tk.LEFT)

    frame.pack(side=tk.TOP)

    root.mainloop()


if __name__ == "__main__":
    main()
Wie gesagt sollte man aber vorher schon OOP drauf haben und auch die Programmlogik ohne die GUI schon damit umgesetzt haben. Wenn das funktioniert, kann man eine GUI drauf setzen.

Es fehlt auch noch eine Überprüfung der Eingabe vor dem Spielstart, also das der Benutzer nur legale Zeichen eingegeben hat und ob er überhaupt etwas eingegeben hat.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Herr Mo
User
Beiträge: 3
Registriert: Freitag 31. Januar 2020, 17:08

@__blackjack__
Okay, danke schon einmal für diese extrem ausführliche Antwort.
Möglicherweise bin ich zu schnell an zu viele verschiedene Dinge herangegangen. Da ich niemanden habe der bisher mal da drüber geschaut hat sind mir diese ganzen Fehler die du aufzählst nie aufgefallen oder zum Problem geworden. Dass viele Dinge zwar deklariert sind, aber nie verwendet werden oder nur schwammig und unfertig funktionieren, liegt ganz einfach daran dass noch nicht alles drin ist was ich mir so vorgestellt habe. Bisher sind alle Funktionen nur ein Test gewesen und dienten eher dem Zweck zu schauen "könnte das so funktionieren?".
Keine absoluten oder relativen Größen und Positionen... aber wie denn dann? ich fand gerade schön, alles selbst zu definieren und genau dort zu haben wo ich es hin möchte, auch wenn ich noch mit relx etc. experimentiere.

Also würdest du mir generell empfehlen das ganze nochmal grundlegend neu anzufangen?
Benutzeravatar
__blackjack__
User
Beiträge: 14092
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Herr Mo: In meinem Zwischenstand ist ja kein `place()` mehr enthalten. Und auch die Fenstergrösse(n) per `geometry()` sind weg, weil sich das ja jetzt von selbst regelt: die Fenster sind automatisch so gross das alles rein passt.

Was heisst denn genau dort zu haben wo Du es hin möchtest? Das die „Raten!“-Schaltfläche in das Eingabefeld hinein ragt war doch sicher nicht so gewollt‽ Es macht einfach keinen Sinn sich selbst um absolute Positionen und Grössen zu kümmern, denn wenn man dann Code schreibt der alles mögliche wie Systemeinstellungen von Schriftgrössen, Bildschirmauflösungen, mit in die Positionierung einbezieht, programmiert man am Ende Layoutmanager nach, die GUI-Rahmenwerke schon fertig anbieten. Die Alternative zu absoluten Positionen sind relative Positionen. Mit `pack()` kann man Anzeigeelemente innerhalb eines Containerwidgets untereinander oder nebeneinander anordnen und mit `grid()` kann man Anzeigeelemente innerhalb eines Containerwidgets in einem Gitter in Spalten und Zeilen anordnen.
“It is easier to change the specification to fit the program than vice versa.” — Alan J. Perlis
Herr Mo
User
Beiträge: 3
Registriert: Freitag 31. Januar 2020, 17:08

@__blackjack__
Ich habe gar nicht gesehen gehabt, dass du dort im Zwischenstand Dinge geändert hast.. Hab ich wohl in der Enttäuschung übersehen. War eine Weile nicht mehr dran aber vielleicht schau ich es mir nochmal an. Vielen Dank nochmal, durch die Überarbeitung wird einiges klarer.
Antworten