Dynamisch verteilte Widgets (Button) je nach Fenstergröße in die nächste Zeile

Fragen zu Tkinter.
Antworten
XxBlueTomatoXx
User
Beiträge: 5
Registriert: Freitag 17. Dezember 2021, 15:30

Moin zusammen,

Dies ist mein erstes Python Projekt, an dem ich versuche programmieren zu lernen. Ich bitte um Nachsicht.

da verständlicherweise auf meinen ersten ansatzlosen Beitrag (viewtopic.php?f=18&t=53669) keine Rückmeldung kam, versuche ich es jetzt noch einmal, diesmal mit einem konkreten Problem. Und zwar möchte ich je nach Fensterbreite die Variable maxbuttons anpassen, ich schaffe es sogar diese in einem Label anzuzeigen, jedoch scheitere ich daran diese in der Funktion buttonserzeugen(rechnerbuttonlist, rechnerbuttons, maxbuttons, 15) aufzurufen, deshalb ist dort vorerst eine 8 eingetragen. Ich habe es schon in buttonresize(e) mit return maxbuttons versucht, ebenso die Variable als global zu deklarieren, hat nicht geholfen. Ich habe die Vermutung das es irgendwie mit diesem (e) zusammenhängt, da ich nicht verstehe, wofür dieses genau gut ist.

Ich hoffe ihr könnt mir helfen, bis zum 17 muss ich zumindest die GUI meines Programmes in meiner Gruppe präsentieren und dann wird sich entweder für Python oder C++ entschieden. Diese funktioniert auch schon wunderbar, bis auf die Anpassung der Buttons je nach Fensterbreite. Wir schreiben als Systemintegratoren ein Support Tool für Client-Administratoren um, das vorher in AutoIT geschrieben wurde. Hierfür soll eine komplett neue GUI erstellt werden.

Code: Alles auswählen

#IMPORTS
import tkinter as tk                     
from tkinter import ttk

#Hauptfenster erzeugen
root = tk.Tk()                                # Fenster erzeugen
root.geometry("1130x843")                     # Größe bestimmen

#Frame erzeugen
widgetrandfarbe= '#646464'
widgethintergrundfarbe= '#323232'
buttonframe=tk.Frame(root, highlightbackground=widgetrandfarbe, highlightthickness=1, bg=widgethintergrundfarbe)
buttonframe.pack(side='top', fill="both", padx=5, pady=5, ipadx=5, ipady=5)

#Funktion zum Auslesen wieviele Buttons in eine Zeile passen
def buttonresize(e):
    buttonspace = buttonframe.winfo_width()
    if buttonspace <= 824:
        maxbuttons=4
    elif buttonspace <= 938:
        maxbuttons=5
    elif buttonspace <= 1144:
        maxbuttons=6
    elif buttonspace <= 1304:
        maxbuttons=7
    elif buttonspace <= 1462:
        maxbuttons=8    
    elif buttonspace <= 1618:
        maxbuttons=9  
    elif buttonspace <= 1785:
        maxbuttons=10
    else:
        maxbuttons=11
    label["text"] =(maxbuttons, " = MAXButtons")   #Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
buttonframe.bind("<Configure>", buttonresize) 

rechnerbuttonlist= ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11","12", "13", "14","15", "16","17","18", "19", "20", "21",)

tab_control = ttk.Notebook(buttonframe)
rechnerbuttons = ttk.Frame(tab_control)
tab_control.add(rechnerbuttons, text='Rechner',)
tab_control.pack(expand=True, fill='both', padx=5, pady=5)

def buttonserzeugen(buttonlist, frame, maxbuttonsinrow, buttonwidth):
    ccounter=0
    rcounter=0
    for buttontext in buttonlist:
        button = tk.Button(frame, text=buttontext, width=buttonwidth)
        button.grid(row=rcounter, column=ccounter, padx='5', pady='5', sticky="news")
        if ccounter == maxbuttonsinrow-1:
            rcounter += 1
            ccounter = 0
        else:
            ccounter += 1

buttonserzeugen(rechnerbuttonlist, rechnerbuttons, 8, 15)

label = tk.Label(master=rechnerbuttons, fg="white", bg='#323232', highlightbackground="#646464", highlightthickness=1, text="Berechne...") #Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
label.grid(row=5, column=0, padx='5', pady='5', sticky="news")

root.mainloop()
__deets__
User
Beiträge: 14529
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich bin skeptisch bei dieser Aufgabenstellung. Also, dem Layout. Und das unabhängig vom der Sprache. Ihr versucht hier einen bestehenden Layout-Algorithmus von außen, und auch nachträglich, zu beeinflussen. Das wird in meinen Augen bestenfalls zu nervigem rumgespringe führen. Schlimmstenfalls dauerhaft instabil bleiben. Denn jedes Mal, wenn du da nach einem resize etwas machst, und Widgets verschiebst, springt der Layouter wieder an. Und rearrangiert ggf die Widgets. Und dann machst du wieder was. Und so weiter.

