Anfängerfragen

Fragen zu Tkinter.
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Hallo,

ich hab vor ca. 3 Tagen mit Python (3.1) angefangen, hab aber schon relativ viel Erfahrung mit Java und PHP. Ich probier gerade mein erstes Tkinter Programm zu schreiben und hab da ein paar Fragen, nach denen ich schon 2 Tage gegooglet habe (im Forum hab ich auch schon geguckt):

1. Kann ein Label automatische Zeilenumbrüche haben? Da mein Text immer über den Rand des Fensters läuft und wrap = 300 oder so auch nicht das liefert was ich will. wraplength oder wrap = 'word' geht auch nicht.

2. Aus einer Listbox will ich das man eine Frage auswählen kann, das abfangen mache ich damit:

Code: Alles auswählen

selected = self.list.curselection()
if selected != self.question:
	self.question = selected
	self.showQuestion()
		
self.wnd.after(250, self.selection)
Wie kann ich jetzt verhindern das "nichts" in self.question eingetragen wird wenn nichts ausgewählt ist, und wie kann ich dafür sorgen das selected ein int ist/wird (default in self.question ist 0, daran lese ich später die frage aus einer datei).

3. Generell will ich auch noch wissen wie man überprüfen kann ob eine variable noch nicht gesetzt ist (PHP: !isset(...)).

4. Kann man mit pack_forget() auch elemente löschen die nicht exestieren (also das nichts passiert wenn ich es aufrufe und keine Fehlermeldung kommt, was man sicher auch mit Frage 3 verhindern kann).

MfG

PS: Wenn es dazu schon was gibt dann ist die Suchfunktion doch ziemlich scheiße^^

EDIT:

ok 4 hab ich gerade gelöst mit try
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Ad 1: Ich glaube, dass kann das Label nicht (ich kenne allerdings Tkinter nicht, aber diverse andere TKs). Schau mal hier: http://effbot.org/tkinterbook/text.htm
Ad 3: Wenn du auf einen Namen zugreifst, der nicht existiert, ist das ein `NameError`, aber ich kann mir nicht vorstellen, dass das ein Konstrukt ist, den man in sauberem Code haben will oder gar benoetigt ... Also erzaehl mal, wo du hinwillst.

Ad 4: Ich hoffe das ist kein `try...except:` - d.h. ein blankes `except`. Ansonsten ist es das gleiche wie mit 3. Eventuell kommen auch Listen zu Hilfe.
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

danke, die seite hab ich mir schon angeschaut. aber es soll ja keine textarea erzeugt werden sondern eine simple textausgabe bei der man das fenster von der größe her verändern kann wie man will und der text sich dementsprechend über mehrere zeilen erstrecken kann.

zu 3 nochmal:

ich hab das problem (gehabt) das ich einen bereich für die ausgabe der frage habe und in dem muss, wenn man eine andere frage auswählt hat, diese erscheinen und die alte soll gelöscht werden, weil sie ja sonst darunter gepackt wird. und ich hatte jetzt die idee die ensprechenden elemente (label, buttons, evt. Toplevel) zu löschen und dann neue einzufügen, was natürlich bei programmstart zu einem fehler führt, da die elemente noch nicht da sind.

Hier jetzt einfach mal der gesammte code:

Code: Alles auswählen

# MC Tutor 2010
# Version 1.0
# (c) 2010 Marvin Blum

from tkinter import *
from tkinter import messagebox

