Entry auslesen und in Textfenster ausgeben

Fragen zu Tkinter.
Antworten
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Hallo Leute,
Es tut mir Leid, dass ich stören muss, aber ich hab ein Problem. Ich bin mir zwar sicher, dass meine Frage hier schon mal beantwortet wurde, aber ich habe über die Suchfunktion keine Beiträge gefunden, die mir weitergeholfen hätten.
Ich möchte Text in ein Entry-Fenster eingeben und dann in einem Textfenster wieder ausgeben. Ich habe dazu ein Entry-Fenster "ZuganfangEingabe" erstellt und es dann mit "ZuganfangEingabe.get()" in einer Variablen gespeichert. Die beiden Zeilen sehen also so aus:
ZuganfangEingabe = Entry(f, width=10)
ZuganfangEingabe.place(x=865, y=1)
z=ZuganfangEingabe.get()
Ich habe dann einen insert-Befehl verwendet, um die Variable im Textfenster auszugeben. Die ganze Codesequenz sieht also so aus:
ZuganfangEingabe = Entry(f, width=10)
ZuganfangEingabe.place(x=865, y=1)
z=ZuganfangEingabe.get()
Zuganfang = Text(f, height=39, width=15, relief=GROOVE, background='black', foreground='white')
Zuganfang.place(x=720, y=60)
Zuganfang.insert('1.0', z)
Das Problem ist nun, dass ich zwar im Entry-Fenster eine Eingabe tätigen kann, diese aber nicht ins Textfenster rüber kriege. Ich vermute, dass ich dazu den PC irgendwie animieren müsste jetzt das Entry-Fenster auszulesen, nur habe ich keine Ahnung wie, da ich noch ein blutiger Anfänger, weshalb ich auch darum bitte mir eventuell "dümmliche" Fehler nachzusehen.

Ich benutze Win XP SP2 und Notepad++ als Editor.

Mit freundlichen Grüßen
Arachnid
BlackJack

@Arachnid: Du solltest zwei Sachen ganz schnell vergessen: `place()` und lineare Programmabläufe.

`place()` ist schlecht, weil Du damit GUIs erschaffst, die nur auf Deinem Rechner gut aussehen oder vielleicht sogar nur dort funktionieren, oder auf welchen, die die gleichen Einstellungen was Schriftarten, Schriftgrössen, und Bildschirmauflösung angeht.

Und GUI-Programmierung funktioniert nicht nach dem linearen Programmablauf der bei Konsolenanwendungen üblich ist, wo man die Anweisungen für die Eingabe gefolgt von denen für die Ausgabe hintereinanderschreibt und die auch in der Reihenfolge ausgeführt werden. Bei GUI-Programmierung erstellt man die GUI, legt Funktionen fest, die bei bestimmten Ereignissen vom GUI-Toolkit aufgerufen werden sollen, und gibt dann die Kontrolle an das GUI-Toolkit ab. Bei `Tkinter` ist das die `mainloop()`-Methode.

Du könntest einen Button einführen, der eine Funktion auslöst, welche dann die Eingabe zur Ausgabe überträgt, oder zum Beispiel das Tasten-Ereignis für die Return-Taste im Eingabe-Widget an eine solche Funktion binden.

Um vernünftige GUIs zu programmieren muss man IMHO auch mindestens Closures oder objektorientierte Programmierung (OOP) beherrschen. Sonst wird das schnell zu einem unübersichtlichen Haufen Code der auf globalen Objekten operiert.

Edit: Fast übersehen: Falls Du einen *-Import verwendest, den bitte auch sein lassen. :-)

Code: Alles auswählen

#!/usr/bin/env python
# coding: utf-8
from __future__ import with_statement
import Tkinter as tk


def main():
    root = tk.Tk()
    
    entry = tk.Entry(root, width=10)
    entry.pack()
    
    output = tk.Text(root,
                     height=39,
                     width=15,
                     relief=tk.GROOVE,
                     background='black',
                     foreground='white')
    output.pack()
    
    def copy_value(_event):
        output.delete(1.0, tk.END)
        output.insert(1.0, entry.get())
    
    entry.bind('<Return>', copy_value)
    
    root.mainloop()


if __name__ == '__main__':
    main()
problembär

- BlackJack's Edit und Code-Beispiel zu spät gesehen -

@Arachnid: Oha, BlackJack hat recht. Ich bin mal so dicht wie möglich an Deinem Code geblieben:

Code: Alles auswählen

