[PySimpleGUI+Tkinter] rundes Eingabefeld, hinter Canvas zeichnen, aber .!frames blockieren durch HG-Farbe

Fragen zu Tkinter.
Antworten
milki2
User
Beiträge: 3
Registriert: Mittwoch 24. März 2021, 16:36

Ich vermute das ist eher eine Tkinter-Frage als streng nur PySimpleGUI (basiert ja auf Tk). Und zwar wollte ich für ein neues kleines Projekt ein rundes Eingegabefeld.
Hab da inzwischen auch einen schlichteren Workaround gefunden (einfach zwei PNGs links und rechts vom TkEntry-Widget). Sieht zwar okay aus, aber ich vermute das ist nicht sonderlich einheitlich auf verschiedenen Plattformen, mit unterschiedlichen System-Schriften oder Tcl/Tk-Versionen:

Bild

Und jetzt stellt sich die Frage ob man das nicht auch besser lösen kann. Mir ist aufgefallen dass hinter dem Eingabefeld bereits ein Canvas existiert. (Unklar ob das jetzt PSG eingefügt hat, oder durchs Tk-Thema automatisch reinkommt).
Jedenfalls könnte man mit dem Canvas ja theoretisch hinter das Input()-Feld zeichnen. Letztlich einfach ne Ellipse und nen Block, um ein abgerundetes Feld zu simulieren. Hab dass hier mal "vereinfacht" (so dass die Struktur aber noch identisch bleibt):

Code: Alles auswählen

#!/usr/bin/env python3
import tkinter as tk, PySimpleGUI as sg  # ⚠ install python3-tk / tkinter in your distro package manager

#-- widget structure
layout = [[
    sg.Menu([["File", ["Exit"]]], key="menu", font="Sans 11"),
    # left pane: blue/gray
    sg.Column(size=(320,725), background_color="#343131", pad=(0,0), layout=[
        [sg.Column(  background_color="#2980b9", size=(320,105), pad=(0,0), element_justification="center", layout=[
            [ sg.T("title text", text_color="#fff", pad=[(20,0),(10,0)], background_color="#2980b9", font="Sans 12 bold") ],
            [ sg.T("filler", text_color="#65a3c8", pad=[(20,0),(0,5)], background_color="#2980b9") ],
            [
                sg.Input(" find.. ", key="search", font="Sans 12", size=(26,1), pad=(0,0), background_color="#fefefe", border_width=0)
            ],
        ])],
        [sg.Multiline("├── btn.png\n├── build\n│   ├── bdist.linux-x86_64", background_color="#555", size=[15,20])]
    ]),
    # content pane: white
    sg.Column(size=(760,725), background_color="#fafafa", pad=((30,5),(10,5)), layout=[
        [sg.T("Content")],
        [sg.Multiline(size=(75,27))],
    ]),
]]
#sg.theme("DarkBlue2")
win = sg.Window(
    title="test3", layout=layout, font="Sans 12", size=(1080,725), margins=(0,0),
    resizable=False, background_color="#fafafa", ttk_theme=None
)
sg.theme("DarkRed")
win = sg.Window(
    title="test3", layout=layout, font="Sans 12", size=(1080,725), margins=(0,0),
    resizable=False#, ttk_theme=None
)
# initialize widgets
win.read(timeout=1)

#----- patch Canvas behind search field -----
w = win["search"].Widget  #  ==   .!toplevel.!frame.!tkfixedframe.!canvas.!frame.!frame.!tkfixedframe.!canvas.!frame.!frame3.!entry
canvas = win.TKroot.nametowidget(".!toplevel.!frame.!tkfixedframe.!canvas.!frame.!frame.!tkfixedframe.!canvas")
print(dir(w))
#w.configure(background="")  # → _tkinter.TclError: unknown color name ""
w.master.configure(background="")  # frame around entry widget
w.master.master.configure(background="") # wrapper frame
print(canvas)
print(dir(canvas))
print(canvas.winfo_x(), canvas.winfo_y(), canvas.winfo_width(), canvas.winfo_height()) # 320x105
canvas.create_line( 0,  9, 320, 105, fill="red", width=10)
canvas.create_oval(10, 15, 300, 95, fill="green")

