PIN-Feld erstellen durch For-Schleife

Fragen zu Tkinter.
Antworten
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo zusammen,

ich beschäftige mich nun seit ein paar Tagen mit Python und möchte eine Anwendung mit folgendem Ziel realisieren:
  • Anzeigen eines Nummernfelds mit 0-9
  • Anzeigen eines Buttons "öffnen" und "zurück" im selben Raster
  • Anhängen gedrückten Zahlen an Variable pin
  • Überprüfen und Löschen der Variable pin
Mit dem unterstehenden Code habe ich das auch abgebildet bekommen. Allerdings meiner Meinung nach etwas umständlich. Ich möchte gerne die Buttons mit Hilfe einer For-Schleife und einer Liste erstellen. Das reine "Erstellen" der Buttons habe ich auch so hinbekommen (siehe unten). Allerdings finde ich keine Möglichkeit unterschiedliche Methoden aufzurufen und auch unterschiedliche Variablen zu übergeben.

Code: Alles auswählen

button = [
    ['1', '2', '3'],
    ['4', '5', '6'],
    ['7', '8', '9'],
    ['zurück', '0', 'Öffnen'],
]

for y, row in enumerate(button, 1):
    for x, key in enumerate(row):
        b = tk.Button(width=6,height=3,bg='#BFBFBF',text=button)
        b.grid(row=y, column=x, padx=1, pady=1)
Da ich Anfänger bin, bin ich auch für jeden Typ was Konvention und sinnvolles Strukturieren des Codes angeht dankbar. Z.B. die Frage ob meine Klasse schon zu umfangreich ist und besser aufgeteilt werden sollte?

Vielen Dank im Voraus für eure Anregungen.

Grüße Patze

Code: Alles auswählen

import tkinter as tk
from PIL import Image, ImageTk
#in dieser Klasse werden Buttons eines Pin Felds erstellt und beim Druck auf
#den Button der entsprechende Wert an die Variable Pin angehängt.