#!/usr/bin/env python
#-*- coding: iso-8859-1 -*-

import Tkinter as tk

def insertTo(entrywidget, textwidget):
    textwidget.insert(tk.END, entrywidget.get() + "\n")

mw = tk.Tk()
ZuganfangEingabe = tk.Entry(mw,
                            width = 10,
                            background = 'white',
                            foreground = 'black')
ZuganfangEingabe.focus() 
ZuganfangEingabe.pack()
Zuganfang = tk.Text(mw,
                    height = 39,
                    width = 15,
                    relief = tk.GROOVE,
                    background = 'black',
                    foreground = 'white')
Zuganfang.pack()
ZuganfangEingabe.bind(sequence = '<Return>',
                      func = lambda event : insertTo(ZuganfangEingabe, Zuganfang))
mw.mainloop()
Aber Du solltest ein Tkinter-Tutorial wie z.B. das hier durcharbeiten und Dich eben auch mit OOP vertraut machen.

Gruß
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Super, vielen Dank Leute, es funktioniert jetzt. Ebenfalls ein Dankeschön für die Tutorialempfehlung un den anderen Tipps.
Danke nochmal!
Mit freundlichen Grüßen
Arachnid
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Sorry, dass ich nochmal stören muss, aber ich habe schon wieder ein Problem.

Code: Alles auswählen

def a1 (figa1):
	la1 = tk.Label(f, image=figa1, relief=tk.GROOVE)
	la1.place(x=18, y=631)
	bilda1 = DameS #Diese Zeile wird einfach nicht ausgeführt
Wie dem Kommentar oben zu entnehmen, habe ich das Problem, dass die letzte Zeile in dem Auszug nicht ausgeführt wird. Schreibe ich dort einen Printbefehl o.ä. hin, wird dieser ausgeführt.
Beide verwendeten Variablen sind zuvor als globale Variablen deklariert worden, in denen Bilder 'gespeichert' sind:

Code: Alles auswählen

bilda1 = tk.PhotoImage(file='leer.gif')
DameS = tk.PhotoImage(file='DameS.gif')
Außerdem habe ich den Code einmal wie folgt modifiziert, um zu sehen, ob das Problem wirklich dort liegt:

Code: Alles auswählen

def a1 (figa1):
	la1 = tk.Label(f, image=figa1, relief=tk.GROOVE)
	la1.place(x=18, y=631)
	bilda1 = DameS #Diese Zeile wird einfach nicht ausgefuhrt
	xyz = tk.Label(f, image=bilda1, relief=tk.GROOVE) #diese auch nicht
	xyz.place(x=720, y=400) #und diese auch nicht
Ich denke, dass Ergebnis, weist eindeutig darauf hin, dass diese 3 Zeilen ignoriert werden, nur habe ich absolut keine Idee wieso.
Ich habe zwar bei google gesucht und auch in das empfohlene Tutorial reingeschaut, aber nichts gefunden, was mir weiterhelfen könnte.
Übrigens ich habe mich daran gehalten und die place Befehle ersetzt. Ich habe hier aber genaz bewusst place-Befehle genutzt, um sicher zu stellen, dass ich das Fenster nicht zwischen den anderen übersehe oder es von irgendwas überlappt wird.

Vielen Dank für eure Hilfe
Mfg.
Arachnid
BlackJack

@Arachnid: Die Zeile wird ausgeführt, nur dass `bilda1` eben nicht ``global`` ist, sondern lokal zur Funktion. ``global`` sollte man sowieso nicht verwenden.
problembär

Was willst Du mit der Zeile denn erreichen?

Wenn irgendwo ein anderes Bild angezeigt werden soll, mußt Du das dem Widget mitteilen, z.B. mit "widget.configure(image = newimage)" oder so.

Ich habe den Verdacht, daß Du viel zu komplizierten Code schreibst, den man mit ein bißchen OOP viel einfacher und sauberer ausdrücken könnte. Braucht natürlich etwas Zeit, das zu lernen. Ging mir auch so: Wenn ich heute Code von mir aus meinen Anfangstagen sehe, bin ich schon etwas befremdet :). Kommt halt alles mit der Zeit.
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