class Tutor:		
	question = 0
	saved = 0
		
	def __init__(self):		
		self.wnd = Tk()
		self.wnd.title("MC Tutor 2010 | (c) 2010 Marvin Blum")		# Fenstertitel
		self.wnd.geometry("800x500")								# Fenstergroesse
		self.wnd.iconbitmap(default = 'graphics/icon.ico')		 	# Fenstericon
		self.wnd.protocol('WM_DELETE_WINDOW', self.exit)
				
		self.drawGUI()												# GUI erstellen
		self.getQuestions()											# Fragen auslesen
							
		self.wnd.mainloop()
	
	# GUI erstellen
	def drawGUI(self):
		# Grafiken laden
		img_new = PhotoImage(file = 'graphics/new.gif')				# Neue Befragung
		img_answer = PhotoImage(file = 'graphics/answer.gif')		# Antwort zeigen
		img_save = PhotoImage(file = 'graphics/save.gif')			# Speichern
		img_exit = PhotoImage(file = 'graphics/exit.gif')			# Beenden
	
		# Menu
		menu = Menu(self.wnd)
		self.wnd.config(menu = menu)
		
		# Dateimenu
		file_menu = Menu(menu)
		menu.add_cascade(label = 'Datei', menu = file_menu)				
		file_menu.add_command(label = 'Neue Befragung', command = self.restart)
		file_menu.add_command(label = 'Speichern', command = self.save)
		file_menu.add_command(label = 'Beenden', command = self.exit)
		
		# Toolbar
		toolbar = Frame(self.wnd)
		toolbar.config(bg = "#dee3f3")
		b = Button(toolbar, image = img_new, command = self.restart)
		b.image = img_new
		b.pack(side = LEFT, padx = 2, pady = 2)
		b = Button(toolbar, image = img_answer, command = self.showAnswer)
		b.image = img_answer
		b.pack(side = LEFT, padx = 2, pady = 2)
		b = Button(toolbar, image = img_save, command = self.save)
		b.image = img_save
		b.pack(side = LEFT, padx = 2, pady = 2)
		b = Button(toolbar, image = img_exit, command = self.exit)
		b.image = img_exit
		b.pack(side = LEFT, padx = 2, pady = 2)
		toolbar.pack(side = TOP, fill = X)
		
		# Liste		
		self.list = Listbox(self.wnd, selectmode = SINGLE)
		scroll = Scrollbar()
		scroll.config(command = self.list.yview)
		self.list.config(yscrollcommand = scroll.set, width = 30)
		self.list.pack(side = LEFT, fill = Y)
		scroll.pack(side = LEFT, fill = Y)

	# Fragen auslesen
	def getQuestions(self):
		f = open('questions.txt')
		tmp = f.read()
		self.questions = tmp.split('\n')
		
		# In Liste eintragen
		for i in self.questions:
			tmp = i.split('||')
			self.list.insert(END, tmp[0])
		
		self.selection()	# Auswahl abfangen
		
	# Frage anzeigen
	def showQuestion(self):
		# Zerlegen
		tmp = self.questions[0].split('||')
	
		# Loeschen
		try:
			self.title.pack_forget()
			self.start.pack_forget()
			self.answer1.pack_forget()
			self.answer2.pack_forget()
			self.answer3.pack_forget()
			self.answer4.pack_forget()
			self.image_wnd.destroy()
		except:
			pass
		
		try:
			self.end.pack_forget()
		except:
			pass
					
		# Ausgabe
		self.answer = tmp[6]	# Richtige Antwort
		
		self.title = Label(self.wnd, text = tmp[0])
		self.title.config(anchor = W, justify = LEFT, bg = '#0000ff', fg = '#ffffff', font = 'Arial 12 normal')
		self.title.pack(side = TOP, fill = BOTH, padx = 2, pady = 2)	
		
		# Frage
		self.start = Label(self.wnd, text = tmp[1])
		self.start.config(anchor = W, justify = LEFT, font = 'Arial 12 normal')
		self.start.pack(side = TOP, fill = BOTH, padx = 2, pady = 10)
		
		# Antwort A
		self.answer1 = Button(self.wnd, text = 'A: '+tmp[2], command = self.answer)
		self.answer1.config(anchor = W, justify = LEFT, bg = '#d0d0d0', relief = FLAT, font = 'Arial 12 normal')
		self.answer1.pack(side = TOP, fill = BOTH, padx = 2, pady = 2)
		
		# Antwort B
		self.answer2 = Button(self.wnd, text = 'B: '+tmp[3], command = self.answer)
		self.answer2.config(anchor = W, justify = LEFT, bg = '#d0d0d0', relief = FLAT, font = 'Arial 12 normal')
		self.answer2.pack(side = TOP, fill = BOTH, padx = 2, pady = 2)
		
		if tmp[4]:
			#Antwort C
			self.answer3 = Button(self.wnd, text = 'C: '+tmp[4], command = self.answer)
			self.answer3.config(anchor = W, justify = LEFT, bg = '#d0d0d0', relief = FLAT, font = 'Arial 12 normal')
			self.answer3.pack(side = TOP, fill = BOTH, padx = 2, pady = 2)
			
		if tmp[5]:
			# Antwort D
			self.answer4 = Button(self.wnd, text = 'D: '+tmp[5], command = self.answer)
			self.answer4.config(anchor = W, justify = LEFT, bg = '#d0d0d0', relief = FLAT, font = 'Arial 12 normal')
			self.answer4.pack(side = TOP, fill = BOTH, padx = 2, pady = 2)
			
		if tmp[7]:
			# Anhang
			self.end = Label(self.wnd, text = tmp[7])
			self.end.config(anchor = W, justify = LEFT, font = 'Arial 12 normal')
			self.end.pack(side = TOP, fill = BOTH, padx = 2, pady = 10)
		
		if tmp[8]:	
			try:
				image = PhotoImage(file = 'images/'+tmp[8])
				self.image_wnd = Toplevel()
				self.image_wnd.title('Bild: '+tmp[8])
				self.image_wnd.transient(self.wnd)
				img = Label(self.image_wnd, image = image)
				img.image = image
				img.pack()
			except:
				messagebox.showwarning('Grafik nicht gefunden', 'Die Grafik "'+tmp[8]+'" konnte nicht gefunden werden!')
				
	# Neue Befragung
	def restart(self):
		self.question = 0
		self.list.select_clear(0, END)
		self.list.select_anchor(0)
		self.list.select_set(0)
		
	# Antwort zeigen
	def showAnswer(self):
		pass
		
	# Antworten
	def answer(self, ans):
		pass
		
	# Speichern
	def save(self):
		pass
	
	# Frage auswaehlen
	def selection(self):
		# Auswahl
		selected = self.list.curselection()
		if selected != self.question:
			self.question = selected
			self.showQuestion()
		
		self.wnd.after(250, self.selection)
				
	# Beenden
	def exit(self):
		if self.saved == 0:
			ask = messagebox.askyesno('Beenden', 'Willst du wirklich beenden ohne zu speichern?')			
			if ask == 1:
				self.wnd.quit()
		else:
			self.wnd.quit()
			