Das wäre in C++ übrigens nicht anders.

Es gibt Layout Engines, bei denen man sowas herstellen kann. Browser zb. CSS ist so viel mächtiger, und kann auch sowas.

Alternativ braucht man volle Kontrolle, um diese Wechselwirkung auszuschließen. Das ginge zb mit einem Canvas. Aber dann baut man ggf Widgets komplett nach.

Wenn das keine strikte Vorgabe ist, würde ich diese Anforderung also fallen lassen.
XxBlueTomatoXx
User
Beiträge: 5
Registriert: Freitag 17. Dezember 2021, 15:30

Danke dir für deine Antwort,

es ist leider nicht das was ich mir erhofft habe, aber absolut nachvollziehbar. Dann werde ich das Ganze wohl mit einem Button verbinden müssen.
Rein theoretisch, selbst wenn es instabil laufen würde und für den Produktiveinsatz nicht geeignet wäre, gibt es eine Möglichkeit den Wert irgendwie zu übergeben. Mich würde einfach interessieren wie sich das Ganze verhält.
Sirius3
User
Beiträge: 17741
Registriert: Sonntag 21. Oktober 2012, 17:20

Kommentare sollten dem Leser einen Mehrwert bieten, viele deiner Kommentare beschreibn nur, was da eh schon dasteht, dass da z.B. Importe sind.
Alles was eine Funktion braucht, muß sie über ihre Argumente bekommen, bei buttonresize fehlt buttonframe, und label.
Auf oberster Ebene sollte kein ausführbarer Code stehen, globale Variablen benutzt man nicht, es fehlt eine main-Funktion.
In buttonresize wird fälschlicherweise buttonframe statt rechnerbuttons für die Breite genommen. Statt vieler if-Abfragen reicht auch eine einfache Rechnung.
Das Erzeugen der Buttons und das Positionieren sollten zwei Funktionen sein, dann kann man die zweite auch in buttonresize benutzen.

Code: Alles auswählen

import tkinter as tk                     
from tkinter import ttk
from functools import partial

def buttons_erzeugen(buttonlist, frame, buttonwidth):
    return [
        tk.Button(frame, text=buttontext, width=buttonwidth)
        for buttontext in buttonlist
    ]

def buttons_positionieren(buttons, maxbuttonsinrow):
    for index, button in enumerate(buttons):
        row, column = divmod(index, maxbuttonsinrow)
        button.grid(row=row, column=column, padx='5', pady='5', sticky="news")

def buttonresize(rechnerbuttons, buttons, label, e):
    buttonspace = rechnerbuttons.winfo_width()
    buttonwidth = buttons[0].winfo_width() + 10
    maxbuttons = buttonspace // buttonwidth
    buttons_positionieren(buttons, maxbuttons)
    label["text"] = f"{maxbuttons} = MAXButtons"

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

    widgetrandfarbe= '#646464'
    widgethintergrundfarbe= '#323232'
    buttonframe=tk.Frame(root, highlightbackground=widgetrandfarbe, highlightthickness=1, bg=widgethintergrundfarbe)
    buttonframe.pack(side='top', fill="both", padx=5, pady=5, ipadx=5, ipady=5)

    tab_control = ttk.Notebook(buttonframe)
    tab_control.pack(expand=True, fill='both', padx=5, pady=5)

    rechnerbuttons = ttk.Frame(tab_control)
    tab_control.add(rechnerbuttons, text='Rechner',)

    rechnerbuttonlist = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11","12", "13", "14","15", "16","17","18", "19", "20", "21"]
    buttons = buttons_erzeugen(rechnerbuttonlist, rechnerbuttons, 15)
    buttons_positionieren(buttons, 8)

    # Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
    label = tk.Label(master=rechnerbuttons, fg="white", bg='#323232', highlightbackground="#646464", highlightthickness=1, text="Berechne...")
    label.grid(row=5, column=0, padx='5', pady='5', sticky="news")

    buttonframe.bind("<Configure>", partial(buttonresize, rechnerbuttons, buttons, label))

    root.mainloop()

if __name__ == "__main__":
    main()
Benutzeravatar
peterpy
User
Beiträge: 188
Registriert: Donnerstag 7. März 2013, 11:35