@Problembär:
Ja, ich weiß, dass der Code unterm Strich sehr kompliziert (komplizierter als nötig) werden wird, aber ich möchte ihn eben schreiben, zum einen, um zu üben undzum anderen, weil ich ihn grade auch brauchen könnte.
Natürlich muss ich dem Widget mitteilen, dass es was anderes anzeigen soll, aber genau, das soll die Zeile erreichen. Ich muss das Bild, das momentan da drin liegt in eine beliebige andere Funktion übertragen, daher wollte ich das Bild in eine globale Variable speichern, die ich dann an anderer Stelle wieder aufrufen kann. Aber wenn er das Schreiben in die Variable unterbindet, wird das schwierig.
Es ist mir klar, dass mein Programm wahrscheinlich min. 200 Zeilen mehr hat als nötig, aber ich habe eben (noch) nicht die Fähigkeiten, das zu verbessern. Ich möchte möglichst erstmal das Programm zum Laufen bringen und dann Schritt für Schritt verbessern.

@BlackJack:
Ich bin mir eigentlich sicher, dass die Variable global ist, da ich sie außerhalb der Funktion bereits deklariert habe. Sie wird in dieser Funktion lediglich verwendet. Und vor allem, warum startet er die Fenster dann nicht? Er zeigt in der Konsole auch keine Fehlermeldung an.
Und angenommen bilda1 wäre nur lokal, wie könnte ich sie dann global machen? (Ich ahne bereits, dass dieAntwort lauten wird, ich solle es Objektorientiert lösen. Gut, das ist ja an sich auch richtig. Nur würde ich dieses Programm, wie oben beschrieben, eben ersteinmal gerne zum laufen bringen, um es dann zu verbessern. Es verschafft einem Anfänger eben etwas mehr Motivation, schonmal was geschafft zu haben. Und sei versichert, ich bin keiner, der sagt: "Wozu soll ichs verbessern, wenns läuft?")
BlackJack

@Arachnid: Ausserhalb einer Funktion kannst Du keine Namen innerhalb der Funktion als ``global`` deklarieren. Falls Du ``global`` irgendwo auf Modulebene stehen hast, dann kannst Du das entfernen -- das hat dort nämlich absolut keinen Effekt. (Ärgerlich dass der Compiler einem das nicht um die Ohren haut.)
problembär

@Arachnid: Ich glaube, ich verstehe nicht genau, was Du vorhast. Vielleicht sowas:

http://paste.pocoo.org/show/243948/

? Guck mal, mit ein bißchen OOP sähe das so aus:

http://paste.pocoo.org/show/243949/

Dann hättest Du nicht das Problem mit lokalen Variablen, und daß die alleinstehenden Funktionen die Widgets kennen müssen.

Gruß
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Super, jetzt funktioniert es. Dank euch beiden. Wie gesagt bei OOP muss ich mich eben erst einmal einarbeiten, aber das werde ich in der nächsten Zeit angehen.

Aber nochmal eine andere Frage. Wie kann ich ein Label-Fenster im Hauptfenster schließen? Also ich hab schon raus gekriegt, dass es wohl irgendwie mit forget gehen muss. Aber ich kenne die Syntax nicht. Die, die mir spontan eingefallen sind, funktionieren nicht und wenn man bei google python tkinter forget label usw. eingibt kriegt man nur haufenweise Einträge a lá "and don't forget to ..."
Könnt ihr mir da weiterhelfen?
BlackJack

@Arachnid: Was hast Du denn probiert? `forget()` auf dem Label-Objekt aufgerufen?
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Genau.
Ich hab folgendes alles probiert: (la1 ist der Name des Labels, f der des Hauptfensters)
la1.forget()
forget(la1)
la1.forget(la1)
f.forget(la1)
tk.forget(la1)
la1.forget(self)
tk.forget(self)
forget(self)

Nichts davon hat funktioniert, daher hab ich keine Ahnung mehr.
BlackJack

@Arachnid: Die allererste Variante ist die Richtige. Wenn `pack()` verwendet wurde. Ansonsten schau Dir die entsprechenden anderen `*_forget()`-Methoden an.

Code: Alles auswählen

import Tkinter as tk


def main():
    root = tk.Tk()
    label = tk.Label(root, text='Spam')
    label.pack()
    button = tk.Button(root, text='Remove Spam', command=lambda: label.forget())
    button.pack()
    root.mainloop()


if __name__ == '__main__':
    main()
problembär

Solche Einzelheiten lese ich auch ganz gern in "pydoc", also "pydoc Tkinter.Label" in diesem Fall.

Gruß
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Danke für die Hilfe schonmal. Ich galube mein Problem ist, dass ich keinen direkten Auslöser für den Befehl habe. BlackJack beispielsweise weist ja durch einen Button das Fenster an sich zu schließen. Bei mir steht der Befehl einfach in einer if-Funktion:

