Seite 1 von 1

Widgets positionieren

Verfasst: Montag 17. Juni 2013, 00:02
von Gwunderi
Hallo liebe Community

Bin Anfängerin in Python und kenne auch keine anderen Programmiersprachen (HTML/CSS beherrsche ich recht gut). Habe nun meine erste Frage:

Will folgendes Konstrukt:
Ein Textfenster (Text) mit Scrollbar rechts (Scrollbar) und darunter ein Eingabefeld (Entry) und daneben ein Button.

Kriege die Positionierung mit "pack()" einfach nicht hin.

Muss für Text pack(LEFT) und für Scrollbar pack(RIGHT) angeben, und das kommt auch richtig heraus, kriege es aber einfach nicht hin, dass Entry und Button unter dem Fenster erscheinen, sondern immer rechts (oder links) davon. Habe auch "anchor" ausprobiert, geht auch nicht.

Sehe im Internet, dass es vielleicht gar nicht möglich ist mit "pack.()"?
Würde es eventuell gehen, wenn ich statt "Text" das Widget "Frame" nehme? Frame habe ich noch nie ausprobiert, möchte aber zuerst wissen, ob diese Positionierung mit "Text" überhaupt möglich ist.

Unten der noch unvollständige Code.

Vielen Dank im voraus
Gwunderi

Code: Alles auswählen

from tkinter import *
root = Tk()
root.title ("Chatten mit Python")

textfeld = Text (root, bg="#ffffbb")
textfeld.pack(side=LEFT, fill=Y)

scroll = Scrollbar (root)
scroll.pack(side=RIGHT, fill=Y)

scroll.config(command=textfeld.yview)
textfeld.config(yscrollcommand=scroll.set)

say = Entry (root)
say.pack()

but = Button (root, text="Button1")
but.pack()

root.mainloop()

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 07:57
von wuf
Hi Gwunderi

Willkommen im Forum. Hier eine mögliche Lösung:

Code: Alles auswählen

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

def app():    
    root = tk.Tk()
    root.title ("Chatten mit Python")

    textbox_frame = tk.Frame(root)
    textbox_frame.pack()

    scrollbar = tk.Scrollbar(textbox_frame)
    scrollbar.pack(side='right', fill='y')

    textfield = tk.Text(textbox_frame, yscrollcommand=scrollbar.set, bg="#ffffbb")
    textfield.pack(side='left')

    scrollbar.config(command=textfield.yview)

    entry_frame = tk.Frame(root)
    entry_frame.pack(pady=2)

    say = tk.Entry (entry_frame)
    say.pack(side='left', padx=(0,4))

    but = tk.Button (entry_frame, text="Button1")
    but.pack(side='left')

    root.mainloop()
    
app()
Gruss wuf :wink:

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 08:02
von BlackJack
@Gwunderi: Du brauchst `Frame` nicht *statt* des `Text`-Widgets sondern *zusätzlich*. `Frame` ist ein Container-Widget in das man andere Widgets platziert und innerhalb eines Container-Widgets darf/sollte man `pack()` immer nur mit dem gleichen Wert für `side` verwenden. Aus HTML/CSS-Sicht ist ein `Frame` also so etwas wie ein <div>, also ein Blockelement in dem man andere Elemente ausrichten kann.

`Text`- und `Scrollbar`-Widget kannst Du also in einem `Frame`-Widget links ausrichten, und dieses `Frame`-Widget und `Entry` und `Button` dann alle untereinander ins `root`-Widget.

Vom Sternchen-Import sollte man übrigens Abstand nehmen. Damit holt man sich gerade bei `tkinter` knapp 200 Namen in das Modul von denen man nur einen ganz kleinen Bruchteil auch tatsächlich verwendet. Man findet aber seine selbst definierten Namen, zum Beispiel in einem Debugger nur schwer wieder. Und wenn man das mit mehreren Modulen macht, weiss man im Modul nicht mehr aus welchem Modul eigentlich welcher verwendete Name kommt. Desweiteren besteht die Gefahr von Namenskollisionen wenn mehrere Module den gleichen Namen für etwas verwenden.