class pin_eingabe(tk.Frame):
    def __init__(self, parent):

       self.pin = "" # Iniatilisieren von Pin
       self.breite = 50 # Button-Breite in px
       self.hoehe = 50 # Button-Höhe in px
       self.button_farbe = "#BFBFBF" #Button Farbe
       self.pixel = tk.PhotoImage(width=1, height=1) #transparentes Image um Größe der Buttons in px zu definieren
       self.hintergrund_foto = Image.open("hintergrund.jpg")
       self.hintergrund_bild = ImageTk.PhotoImage(self.hintergrund_foto)

       self.hintergrund_label = tk.Label(parent, image=self.hintergrund_bild) #Label als Hintergrundbild
       self.hintergrund_label.place(x=0, y=0, relwidth=1, relheight=1)

        #Definition der Button Eigenschaften
       button_1 = tk.Button(width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=1,
                            command =lambda :self.zahl_eingeben("1"))
       button_2 = tk.Button(width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=2,
                            command =lambda :self.zahl_eingeben("2"))
       button_3 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=3,
                            command =lambda :self.zahl_eingeben("3"))
       button_4 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=4,
                            command =lambda :self.zahl_eingeben("4"))
       button_5 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=5,
                            command =lambda :self.zahl_eingeben("5"))
       button_6 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=6,
                            command =lambda :self.zahl_eingeben("6"))
       button_7 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=7,
                            command =lambda :self.zahl_eingeben("7"))
       button_8 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=8,
                            command =lambda :self.zahl_eingeben("8"))
       button_9 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=9,
                            command =lambda :self.zahl_eingeben("9"))
       button_0 = tk.Button(
                            width=self.breite,
                            font = "futura 15 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text=9,
                            command =lambda :self.zahl_eingeben("0"))
       button_oeffnen = tk.Button(
                                  width=self.breite,
                            font = "futura 8 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text="Öffnen",
                            command =lambda :self.pin_kontrollieren())

       button_loeschen = tk.Button(
                                   width=self.breite,
                            font = "futura 8 bold",
                            height=self.hoehe,
                            bg=self.button_farbe,
                            compound = "c",
                            image=self.pixel,
                            text="Zurück",
                            command =lambda :self.zahl_loeschen())

        #Initalisieren der Buttons
       self.abstand_x = 10
       self.abstand_y =10
       button_1.grid(row=1, column=0, padx=self.abstand_x, pady=self.abstand_y)
       button_2.grid(row=1, column=1, padx=self.abstand_x, pady=self.abstand_y)
       button_3.grid(row=1, column=2, padx=self.abstand_x, pady=self.abstand_y)
       button_4.grid(row=2, column=0, padx=self.abstand_x, pady=self.abstand_y)
       button_5.grid(row=2, column=1, padx=self.abstand_x, pady=self.abstand_y)
       button_6.grid(row=2, column=2, padx=self.abstand_x, pady=self.abstand_y)
       button_7.grid(row=3, column=0, padx=self.abstand_x, pady=self.abstand_y)
       button_8.grid(row=3, column=1, padx=self.abstand_x, pady=self.abstand_y)
       button_9.grid(row=3, column=2, padx=self.abstand_x, pady=self.abstand_y)
       button_0.grid(row=4, column=1, padx=self.abstand_x, pady=self.abstand_y)

       button_loeschen.grid(row=4, column=0, padx=self.abstand_x, pady=self.abstand_y)
       button_oeffnen.grid(row=4, column=2, padx=self.abstand_x, pady=self.abstand_y)

       self.pin_anzeige = tk.Entry(width=20, font = "futura 15 bold" )
       self.pin_anzeige.grid(row=0, column=0, columnspan=3, ipady=7, pady=30, padx=20)

    #Anhägen des Buttonwert an "pin"
    def zahl_eingeben(self,zahl):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        print(self.pin)
        self.pin += zahl
        print(self.pin)
        self.pin_anzeige.insert('end',self.pin)
    #Löschen der letzten Zahl
    def zahl_loeschen(self):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        print(self.pin)
        self.pin = self.pin[:-1]
        print(self.pin)
        self.pin_anzeige.insert('end',self.pin)
    #Überprüfen von Pin
    def pin_kontrollieren(self):

        if self.pin == "1988":
            self.pin_anzeige["bg"] = "green"
        else:
            print('Pin wiederholen')
            self.pin_anzeige["bg"]="red"
            self.pin = ''  # Pin leeren
            self.pin_anzeige.delete('0', 'end')

