Passwortgenerator mit GUI verbinden

Fragen zu Tkinter.
Antworten
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Hallo Leute,

ich habe einen kleinen Passwortgenerator geschrieben. Diesem möchte ich jetzt eine GUI basteln.

Der Code des Passwortgenerators sieht wie folgt aus:

Code: Alles auswählen

from random import randint
import string

KLEINBUCHSTABEN = list(string.ascii_lowercase)
GROSSBUCHSTABEN = list(string.ascii_uppercase)
ZAHLEN = list(string.digits)
SONDERZEICHEN = list(string.punctuation) 


def erstelle_passwort(passwortlaenge, zeichen_liste):
    passwort = ""
    for i in range(passwortlaenge):
        passwort += zeichen_liste[randint(0, len(zeichen_liste))]
        #print(zeichen_liste[randint(0, len(zeichen_liste))], end ="")     
    print(passwort)                                    
                                        
passwortlaenge = int(input("\nPasswortlänge: "))
zeichenauswahl = int(input(
"""\nWelche Zeichen sollen vorkommen?

1 - Nur Kleinbuchstaben
2 - Nur Großbuchstaben
3 - Nur Zahlen
4 - Nur Sonderzeichen
5 - Alle

Auswahl: """))


if zeichenauswahl == 1:
    zeichen_liste = KLEINBUCHSTABEN
    erstelle_passwort(passwortlaenge, zeichen_liste)
elif zeichenauswahl == 2:
    zeichen_liste = GROSSBUCHSTABEN
    erstelle_passwort(passwortlaenge, zeichen_liste)
elif zeichenauswahl == 3:
    zeichen_liste = ZAHLEN
    erstelle_passwort(passwortlaenge, zeichen_liste)
elif zeichenauswahl == 4:
    zeichen_liste = SONDERZEICHEN
    erstelle_passwort(passwortlaenge, zeichen_liste)
elif zeichenauswahl == 5:
    zeichen_liste = KLEINBUCHSTABEN + GROSSBUCHSTABEN + ZAHLEN + SONDERZEICHEN
    erstelle_passwort(passwortlaenge, zeichen_liste)
else:
    print("Keine gültige Eingabe!")
Statt der Zeichenauswahl per Eingabe möchte ich das ganze über Checkboxen regeln. Ich hab mich mal ein bisschen versucht und die GUI aufgebaut, hier der Code:

Code: Alles auswählen

from tkinter import *
master = Tk()

Label(master, text="Passwortlaenge: ").grid(row=0, sticky=W)
eingabefeld_passwortlaenge = Entry(master).grid(row=1, sticky=W)
Label(master, text="Welche Zeichen soll das Passwort enthalten?").grid(row=2, sticky=W)
var1 = IntVar()
Checkbutton(master, text="Kleinbuchstaben", variable=var1).grid(row=3, sticky=W)
var2 = IntVar()
Checkbutton(master, text="Grossbuchstaben", variable=var2).grid(row=4, sticky=W)
var3 = IntVar()
Checkbutton(master, text="Zahlen", variable=var3).grid(row=5, sticky=W)
var4 = IntVar()
Checkbutton(master, text="Sonderzeichen", variable=var4).grid(row=6, sticky=W)
Button(master, text='Generate', command=erstelle_passwort(passwortlaenge, zeichen_liste).grid(row=7, sticky=W, pady=8))
ausgabefeld_passwort = Entry(master).grid(row=8, sticky=W)
mainloop()
Was mir jetzt noch fehlt ist eine Verbindung zwischen Logik und GUI. Leider bekomme ich schon beim auslesen der Passwortlänge einen Fehler. (AttributeError: 'NoneType' object has no attribute 'get')'
Der Fehler wird in der Zeile ausgelöst, in der ich versuche das Entry auszulesen. Hier mal mein kompletter Code bis jetzt. Bin über JEDE Kritik dankbar.

Code: Alles auswählen

from tkinter import *
from random import randint
import string

KLEINBUCHSTABEN = list(string.ascii_lowercase)
GROSSBUCHSTABEN = list(string.ascii_uppercase)
ZAHLEN = list(string.digits)
SONDERZEICHEN = list(string.punctuation) 

master = Tk()

def erstelle_passwort():
    passwort = ""
    passwortlaenge = eingabefeld_passwortlaenge.get()
    zeichen_liste = KLEINBUCHSTABEN
    for i in range(passwortlaenge):
        passwort += zeichen_liste[randint(0, len(zeichen_liste))]
    ausgabefeld_passwort.insert(passwort)

Label(master, text="Passwortlaenge: ").grid(row=0, sticky=W)
eingabefeld_passwortlaenge = Entry(master).grid(row=1, sticky=W)
Label(master, text="Welche Zeichen soll das Passwort enthalten?").grid(row=2, sticky=W)
var1 = IntVar()
Checkbutton(master, text="Kleinbuchstaben", variable=var1).grid(row=3, sticky=W)
var2 = IntVar()
Checkbutton(master, text="Grossbuchstaben", variable=var2).grid(row=4, sticky=W)
var3 = IntVar()
Checkbutton(master, text="Zahlen", variable=var3).grid(row=5, sticky=W)
var4 = IntVar()
Checkbutton(master, text="Sonderzeichen", variable=var4).grid(row=6, sticky=W)
Button(master, text='Generate', command=erstelle_passwort()).grid(row=7, sticky=W, pady=8)
ausgabefeld_passwort = Entry(master).grid(row=8, sticky=W)
mainloop()
Und wie kann ich jenachdem welche Checkbox ausgewählt ist die dazugehörige Liste mitgeben?