Die Leerzeichensetzung zwischen Namen und öffnender Klammer für den Aufruf ist inkonsistent. Konventionell steht dort *kein* Leerzeichen.

Bei der Namenswahl sollte man sich auf eine Sprache beschränken und keinen Deutsch/Englisch-Mix verwenden. Ausserdem sollte man Abkürzungen vermeiden die nicht gebräuchlich sind. Speicher ist nicht mehr so knapp, es gibt kaum noch künstliche Beschränkungen von Compilern bei der Namenslänge, und moderne Programmiersprachen sind so aussdrucksstark, dass man auch deutlich weniger Probleme hat eine maximale Zeilenlänge von 80 Zeichen einzuhalten als früher. :-)

Code: Alles auswählen

#!/usr/bin/env python
try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk


def main():
    root = tk.Tk()
    root.title('Chatten mit Python')

    text_frame = tk.Frame(root)
    textfield = tk.Text(text_frame, bg='#ffffbb')
    textfield.pack(side=tk.LEFT, fill=tk.Y)

    scrollbar = tk.Scrollbar(text_frame)
    scrollbar.pack(side=tk.LEFT, fill=tk.Y)

    scrollbar.config(command=textfield.yview)
    textfield.config(yscrollcommand=scrollbar.set)

    text_frame.pack()

    entry = tk.Entry(root)
    entry.pack(fill=tk.X)

    button = tk.Button(root, text='Senden')
    button.pack()

    root.mainloop()


if __name__ == '__main__':
    main()

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 13:04
von Gwunderi
wuf hat geschrieben:Willkommen im Forum. Hier eine mögliche Lösung:
Hallo wuf

Juhuiii, es funktioniert! Dann ist es also doch nur mit "Frame" möglich ...
Es funktioniert auch mit zwei Änderungen:

1. Mit "from tkinter import *" importiert und entsprechend das "tk" überall weggelassen.
2. Du hast das Ganze mit einer Funktion aufgerufen, habe ich einfach weggelassen. Kann mir vorstellen, dass eine Funktion sinnvoll ist, wenn man die App mehrmals verwenden und das Ganze nicht nochmals hinschreiben will?

Habe ich alles richtig verstanden?
"root" ist das "Hauptfenster" und "Frame" ein "Unterfenster" und z.B. "Label", "Entry", "Button" usw. sind Unter-Unterfenster? (nennt man glaube ich "parent" und "child"?)

Du hast oben zwei Frames genommen, im ersten "Text" und "Scrollbar" hineingepackt und im zweiten "Entry" und "Button".

Im zweiten Frame positionierst Du ja Entry mit "side=LEFT", aber es erscheint (mit dem Button) zentriert unter dem Textfeld; wohl weil das zweite Frame die Breite des ersten übernimmt und LEFT dann nicht "ganz links", sondern "auf der linken Seite" (von Button) bedeutet?
P.S. habe es auch so versucht: .pack(side=LEFT, fill=X) - ändert nichts. Aber ist jetzt auch nicht sooo wichtig, werde mich eh später noch mit der "Geometrie" genauer befassen.

Herzlichen Dank vorab für das - funktionierende ! - Programm (siehe unten).

Grüsslein, Gwunderi

Code: Alles auswählen

from tkinter import *
root = Tk()
root.title ("Chatten mit Python")

textbox_frame = Frame (root)
textbox_frame.pack()

scrollbar = Scrollbar (textbox_frame)
scrollbar.pack(side=RIGHT, fill=Y)

textfield = Text (textbox_frame, yscrollcommand=scrollbar.set, bg="light blue")
textfield.pack(side=LEFT)

scrollbar.config(command=textfield.yview)

entry_frame = Frame (root)
entry_frame.pack(pady=2)

say = Entry (entry_frame)
say.pack(side=LEFT, padx=(0, 5))

but = Button (entry_frame, text="Button1")
but.pack(side=LEFT)

