Eingabe übergeben

Fragen zu Tkinter.
Antworten
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

Hallo,

ich muss für ein Uni Projekt ein Billiard Spiel programmieren. Dazu haben ich nun eine kleine Oberfläche geschaffen(ist noch in ihren Anfängen) die es ermöglicht Spielernamen einzugeben und anschließend das Spiel mit den Namen zu starten. Zusätzlich gibt es noch ein Error-Fenster.
Nun zu meinem Problem, die Namen die ich eingebe werden nicht übermittelt bzw. die "error-abfrage" funktioniert nicht richtig.

Ich bin noch ein Python-Leihe, deshalb ist der Code wsh. etwas unübersichtlich.
Vielen Dank vorab für eure Hilfe !

Code: Alles auswählen

import tkinter as tk

root = tk.Tk()
root.wm_title("Billard") # WindowTitle

#Input Player Name
player1_name = tk.StringVar()
player2_name = tk.StringVar()

label1 = tk.Label(root, text = 'Name: Player 1')
label1.pack()
player1 = tk.Entry(root, textvariable = player1_name)
player1.pack()

label2 = tk.Label(root, text = 'Name: Player 2')
label2.pack()
player2 = tk.Entry(root, textvariable = player2_name)
player2.pack()

#starts the game and opens the game window
def click():
	
	#close window for name input
	def close_root(): 
		root.destroy()
    
	if player1 == None or player2 == None:
		errorwindow()
		
	else: 
		gamewindow()
		close_root()
    
#gamewindow
def gamewindow():
	import tkinter as tk
	
	window = tk.Tk()
	window.wm_title("Billard") # WindowTitle
	
	name1 = tk.Label(window, player1)
	name1.pack()
	
	name2 = tk.Label(window, player2)
	name2.pack()
	
	#hier kommt der Befehl zum Hauptprogramm
	
	window.mainloop

#errorwindow open if name isn't given	
def errorwindow():
	import tkinter as tk
	
	e_window = tk.Tk()
	e_window.wm_title("Error") # WindowTitle
	
	label = tk.Label(root, text = 'Error: Please give 2 Names')
	label.pack()
	
	#close errorwindow
	def close(): 
		e_window.destroy()
	
	#ok button in errorwindow		
	button = tk.Button(root, text='OK', command = close())
	button.pack()
	
	e_window.mainloop

	
button = tk.Button(root, text='Start the Game', command = click)
button.pack()

root.mainloop()
Zuletzt geändert von Anonymous am Mittwoch 28. September 2016, 20:26, insgesamt 1-mal geändert.
Grund: Quelltext in Python-Codebox-Tags gesetzt.
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@HeroTurtle: ich verstehe nicht was der Code machen soll. Auch Deine Erklärung helfen nicht wirklich. Was willst Du erreichen?

Zum Code: es darf nur ein Tk-Objekt geben, weitere Fenster werden von TopLevel abgeleitet. Imports sollten immer ganz am Anfang der Datei stehen, damit man gleich die Abhängigkeiten erkennt. Mehrmals tktinter zu importieren hat auch keinen Effekt. Die Bedingung in Zeile 17 wird nie erfüllt sein, weil player1 und player2 ja tk.Entry-Objekte sind. In Zeilen 41/44 was sollen Labels mit Entry-Objekten anfangen? Meinst Du player1_name? Zeile 49: Funktionen müssen aufgerufen werden.
Funktionen innerhalb von Funktionen zu definieren, macht nur in seltenen Fällen Sinn. Für GUI-Programmierung braucht man fast immer Klassen um Zustände zu speichern, vor allem wenn man mehrere Fenster hat.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

Also ich möchte zu Beginn ein Fenster haben, in dem man die beiden Spielernamen eingeben kann. Das Design etc folgt später noch. Zudem soll das Fenster einen Start Button beinhalten, der das aktuelle Fenster schließt und dann das Hauptprogramm lädt. Sprich das Billard Spiel. In dem Fenster soll dann über dem Brett die beiden Namen angezeigt werden die zuvor eingegeben wurden plus die Kugeln welche versenkt wurden(das billard programm habe ich soweit fertig).
Wenn nur ein Name eingeben wird, soll ein Fehler Fenster geöffnet werden, welches den Fehler anzeigt und wenn man dort auf "OK" klickt dieses geschlossen wird.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