Mit freundlichen Grüßen

Jankie
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

So funktioniert GUI-Programmierung auch nicht. An der Stelle ist ja eingabefeld_passwortlaenge wirklich nicht definiert und falls doch, wird der Wert, der zum Zeitpunkt der Ausführung in eingabefeld_passwortlaenge steht, genommen, also nichts, weil Du das Eingabefeld eben erst erzeugt hast.
Neben dem Klammerfehler hat die Zeile mit `Button´ dann noch das Problem, dass Du statt eine Funktion an `command` zu übergeben, die Funktion aufrufst und deren Rückgabewert benutzt.
Wenn Du anfängst, Variablen durchzunummerieren, dann willst Du eigentlich eine passende Datenstruktur benutzen. Hier z.B. ein Wörterbuch, das die Zeichenstrings auf IntVar-Objekte mapped.
Für jedes nicht-triviale GUI-Programm braucht man Klassen; hast Du Dich damit schon beschäftigt?
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Zu erst mal sorry, habe oben am Code noch etwas geändert, daher ist auch der Klammerfehler schon weg.

Habe mich schon mit Klassen beschäftigt, also jedenfalls das was in der Python Doku dazu steht. Wüsste aber leider nicht weshalb es nützlich wäre für diese eine Methode eine Klasse zu erstellen. Was IntVar() ist weiß ich leider nicht, habe zugegebenermaßen die GUI mehr oder weniger "zusammenkopiert". Den einzigen Effekt den ich gemerkt habe war, das wenn ich unterschiedlichen Checkboxen eine gleiche Variable zugewiesen habe, dann auch alle mit dieser Variablen angehakt wurden sind.


#edit: Also wird beim Programmaufruf quasi schon einmal das Programm durchlaufen und da keine Werte gegeben sind (da gerade erst erzeugt) wird ein Fehler erzeugt?
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

@Jankie: jetzt rufst Du immer noch die Funktion auf, statt sie command zu übergeben.
Der Rückgabewert von `Entry.grid` ist None, es ist also nicht verwunderlich, dass `eingabefeld_passwortlaenge` an None gebunden ist.
`erstelle_passwort` benutzt munter globale Variablen, was nicht sein sollte.
Jankie
User
Beiträge: 592
Registriert: Mittwoch 26. September 2018, 14:06

Stehe gerade bisschen auf dem Schlauch, ich verstehe den unterschied nicht ganz zwischen "Funktion aufrufen" und "Funktion command übergeben". Meinst du mit Aufrufen, dass ich keine Parameter mit übergebe sondern die Werte erst in der Funktion selbst abgerufen werden? Und die Liste KLEINBUCHSTABEN ist die globale Variable?

Also ursprünglich sah die Funktion so aus:

Code: Alles auswählen

def erstelle_passwort(passwortlaenge, zeichen_liste):
    passwort = ""
    for i in range(passwortlaenge):
        passwort += zeichen_liste[randint(0, len(zeichen_liste))]     
    print(passwort)
Nur wüsste ich leider, wie ich die Parameter korrekt in einer GUI übergebe.


#edit: habe jetzt

Code: Alles auswählen

Button(master, text='Generate', command=erstelle_passwort()).grid(row=7, sticky=W, pady=8)
durch das erzetzt:

Code: Alles auswählen

Button(master, text='Generate', command=erstelle_passwort).grid(row=7, sticky=W, pady=8)

#edit2: und habe den Code für die Entrys jetzt ab dem .grid in zwei Zeilen geteilt. Jetzt kommt kein AttributeError mehr.
Sirius3
User
Beiträge: 17750
Registriert: Sonntag 21. Oktober 2012, 17:20

So könnte das aussehen:

Code: Alles auswählen

import tkinter as tk
from random import choice
import string

GROUPS = {
    "Kleinbuchstaben": string.ascii_lowercase,
    "Grossbuchstaben": string.ascii_uppercase,
    "Zahlen": string.digits,
    "Sonderzeichen": string.punctuation,
}

class Application(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        tk.Label(self, text="Passwortlaenge: ").grid(row=0, sticky=tk.W)
        self.eingabefeld_passwortlaenge = tk.Entry(self)
        self.eingabefeld_passwortlaenge.grid(row=1, sticky=tk.W)
        tk.Label(self, text="Welche Zeichen soll das Passwort enthalten?").grid(row=2, sticky=tk.W)
        self.options = {}
        for idx, (name, characters) in enumerate(GROUPS.items(), 3):
            var = tk.IntVar()
            tk.Checkbutton(self, text=name, variable=var).grid(row=idx, sticky=tk.W)
            self.options[characters] = var
        tk.Button(self, text='Generate', command=self.erstelle_passwort).grid(row=7, sticky=tk.W, pady=8)
        self.ausgabefeld_passwort = tk.Entry(self)
        self.ausgabefeld_passwort.grid(row=8, sticky=tk.W)

    def erstelle_passwort(self):
        passwortlaenge = int(self.eingabefeld_passwortlaenge.get())
        zeichen_liste = ''.join(
            characters
            for characters, active in self.options.items()
            if active.get()
        )
        passwort = ''.join(
            choice(zeichen_liste)
            for _ in range(passwortlaenge)
        )
        self.ausgabefeld_passwort.insert(0, passwort)

def main():
    app = Application()
    app.mainloop()

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