root.mainloop()

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 13:14
von Gwunderi
Hallo BlackJack

Werde mir dann auch Deine Antwort anschauen, muss nur erst noch was essen 8)

Grüsslein, Gwunderi

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 14:35
von wuf
Hi Gwunderi
Gwunderi hat geschrieben:Juhuiii, es funktioniert
Freut mich. :wink:
Gwunderi hat geschrieben:1. Mit "from tkinter import *" importiert und entsprechend das "tk" überall weggelassen.
Das geht natürlich für kleine Codesnippets. Autoren gängiger Bücher brauchen für ihre Beispielsnippets auch vermehrt Sternchenimporte. Da würde ich aber unbedingt die Hinweise von BlackJack beachten. Sobald du grössere Skripts schreibst die auch weitere Module importieren kann es Probleme geben. Namenkonflikte usw.
Gwunderi hat geschrieben:2. Du hast das Ganze mit einer Funktion aufgerufen, habe ich einfach weggelassen. Kann mir vorstellen, dass eine Funktion sinnvoll ist, wenn man die App mehrmals verwenden und das Ganze nicht nochmals hinschreiben will?
Ja die Regel ist, dass möglichst wenig Code auf Modulebene geschrieben wird.
Gwunderi hat geschrieben:"root" ist das "Hauptfenster"
Stimmt!
Gwunderi hat geschrieben: "Frame" ein "Unterfenster"
Sind Behälter (Container) wie BlackJack auch schon angedeutet hat in denen zusammengehörende Widgets zu einer Gruppe vereint werden. Frame ist ein wichtiges Widget, welches bei Verwendung des Pack-Layouters eingesetzt wird.
Gwunderi hat geschrieben:und z.B. "Label", "Entry", "Button" usw. sind Unter-Unterfenster?
Nein keine Unter-Unterfenster sondern sogenannte Widgets. Das sind grafische Komponenten welche für die Eingabe, Ausgabe, Anzeige und Bedienung bei Verwendung einer grafischen Oberfläche (Gui) zum Einsatz kommen.
[quote"Gwunderi"]nennt man glaube ich "parent" und "child"?[/quote]Wenn du z.B. ein Button-Widget in einem Frame ablegen möchtest ist das Frame='parent' und das Button-Widget ein 'child' vom Frame-Widget.
Gwunderi hat geschrieben:Im zweiten Frame positionierst Du ja Entry mit "side=LEFT", aber es erscheint (mit dem Button) zentriert unter dem Textfeld; wohl weil das zweite Frame die Breite des ersten übernimmt und LEFT dann nicht "ganz links", sondern "auf der linken Seite" (von Button) bedeutet? P.S. habe es auch so versucht: .pack(side=LEFT, fill=X) - ändert nichts.
Das zweite Frame wird ohne weitere 'fill'-Angabe automatisch zentriert und ist nur so breit wie die Gesamtbreite der im Frame enthaltenen Widgets. In diesem Fall wären dies die Gesamtbreite des Entry- und Button-Widgets. Wenn du im zweiten Frame ('entry_frame') die fill-Option mit 'x' erweiterst so werden das Entry- und Button-Widget ganz nach links verschoben. Siehe folgende Änderung:

Code: Alles auswählen

entry_frame = Frame (root)
entry_frame.pack(fill='x', pady=2)
Hoffe alle deine momentanen Fragen hiermit einigermassen beantwortet zu haben. Wünsche dir noch viel Spass mit Python, Tkinter, wxWindows...usw.

N.B. Unbedingt noch die Hinweise und Anregungen von BlackJack beachten.