Tutor()
da auch noch mal ein hinweis: in self.question soll die id der ausgewählten frage sein (am anfang 0), ändern will ich die über diese selection methode. nur kann ich damit nichts anfangen, weil es kein int ist (tmp = self.questions[0].split('||') soll tmp = self.questions[self.question].split('||') sein)
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Ich hab da noch eine Frage: Wie kann man Argumente an eine Methode/Funktion bei einem command = ... übergeben? Ich versteh das nicht so ganz...
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Bitte lagere deinen Code auf eine Pastebin (z.b. paste.pocoo.org) aus, das Forum hat Probleme mit laengerem Code.

`command` kannst du nur ein Funktionsobjekt uebergeben.
Loesungen siehe http://www.python-forum.de/topic-22047.html

Edit: Link gefixt.
Zuletzt geändert von cofi am Sonntag 28. Februar 2010, 20:02, insgesamt 1-mal geändert.
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Not found... :D
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Ja ok ich hab ein bisschen rumprobiert und das geht über lambda, aber nicht in dem jetzigen Programm?! Liegt es vielleicht daran das der "Fragenblock" nachträglich erstellt wird? Weil in meinem Testprogramm geht es wenn ich das mainloop() nach dem Button schreibe.
Achja und wie sieht mein Programm sonst so aus? :)

http://paste.pocoo.org/show/183951/
BlackJack

@DeKugelschieber: Scheint etwas tief eingerückt -- mit Tabs!? Solltest Du durch 4 Leerzeichen pro Ebene ersetzen.

Sternchenimport sollte man vermeiden. Sonst könnte man sich das Konzept von Namensräumen und Modulen auch gleich sparen.

Sollen `question` und `saved` wirklich Klassenattribute sein? Ist `saved` eine Zahl? Sieht eher nach einem Wahrheitswert aus → dafür gibt es `True`/`False`.

Namenskonvention für alles ausser Klassen (und "Konstanten") ist bei Python `kleingeschrieben_und_mit_unterstrichen`.

