wie lassen sich Eigenschaften von Widgets ermitteln

Fragen zu Tkinter.
Antworten
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Hallo,
In VB kann ich z.b. die Position bzw. die Farbe eines Buttons wie folgt ermitteln
X = Button1.Legt
Farbe = Button1.Backcolor

Wie geht das bei Python?

(Ist z.B. bei Entscheidungen wie:
if ... == 'red':
... = 'blue'

Interessant.
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MissMapeL: Das würde ich einfach nicht in der GUI speichern und abfragen. Die Farbe hängt ja sicher irgendwo von der Geschäftslogik ab und kann da entweder direkt als Farbe oder als Zustand der als eine bestimmte Farbe angezeigt wird, abgefragt werden.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Lieber blackjack,
gerade lese ich deine zweite Antwort auf einen Eintrag von mir. - So gefällt mir das! Falls ich dich vorhin falsch verstanden habe - Entschuldigung!
Generell löst deine Antowort hier aber nicht meine Frage. Es muss doch möglich sein bei GUI-Elementen nicht nur die Eigschaften zu setzen sondern auch auszulesen (SET-GET).
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

Wie setzt du denn die Hintergrundfarbe?
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MissMapeL: Es ist aber nicht immer möglich. Und da wo es möglich ist, ist es umständlich und schreit IMHO „das ist so nicht vorgesehen“. Wie man an die Optionen kommt steht in der Dokumentation: https://docs.python.org/3.6/library/tki ... ng-options
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo MissMapeL,
vielleicht hilft dir das:https://www.tcl.tk/man/tcl8.6/TkCmd/winfo.htm
oder das:

Code: Alles auswählen

import tkinter as tk
from functools import partial

def main():
    root = tk.Tk()
    taste = tk.Button(root, bg = "lightgreen",
                      text = "Meine\nTaste")
    taste.config(command = partial(zeige_farbe, taste))
    taste.pack()
    root.mainloop()

def zeige_farbe(taste):
    print(taste['bg'])

if __name__ == "__main__":
    main()
Gruss Peter
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Hallo Peter,
danke für die Antwort -
'warum bin ich nicht darauf gekommen???' - Danke!
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo MissMapeL
bitte gern geschehen.
Ich glaub, es geht uns allenn ab und zu so, dass man den Wald vor lauter Bäumen nicht sieht.
Aber es ist schön, wenn ein Danke zurückkommt.
Gruss Peter
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Na, klappt evtl. doch noch nicht so.
Habe jetzt mal mit Klassen gespielt.
Die Überlegung ist auf eine Form eine oder mehrere andere Formen (hier form1) zu legen (vgl. Panel in C#).
Auf der inneren Form liegen dann z.B. Entrys und Buttons.
Die Buttons sollen die Textboxen abfragen. Aber was ist im button_handler1 an (form.e1.get(), form.e2.get()) falsch?

Code: Alles auswählen

from tkinter import *
try:
    import ttk
    py3 = False
except ImportError:
    import tkinter.ttk as ttk
    py3 = True

class Toplevel1:
    def __init__(self, top=None):
        top_width = 800
        top_height = 600
        top_x = 279
        top_y = 154
        top.geometry('{}x{}+{}+{}'.format(top_width, top_height, top_x, top_y))
        top.configure(background="lightblue")
        f1 = form_auf_Top(top)

class form_auf_Top:
    def __init__(self, name_top):
        self.Frame1 = Frame(name_top)
        self.Frame1.place(relx=0.05, rely=0.05, relheight=0.9, relwidth=0.9)
        self.Frame1.configure(background="red")
        
        e1 = entry_auf_form1(self.Frame1, _relx=0.2, _rely=0.25, _relwidth=0.15, _relheight=0.05)
        e2 = entry_auf_form1(self.Frame1, _relx=0.6, _rely=0.25, _relwidth=0.15, _relheight=0.05)
        b1 = button_auf_form1(self.Frame1, _relx=0.2, _rely=0.55, _relwidth=0.55, _relheight=0.15, _text = 'drucken')

class entry_auf_form1:
    def __init__(self, name_der_form, _relx=0.05, _rely=0.05, _relwidth=0.15, _relheight=0.15):
        global form
        form = name_der_form
        self.farb_index = 0
        self.Entry1 = Entry(name_der_form)
        self.Entry1.place(relx=_relx, rely=_rely, relwidth=_relwidth, relheight=_relheight)
        
class button_auf_form1:
    def __init__(self, name_der_form, _relx=0.05, _rely=0.05, _relwidth=0.15, _relheight=0.15, _text=''):
        global form
        form = name_der_form
        self.farb_index = 0
        self.text=_text
        self.Button1 = Button(name_der_form)
        self.Button1.place(relx=_relx, rely=_rely, relwidth=_relwidth, relheight=_relheight)
        self.Button1.configure(pady="0", text=_text)
        self.Button1.bind('<Button-1>',lambda e:self.button_handler1(self.farb_index, self.text))

    def button_handler1(self, farb_index, _text):
        print("First Name: %s\nLast Name: %s" % (form.e1.get(), form.e2.get())) 
        sys.stdout.flush()

def vp_start_gui():
    master = Tk()
    top = Toplevel1 (master)
    mainloop()

if __name__ == '__main__':
    vp_start_gui()
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MissMapeL: Vergiss ``global``, das ist Mist und wenn man tatsächlich Klassen verwenden würde, bräuchte man das auch gar nicht. Die Klassen sind entweder einfach nur überkomplex ausgedrückte Funktionen oder werden falsch verwendet.

Sternchen-Importe sind Böse™. Da holt man sich gerade bei `tkinter` fast 200 Namen ins Modul von denen nur ein kleiner Bruchteil verwendet wird. Auch Namen die gar nicht in `tkinter` definiert werden, sondern ihrerseits von woanders importiert werden. Das macht Programme unnötig unübersichtlicher und fehleranfälliger und es besteht die Gefahr von Namenskollisionen.

Die Importweiche für Python 2 kann man sich sparen. Python 2 ist am Ende. Und von dem Konstrukt wird im Modul auch überhaupt nichts verwendet, das kann also ohne das sich am Programmverhalten etwas ändert entfernt werden.

Namen werden in Python klein_mit_unterstrichen geschrieben. Ausnahmen sind Konstanten (KOMPLETT_GROSS) und Klassen (MixedCase).

Namen nummeriert man nicht. Entweder ist die Nummer total sinnfrei, dann lässt man sie weg, oder man möchte sich einen eigenen, sinnvollen Namen ausdenken, oder man möchte gar keine einzelnen Namen sondern die Werte in einer Datenstruktur zusammenfassen.

`Toplevel1` ist keine Klasse. Das ist einfach nur eine Funktion.

Die Argumentnamen `name_of_top` und `name_der_form` sind falsch. Da wird kein Name übergeben sondern ein Elternwidget. Der Tk-Nomenklatur entsprechend `master`. Wenn man mit dem Namen ein moralisches Problem hat wäre `parent` nicht unüblich.

`form_auf_Top` ist ebenfalls keine Klasse sondern eine Funktion. Da *sollte* vielleicht Zustand gemerkt werden, denn Du willst auf die dort erstellten Eingabeelemente ja später woanders zugreifen, aber die Aufteilung ist so auch komplett Banane.

`place()` sollte man nicht verwenden. Auch nicht mit relativen Angaben. Man muss trotzdem noch die Grösse der GUI selbst angeben und die kann immer noch zu klein für den Inhalt sein.

`entry_auf_form()` ist keine Klasse, sondern eine Funktion. `farb_index` wird dort nicht verwendet.

Was sollen die Unterstriche bei den Argumentnamen? Das ist die Konvention für Argumente die da sein müssen weil die API das verlangt, die aber nicht verwendet werden. Bei Dir werden die Argumente mit dem führenden Unterstrich aber alle verwendet.

Wenig überraschend ist auch `button_auf_form1` keine Klasse. Die ”Klasse” hat zwar eine ”Methode”, die macht aber von dem Objekt auf dem sie definiert ist aber überhaupt gar keinen Gebrauch, ist also auch einfach nur eine Funktion.

Die Defaultwerte bei den ”Methoden” machen allesamt keinen Sinn weil bei jedem Aufruf dann doch alle angegeben werden und so etwas wie eine Defaultposition auch gar keinen Sinn macht.

`bind()` ist der falsche Weg die normale Aktion für einen `Button` anzugeben. Das Eingabeelement verhält sich dann nicht so wie der Benutzer das normalerweise von anderen Programmen gewohnt ist.

Funktionen und Methoden bekommen alles was sie ausser Konstanten benötigen als Argument(e) übergeben. Darum funktioniert Dein ``e1.get()`` & Co nicht, weil `e1` und `e2` undefiniert sind. Die Namen gab es mal lokal in der `form_auf_Top.__init__()` aber eben nur da und nur solange wie diese Methode ausgeführt wurde.

Den ``%``-Operator würde ich nicht mehr zum formatieren von Werten in Zeichenketten verwenden. Dafür gibt es die `format()`-Methode auf Zeichenketten und ab Python 3.6 f-Zeichenkettenliterale.

`print()` kennt ein `flush`-Argument, da braucht man `sys.stdout.flush()` nicht.

Die ”Klassen” als Funktionen geschrieben sähe das so aus:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial


def create_entry(master, relx, rely, relwidth, relheight):
    entry = tk.Entry(master)
    entry.place(relx=relx, rely=rely, relwidth=relwidth, relheight=relheight)
    return entry


def on_button(first_name_entry, last_name_entry):
    print(
        f"First Name: {first_name_entry.get()}\n"
        f"Last Name: {last_name_entry.get()}",
        flush=True,
    )


def create_button(
    master,
    text,
    first_name_entry,
    last_name_entry,
    relx,
    rely,
    relwidth,
    relheight,
):
    button = tk.Button(
        master,
        text=text,
        pady=0,
        command=partial(on_button, first_name_entry, last_name_entry),
    )
    button.place(relx=relx, rely=rely, relwidth=relwidth, relheight=relheight)
    return button


def populate_widget(master):
    frame = tk.Frame(master, background="red")
    frame.place(relx=0.05, rely=0.05, relheight=0.9, relwidth=0.9)
    first_name_entry = create_entry(frame, 0.2, 0.25, 0.15, 0.05)
    last_name_entry = create_entry(frame, 0.6, 0.25, 0.15, 0.05)
    create_button(
        frame,
        "drucken",
        first_name_entry,
        last_name_entry,
        0.2,
        0.55,
        0.55,
        0.15,
    )


def configure_and_populate_toplevel(toplevel):
    toplevel.geometry("800x600+279+154")
    toplevel.configure(background="lightblue")
    populate_widget(toplevel)


def main():
    root = tk.Tk()
    configure_and_populate_toplevel(root)
    root.mainloop()


if __name__ == "__main__":
    main()
Das ist aber sehr komisch strukturiert. Die beiden `create_*()`-Funktionen sind kaum mehr als Zusammenfassen von erstellen eines Widgets und den `place()`. Und die `create_button()` ist auch ein bisschen sehr speziell, weil da ein festes Verhalten für die Aktion hinter dem Button auch gleich mit drin ist. Bei `configure_and_populate_toplevel()` ist im Namen schon abzulesen das die Funktion vielleicht zu viel tut.

Was letztlich von dem ganzen Code übrig bleibt ist das hier:

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk
from functools import partial


def on_button(first_name_entry, last_name_entry):
    print(
        f"First Name: {first_name_entry.get()}\n"
        f"Last Name: {last_name_entry.get()}",
        flush=True,
    )


def main():
    root = tk.Tk()
    root.geometry("800x600+279+154")
    root.configure(background="lightblue")

    frame = tk.Frame(root, background="red")
    frame.place(relx=0.05, rely=0.05, relheight=0.9, relwidth=0.9)

    first_name_entry = tk.Entry(frame)
    first_name_entry.place(relx=0.2, rely=0.25, relwidth=0.15, relheight=0.05)

    last_name_entry = tk.Entry(frame)
    last_name_entry.place(relx=0.6, rely=0.25, relwidth=0.15, relheight=0.05)

    tk.Button(
        frame,
        text="drucken",
        pady=0,
        command=partial(on_button, first_name_entry, last_name_entry),
    ).place(relx=0.2, rely=0.55, relwidth=0.55, relheight=0.15)

    root.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Hallo __blackjack__,
boa, da hast Du dir ja echt Mühe gemacht und mir ganz viel beachtenswertes geschrieben.
Die Globals brauche ich hier wirklich nicht - die kann ich problemlos löschen.
Mit der Namensgebung in Python muss ich mich noch intensiv auseinandersetzen. In C# werden Klassen mit C... begonnen, die Kamelhöcker-Notation verwendet, ... Bei Python ist ds alles offensichtlich anders.
Ich sehe schon, beim Meisten werde ich vermutlich noch eine Weile drüber nachdenken müssen, warum das so ist.
Vielen Dank!!!

Aber - die Fragen gehen wohl nie aus - du verwendest in deiner umgeschriebenen Lösung keine Klassen.
Meine Überlegung waren (zunächst) folgende - ich probiere es mit einer Parabel:
- Eine Ampel ist ein Kasten, der zur Klasse Kasten gehört.
- diese besitzt drei Lampen, die zur Klasse Leuchten gehören und sich in Farbe und Position unterscheiden
- viele Ampeln besitzen noch einen Drücker, der zur Klasse Taster gehört.
- Taster besitzen die Funktion 'zeitsteuerung'
- Lampen besitzen die Eigenschaften an und aus
Somit ergeben sich die Klassen: Kasten, Leuchten und Tater.

Übertragen auf mein Programm dachte ich:
- die Grundlage ist das Objekt Form
- diese enthält die Objekte Entry und Button. Deshalb dachte ich an die Klassen Buttons_auf_Form usw.

Warum ist diese Überlegung falsch?
und zurück zum Anfang dieser Forums-Seite
Wenn ich Eigenschaften von Widgets setzen kann, wie kann ich dann diese am Einfachsten wieder auslesen?
(z.B. Größe, Position, Farbe eines Buttons - Größe, Position, Farbe, Inhalt eines Entrys - ...)
peterpy hat's zwar schon versucht mir zu zeigen - aber -
Wo finde ich eine Liste von allen Eigenschaften der wichtigsten Widgets? - Und wie kann ich diese veranschaulichen? (vgl. print(taste['bg'])

Vielen Dank
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

Hallo MissMapeL,
Wo finde ich eine Liste von allen Eigenschaften der wichtigsten Widgets?
https://anzeljg.github.io/rin2/book2/24 ... index.html
https://www.tcl.tk/man/tcl8.6/TkCmd/contents.htm
Gruss Peter
Benutzeravatar
__blackjack__
User
Beiträge: 13111
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@MissMapeL: Die Namenskonvention für Klassen unterscheidet sich eigentlich nicht zwischen Python und C#. Das ist `NameOfTheClass` in beiden.

Das Problem mit `ButtonAufForm` & Co ist, dass es Klassen für Dinge sind die es bereits gibt. `Entry` und `Button` existieren ja bereits. Warum sollte man da noch einmal Klassen drum herum schreiben die nichts weiter tun als so ein Ding zu verpacken und nur über eine zusätzliche Indirektion ansprechbar machen, und dann noch nicht einmal Mehrwert liefern.

Klassen sind dazu zusammengehörende Daten und Operationen zu einem Objekt zusammen zu fassen. Klassen die nur *ein* Datenattribut haben oder nur aus einer `__init__()` bestehen oder aus einer `__init__()` und nur *einer* zusätzlichen Methode, sind aus diesem Gesichtspunkt gesehen erst einmal verdächtig. Das heisst nicht, dass das niemals Sinn macht. Es kann sinnvoll sein ein einziges Objekt zu verpacken, beispielsweise um eine andere API nach aussen zur Verfügung zu stellen. Man kann nur Datenklassen haben die einfach nur mehrere Daten in einem Objekt zusammenfassen, ohne das sie zusätzliche Methoden haben. Und bei einer `__init__()` und nur einer zusätzlichen Methode kann es sein, dass es sich doch nicht nur um eine verkleidete Funktion handelt. Aber in all diesen Fällen sollte man kurz innehalten und sich fragen ob man wirklich eine Klasse braucht.

Beim Ampelbeispiel sehe ich zwingend erst einmal nur die Klasse `Ampel`, was bei Dir `Kasten` wäre, aber so eine Ampel hat ja eine spezifischere Funktion als einfach nur ein Kasten zu sein. Die einzelnen Lampen kann man extra modellieren, muss man aber nicht. `Taster` sehe ich erst einmal nicht als eigene Klasse, vor allem weil die Zeitsteuerung da IMHO nicht rein gehört. Das ist etwas eigenes, ausserhalb von *den Ampeln* (Mehrzahl), denn Ampelsteuerungen wo man einen Taster hat, steuern nicht eine Ampel sondern mehrere. Wenn man da drauf drückt springt nicht einfach nur die Fussgängerampel für eine gewisse Zeit auf Grün, sondern erst einmal muss der Autoverkehr eine rote Ampel angezeigt bekommen.

Die Eigenschaften/Optionen von Widgets stehen in der Docstrings der Widgets und können mit `configure()` abgefragt werden. Also ich meine nicht einzeln, sondern alle die es auf dem Widget gibt als Wörterbuch. Was die einzelnen Werte bedeuten steht in der `tkinter`-Dokumentation. Position und Grösse müsste man mit `winfo_geometry()`, `winfo_width()`, `winfo_height()`, `winfo_x()`, und `winfo_y()` abfragen können.

Was Dokumentation angeht ist natürlich die letzte Instanz die Tk-Dokumentation, denn `tkinter` ist nur eine recht dünne Python-Schicht über Tk/Tcl.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
MissMapeL
User
Beiträge: 22
Registriert: Mittwoch 30. Oktober 2019, 10:26

Danke peterpy und __blackjack__,
ich werde versuchen mir das zwischen die Ohren zu schieben.
Euch ein schönes Wopchenende!!!
Antworten