Gruss wuf :wink:

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 15:29
von Gwunderi
BlackJack hat geschrieben:Du brauchst `Frame` nicht *statt* des `Text`-Widgets sondern *zusätzlich*. `Frame` ist ein Container-Widget in das man andere Widgets platziert und innerhalb eines Container-Widgets darf/sollte man `pack()` immer nur mit dem gleichen Wert für `side` verwenden. Aus HTML/CSS-Sicht ist ein `Frame` also so etwas wie ein <div>, also ein Blockelement in dem man andere Elemente ausrichten kann.
Ja, habe ich jetzt gemerkt/herausgefunden. Frame kann man ja auch nicht formatieren, soweit ich ausprobiert habe, es ist wirklich nur ein Container. Werde mir auch merken, pack() innerhalb eines Frames immer dieselbe "side" zuzuweisen.
Vom Sternchen-Import sollte man übrigens Abstand nehmen. Damit holt man sich gerade bei `tkinter` knapp 200 Namen in das Modul von denen man nur einen ganz kleinen Bruchteil auch tatsächlich verwendet. Man findet aber seine selbst definierten Namen, zum Beispiel in einem Debugger nur schwer wieder. Und wenn man das mit mehreren Modulen macht, weiss man im Modul nicht mehr aus welchem Modul eigentlich welcher verwendete Name kommt. Desweiteren besteht die Gefahr von Namenskollisionen wenn mehrere Module den gleichen Namen für etwas verwenden.
Dann sollte man so importieren:
"from tkinter import root" z.B. - auch möglich: "from tkinter import root as r".
Und was, wenn man im selben App mehrere Module benötigt? Die einfach alle nacheinander importieren? (aber das kann ich selber nachsehen).
Wegen der Namenkollision: die ist ja eben gegeben, wenn ich Module aus verschiedenen "Quellen" (z.B. aus tkinter, eigener Ordner usw.) importiere, das meinst Du wohl?
Die Leerzeichensetzung zwischen Namen und öffnender Klammer für den Aufruf ist inkonsistent. Konventionell steht dort *kein* Leerzeichen.

Bei der Namenswahl sollte man sich auf eine Sprache beschränken und keinen Deutsch/Englisch-Mix verwenden. Ausserdem sollte man Abkürzungen vermeiden die nicht gebräuchlich sind.
Das sind alles Konventionen, o. k. Aber warum soll ich mich daran halten? Für den Fall, dass ich mein Programm mit jemandem teile (wie hier im Forum z.B.)?

Zu Deinem Code noch:
Ist ganz ähnlich wie der von wuf. Entry nimmt jetzt bei Dir wegen dem "fill=X" die ganze Breite ein und der Button wird darunter plaziert. Geschmacksache, welche Variante man bevorzugt. Wenn noch mehrere Buttons dazukommen und ich bei Entry eine Breite definiere, füllt es dann eh (fast) die ganze Breite aus.

Das verstehe ich nicht: if __name__ == '__main__':
Bedeutet das, wenn irgendwo die Funktion mit Namen "main" aufgerufen wird, soll die App starten? (Tut sie das nicht eh?)

Auch Dir herzlichen Dank für Deine Mühe
Grüsslein, Gwunderi

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 15:59
von BlackJack
@Gwunderi: Bei `tkinter` ist das Importieren des Moduls unter dem Namen `tk` üblicher als die Namen aus dem Modul zu importieren. Es ist weniger Arbeit und man sieht bei der Verwendung auch immer gleich, dass es um GUI-Objekte geht.

Wenn man mehrere Module benötigt, dann muss man die natürlich alle importieren.

Ja, mit Namenskollision ist gemeint, dass mehr als ein Modul den gleichen Namen auf Modulebene definiert. Bei `tkinter`-Programmen sieht man zum Beispiel manchmal das Problem, dass jemand zusätzlich die Python Imaging Library (PIL) verwendet um auch andere Bildformate als XPM, GIF, und JPEG anzeigen zu können. Und beide Module haben eine `Image`-Klasse.

An einige Konventionen sollte man sich halten weil sie die Zusammenarbeit erleichtern, wobei auch das Lesen von Quelltext von anderen drunter fällt. Aber oft ist „der Andere” man selbst mit genügend zeitlichem Abstand. Was jetzt noch völlig klar und logisch erscheint, zum Beispiel irgendwelche kryptischen Abkürzungen, hat man in ein paar Monaten oft vergessen, und dann muss man sich erst wieder mühsam erarbeiten was man sich bei dem Buchstabenkürzeln eigentlich gedacht hat.

