Grundsätzlicher Aufbau eines Programms mit mehreren Fenstern

Fragen zu Tkinter.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Einen sonnig-sommerlichen Tag wünsche ich,
Ich habe zwar schon ein etwas größeres Tkinter Program geschrieben, ich bin aber mit dem Aufbau garnicht zufrieden, ich habe einfach jedes Fenster in eine eigene Funktion gesteckt:

Code: Alles auswählen

import...
def fenster1():
	global root
	root=tk.Tk()
	Labels, Buttons, tralalala
	tk.Button(root, text="weiter", command=fenster2)

def fenster2():
	global root
	root.destroy()
	root=tk.Tk()
	Labels, Buttons, tralalala
	tk.Button(root, text="weiter", command=fenster3)
...

fenster1()
In einigen Beispielen habe ich das Ganze aber auch in Klassen gesehen...
wie genau baue ich so ein Programm auf?
Gibts da ein gutes Tutorial? Kann jemand das Grundgerüst schildern?

Vielen Dank
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Scriptinggamer

Zuerst einmal einige Fragen. Was willst du mit einem neuen Fenster erreichen? Möchtest du damit eine neue Tkinter GUI erstellen oder willst du einfach weitere Fenster zu einer laufenden GUI generieren?

Tkinter Tutorial:
° Effbot
° TkDocs
° New Mexico Tech

Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Hi, es geht um eine neue Version dieses Programms: http://scriptinggamer.de.cg/software.php#easylearn
da kommt man von einem Fenster zum nächsten, das will ich zwar einschränken, aber es soll mindestens ein Startfenster, ein Verwaltungsfenster, ein Erstellenfenster, und ein Bearbeitenfenster geben, dann noch ein Infofenster
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Scriptinggamer

Ich nehme einmal an es geht hier um eine Anwendung mit einem Hauptfenster die mehrere Zusatzfenster (Toplevels) benötigt. Hier etwas zum ausprobieren:

Code: Alles auswählen

#!/usr/bin/env python
# coding: UTF-8

try:
    #~~ For Python 2.x
    import Tkinter as tk
except ImportError:
    #~~ For Python 3.x
    import tkinter as tk

APP_WIN_XPOS = 50
APP_WIN_YPOS = 50
APP_WIN_WIDTH = 400
APP_WIN_HEIGHT = 400
APP_WIN_TITLE = 'Tk App-Template'
APP_BACK_GND = 'palegoldenrod'

class App(object):
    
    def __init__(self):
        
        self.win = tk.Tk()
        self.win.geometry('+{0}+{1}'.format(APP_WIN_XPOS, APP_WIN_YPOS))
        #self.win.geometry('{0}x{1}'.format(APP_WIN_WIDTH, APP_WIN_HEIGHT))
        self.win.protocol("WM_DELETE_WINDOW", self.close)
        self.win.config(bg=APP_BACK_GND)
    
    def create_top_win(self):
        
        top_win = tk.Toplevel()
        top_win.geometry('{0}x{1}+{2}+{3}'.format(250, 100, self.top_win_xpos,
            self.top_win_ypos))
        top_win.update_idletasks()
        self.top_win_collector.append(top_win)
        
            
        self.top_win_xpos += self.top_win_xyoff
        self.top_win_ypos += self.top_win_xyoff
        top_win.title('Toplevel-Fenster-{}'.format(len(self.top_win_collector)))
        top_win.update_idletasks()
        
    def close(self):
        self.win.destroy()
        print ("Shut down application")

    def run(self):
        self.win.mainloop()
        
app = App()
app.win.title("Toplevel-Generator")
app.win.withdraw()

app.top_win_xpos = 300
app.top_win_ypos = APP_WIN_YPOS
app.top_win_xyoff = 20

app.top_win_collector = list()

# Erstelle 10 Toplevel-Fenster
[app.create_top_win() for top_win in range(10)]

# Erstelle weitere Toplevel-Fenster
tk.Button(app.win, text='Erstelle weiteres Fester', command=app.create_top_win
    ).pack()
app.win.deiconify()
app.win.update_idletasks()

app.run()
Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Danke, einige nützliche sachen drinn, aber sowas hatte ich auch schon mal gemacht, ich meine aber ein Fenstersystem in dem ich mehrere Fenster habe, wenn ich zum Beispiel hier klicke öffnet sich ein neues Fenster und das alte schließt sich, wenn ich da klicke komme ich wieder zurück, mache ich dann also am besten eine Klasse und für jedes Fenster eine Methode? wie löse ich das, wenn ein fenster sich öffnet das andere sich aber schließt? also ist das Prinzip was ich im Code Beispiel genannt habe, nur noch in ne klasse gepackt, das richtige?
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Scriptinggamer