und damit das nicht passiert:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib64/python3.8/tkinter/__init__.py", line 1892, in __call__
    return self.func(*args)
  File "XxBlueTomatoXx_1.py", line 20, in buttonresize
    buttons_positionieren(buttons, maxbuttons)
  File "XxBlueTomatoXx_1.py", line 13, in buttons_positionieren
    row, column = divmod(index, maxbuttonsinrow)
ZeroDivisionError: integer division or modulo by zero
In buttons_positionieren, den Wert von maxbuttonsinrow prüfen.
Und die Zeilennummer für das Label hochsetzen.

Code: Alles auswählen

import tkinter as tk                     
from tkinter import ttk
from functools import partial

def buttons_erzeugen(buttonlist, frame, buttonwidth):
    return [
        tk.Button(frame, text=buttontext, width=buttonwidth)
        for buttontext in buttonlist
    ]
    
def buttons_positionieren(buttons, maxbuttonsinrow):
    for index, button in enumerate(buttons):        
        if maxbuttonsinrow != 0:
            row, column = divmod(index, maxbuttonsinrow)
            button.grid(row=row, column=column, padx='5', pady='5',
                        sticky="news")    

def buttonresize(rechnerbuttons, buttons, label, e):
    buttonspace = rechnerbuttons.winfo_width()
    buttonwidth = buttons[0].winfo_width() + 10
    maxbuttons = buttonspace // buttonwidth
    buttons_positionieren(buttons, maxbuttons)
    label["text"] = f"{maxbuttons} = MAXButtons"

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

    widgetrandfarbe= '#646464'
    widgethintergrundfarbe= '#323232'
    buttonframe=tk.Frame(root, highlightbackground=widgetrandfarbe,
                         highlightthickness=1, bg=widgethintergrundfarbe)
    buttonframe.pack(side='top', fill="both", padx=5, pady=5, ipadx=5, ipady=5)

    tab_control = ttk.Notebook(buttonframe)
    tab_control.pack(expand=True, fill='both', padx=5, pady=5)

    rechnerbuttons = ttk.Frame(tab_control)
    tab_control.add(rechnerbuttons, text='Rechner',)

    rechnerbuttonlist = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
                         "11","12", "13", "14","15", "16","17","18", "19", "20", "21"]
    buttons = buttons_erzeugen(rechnerbuttonlist, rechnerbuttons, 15)
    buttons_positionieren(buttons, 8)

    #Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
    label = tk.Label(master=rechnerbuttons, fg="white", bg='#323232',
                     highlightbackground="#646464", highlightthickness=1,
                     text="Berechne...")
    label.grid(row=25, column=0, padx='5', pady='5', sticky="news")

    buttonframe.bind("<Configure>", partial(buttonresize, rechnerbuttons,
                                            buttons, label))

    root.mainloop()

if __name__ == "__main__":
    main()
Gruss
Peter
XxBlueTomatoXx
User
Beiträge: 5
Registriert: Freitag 17. Dezember 2021, 15:30

Ich danke dir, ich werde mich mal mehr mit dem grundsätzlichen Aufbau von Quellcode beschäftigen und bin schon dabei alles umzuschreiben.
Sirius3 hat geschrieben: Dienstag 4. Januar 2022, 12:30 Kommentare sollten dem Leser einen Mehrwert bieten, viele deiner Kommentare beschreibn nur, was da eh schon dasteht, dass da z.B. Importe sind.
Alles was eine Funktion braucht, muß sie über ihre Argumente bekommen, bei buttonresize fehlt buttonframe, und label.
Auf oberster Ebene sollte kein ausführbarer Code stehen, globale Variablen benutzt man nicht, es fehlt eine main-Funktion.
In buttonresize wird fälschlicherweise buttonframe statt rechnerbuttons für die Breite genommen. Statt vieler if-Abfragen reicht auch eine einfache Rechnung.
Das Erzeugen der Buttons und das Positionieren sollten zwei Funktionen sein, dann kann man die zweite auch in buttonresize benutzen.

Code: Alles auswählen

import tkinter as tk                     
from tkinter import ttk
from functools import partial

def buttons_erzeugen(buttonlist, frame, buttonwidth):
    return [
        tk.Button(frame, text=buttontext, width=buttonwidth)
        for buttontext in buttonlist
    ]

def buttons_positionieren(buttons, maxbuttonsinrow):
    for index, button in enumerate(buttons):
        row, column = divmod(index, maxbuttonsinrow)
        button.grid(row=row, column=column, padx='5', pady='5', sticky="news")

