Button aktivieren

Fragen zu Tkinter.
Antworten
schmugo
User
Beiträge: 2
Registriert: Sonntag 10. Januar 2021, 22:22

Hallo Leute,

zunächst mal Hi, ich bin neu im Forum und auch noch sehr unerfahren mit Python bzw. generell mit Programmierung.
Vielleicht könnt ihr mir bei folgendem Problem mit Tkinter helfen. Ich will mich so kurz wie möglich fassen:

Ich habe eine Matrix aus 11x11 Reihen / Spalten erstellt, also insgesamt 121 Felder. Jedes Feld soll verschiedene Funktionen erfüllen können, deshalb habe ich die Klasse "Feld" erstellt. Die Darstellung der Felder erfolgt je nach Bedarf als tk.Button oder tk. Canvas. Nun hat jedes Feld als default das Attribut "aktiv" == False. Ich möchte dass sobald ich den Button / das Feld anklicke, "aktiv" == True gesetzt wird. Dafür habe ich die Funktion "aktiviere_button" geschrieben und als command dem Button zugewiesen. Sobald ich den Code aber starte ist das jeweilige Feld bereits aktiv bevor ich auf den Button klicke, obwohl als default "aktiv == False" ist.

Hier eine stark verkürzte Fassung von meinem Code:

Code: Alles auswählen

import tkinter as tk

root = tk.Tk()

def aktiviere_button(self):
    self.aktiv = True

sa1 = tk.PhotoImage(file='SA1.png')

class Feld:
    def __init__(self, feld_name, row,col,zustand = 'empty', aktiv = False):
        self.feld_name = feld_name
        self.row = row
        self.col = col
        self.zustand = zustand
        self.aktiv = aktiv


    def rendere_figur(self):
        if self.zustand == 'empty':
            feldzeichnung = tk.Button(root, image = sa1, bd = 0)
            feldzeichnung.grid(row = self.row, column = self.col)
            feldzeichnung.configure(command = aktiviere_button(self))

if __name__ == '__main__':
    test = Feld('Test-Name',0,0)
    test.rendere_figur()
    print(test.aktiv)
    tk.mainloop()

Kann mich jemand erleuchten wo mein Denkfehler liegt?
Sirius3
User
Beiträge: 17757
Registriert: Sonntag 21. Oktober 2012, 17:20

Du übergibst command nicht die Funktion aktiviere_button, sondern den Rückgabewert nach Aufrufen der Funktion.

Warum ist das überhaupt eine Funktion, und keine Methode von Feld? Und rendere_figur benutzt einige globale Variablen, was nicht sein sollte:

Code: Alles auswählen

import tkinter as tk


class Feld:
    def __init__(self, feld_name, row, col, zustand='empty', aktiv=False):
        self.feld_name = feld_name
        self.row = row
        self.col = col
        self.zustand = zustand
        self.aktiv = aktiv

    def rendere_figur(self, root, image):
        if self.zustand == 'empty':
            feldzeichnung = tk.Button(root, image=image, bd=0, command=self.aktiviere_button)
            feldzeichnung.grid(row = self.row, column = self.col)

    def aktiviere_button(self):
        self.aktiv = True

def main():
    root = tk.Tk()
    sa1 = tk.PhotoImage(file='SA1.png')
    test = Feld('Test-Name',0,0)
    test.rendere_figur(root, sa1)
    print(test.aktiv)
    root.mainloop()

if __name__ == '__main__':
    main()
Benutzeravatar
__blackjack__
User
Beiträge: 13120
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Noch ein paar Anregungen: Das `feld_` bei `feld_name` kann man sich sparen, denn der Datentyp heisst ja bereits `Feld`. ``print(feld.feld_name)`` ist nicht wirklich verständlicher als ``print(feld.name)``.

Keine kryptischen Abkürzungen. Nicht `col` wenn man `column` meint. Wäre nämlich auch eine prima Abkürzung für `colour` und würde bei einem `Feld` in einer GUI sogar nicht mal unwahrscheinlich sein, dass man dort eine Farbe angeben könnte.

Der Name `aktiviere_button()` ist falsch weil da gar kein Button aktiviert wird, sondern das Feld.

Für den Zustand würde ich mindestens normale Konstanten definieren, statt literale Zeichenketten mehrfach im Code stehen zu haben. Das ist fehleranfällig weil man sich vertippen kann. Wenn mehrere Konstanten zusammengehören, also zum Beispiel den Zustand eines Felds beschreiben, würde sich auch ein `enum`-Typ anbieten.

Zwischenstand:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from enum import Enum