Jetzt geht es ans Eingemachte. Ich meine damit Tutorials durcharbeiten. Wünsch dir viel Spass und Ausdauer beim programmieren und studieren. Wenn du anfängs Code zu produzieren und hier präsentierst gibt es viele erfahrene Forumbenutzer die dir weiterhelfen können.

Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Na dann finde ich da hoffentlich die Lösung des Problems, ich hab das http://www.Python-kurs.eu/ Tutorial nämlich durchgearbeitet und keine Lösung finden können...
Danke,
Gruß
BlackJack

@Scriptinggamer: Dann arbeite mal die durch, die Dir empfohlen werden und nicht diesen Schrott den ein SEO-Mensch nur fürs Suchmaschinenranking zusammengebastelt und verlinkt hat.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Hi, ich meine das ich das vorher durchgearbeitet habe, darauf basiert mein bissheriges tkinter Wissen...
Gruß
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Scriptinggamer hat geschrieben:Hi, ich meine das ich das vorher durchgearbeitet habe, darauf basiert mein bissheriges tkinter Wissen...
Ja, das spricht nicht gerade für dieses Tutorial, ohne dir jetzt zu nahe treten zu wollen. Also besser wegwerfen und nie wieder anschauen.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Jaja, schon klar, meine scripts sind schon ... anders aufgebaut :P
Naja, in dem Tutorial sind nur die einzelnen Widgets erklärt.
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

So, in dem ersten der drei Links habe ich jetzt also erstmal 1. gelesen das eine App möglichst nur ein Fenster haben sollte, ups, und 2. eine Idee bekommen wie ich das mit Klassen mache. Und siehe da, mit self wird global überflüssig :shock: .
Jetzt hab ich das Ganze also mit einer Klasse und über Frames gelöst, das hier ist jetzt mal ein 2 frame-iges Progrämmchen:

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: cp1252 -*-

#Module importieren
import tkinter as tk

#Klasse definieren
class App:
    #root/master initialisieren
    def __init__(self):
        self.master = tk.Tk()
        self.master.protocol("WM_DELETE_WINDOW", self.quit_app)
        self.master.title("Frames")
        self.set_main_frame()
        
    #Hauptfenster starten
    def set_main_frame(self):
        #Hauptframe definieren
        self.main_frame = tk.Frame(self.master)
        self.main_frame.pack()
        #Widgets initialisieren
        self.hallo_label = tk.Label(self.main_frame, text = "Hallo Tkinter!")
        self.quit_button = tk.Button(self.main_frame, text = "beenden", command = self.quit_app)
        self.hallo_button = tk.Button(self.main_frame, text = "Weiter", command = self.set_settings_frame)
        #Widgets positionieren
        self.hallo_label.grid(row = 0, column = 0)
        self.quit_button.grid(row = 0, column = 1)
        self.hallo_button.grid(row = 0, column = 2)
        #Bestätigung in der Konsole
        print("Hauptfenster geöffnet")
        #Fenster neustarten
        self.master.mainloop()
        
    #Einsellungsfenster starten
    def set_settings_frame(self):
        #Einstellungsframe definieren
        self.settings_frame = tk.Frame(self.master)
        #Hauptframe schließen
        self.main_frame.destroy()
        self.settings_frame.pack()
        #Widgets initialisieren
        self.head_label = tk.Label(self.settings_frame, text = "Einstellungen")
        self.setting1_label = tk.Label(self.settings_frame, text = "Einstellung 1")
        self.setting1_entry = tk.Entry(self.settings_frame)
        self.setting2_label = tk.Label(self.settings_frame, text = "Einstellung 2")
        self.setting2_entry = tk.Entry(self.settings_frame)
        self.safe_settings_button = tk.Button(self.settings_frame, text = "Speichern", command = self.safe_settings)
        #Widgets positionieren
        self.head_label.grid(row = 0, column = 0, columnspan = 2)
        self.setting1_label.grid(row = 1, column = 0)
        self.setting1_entry.grid(row = 1, column = 1)
        self.setting2_label.grid(row = 2, column = 0)
        self.setting2_entry.grid(row = 2, column = 1)
        self.safe_settings_button.grid(row = 3, column = 0, columnspan = 2)
        #Bestätigung in der Konsole
        print("Einstellungen geöffnet")
        #Fenster neustarten
        self.master.mainloop()

    #Einstellungen verwerten und zum Hauptfenster zurückkehren
    def safe_settings(self):
        #Einstellungen auslesen
        self.settings = [self.setting1_entry.get(), self.setting2_entry.get()]
        ###Einstellungen verwerten####
        for setting in self.settings:#
            print(setting)           #
        ##############################
        #Einstellungsframe schließen
        self.settings_frame.destroy()
        #Hauptframe starten
        self.set_main_frame()

    def quit_app(self):
        print("Programm beendet")
        self.master.destroy()