def main():
    root = tk.Tk()
    w, h = 800, 460
    root.geometry("%dx%d+0+0" % (w, h))
    root.configure(background='white')
    fenster = pin_eingabe(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Die unterschiedlichen Methoden sind bei Dir ja nur zwei Spezialknöpfe. Für die anderen brauchst Du functools.partial, um Parameter zu übergeben.

Code: Alles auswählen

for y, row in enumerate(button, 1):
    for x, key in enumerate(row):
        if key == 'Zurück':
            command = self.zahl_loeschen
        elif key == 'Öffnen':
            command = self.pin_kontrollieren
        else:
            command = partial(self.zahl_eingeben, key)
        b = tk.Button(width=6,height=3,bg='#BFBFBF',text=button, command=command)
        b.grid(row=y, column=x, padx=1, pady=1)
oder eben statt der if.s ein Wörterbuch.
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Danke für die Antwort. Die Idee ist super allerdings bekomme ich Sie wohl nicht richtig umgesetzten. Wenn ich meine Buttons mit:

Code: Alles auswählen

import tkinter as tk
from PIL import Image, ImageTk
from functools import partial
#in dieser Klasse werden BUttons eines Pin Felds erstellt und beim Druck auf
#den Button der entsprechende Wert an die Variable Pin angehängt.

class pin_eingabe(tk.Frame):
    def __init__(self, parent):

       self.pin = "" # Iniatilisieren von Pon
       self.breite = 50 # Button-Breite in px
       self.hoehe = 50 # Button-Höhe in px
       self.button_farbe = "#BFBFBF" #Button Farbe
       self.pixel = tk.PhotoImage(width=1, height=1) #transparentes Image um Größe der Buttons in px zu definieren
       self.hintergrund_foto = Image.open("hintergrund.jpg")
       self.hintergrund_bild = ImageTk.PhotoImage(self.hintergrund_foto)

       self.hintergrund_label = tk.Label(parent, image=self.hintergrund_bild) #Label als Hintergrundbild
       self.hintergrund_label.place(x=0, y=0, relwidth=1, relheight=1)
       self.button =[
                ['1','2','3'],
                ['4','5','6'],
                ['7','8','9'],
                ['zurück','0','Öffnen'],
                    ]
       print((self.button, 1))
       for y, row in enumerate(self.button, 1):
           for x, self.button in enumerate(row):
               if self.button == 'Zurück':
                   command = self.zahl_loeschen
               elif self.button == 'Öffnen':
                   command = self.pin_kontrollieren
               else:
                   command = partial(self.zahl_eingeben, self.button)
               b = tk.Button(self.breite, self.hoehe,  bg=self.button_farbe,compound = "c",image=self.pixel, text=self.button, command=command)
               b.grid(row=y, column=x, padx=1, pady=1)



       self.pin_anzeige = tk.Entry(width=20, font = "futura 15 bold" )
       self.pin_anzeige.grid(row=0, column=0, columnspan=3, ipady=7, pady=30, padx=20)

    #Anhägen des Buttonwert an "pin"
    def zahl_eingeben(self,zahl):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        print(self.pin)
        self.pin += zahl
        print(self.pin)
        self.pin_anzeige.insert('end',self.pin)
    #Löschen der letzten Zahl
    def zahl_loeschen(self):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        print(self.pin)
        self.pin = self.pin[:-1]
        print(self.pin)
        self.pin_anzeige.insert('end',self.pin)
    #Überprüfen von Pin
    def pin_kontrollieren(self):

        if self.pin == "1988":
            self.pin_anzeige["bg"] = "green"
        else:
            print('Pin wiederholen')
            self.pin_anzeige["bg"]="red"
            self.pin = ''  # Pin leeren
            self.pin_anzeige.delete('0', 'end')

def main():
    root = tk.Tk()
    w, h = 800, 460
    root.geometry("%dx%d+0+0" % (w, h))
    root.configure(background='white')
    fenster = pin_eingabe(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Erstellen möchte erhalte ich immer den Fehler:

Code: Alles auswählen

Traceback (most recent call last):
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 106, in _cnfmerge
    cnf.update(c)
TypeError: 'int' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "C:/Users/Patrick/PycharmProjects/tuer_oeffner/pin_eingabe2.py", line 79, in <module>
    main()
([['1', '2', '3'], ['4', '5', '6'], ['7', '8', '9'], ['zurück', '0', 'Öffnen']], 1)
_cnfmerge: fallback due to: 'int' object is not iterable
  File "C:/Users/Patrick/PycharmProjects/tuer_oeffner/pin_eingabe2.py", line 75, in main
    fenster = pin_eingabe(root)
  File "C:/Users/Patrick/PycharmProjects/tuer_oeffner/pin_eingabe2.py", line 35, in __init__
    b = tk.Button(self.breite, self.hoehe,  bg=self.button_farbe,compound = "c",image=self.pixel, text=self.button, command=command)
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2369, in __init__
    Widget.__init__(self, master, 'button', cnf, kw)
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 2290, in __init__
    cnf = _cnfmerge((cnf, kw))
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 109, in _cnfmerge
    for k, v in c.items():
AttributeError: 'int' object has no attribute 'items'
Und leider verstehe ich den Fehler nicht wirklich...
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patze: Was ist denn die Bedeutung der ersten beiden Argumente von `tk.Button()`? Also ich meine nicht das was Du da im Code geschrieben hast, das ist nämlich falsch, sondern was erwartet denn `Button` an der Stelle? Wie kommst Du darauf das das Höhe und Breite wären?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo Blackjack,

danke für den Tipp. Nachdem ich alle Eigenschaften an die richtige Stelle verschoben hatte läuft das Ganze super.
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo zusammen,

nach einiger Zeit habe ich das Projekt wieder aufgenommen und es funktioniert auch soweit ich das möchte. Eine letzte Funktion bekomme ich allerdings nicht realisiert.
Nach dem der Richtige oder der Falsche Pin eingeben wurde verfärbt sich das Eingabefeld. Dies ist auch so gewünscht. Allerdings würde ich gerne nach einer bestimmten Zeit das Feld wieder in den Grundzustand zurück versetzten. Vielleicht hat jemand eine Idee ich habe schon verschieden Varianten mit time.sleep und der .after funktion ausprobiert aber noch nicht den gewünschten Effekt erzielt.

Code: Alles auswählen

import tkinter as tk
from PIL import Image, ImageTk
from functools import partial

class pin_eingabe(tk.Frame):
    def __init__(self, parent):

        self.pin = ""  # Iniatilisieren von Pon
        self.breite = 65  # Button-Breite in px
        self.hoehe = 50  # Button-Höhe in px
        self.button_farbe = "#F4F4F4"  # Button Farbe
        self.pixel = tk.PhotoImage(width=1, height=1)  # transparentes Image um Größe der Buttons in px zu definieren
        self.hintergrund_foto = Image.open("hintergrund.jpg")
        self.hintergrund_bild = ImageTk.PhotoImage(self.hintergrund_foto)

        #self.hintergrund_label = tk.Label(parent, image=self.hintergrund_bild)  # Label als Hintergrundbild
        #self.hintergrund_label.place(x=0, y=0, relwidth=1, relheight=1)
        self.button = [
            ['1', '2', '3'],
            ['4', '5', '6'],
            ['7', '8', '9'],
            ['zurück', '0', 'Öffnen'],
        ]
        for y, row in enumerate(self.button, 1):
            for x, self.button in enumerate(row):
                if self.button == 'zurück':
                    command = self.zahl_loeschen
                elif self.button == 'Öffnen':
                    command = self.pin_kontrollieren
                else:
                    command = partial(self.zahl_eingeben, self.button)
                b = tk.Button(bg=self.button_farbe,
                              compound="c",
                              font="Futura 14 bold",
                              width=self.breite,
                              height=self.hoehe,
                              image=self.pixel,
                              text=self.button,
                              command=command)
                b.grid(row=y + 1, column=x + 1, padx=15, pady=10)

        self.platzhalter = tk.Label(bg="black", compound="c", image=self.pixel, width=55, height=1)
        self.platzhalter.grid(row=0, column=0, padx=0)

        self.pin_anzeige = tk.Entry(justify="center", width=30, font="futura 15 bold")
        self.pin_anzeige.grid(row=1, column=1, columnspan=3, ipady=7, pady=35, padx=20)
        print("erstellen")
        self.pin_anzeige.insert('end', "Bitte PIN eingeben")

    # Anhägen des Buttonwert an "pin"
    def zahl_eingeben(self, zahl):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        self.pin += zahl
        self.pin_anzeige.insert('end', self.pin)

    # Löschen der letzten Zahl
    def zahl_loeschen(self):
        self.pin_anzeige["bg"] = "white"
        self.pin_anzeige.delete('0', 'end')
        self.pin = self.pin[:-1]
        self.pin_anzeige.insert('end', self.pin)

    # Überprüfen des Pins
    def pin_kontrollieren(self):

        if self.pin == "1988":
            self.pin_anzeige.delete('0', 'end')
            self.pin_anzeige["bg"] = "green"
            self.pin_anzeige.insert('end', "Tür geöffnet")
            self.pin = ''  # Pin leeren


        else:
            self.pin_anzeige["bg"] = "red"
            self.pin = ''  # Pin leeren
            self.pin_anzeige.delete('0', 'end')
            self.pin_anzeige.insert('end', "Bitte PIN eingeben")


def main():
    root = tk.Tk()
    w, h = 800, 460
    root.geometry("%dx%d+0+0" % (w, h))
    root.configure(background='white')
    pin_eingabe(root)
    root.mainloop()

if __name__ == '__main__':
    main()
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Ein Attribut, wie ›self.button‹, in einer for-Schleife als Variable zu benutzen ist eigentlich nie sinnvoll. Hier kommt noch dazu, dass ›self.button‹ schon an was anderes gebunden ist, was eigentlich BUTTONS heißen müßte.
Zu Deinem Problem: ›after‹ ist schon das richtige. Was hast Du versucht?
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo Sirius,

danke für deine Antwort hat du einen Vorschlag wie ich die for-Schleife anders aufbauen kann?

Zu dem "after"-Problem; Ich hatte versucht eine Funktion zu definieren in der die Farbe wieder geändert wird und diese nach eine Zeit aufzurufen. Siehe unten.. Erhalte jedoch einem Fehler:

Code: Alles auswählen

def pin_kontrollieren(self):

        if self.pin == "1988":
            self.pin_anzeige.delete('0', 'end')
            self.pin_anzeige["bg"] = "green"
            self.pin_anzeige.insert('end', "Tür geöffnet")
            self.pin = ''  # Pin leeren
            self.after(500,self.ursprung)


        else:
            self.pin_anzeige["bg"] = "red"
            self.pin = ''  # Pin leeren
            self.pin_anzeige.delete('0', 'end')
            self.pin_anzeige.insert('end', "Bitte PIN eingeben")

    def ursprung(self):
        self.pin_anzeige["bg"] = "white"
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Jetzt wäre es noch interessant, den Fehler zu kennen.
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo Sirius,

das war der ausgebene Fehler

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "C:/Users/Patrick/PycharmProjects/tuer_oeffner/pin_eingabe_forum.py", line 72, in pin_kontrollieren
    self.after(500,self.ursprung)
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 756, in after
    name = self._register(callit)
  File "C:\Users\Patrick\AppData\Local\Programs\Python\Python37\lib\tkinter\__init__.py", line 1372, in _register
    self.tk.createcommand(name, f)
AttributeError: 'pin_eingabe' object has no attribute 'tk'
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patze: Wenn man von einer Basisklasse erbt, muss man in aller Regel auch die `__init__()` der Basisklasse aufrufen. Sonst wird die Initialisierung die dort gemacht werden muss, nicht gemacht.

Und die Klasse sollte von der Schreibweise her `PinEingabe` heissen und nicht `pin_eingabe`.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo zusammen,

nachdem ich gerade dabei bin das damals auf Eis gelegte Projekt wieder weiter zu verfolgen öffne ich die Frage mal wieder, da ich damals leider mit deiner Antwort nicht auf den richtigen Lösungsweg gekommen bin. Hast du noch mehr Tipps für mich an welcher stelle muss ich die Basisklasse initialisieren?
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Meist als erster Aufruf in __init__ der Kindklasse.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@Patze: Spätestens bevor irgendetwas benutzt wird was diese Initialisierung voraussetzt, am besten so früh wie möglich damit man sich gar keine Gedanken machen muss wann das wohl der Fall sein mag. Also gleich am Anfang von Deinem Initialisierungscode.
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Patze
User
Beiträge: 8
Registriert: Dienstag 15. Januar 2019, 18:18

Hallo Ihr beiden ich stehe wohl mächtig auf dem Schlauch könnt ihr mir bitte ein Code Beispiel geben. Bin hier am verzweifeln ;-). Vielen Dank.
Sirius3
User
Beiträge: 17711
Registriert: Sonntag 21. Oktober 2012, 17:20

Das sollte eigentlich in jedem Tutorial zu OOP vorkommen.

Code: Alles auswählen

class PinEingabe(tk.Frame):
    def __init__(self, parent):
        super().__init__(parent)
        ...
Antworten