Habe es jetzt soweit geschafft, dass die Namen übernommen werden. Und ich hoffe der Code ist jetzt etwas strukturierter.
Allerdings will er nicht das Error-Fenster öffnen, da e_window nicht definiert ist, "line 26, in close_ewindow e_window.destroy() NameError: name 'e_window' is not defined".

Woran liegts ?

Code: Alles auswählen

import tkinter as tk

root = tk.Tk()
root.wm_title("Billard") # WindowTitle

player1_name = tk.StringVar()
label1 = tk.Label(root, text = 'Name: Player 1')
label1.pack()
player1 = tk.Entry(root, textvariable = player1_name)
player1.pack()
player1_name.set('')

player2_name = tk.StringVar()
label2 = tk.Label(root, text = 'Name: Player 2')
label2.pack()
player2 = tk.Entry(root, textvariable = player2_name)
player2.pack()
player2_name.set('')
	
#close window for name input
def close_root(): 
	root.destroy()
	
#close errorwindow
def close_ewindow(): 
	e_window.destroy()

def click():
	player1_name.set(player1_name.get())
	player2_name.set(player2_name.get())
	
	if player1_name.get() == '' or player2_name.get() == '':
		errorwindow()
		
	else: 
		gamewindow()
		close_root()
	
#gamewindow
def gamewindow():
	
	window = tk.Tk()
	window.wm_title("Billard") # WindowTitle
	
	label1 = tk.Label(window, text = player1_name.get())
	label1.pack()
	
	label2 = tk.Label(window, text = player2_name.get())
	label2.pack()
	
	window.mainloop
	
#errorwindow open if name isn't given	
def errorwindow():
	
	e_window = tk.Tk()
	e_window.wm_title("Error") # WindowTitle
	
	label = tk.Label(e_window, text = 'Error: Please give 2 Names')
	label.pack()
	
	
	#ok button in errorwindow		
	button = tk.Button(e_window, text='OK', command = close_ewindow)
	button.pack()
	
	e_window.mainloop


button = tk.Button(root, text='Start the Game', command = click)
button.pack()

root.mainloop()
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@HeroTurtle: der NameError ist nicht das einzige Problem. Wie schon geschrieben solltest Du unbedingt objektorientiert mit Klassen programmieren, und keine globalen Variablen verwenden, bzw. an deren Nicht-Vorhandensein scheitern. Alle Anweisungen, die nicht (Funktions- oder Klassen-) Definitionen sind, sollten in einer Funktion stehen, alles, was Du jetzt noch auf oberster Ebene stehen hast, gehört also auch in eine Funktion, die üblicherweise main genannt wird. Dann kommt nämlich noch dazu, dass click z.B. player1_name und player2_name nicht mehr kennt; die Lösung ist Objektorientierung. Im Übrigen gilt das zuletzt gesagte: es darf nur eine Tk-Instanz und nur einen mainloop-Aufruf geben. Zeilen 29/30 sind ziemlich sinnfrei.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

könntest du mir eventuell helfen das ganze objektorientiert zu programmieren ? Habe damit nicht all zu viel erfahrung :?
Sirius3
User
Beiträge: 17746
Registriert: Sonntag 21. Oktober 2012, 17:20

@HeroTurtle: eine erste Version könnte so aussehen

Code: Alles auswählen

import tkinter as tk
 
class ErrorWindow(tk.Toplevel):
    def __init__(self, parent):
        tk.Toplevel.__init__(self, parent)
        self.wm_title("Error") # WindowTitle
        tk.Label(self, text='Error: Please give 2 Names').pack()
        tk.Button(self, text='OK', command=self.destroy).pack()