def buttonresize(rechnerbuttons, buttons, label, e):
    buttonspace = rechnerbuttons.winfo_width()
    buttonwidth = buttons[0].winfo_width() + 10
    maxbuttons = buttonspace // buttonwidth
    buttons_positionieren(buttons, maxbuttons)
    label["text"] = f"{maxbuttons} = MAXButtons"

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

    widgetrandfarbe= '#646464'
    widgethintergrundfarbe= '#323232'
    buttonframe=tk.Frame(root, highlightbackground=widgetrandfarbe, highlightthickness=1, bg=widgethintergrundfarbe)
    buttonframe.pack(side='top', fill="both", padx=5, pady=5, ipadx=5, ipady=5)

    tab_control = ttk.Notebook(buttonframe)
    tab_control.pack(expand=True, fill='both', padx=5, pady=5)

    rechnerbuttons = ttk.Frame(tab_control)
    tab_control.add(rechnerbuttons, text='Rechner',)

    rechnerbuttonlist = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11","12", "13", "14","15", "16","17","18", "19", "20", "21"]
    buttons = buttons_erzeugen(rechnerbuttonlist, rechnerbuttons, 15)
    buttons_positionieren(buttons, 8)

    # Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
    label = tk.Label(master=rechnerbuttons, fg="white", bg='#323232', highlightbackground="#646464", highlightthickness=1, text="Berechne...")
    label.grid(row=5, column=0, padx='5', pady='5', sticky="news")

    buttonframe.bind("<Configure>", partial(buttonresize, rechnerbuttons, buttons, label))

    root.mainloop()

if __name__ == "__main__":
    main()
XxBlueTomatoXx
User
Beiträge: 5
Registriert: Freitag 17. Dezember 2021, 15:30

Danke dir :)
peterpy hat geschrieben: Dienstag 4. Januar 2022, 14:50 und damit das nicht passiert:

Code: Alles auswählen

Exception in Tkinter callback
Traceback (most recent call last):
  File "/usr/lib64/python3.8/tkinter/__init__.py", line 1892, in __call__
    return self.func(*args)
  File "XxBlueTomatoXx_1.py", line 20, in buttonresize
    buttons_positionieren(buttons, maxbuttons)
  File "XxBlueTomatoXx_1.py", line 13, in buttons_positionieren
    row, column = divmod(index, maxbuttonsinrow)
ZeroDivisionError: integer division or modulo by zero
In buttons_positionieren, den Wert von maxbuttonsinrow prüfen.
Und die Zeilennummer für das Label hochsetzen.

Code: Alles auswählen

import tkinter as tk                     
from tkinter import ttk
from functools import partial

def buttons_erzeugen(buttonlist, frame, buttonwidth):
    return [
        tk.Button(frame, text=buttontext, width=buttonwidth)
        for buttontext in buttonlist
    ]
    
def buttons_positionieren(buttons, maxbuttonsinrow):
    for index, button in enumerate(buttons):        
        if maxbuttonsinrow != 0:
            row, column = divmod(index, maxbuttonsinrow)
            button.grid(row=row, column=column, padx='5', pady='5',
                        sticky="news")    

def buttonresize(rechnerbuttons, buttons, label, e):
    buttonspace = rechnerbuttons.winfo_width()
    buttonwidth = buttons[0].winfo_width() + 10
    maxbuttons = buttonspace // buttonwidth
    buttons_positionieren(buttons, maxbuttons)
    label["text"] = f"{maxbuttons} = MAXButtons"

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

    widgetrandfarbe= '#646464'
    widgethintergrundfarbe= '#323232'
    buttonframe=tk.Frame(root, highlightbackground=widgetrandfarbe,
                         highlightthickness=1, bg=widgethintergrundfarbe)
    buttonframe.pack(side='top', fill="both", padx=5, pady=5, ipadx=5, ipady=5)

    tab_control = ttk.Notebook(buttonframe)
    tab_control.pack(expand=True, fill='both', padx=5, pady=5)

    rechnerbuttons = ttk.Frame(tab_control)
    tab_control.add(rechnerbuttons, text='Rechner',)

    rechnerbuttonlist = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
                         "11","12", "13", "14","15", "16","17","18", "19", "20", "21"]
    buttons = buttons_erzeugen(rechnerbuttonlist, rechnerbuttons, 15)
    buttons_positionieren(buttons, 8)

    #Test-Label zum Anzeigen des Wertes wieviele Buttons in eine Zeile passen
    label = tk.Label(master=rechnerbuttons, fg="white", bg='#323232',
                     highlightbackground="#646464", highlightthickness=1,
                     text="Berechne...")
    label.grid(row=25, column=0, padx='5', pady='5', sticky="news")

    buttonframe.bind("<Configure>", partial(buttonresize, rechnerbuttons,
                                            buttons, label))

    root.mainloop()

if __name__ == "__main__":
    main()
Gruss
Peter
Antworten