Abkürzungen sollte man vermeiden. `wnd`? Warum nicht `window`?

Der Aufbau der GUI bei dem Elemente immer wieder erstellt und dann wieder gelöscht werden, ist IMHO unschön. Alle erstellen und dann entsprechend mit Daten befüllen scheint mir vernünftiger zu sein. Damit würde sich Dein Ausgangsproblem auch gar nicht stellen -- die Elemente sind halt einfach immer da. Dann könnte man sich vielleicht auch die fixe Grössenvorgabe für das Hauptfenster sparen. Nach Ablauf der `__init__()`-Methode sollten alle Attribute auf einem Objekt existieren. Später in Methodenaufrufen welche hinzuzufügen ist schlechter Stil. Nach `__init__()` sollte ein Objekt möglichst voll funktionsfähig sein.

Kommentare sollten Mehrwert gegenüber dem Quelltext liefern und nicht triviales/offensichtliches nochmal wiederholen.

Die Kommentare über den Methoden würden sich vielleicht als Docstrings besser machen.

`getQuestions()` hiesse besser `read_questions()`.

`i` sollte man nicht an etwas anderes als ganze Zahlen binden, insbesondere nicht wenn es als Schleifenvariable verwendet wird.

Wenn man oft das gleiche schreibt, oder zumindest fast, und dabei aufsteigende Zahlen im Quelltext hat, und Namen durchnummeriert, sollte man *dringend* über eine Schleife nachdenken.

Ausführbarer Code auf Modulebene sollte vor dem ausführen beim importieren geschützt werden:

Code: Alles auswählen

if __name__ == '__main__':
    Tutor()
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Danke für deine ganzen Tipps :)
Das mit dem Stil ist natürlich teils auch Personenbezogen, aber wenn man das bei Python so macht... Ich kenns nen bisschen anders, aber egal. Das mit den offensichtlichen Kommentaren hab ich nur hier erstmal so gemacht, damit ich mich an die fehlenden {} und was weiß ich nicht noch alles gewöhne (insbesondere ohne ; zu schreiben ist komisch^^). Und ja, das waren in Notepad++ noch 4 Leerzeichen... Der scheint das da geändert zu haben

Ich liefer hier morgen die verbesserte Version
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

So ich habs doch gerade noch verbessert: http://paste.pocoo.org/show/184045/

aber das lambda in den Buttons funktiniert nicht:

TypeError: 'str' object is not callable

und bei der Fragenmethode:

list indices must be integers, not str

beide kann ich nicht so ganz nachvollziehen, vorallem der letzt, weil es eigentlich ein int sein müsste, und hat python nicht automatische typeconvertierung so wie php?

und sag mir mal wie ich das mit der schleife für die bottons regeln soll, kann man dann einfach self.i oder so schreiben?
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

Nein Python hat keine automatische Konvertierung, da Python streng typisiert ist.

Deine `lambda` schaun mir auch kaputt aus. Wenn du einfach feste Zahlen nutzen willst, macht das auch `lambda: func(1)`.

Aber schau dir nochmal die Dokumentation zu `lambda` an.
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

ok, danke werd ich machen, aber wie bekomme ich das jetzt hin das die zahl aus curselection auch ein int ist?
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Ich hab da jetzt mal noch eine Frage die mir gerade so gekommen ist:

Kann man irgendwie verhindern das sich das python.exe Fenster mit öffnet? Oder ist das nach dem umwandeln zu exe Datei eh erledigt?

Ach ja und nochmal ein 5-Tage-Python-Fazit: Unglaublich einfach zu lernen (ja gut ich bin vorbelastet), super lesbar, zum "mal eben was schreiben" super geeignet. Nicht so toll ist das man nicht sieht was für typen man da hat und das es keine richtige sichtbarkeit bei OOP gibt (oder täusche ich mich da?).
Benutzeravatar
HWK
User
Beiträge: 1295
Registriert: Mittwoch 7. Juni 2006, 20:44

DeKugelschieber hat geschrieben:Kann man irgendwie verhindern das sich das python.exe Fenster mit öffnet?
Indem Du das Script mit pythonw.exe startest.
MfG
HWK
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

