Scrollbar scrollt nicht

Fragen zu Tkinter.
Antworten
thstehr
User
Beiträge: 1
Registriert: Donnerstag 4. Januar 2024, 12:54

Hallo Zusammen,

ich gehe gerade meine ersten Python schritte, noch mit sehr viel Hilfe der Lieben AI..
langsam aber sicher verstehe ich aber die ersten Züge der Sprache :)

zu meinem Problem:
ich habe jetzt mal das grobe Framework für eine kleine Anwendung erstellt:
- 3 Menüleisten (Oben, Unten und Seitlich)
- eine Dateneingabe
- und dann soll darüber noch ein Diagram auf basis der Daten hin kommen
soweit so gut, ist noch nicht schön, aber es ist da..

in der unteren Menüleiste habe ich einen Button zum Hinzufügen von Zeilen eingefügt, das funktioniert auch.
Leider kann ich mit der Scrollbar die Tabellenzeilen nicht scrollen..

ich denke es hat was mit dem canvas.itemconfig zu tun, aber das habe ich ursprünglich mal gebraucht, damit die Tabelle in der Position bleibt wo sie soll..

würd mich freun wenn mir da wer helfen könnte :roll:

hier der Gesamte Code:

Code: Alles auswählen

import tkinter as tk

root = tk.Tk()
root.title("Testprogram")

# Start the window maximized (this does not cover the taskbar)
root.state('zoomed')

row_count = 10

# Function to exit fullscreen or maximized state
def exit_fullscreen(event=None):
    root.attributes('-fullscreen', False)
    return "break"


def create_input_fields(parent, start_row, row_count, columns):
    entries = []
    for i in range(start_row, start_row + row_count):
        row_entries = []
        for j in range(columns):
            entry = tk.Entry(parent)
            entry.grid(row=i, column=j, sticky="nsew")
            row_entries.append(entry)
        entries.append(row_entries)
    return entries

def add_new_row():
    global row_count
    new_row_entries = create_input_fields(inner_frame, row_count, 1, 12)  # Add one new row
    input_fields.extend(new_row_entries)
    row_count += 1
    # Update the scroll region to encompass the new row
    update_scrollregion()

def update_scrollregion():
    # Update the scroll region to encompass the new row
    canvas.configure(scrollregion=canvas.bbox("all"))

def update_canvas_window_size(event):
    canvas_width = event.width
    canvas_height = event.height
    canvas.itemconfig(canvas_window, width=canvas_width, height=canvas_height)
    update_scrollregion()

# Bind escape key to exit fullscreen or maximized state
root.bind('<Escape>', exit_fullscreen)

# Create the main layout frames
header_frame = tk.Frame(root, borderwidth=2, relief="ridge", background="blue")
content_frame = tk.Frame(root, borderwidth=2, relief="ridge")
side_inputs_frame = tk.Frame(root, borderwidth=2, relief="ridge", background="orange")
bottom_menu_frame = tk.Frame(root, borderwidth=2, relief="ridge", background="teal")

# Layout the main frames using grid
header_frame.grid(row=0, column=0, columnspan=2, sticky="nsew")
content_frame.grid(row=1, column=0, sticky="nsew")
side_inputs_frame.grid(row=1, column=1, sticky="nsew")
bottom_menu_frame.grid(row=2, column=0, columnspan=2, sticky="nsew") # Fixed grid placement here

# Configure weights to make frames resizable
root.grid_rowconfigure(0, weight=1) # Header area
root.grid_rowconfigure(1, weight=30) # Content area, larger weight to give more space
root.grid_rowconfigure(2, weight=1) # Bottom menu area
root.grid_columnconfigure(0, weight=5) # Main content area is larger
root.grid_columnconfigure(1, weight=1) # Side inputs area is smaller

# Create nested frames within the content frame for diagram and table
diagram_frame = tk.Frame(content_frame, borderwidth=2, relief="ridge", background="orange")
Htable_frame = tk.Frame(content_frame, borderwidth=2, relief="ridge", background="green")
table_frame = tk.Frame(content_frame, borderwidth=2, relief="ridge", background="yellow")

# Layout the nested frames using grid within the content frame
diagram_frame.grid(row=0, column=0, sticky="nsew")
Htable_frame.grid(row=1, column=0, sticky="nsew")
table_frame.grid(row=2, column=0, sticky="nsew")

# Configure weights to make nested frames resizable
content_frame.grid_rowconfigure(0, weight=80) # Diagram area 
content_frame.grid_rowconfigure(1, weight=2) # column header
content_frame.grid_rowconfigure(2, weight=30) # Table area
content_frame.grid_columnconfigure(0, weight=1)

# Create a Canvas and a Scrollbar within table_frame
canvas = tk.Canvas(table_frame)
scrollbar = tk.Scrollbar(table_frame, orient="vertical", command=canvas.yview)