#App ausführen (Instanz der Klasse App erstellen)        
app = App()
Das ist ja sicherlich schon besser als vorher, aber ist das die beste und korekteste Lösung?
Vielen Dank im Voraus,
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Hi Scriptinggamer

Super! Da hat sich ja schon etwas getan.
Du hast hat geschrieben:So, in dem ersten der drei Links habe ich jetzt also erstmal 1. gelesen das eine App möglichst nur ein Fenster haben sollte
Damit ist gemeint nur ein Hauptfenster. Weitere Fenster kannst du natürlich jederzeit mit dem Toplevel-Widget zur bestehenden Anwendung hinzufügen.

Die Anweisung:

Code: Alles auswählen

self.master.mainloop()
sollte in Anwendungen nur einmal vorkommen. Die zweite mainloop-Anweisung in deinem Snippet kannst du weglassen. Wie ich sehe erstellst du nur weitere Frames in deinem Hauptfenster. So wie ich dich verstanden habe möchtest du aber weiter eigenständige Fenster (eben Toplevel-Fenster evt. als Popup-Fenster) haben. Oder willst du diese nur als Popups-Frames erstellen, welche sich im innern des Hauptfensters befinden?

Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Hi, das Ganze über Frames zu lösen finde ich, jetzt wo ich es ausprobiert habe, besser. Die neuen Fenster sind immer an anderen stellen aufgetaucht, ich weiß, das kann man anpassen, aber so ist es eben alles in einem Fenster. Wenn das so alles stimmt, kann ich ja noch um ein Popupfenster erweitern.
Wenn du sagst, dass ich nur beim Start mainloop() benutzen soll, wie soll ich dann nach dem Löschen der alten Frame (frame.destroy()) und dem Konfigurieren der neuen (frame = tk.Frame()) das Fenster aktualisieren?
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Scriptinggamer hat geschrieben:Wenn du sagst, dass ich nur beim Start mainloop() benutzen soll, wie soll ich dann nach dem Löschen der alten Frame (frame.destroy()) und dem Konfigurieren der neuen (frame = tk.Frame()) das Fenster aktualisieren?
Die 'mainloop' wie folgt platzieren:

Code: Alles auswählen

class App:
    #root/master initialisieren
    def __init__(self):
        self.master = tk.Tk()
        self.master.protocol("WM_DELETE_WINDOW", self.quit_app)
        self.master.title("Frames")
        self.set_main_frame()
        self.master.mainloop()
Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

:D habe ich grade selbst rausgefunden, um das Fenster zu aktualisieren benutze ich jetzt self.master.update() .
und das in set_main_frame jetzt master.update() ausgeführt wird bevor der Mainloop gestartet wird ist kein Problem?
Und noch was: Wie kann ich mir das vorstellen, wenn der Mainloop läuft, gehts dannach ja erst weiter wenn das Fenster zerstört/geschlossen wird. Es können aber Funktionen aus dem Mainloop gestartet werden, die werden dann so in den mainloop gezogen und werden da ausgeführt, kann man sich das so in etwa vorstellen?
Das script ist also so wie es ist (mit update() und dem einmaligen mainloop()) perfekt?
Gruß
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

Gibt es Schwierigkeiten, wenn du master.update() nicht benützt? Arbeitest du unter Windows?

Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Nö, gibts nicht :D
Hatte das für die Lösung eines Problems gehalten, das scheinbar ein ganz anderes war, danke
Habe das Ganze also ausgebessert und noch ein Toplevelfenster hinzugefügt: Ist das so also alles ok?

Code: Alles auswählen

#!/usr/bin/python
# -*- coding: cp1252 -*-

#Module importieren
import tkinter as tk