class NamesWindow(tk.Toplevel):
    def __init__(self, parent, player1_name, player2_name):
        tk.Toplevel.__init__(self, parent)
        self.parent = parent
        self.player1_name = player1_name
        self.player2_name = player2_name
        self.wm_title("Billard") # WindowTitle
        tk.Label(self, text = 'Name: Player 1').pack()
        tk.Entry(self, textvariable=player1_name).pack()
        tk.Label(self, text = 'Name: Player 2').pack()
        tk.Entry(self, textvariable=player2_name).pack()
        tk.Button(self, text='Start the Game', command=self.click).pack()
        self.protocol("WM_DELETE_WINDOW", parent.destroy)
    
    def click(self):
        if self.player1_name.get() == '' or self.player2_name.get() == '':
            ErrorWindow(self)
        else:
            self.parent.deiconify()
            self.destroy()

class GameWindow(tk.Tk):
    def __init__(self):
        tk.Tk.__init__(self)
        self.wm_title("Billard") # WindowTitle
        self.player1_name = tk.StringVar()
        self.player2_name = tk.StringVar()
        tk.Label(self, textvariable=self.player1_name).pack()
        tk.Label(self, textvariable=self.player2_name).pack()
        NamesWindow(self, self.player1_name, self.player2_name)

def main():
    root = GameWindow()
    root.withdraw()
    root.mainloop()

if __name__ == '__main__':
    main()
Da gibt es natürlich noch viel zu tun. Die Dialoge sollten modal sein, Eingaben möchte man gerne mit Enter abschließen oder per Escape abbrechen. Und eigentlich möchte man Dialoge gar nicht selbst programmieren, sondern welche mit Hilfe von tkinter.simpledialog erzeugen.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

@Sirius3 : Vielen Dank schon mal für deine Mühe :) !!
Ich bin wie gesagt noch ein Leihe was das Programmieren mit Python angeht. Was genau meinst du mit "modale Dialoge" ?

Mein Plan ist es jetzt noch dem Nameswindow einen Hintergrund zu verpassen und etwas schöner zu gestalten. Zudem soll natürlich in dem GamesWindow das Spiel eingebaut werden und die Namen oberhalb des Spiels nebeneinander liegen mit den versenkten Kugeln darunter. Ich bin es gewohnt von anderen Sprachen, das Fenster in Bereiche zu unterteilen und diesen dann die Inhalte zuzuweisen. Ist dies in Python ähnlich ?

Noch ein paar Fragen zum Code, was machen folgende Befehle?:

-Zeile 22: self.protocol("WM_DELETE_WINDOW", parent.destroy)
-Zeile 43: root.withdraw()
-generell was ist dieses "tk.Toplevel"
BlackJack

@HeroTurtle: Modale und nicht-modale Dialoge.

Hintergründe scheitern oft daran das Widgets nicht durchsichtig sind. `Label` beispielsweise hat immer seine eigene Hintergrundfarbe. Solche Spielereien würde ich mir bei Tk verkneifen. Das ist mehr Aufwand als es Wert ist.

Das Layout von GUIs hat nichts mit Python zu tun, sondern wie die GUI-Bibliothek das handhabt. Tk bietet `pack()` und `grid()` als Layouts an, bei denen sich Tk um alles wichtige kümmert. Man darf die beiden Varianten innerhalb eines Container-Widgets nicht mischen, das kann zu eingefrorenen Programmen führen, und das visuelle Ergebnis lässt sich nicht wirklich vorhersagen. Container-Widgets sind die Fenster, also `Tk` und `Toplevel`, und `Frame` und `LabeledFrame`. Die letzten beiden kann man verschachteln. Damit kann man also die Aufteilung der GUI erreichen.

Die protocol()- und withdraw()-Methode sind bei Effbot dokumentiert.

`Toplevel` hat Sirius3 schon gesagt: Fenster. `Tk` ist *das* Hauptfenster, davon darf es nur ein Exemplar im Programm geben weil da der komplette Tk/Tcl-Interpreter dran hängt. Also muss man andere, zusätzliche Fenster mit `Toplevel` erstellen.

Die GUI-Anbindungen in Python sind fast alle nur sehr dünne Schichten über die jeweiligen Bibliotheken. Es ist also hilfreich wenn man auch mal einen Blick in die Originaldokumentation der jeweiligen Bibliothek schaut, falls irgend etwas nicht klar ist.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

@BlackJack: Vielen Dank ! Solche Erklärungen helfen mir sehr ! :)