# Grid and pack the widgets
canvas.grid(row=0, column=0, sticky="nsew")
scrollbar.grid(row=0, column=1, sticky="ns")

# Configure the canvas to work with the scrollbar
canvas.configure(yscrollcommand=scrollbar.set)

# Configure the table_frame to accommodate the canvas and scrollbar
table_frame.grid_columnconfigure(0, weight=1) # Canvas should expand to fill the frame
table_frame.grid_rowconfigure(0, weight=1)

# Create a frame inside the canvas to hold the input fields
inner_frame = tk.Frame(canvas)
canvas_window = canvas.create_window((0, 0), window=inner_frame, anchor='nw')

# Bind the function to the table_frame's configure event
table_frame.bind("<Configure>", update_canvas_window_size)


# Add headers to Htable_frame
headers = ["Header {}".format(i+1) for i in range(12)]
for idx, header in enumerate(headers):
    label = tk.Label(Htable_frame, text=header, bg='green', fg='white')
    label.grid(row=0, column=idx, sticky="nsew")
    Htable_frame.grid_columnconfigure(idx, weight=1, uniform='header')

# Add input fields to inner_frame instead of table_frame
    
input_fields = create_input_fields(inner_frame, 0, row_count, 12)  # 10 rows of input fields

# Ensure the input fields align with the headers
for j in range(12):
    inner_frame.grid_columnconfigure(j, weight=1, uniform='header')

# Update the canvas's scrollregion whenever the inner_frame's size changes
inner_frame.bind("<Configure>", lambda event: canvas.configure(scrollregion=canvas.bbox("all")))

add_row_button  = tk.Button(bottom_menu_frame,text="Neue Zeile", command=add_new_row)
add_row_button.grid(row=0, column=idx, sticky="nsew")


root.mainloop()
Benutzeravatar
sparrow
User
Beiträge: 4195
Registriert: Freitag 17. April 2009, 10:28

@thstehr: Ich schaue mir keinen Code an, der 'globale verwendet. Globale Variablen haben in einem Programm nichts zu suchen. Sie machen die Nachvollziehbarkeit und Fehlersuche unnötig komplex.

Auf Modulebene, also ohne Einrückung, stehen in einem Programm nur die Importe, die Definition von Klassen und Funktionen und Konstanten (also Werte, die sich nie ändern).

GUI-Programmierubg ist komplex und erfordert in der Regel Objektorientierung. Ich empfehle daher, erst einmal Funktionen zu verstehen und anschließend in die Objektorientierung einzusteigen.
Benutzeravatar
__blackjack__
User
Beiträge: 13117
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@thstehr: Weitere Anmerkungen: Wenn es für Zeichenketten mit einer speziellen Bedeutung als Argument in `tkinter` eine Konstante gibt, sollte man die benutzen, statt die Zeichenkette als literalen Wert zu schreiben.

Kommentare sollen dem Leser einen Mehrwert über den Code geben. Faustregel: Kommentare beschreiben nicht *was* der Code macht, denn das steht da bereits als Code, sondern warum er das macht. Sofern das nicht offensichtlich ist. Offensichtlich ist in aller Regel auch was in der Dokumentation von Python und den verwendeten Bibliotheken steht.

Besonders fehleranfällig wird es wenn man in Kommentaren Informationen hat die an ganz anderer Stelle im Code stehen. Wie beispielsweise das 10 Zeilen hinzugefügt werden, wenn dort im Code eine Variable steht, und die tatsächliche 10 als Wert im Code zig Zeilen davon entfernt zugewiesen wird. Da ist fast schon garantiert, dass man vergisst den Kommentar anzupassen wenn man diese Zuweisung mal ändert.

Die Spaltenanzahl steht als magische 12 mehrfach im Programm. Das sollte eine Konstante sein.

Namen sollten keine kryptischen Abkürzungen enthalten. Um zu verstehen, das `htable_frame` eigentlich `table_headers_frame` heissen sollte, muss man erst den Code lesen der etwas mit dem Namen macht.

Die Spaltenangabe beim "Neue Zeile"-Button macht keinen Sinn weil der ja das einzige ist was in diesem Frame angezeigt wird.

Zwischenstand (ungetestet):

Code: Alles auswählen

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

COLUMN_COUNT = 12


def exit_fullscreen(window, _event=None):
    window.attributes("-fullscreen", False)
    return "break"


def update_scrollregion(canvas, _event=None):
    canvas.configure(scrollregion=canvas.bbox(tk.ALL))


def update_canvas_window_size(canvas, canvas_window, event):
    canvas_width = event.width
    canvas_height = event.height
    canvas.itemconfig(canvas_window, width=canvas_width, height=canvas_height)
    update_scrollregion(canvas)


def create_input_fields(master, start_row, row_count, columns):
    entries = []
    for i in range(start_row, start_row + row_count):
        row = []
        for j in range(columns):
            entry = tk.Entry(master)
            entry.grid(row=i, column=j, sticky=tk.NSEW)
            row.append(entry)
        entries.append(row)
    return entries