Code: Alles auswählen

def a1 (figa1):
	la1 = tk.Label(f, image=figa1, relief=tk.GROOVE)
	la1.pack()
	global bilda1
	bilda1 = figa1
	if c == 1:
		la1.forget()
Wobei der Block, der a1 aufruft auch c = 1 setzt. Es liegt definitiv nicht daran, dass er die if-Fuktion nicht aufruft, da ein print-Befehl,den ich zu Testzwecken dahinter geschrieben habe, ausgeführt wurde.
Glaubt ihr das könnte ein Problem sein? Oder habe ich mich doch in der Syntax vertan, denn momentan tut er nichts, wenn ich den Code da oben ausführe.
BlackJack

@Arachnid: Wo kommt `c` denn her? Und warum zeigst Du das Label im Falle von `c == 1` überhaupt an, wenn Du es dann sowieso gleich wieder entfernst!? Und was sind das für nichtssagende Namen? Warum steht da bei vielen `a1` als Postfix?

Zeig doch mal ein minimal lauffähiges Beispiel, dass das Verhalten zeigt, so dass man das auch nachvollziehen kann.

``if`` ist übrigens eine Anweisung und keine Funktion.
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Das Problem ist, dass das Fenster grundsätzlich angezeigt werden soll und erst wenn c = 1 gesetzt wird, abgeschlatet werden soll. Und wenn ich es so einrichte, dass er das Fenster nur anzeigt solange c ungleich null ist, funktioniert es auch nicht. Er zeigt dann das Fenster immer noch an.
Dass da so oft a1 steht, liegt daran, dass das Fenster a1 heißt. Alle anderen entsprechenden Variablen beziehen sich also in irgendeinerweise auf das Fenster.
Ich hab bis jetzt immer nur Codeschnipsel gezeigt, weil das ganze Programm über 900 Zeilen lang ist und die entsprechenden Zeilen oft weit auseinander stehen, deswegen hab ich dir mal die entsprechenden Codezeilen zusammengebastelt:

Code: Alles auswählen

import Tkinter as tk
f = tk.Tk()
f.geometry('1000x700')
f.title('Schach')
f.iconbitmap('Schach_Icon.ico')
c = 0
a = 0
if a == 0:
	bilda1 = tk.PhotoImage(file='leer.gif')
	currentfigur = tk.PhotoImage(file='leer.gif')
leer = tk.PhotoImage(file='leer.gif')
bild = tk.PhotoImage(file='Schach700.gif')
TurmW = tk.PhotoImage(file='Turm.gif')
l = tk.Label(f, image=bild, relief=tk.GROOVE)
l.place(x=1, y=1)
def a1 (figa1):
	if c == 0:
		la1 = tk.Label(f, image=figa1, relief=tk.GROOVE)
		la1.place(x=18, y=631)
		global bilda1
		bilda1 = figa1
		# Hier soll jetzt la1 entfernt werden
def a2 (figa2):
	la1 = tk.Label(f, image=figa2, relief=tk.GROOVE)
	la1.place(x=18, y=544)
	global bilda2
	bilda2 = figa2
if a == 0:
	a1 (TurmW)
Zuganfang = tk.Text(f, height=39, width=15, relief=tk.GROOVE, background='black', foreground='white')
Zuganfang.place(x=720, y=60)
def show(self):
	Anfangskoordinate = ZuganfangEingabe.get()
	Zuganfang.insert(tk.END, Anfangskoordinate + "\n")
	ZuganfangEingabe.delete(0, tk.END)
	ZugendeEingabe.focus()
	a = 1
	if Anfangskoordinate == 'a1':
		global currentfigur
		currentfigur = bilda1
		global c
		c = 1
		a1 (leer)
def showend(self):
	Endkoordinate = ZugendeEingabe.get()
	Zugende.insert(tk.END, Endkoordinate + "\n")
	ZugendeEingabe.delete(0, tk.END)
	ZuganfangEingabe.focus()
	if Endkoordinate == 'a2':
		a2 (currentfigur)