Habe mittlerweile mal versucht ein Hintergrund einzubauen, aber dein erster Satz erklärt wsh. warum es nicht klappt :/ ! Wenn ich den Code alleine in ein Skript schreibe funktioniert es. Allerdings in meinem Skript nicht. Hier mal der Auszug (Zeile 9-14 sind hinzugefügt bzw geändert worden):

Code: Alles auswählen

class NamesWindow(tk.Toplevel):
    def __init__(self, parent, player1_name, player2_name):
        tk.Toplevel.__init__(self, parent)
        self.parent = parent
        self.player1_name = player1_name
        self.player2_name = player2_name
        self.wm_title("Billard") # WindowTitle
        
        image = tk.PhotoImage(file = "billard.gif")
        w = image.width()
        h = image.height()
        self.geometry("%dx%d" % (w,h))
        hintergrund = tk.Label(self, image = image)
        hintergrund.pack(fill='both', expand='yes')
        
        tk.Label(hintergrund, text = 'Name: Player 1').pack()
        tk.Entry(hintergrund, textvariable=player1_name).pack()
        tk.Label(hintergrund, text = 'Name: Player 2').pack()
        tk.Entry(hintergrund, textvariable=player2_name).pack()
        tk.Button(hintergrund, text='Start the Game', command=self.click).pack()
        self.protocol("WM_DELETE_WINDOW", parent.destroy)
BlackJack

@HeroTurtle: Das mit dem Hintergrund funktioniert so nicht, weil das `pack()`-Layout die Elemente natürlich nicht übereinander legt, sondern ”stapelt”. Also ohne Angabe einer Seite die Elemente untereinander anordnet. Im Falle eines Hintergrundbildes muss man dann tatsächlich mal Layout-Manager in einem Container mischen, und zwar `place()` und zum Beispiel `pack()`.
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

@BlackJack
Entschuldigung aber das mit dem Layout-Manager und dem place() versteh ich nicht so ganz. Was genau muss ich denn jetzt machen, damit mein Bild angezeigt wird ?
BlackJack

@HeroTurtle: Ich sehe gerade das Du da noch einen Fehler gemacht hast: `Label` sind keine Container-Widgets, die kann man nicht als Elternwidget für andere verwenden. Du musst in das gleiche Container-Widget das Bild und die anderen Widgets stecken und das Bild mit `place()` absolut positionieren und die anderen mit `pack()` oder `grid()` anordnen.

Code: Alles auswählen

import tkinter as tk


def main():
    root = tk.Tk()
    background_image = tk.PhotoImage(file='test.gif')
    tk.Label(root, image=background_image).place(x=0, y=0)
    root.geometry(
        '{0}x{1}'.format(background_image.width(), background_image.height())
    )
    tk.Label(root, text='Test').pack()
    tk.Entry(root).pack()
    root.mainloop()


if __name__ == '__main__':
    main()
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

@BlackJack:
Hast ne Pn !
HeroTurtle
User
Beiträge: 9
Registriert: Mittwoch 28. September 2016, 19:04

Genügt es, wenn ich deinen Code für "main" in meinem Programm ersetze oder muss ich noch mehr ändern ?
BlackJack

@HeroTurtle: Nein, das genügt natürlich nicht, was leicht auszuprobieren gewesen wäre. Ich habe ja nur gezeigt wie man ein Hintergrundbild in einem Container-Widget (schlecht und grafisch unschön) realisieren kann. Du musst das Wissen dann schon noch auf das Container-Widget übertragen wo Du diese Hintergrundbild haben möchtest. Ich habe Dein Programm ja nicht nachgebaut, sondern einfach nur ein Hauptfenster erstellt und die Elemente dort drin entsprechend angeordnet.

Oh, und auch noch wichtig: Man muss eine Referenz auf das Bild-Objekt auf Python-Seite behalten. Das passiert bei mir ”automatisch” weil es an einen lokalen Namen in der Funktion gebunden ist, die während des Anzeigens aktiv ist. Wenn die Funktion oder Klasse aber verlassen würde und der `mainloop()`-Aufruf ausserhalb der Funktion stehen würde, dann verschwindet der lokale Name, und damit auch das Objekt auf Python-Seite und damit auch das Bild auf Tk-Seite und es kann nicht mehr angezeigt werden.
Antworten