# sg wait
win.read()
sg.Column() erstellt die Struktur, und das TkEntry liegt dann zwei Ebenen hinter dem Canvas-Widget:

Code: Alles auswählen

                                                               hier Ellipse malen
                                                                       ↓
.!toplevel.!frame.!tkfixedframe.!canvas.!frame.!frame.!tkfixedframe.!canvas.!frame.!frame3.!entry
                                                                                              ↑
                                                                                         Eingabefeld
Jetzt stellt sich aber raus, die zwei Frames/FixedFrames dazwischen blockieren die Ansicht aufs Canvas:

Bild

Jetzt hab ich natürlich im zweiten Schritt schon probiert die `background_color=` Parameter wegzulassen, oder das sg.theme() nicht zu setzen. Aber die Frames behalten einen festen Hintergrund. Im zweiten Beispiel sieht man dass sogar durch den Abstandsparameter `pad=(…)` das Frame mit HG-Farbe vergrößert wird.
Und `w.master.configure(background="")` (das wäre der erste Frame unter dem Entry) hat scheinbar null Effekt.

Und an der Stelle bin ich mir nicht sicher was ich noch probieren könnte. Entweder fixiert PySimpleGUI das irgendwie, oder die TkWidgets lassen sich nach der Erstellung nicht mehr anpassen. Könnte auch mit dem Thema zusammenhängen, oder den Style-Defaults o.ä. Mit -colormap z.B. lässt sich da aber auch nichts ändern.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

TkInter ist, was das Styling betrifft sehr eingeschränkt. Wenn Du mehr Freiheiten willst, schau Dich am besten nach einem anderen GUI-Framework um.
milki2
User
Beiträge: 3
Registriert: Mittwoch 24. März 2021, 16:36

Klar gibts komplexere Toolkits. Der Preis ist natürlich dass dann gleich alles etwas aufwändiger wird; von den Abhängigkeiten mal abgesehen.
Hier in dem Fall ist es ein einzelnes optisches Spe­renz­chen. Wenns nicht besser geht, dann bleibts halt bei dem Workaround mit den zwei PNGs ringsrum.
Sirius3
User
Beiträge: 17703
Registriert: Sonntag 21. Oktober 2012, 17:20

Die Entscheidung wäre, das rechteckige Eingabefeld zu lassen und nicht einen Workaround, der nicht sauber funktioniert, zu benutzen.

Wenn man den Code so anschaut, sieht das auch nicht wie Python aus, sondern nach einem Wettbewerb, wie man möglichst viel in eine Zeile stopft.
milki2
User
Beiträge: 3
Registriert: Mittwoch 24. März 2021, 16:36

Naja, den Code kann man sicher auch anders schreiben. Für die Verschachtelung bei PSG macht sich kompakt aber besser. Die Obsession über 80 Zeichen ist hier z.B. kontraproduktiv: black macht aus den ~60 Zeilen sonst 338. Also letztlich nur das umgekehrte Extrem, nicht unbedingt lesbarer.

Und es ist ja auch mehr so eine Frage des Aufwands für das gewünschte (wenn auch voll redundante) Gimmick.
Statt das Frame transparent zu bekommen, kann ich auch einfach Platz lassen und das Tk-Widget selbst erstellen:

Code: Alles auswählen

canvas.create_rectangle(0, 0, 320, 105, fill="#2980b9")
E = tk.Entry(canvas, text="Input", border="0", borderwidth=0, highlightthickness=0)
E.pack()
E.place(x=50, y=72, height=20, width=220)
canvas.create_oval(20, 65, 50, 95, fill="white", outline="white")
canvas.create_rectangle(35, 65, 285, 95, fill="white", outline="white")
canvas.create_oval(270, 65, 300, 95, fill="white", outline="white")
Antworten