#Klasse definieren
class App:
    #root/master initialisieren
    def __init__(self):
        self.master = tk.Tk()
        self.master.protocol("WM_DELETE_WINDOW", self.quit_app)
        self.master.title("Frames")
        self.set_main_frame()
        self.master.mainloop()
        
    #Hauptfenster starten
    def set_main_frame(self):
        #Hauptframe definieren
        self.main_frame = tk.Frame(self.master)
        self.main_frame.pack()
        #Widgets initialisieren
        self.hallo_label = tk.Label(self.main_frame, text = "Hallo Tkinter!")
        self.hallo_button = tk.Button(self.main_frame, text = "Weiter", command = self.set_entry_frame)
        self.quit_button = tk.Button(self.main_frame, text = "Beenden", command = self.quit_app)
        #Widgets positionieren
        self.hallo_label.grid(row = 0, column = 0, columnspan = 2)
        self.hallo_button.grid(row = 1, column = 0)
        self.quit_button.grid(row = 1, column = 1)
        #Bestätigung in der Konsole
        print("Hauptfenster geöffnet")
        
    #Einsellungsfenster starten
    def set_entry_frame(self):
        #Einstellungsframe definieren
        self.entry_frame = tk.Frame(self.master)
        #Hauptframe schließen
        self.main_frame.destroy()
        self.entry_frame.pack()
        #Widgets initialisieren
        self.head_label = tk.Label(self.entry_frame, text = "Eingaben")
        self.entry1_label = tk.Label(self.entry_frame, text = "Eingabe 1")
        self.entry1_entry = tk.Entry(self.entry_frame)
        self.entry2_label = tk.Label(self.entry_frame, text = "Eingabe 2")
        self.entry2_entry = tk.Entry(self.entry_frame)
        self.safe_entry_button = tk.Button(self.entry_frame, text = "Ausgeben", command = self.set_request_win)
        #Widgets positionieren
        self.head_label.grid(row = 0, column = 0, columnspan = 2)
        self.entry1_label.grid(row = 1, column = 0)
        self.entry1_entry.grid(row = 1, column = 1)
        self.entry2_label.grid(row = 2, column = 0)
        self.entry2_entry.grid(row = 2, column = 1)
        self.safe_entry_button.grid(row = 3, column = 0, columnspan = 2)
        #Bestätigung in der Konsole
        print("Eingabefenster geöffnet")

    #Bestätigung oder Abbruch
    def set_request_win(self):
        #Eingaben auslesen
        self.entry = [self.entry1_entry.get(), self.entry2_entry.get()]
        #Toplevel Fenster definieren
        self.request_win = tk.Toplevel()
        self.request_win.title("Wirklich?")
        #Widgets initialisieren
        self.question_label = tk.Label(self.request_win, text = "Möchtest du den Text wirklich ausgeben?")
        self.agree_button = tk.Button(self.request_win, text = "Also los, gib ihn aus!", command = self.print_entry)
        self.main_button = tk.Button(self.request_win, text = "Ich überlegs mir...", command = self.to_main)
        self.quit_button = tk.Button(self.request_win, text = "Sofort alles beenden!", command = self.quit_app)
        #Widgets positionieren
        self.question_label.grid(row = 0)
        self.agree_button.grid(row = 1, sticky = "w,e")
        self.main_button.grid(row = 2, sticky = "w,e")
        self.quit_button.grid(row = 3, sticky = "w,e")        

    #Eingaben verwerten und zum Hauptfenster zurückkehren
    def print_entry(self):
        ###Eingaben verwerten#########
        for setting in self.entry:   #
            print(setting)           #
        ##############################
        #Nachfragefenster schließen
        self.request_win.destroy()
        #Ausgabeframe schließen
        self.entry_frame.destroy()
        #Hauptframe starten
        self.set_main_frame()

    def to_main(self):
        self.request_win.destroy()
        self.entry_frame.destroy()
        self.set_main_frame()

    def quit_app(self):
        print("Programm beendet")
        self.master.destroy()

#App ausführen (Instanz der Klasse App erstellen)        
app = App()
Benutzeravatar
wuf
User
Beiträge: 1529
Registriert: Sonntag 8. Juni 2003, 09:50

OK. Es geht doch mit dem Toplevel-Fenster. Ich persönlich brauche eigentlich nur Toplevels für Dialoge. Platzieren und Größe ändern kannst du mit der geometry-Methode wie bei einem Hauptfenster. Für die Position auf dem Bildschirm brauche:

Code: Alles auswählen

toplevel_win.geometry('+{0}+{1}'.format(xpos, ypos))
Für die Abmessungen brauche:

Code: Alles auswählen

toplevel_win.geometry('{0}x{1}'.format(width, height))
Bei Toplevel-Fenstern kann es vorkommen dass sie sich hinter den auf dem Desktop vorhandene Fenstern verstecken (rutschen).

Gruß wuf :wink:
Take it easy Mates!
Scriptinggamer
User
Beiträge: 107
Registriert: Sonntag 24. Juni 2012, 16:38
Wohnort: Werder/Havel

Naja, Problem 1 ist ein Fenster was das vorherige ersetzt (also die Frame-Methode)
und Problem 2 ist eben so ein Fenster was noch zusetzlich angezeigt wird (Toplevels).
Steckt ja nun beides in dem Script, und wenn der Code sauber ist, mache ich mich drann eine saubere Version meines Gamelimiters zu Programmieren, dazu habe ich dieses Thema erstellt.
Ist der Code so wie er jetzt ist also sauber?
Gruß
Antworten