def add_new_row(master, row_count_var, input_fields, canvas):
    new_row_entries = create_input_fields(
        master, row_count_var.get(), 1, COLUMN_COUNT
    )
    input_fields.extend(new_row_entries)
    row_count_var.set(row_count_var.get() + 1)
    update_scrollregion(canvas)


def main():
    root = tk.Tk()
    root.title("Testprogram")
    # root.state("zoomed")  # Attention: This does not work on all plattforms.
    root.bind("<Escape>", partial(exit_fullscreen, root))

    row_count_var = tk.IntVar(value=10)

    header_frame = tk.Frame(
        root, borderwidth=2, relief=tk.RIDGE, background="blue"
    )
    header_frame.grid(row=0, column=0, columnspan=2, sticky=tk.NSEW)

    content_frame = tk.Frame(root, borderwidth=2, relief=tk.RIDGE)
    content_frame.grid(row=1, column=0, sticky=tk.NSEW)

    side_inputs_frame = tk.Frame(
        root, borderwidth=2, relief=tk.RIDGE, background="orange"
    )
    side_inputs_frame.grid(row=1, column=1, sticky=tk.NSEW)

    bottom_menu_frame = tk.Frame(
        root, borderwidth=2, relief=tk.RIDGE, background="teal"
    )
    bottom_menu_frame.grid(row=2, column=0, columnspan=2, sticky=tk.NSEW)

    root.grid_rowconfigure(0, weight=1)  # Header area.
    root.grid_rowconfigure(1, weight=30)  # Content area.
    root.grid_rowconfigure(2, weight=1)  # Bottom menu area.
    root.grid_columnconfigure(0, weight=5)  # Main content area is larger.
    root.grid_columnconfigure(1, weight=1)  # Side inputs area is smaller.

    diagram_frame = tk.Frame(
        content_frame, borderwidth=2, relief=tk.RIDGE, background="orange"
    )
    diagram_frame.grid(row=0, column=0, sticky=tk.NSEW)
    table_headers_frame = tk.Frame(
        content_frame, borderwidth=2, relief=tk.RIDGE, background="green"
    )
    table_headers_frame.grid(row=1, column=0, sticky=tk.NSEW)
    table_frame = tk.Frame(
        content_frame, borderwidth=2, relief=tk.RIDGE, background="yellow"
    )
    table_frame.grid(row=2, column=0, sticky=tk.NSEW)

    content_frame.grid_rowconfigure(0, weight=80)  # Diagram area.
    content_frame.grid_rowconfigure(1, weight=2)  # Column headers.
    content_frame.grid_rowconfigure(2, weight=30)  # Table area.
    content_frame.grid_columnconfigure(0, weight=1)

    canvas = tk.Canvas(table_frame)
    canvas.grid(row=0, column=0, sticky=tk.NSEW)
    scrollbar = tk.Scrollbar(
        table_frame, orient=tk.VERTICAL, command=canvas.yview
    )
    scrollbar.grid(row=0, column=1, sticky=tk.NS)
    canvas.configure(yscrollcommand=scrollbar.set)

    table_frame.grid_columnconfigure(0, weight=1)
    table_frame.grid_rowconfigure(0, weight=1)

    inner_frame = tk.Frame(canvas)
    canvas_window = canvas.create_window(
        (0, 0), window=inner_frame, anchor=tk.NW
    )

    table_frame.bind(
        "<Configure>",
        partial(update_canvas_window_size, canvas, canvas_window),
    )

    headers = [f"Header {i + 1}" for i in range(COLUMN_COUNT)]
    for column_index, header in enumerate(headers):
        tk.Label(
            table_headers_frame, text=header, bg="green", fg="white"
        ).grid(row=0, column=column_index, sticky=tk.NSEW)
        table_headers_frame.grid_columnconfigure(
            column_index, weight=1, uniform="header"
        )

    input_fields = create_input_fields(
        inner_frame, 0, row_count_var.get(), COLUMN_COUNT
    )
    #
    # Ensure the input fields align with the headers.
    #
    for i in range(COLUMN_COUNT):
        inner_frame.grid_columnconfigure(i, weight=1, uniform="header")

    inner_frame.bind("<Configure>", partial(update_scrollregion, canvas))

    tk.Button(
        bottom_menu_frame,
        text="Neue Zeile",
        command=partial(
            add_new_row, inner_frame, row_count_var, input_fields, canvas
        ),
    ).grid(row=0, column=COLUMN_COUNT - 1, sticky=tk.NSEW)

    root.mainloop()


if __name__ == "__main__":
    main()
Gerade noch so an einer eigenen Klasse vorbeigemogelt, aber das ist nicht wirklich übersichtlich. Und mindestens die Tabelle würde ich auch noch mal in eine eigene Klasse herausziehen.
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
Antworten