DeKugelschieber hat geschrieben:aber wie bekomme ich das jetzt hin das die zahl aus curselection auch ein int ist?
`int(curselection)` ? Wenn das keine Zahl ist, schmeisst das entsprechende Exceptions (das ist btw kein Cast, sondern eine echte Umwandlung).
DeKugelschieber hat geschrieben:Nicht so toll ist das man nicht sieht was für typen man da hat und das es keine richtige sichtbarkeit bei OOP gibt (oder täusche ich mich da?).
Nein, das stimmt schon. Ich frage mich allerdings, wo dir das konkret im Weg stand? Python regelt das ueber die Konvention, dass alles was mit einem `_` anfaengt als implementierungsdetails zu betrachten ist. Wer es dennoch anfasst, sollte eben wissen was er tut. Python folgt da dem Prinzip des nicht bloeden Entwicklers ;)
http://tutorial.pocoo.org/classes.html# ... -variablen

Echte Zugangsbeschraenkungen bekommt man mit Deskriptoren hin.
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

@HWK danke

naja wenn man vorher int davorschreiben würde, würde der ja bei der zuordnung mit curselection eine fehlermeldung ausgeben, da hät ich das dann eher gemerkt. egal, funktioniert die typconvertierung generell so (oder so ähnlich) wie in java (auch mit string())?

achja, habt ihr vielleicht eine bessere idee wie man das mit den fragen regeln kann? ich mach das im moment so:

1 Zeile in der txt = 1 Frage
zerlegen mit || und dann:
[0] = titel
[1] = frage
[2] - [5] = antworten
[6] = evt. anhang (text)
[7] = richtige antwort
[8] = evt. grafik
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

Hallo.

Ja, die Umwandlung macht man immer so. Man geht erstmal davon aus, dass das gewünschte funktioniert und wenn nicht, dann behandelt man die entsprechende Ausnahme (EAFP-Prinzip).

Zu deiner Struktur: Ich würde das Feld mit der richtigen Antwort weglassen und dafür definieren, dass die erste Antwortmöglichkeit immer die richtige ist. Wenn du die Fragen dem Benutzer anzeigst, dann mischt du die Antworten die zu einer Frage gehören (random.shuffle).

Sebastian
Das Leben ist wie ein Tennisball.
Benutzeravatar
cofi
Python-Forum Veteran
Beiträge: 4432
Registriert: Sonntag 30. März 2008, 04:16
Wohnort: RGFybXN0YWR0

DeKugelschieber hat geschrieben:funktioniert die typconvertierung generell so (oder so ähnlich) wie in java (auch mit string())?
Ich weiss ja nicht was du fuer ein Java benutzt, aber ich muss da immer `Integer.valueOf`, `String.valueOf` und dergleichen bemuehen ;)
Aber ja, ganz grob gesagt es funktioniert so aehnlich. (Aber eben nicht Casts wie `(String)`)
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Ja dann vielen Dank erstmal, wenn ich hier weg komme kann ich das dann zuhause weitermachen und geb euch das ergebnis, das mit dem shuffle klingt gut, aber ich machs erstmal so und bau das dann später erst ein :) muss ja erstmal funktionieren.

Bei Java schreib ich nur a = (int)b; Version ist 1.6.17 (aktuell ist 1.6.18 )

MfG
Benutzeravatar
DeKugelschieber
User
Beiträge: 82
Registriert: Sonntag 28. Februar 2010, 12:23
Kontaktdaten:

Ja ok, das mit dem int() klappt super, aber zwei Fragen hab ich noch:

1. Wie geht das mit dem lambda jetzt? So nicht:

Code: Alles auswählen

command = lambda: self.answer(1)
edit:

1 hat sich erledigt! Ich wusste nicht das es Python nicht egal ist ob eine Variable und eine Methode den selben Namen haben.

2. Es muss doch möglich sein automatische Zeilenumbrüche in einem Label zu machen, und wenn nicht (was eigentlich total bescheuert ist), was für alternativen gibt es (kein Text()!)?

edit:

Was mir gerade noch so einfällt, kann man ein kleines icon in eine liste vor einen string setzen?
MfG DeKugelschieber
Antworten