Es wird nur die `main()`-Funktion aufgerufen wenn die ``if``-Bedingung wahr ist. Und `__name__` ist der Name des Moduls — ausser wenn genau das Modul als Programm gestartet wurde statt per ``import`` importiert zu werden. In dem Falle ist `__name__` in dem Modul an den Wert '__main__' gebunden. Damit kann man Module schreiben, die man sowohl ausführen kann, als auch importieren ohne dass das Programm gleich automatisch abläuft. Das kann zur Fehlersuche nützlich sein, wenn man ein Modul mit mehreren Funktionen und Klassen in einer Python-Shell importieren und live die Einzelteile ausprobieren kann. Und für automatisierte Tests ist das auch wichtig.

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 16:44
von Gwunderi
BlackJack hat geschrieben:Bei `tkinter` ist das Importieren des Moduls unter dem Namen `tk` üblicher als die Namen aus dem Modul zu importieren. Es ist weniger Arbeit und man sieht bei der Verwendung auch immer gleich, dass es um GUI-Objekte geht.
Ach so, dachte, man solle das Sternchen vermeiden, um nicht alle Module aus tkinter zu importieren - aber es ist wegen der Erkennung als GUI (denn ob "from tkinter import *" oder "import tkinter as tk": es werden ja in beiden Fällen alle tkinter-Module importiert.
Edit: und um Namenkollisionen (aus anderen Quellen) zu vermeiden !
An einige Konventionen sollte man sich halten weil sie die Zusammenarbeit erleichtern, wobei auch das Lesen von Quelltext von anderen drunter fällt.
Wenn ich jemandem das Leben erleichtern kann :D
Aber oft ist „der Andere” man selbst mit genügend zeitlichem Abstand. Was jetzt noch völlig klar und logisch erscheint, zum Beispiel irgendwelche kryptischen Abkürzungen, hat man in ein paar Monaten oft vergessen, und dann muss man sich erst wieder mühsam erarbeiten was man sich bei dem Buchstabenkürzeln eigentlich gedacht hat.
... und vor allem mir selbst ... :mrgreen:

Deinen letzten Abschnitt habe ich nicht recht verstanden, aber ist für mich im Moment auch nicht so wichtig, werde mich bestimmt daran erinnern, wenn ich mal so weit bin.
wuf hat geschrieben:Hoffe alle deine momentanen Fragen hiermit einigermassen beantwortet zu haben. Wünsche dir noch viel Spass mit Python, Tkinter, wxWindows...usw.
Ja, hast Du. Habe echt grossen Spass mit Python, die Möglichkeiten, die es bietet - und die vielen Erfolgserlebnisse :wink:

Herzlichen Dank nochmals Euch beiden und bis zum nächsten "unlösbaren Problem".

Grüsslein, Gwunderi

Re: Widgets positionieren

Verfasst: Montag 17. Juni 2013, 17:53
von Gwunderi
Hallo zusammen,

Es geht doch, Entry und Button ganz links auszurichten.
Wie Du BlackJack einmal erwähnt hattest:
Text und Scrollbar in einem Frame.
Aber Entry und Button dann nicht in einem zweiten Frame, sondern in root und bei beiden pack(side=LEFT). Dann kommt es goldrichtig raus - siehe unten:

Code: Alles auswählen

from tkinter import *
root = Tk()
root.title("Chatten mit Python")

text_frame = Frame(root)

textfield = Text(text_frame, bg="light blue")
textfield.pack(side=LEFT)

scrollbar = Scrollbar(text_frame)
scrollbar.pack(side=LEFT, fill=Y)

scrollbar.config(command=textfield.yview)
textfield.config(yscrollcommand=scrollbar.set)

text_frame.pack()

entry = Entry(root)
entry.pack(side=LEFT, padx=(0,5))

button = Button(root, text="Senden")
button.pack(side=LEFT)

root.mainloop()