ZuganfangEingabe = tk.Entry(f, width=10)
ZuganfangEingabe.place(x=720, y=1)
ZuganfangEingabe.focus()
ZuganfangEingabe.bind(sequence = '<Return>', func = show)
ZugendeEingabe = tk.Entry(f, width=10)
ZugendeEingabe.place(x=865, y=1)
ZugendeEingabe.bind(sequence = '<Return>', func = showend)
Zugende = tk.Text(f, height=39, width=15, relief=tk.GROOVE, background='black', foreground='white')
Zugende.place(x=865, y=60)
Zuganfang_Uberschrift = tk.Label(f, text='Zugstartfeld eingeben:  ', relief=tk.GROOVE, background='black', foreground='white')
Zuganfang_Uberschrift.place(x=720, y=41)
Zugende_Uberschrift = tk.Label(f, text='Zugendfeld eingeben:    ', relief=tk.GROOVE, background='black', foreground='white')
Zugende_Uberschrift.place(x=865, y=41)
f.mainloop()
Dieser Code ist ein Auszug, der das Programm im Miniformat darstellt. Leider kann ich hier nichts anhängen, sonst würde ich dir die Originalbilder mitliefern.
Und falls du befürchten solltest, dass ich ein richtiges Schachprogramm schreiben wollte, sei beruhigt, diese Programm soll nur Züge ausführen können, die ich eingebe. Ich werde ihm keine Schachregeln oder KI einprogrammieren. Nur, dass du nicht glaubst ich wäre jemand, der sich son Riesenprojekt vornimmt und dann irgendwann aufgibt, weil er sein Ziel nicht schnell genug erreicht.
Ich weiß auch, dass die place-Befehle nicht so schön sind, nur mit den Methoden, die ich zu erst ausprobiert habe und die zunächst auch funktioniert haben, brachten mich nicht weit, so dass ich erst mal wieder place-Befehle verwendet habe. Ich muss mich einfach nochmal intensiver mit dem Layoutmanager auseinandersetzen, das steht weit oben auf der Prioritätenliste.
Ich hoffe, dass das weiterhilft.
MfG
Arachnid
BlackJack

@Arachnid: Mit der entsprechenden `*_forget()`-Methode sollte das kein Problem sein, da steht ja aber nur ein Kommentar.

Aber grundsätzlich denke ich Du solltest das Vorhaben noch ein wenig verschieben und Grundlagen lernen. Funktionen und Datenstrukturen um da ein wenig Ordnung reinzubringen. Und ich denke OOP wäre ebenfalls eine sehr gute Idee.

So ein Schachfeld modelliert man nicht mit einzelnen Namen und Funktionen *pro* Feld. Das gehört in eine entsprechende Datenstruktur. Und da kann man sich auch ohne Grafik schon dransetzen.

Werte sollten Funktionen möglichst als Argumente betreten und als Rückgabewerte verlassen. Alles andere ist "magisch" und sehr unübersichtlich. Wenn sich mehrere Funktionen Werte "teilen" sollen, kann man sie mit einer Klasse zu Objekten zusammenfassen.

Auf Modulebene sollten möglichst nur Definitionen von Konstanten, Funktionen, und Klassen stehen und der Aufruf einer Hauptfunktion. Sonst kein ausführbarer Code.

Wenn Du das Schachfeld selber zeichnen möchtest, würde ich ein `Canvas` verwenden. Da kann man die Objekte auch nachträglich noch neu positionieren. Wenn der Hintergrund keine Grafik sein muss, bietet sich auch ein Frame mit einem 8x8 Grid darin an.
Arachnid
User
Beiträge: 16
Registriert: Dienstag 27. Juli 2010, 18:32

Das dieser Code im Endeffekt dämlich ist, war mir klar. Es war mehr oder weniger ein Notbehelf. Ich bin hier grade im Sommerlager und es gibt hier einfach nicht genug Platz, um gleichzeitig Schachfeld und Notebook aufzustellen und wir haben hier zwar Internet, das ist aber so langsam, dass es unmöglich ist ein Freeware-Programm runterzuladen. Da ich aber gerne auch hier noch ein bisschen Schach üben wollte, dachte ich mir, ich könnte mir so irgendwie aushelfen und es wär eine Superausgangsposition, um OOP und Ähnliches zu lernen.
Wie gesagt den forget-Befehl führt er einfach nicht aus. Na gut, geht auch so, lass ich eben leere Fenster stehen.
Vielen Dank euch nochmal.
Wenn ich in zwei Wochen zu Hause bin, werde ich mich dann dran setzen Python ordentlich zu lernen. Hier hab ich immer höchstens 2 Stunden am Stück Zeit, da ist es mir zu umständlich ständig wieder neu anzusetzen. Spiel ich eben Schach so gut es geht.
Vielen Dank nochmal.
MfG
Arachnid
Antworten