class FeldZustand(Enum):
    EMPTY = "empty"
    FULL = "full"
    ...


class Feld:
    def __init__(
        self, name, row, column, zustand=FeldZustand.EMPTY, aktiv=False
    ):
        self.name = name
        self.row = row
        self.column = column
        self.zustand = zustand
        self.aktiv = aktiv

    def rendere_figur(self, root, image):
        if self.zustand is FeldZustand.EMPTY:
            tk.Button(root, image=image, bd=0, command=self.aktiviere).grid(
                row=self.row, column=self.column
            )

    def aktiviere(self):
        self.aktiv = True


def main():
    root = tk.Tk()
    image = tk.PhotoImage(file="SA1.png")
    feld = Feld("Test-Name", 0, 0)
    feld.rendere_figur(root, image)
    print(feld.aktiv)
    root.mainloop()


if __name__ == "__main__":
    main()
Besonders beim Zustand und Empty fällt ein gewisses Sprachwirrwarr auf. Man sollte sich für eins entscheiden, Englisch oder Deutsch. Ich würde zu Englisch neigen weil das weniger Probleme macht, beispielsweise wenn man die Mehrzahl für Listen, Mengen und ähnliche Datenstrukturen braucht.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
schmugo
User
Beiträge: 2
Registriert: Sonntag 10. Januar 2021, 22:22

Wenn ich "aktiviere_button" wie von Sirius vorgeschlagen als Methode von Feld schreibe wird das Problem tatsächlich gelöst. Ich versuche noch zu verstehen womit das zusammenhängt, da meine ursprüngliche Vorgehensweise mit anderen Funktionen vorher funktioniert hat. Beispiel:

Code: Alles auswählen


import tkinter as tk
import random

root = tk.Tk()

anzeige_zahl = tk.Label(root, text = 'leer')
anzeige_zahl.grid(row = 1, column = 1)

def wuerfeln():
    global zahl
    zahl = random.randint(42,1337)
    anzeige_zahl.configure(text=zahl)


class Feld:
    def __init__(self, feld_name, row,col,zustand = 'empty', aktiv = False):
        self.feld_name = feld_name
        self.row = row
        self.col = col
        self.zustand = zustand
        self.aktiv = aktiv

    def aktiviere_button(self):
        self.aktiv = True


    def rendere_figur(self):
        feldzeichnung = tk.Button(root,text = "wuerfel")
        feldzeichnung.grid(row = self.row, column = self.col)
        feldzeichnung.configure(command = wuerfeln)

if __name__ == '__main__':
    test = Feld('Test-Name',0,0)
    test.rendere_figur()
    tk.mainloop()
    
Ich danke euch für eure zahlreichen Anmerkungen, wirklich hilfreich. Vor allem bei der Benennung meiner Variablen etc. werde ich in Zukunft präziser und konsistenter vorgehen.
Sirius3
User
Beiträge: 17757
Registriert: Sonntag 21. Oktober 2012, 17:20

Der Unterschied ist, ob eine Funktion aufgerufen wird wuerfeln() oder ob man die Funktion an sich übergibt: wuerfeln ohne Klammern.

Es ist schlecht, globale Variablen zu benutzen, auf oberster Ebene sollte kein ausführbarer Code stehen und alles was eine Funktion braucht, muß sie auch über die Parameter bekommen, bei wuerfeln also anzeige_zahl.

Weil man wuerfeln also ein Argument übergeben muß, braucht man einen Mechanismus, der das kann, z.B. functools.partial:

Code: Alles auswählen

import tkinter as tk
import random
from functools import partial

def wuerfeln(anzeige_zahl):
    zahl = random.randint(42,1337)
    anzeige_zahl.configure(text=zahl)


class Feld:
    def __init__(self, feld_name, row, column, zustand='empty', aktiv=False):
        self.feld_name = feld_name
        self.row = row
        self.column = column
        self.zustand = zustand
        self.aktiv = aktiv

    def aktiviere_button(self):
        self.aktiv = True

    def rendere_figur(self, root, anzeige_zahl):
        feldzeichnung = tk.Button(root, text="wuerfel")
        feldzeichnung.grid(row=self.row, column=self.column)
        feldzeichnung.configure(command=partial(wuerfeln, anzeige_zahl))


def main():
    root = tk.Tk()
    anzeige_zahl = tk.Label(root, text='leer')
    anzeige_zahl.grid(row=1, column=1)
    test = Feld('Test-Name', 0, 0)
    test.rendere_figur(root, anzeige_zahl)
    root.mainloop()

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