Make ReminderDialog resizable/wider and fix Options dialog bottom clipping

ReminderDialog: override the shared dialog base's resizable(False, False),
widen the initial layout, and pin the items-table edit row's amount field
to the exact same pixel width as the table's fixed "amount" column (via a
pack_propagate(False) frame) while the description field stretches like
the table's own column does -- keeps alignment exact even after resizing.

OptionsDialog: compute the window's initial geometry from its actual
required size after all tabs (incl. "Mahnungen") are built, instead of a
static guess that clipped the Mahnungen tab's tables/buttons at the bottom.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Marcel Peterkau
2026-08-15 03:13:04 +02:00
co-authored by Claude Sonnet 5
parent 3940926a22
commit f3bdffd347
2 changed files with 38 additions and 15 deletions
+28 -13
View File
@@ -878,6 +878,10 @@ class ReminderDialog(_Dialog):
def __init__(self, master, repository, member_id, claim_id, on_saved):
super().__init__(master, "Mahnung vorbereiten", on_saved)
# Unlike the small fixed-size dialogs sharing _Dialog, this one holds a table
# of freely-editable fee items whose content varies a lot -- make it resizable
# so a long/multi-item Mahnung doesn't get cramped.
self.resizable(True, True)
self.repository, self.member_id, self.claim_id = repository, member_id, claim_id
data, _claim = repository.get_claim(member_id, claim_id)
existing_levels = {
@@ -891,6 +895,7 @@ class ReminderDialog(_Dialog):
self.items: list[dict[str, str]] = []
self.frame.columnconfigure(1, weight=1)
self.frame.rowconfigure(6, weight=1)
ttk.Label(self.frame, text="Mahnstufe").grid(row=0, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Label(self.frame, text=str(self.level), style="Mono.TLabel").grid(
row=0, column=1, sticky="w", pady=5
@@ -904,20 +909,20 @@ class ReminderDialog(_Dialog):
textvariable=self.preset_var,
values=list(self.preset_by_label),
state="readonly",
width=38,
width=55,
)
preset_combo.grid(row=1, column=1, sticky="ew", pady=5)
preset_combo.bind("<<ComboboxSelected>>", lambda _event: self._apply_preset())
self.name_var = tk.StringVar()
ttk.Label(self.frame, text="Bezeichnung").grid(row=2, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Entry(self.frame, textvariable=self.name_var, width=45).grid(
ttk.Entry(self.frame, textvariable=self.name_var, width=64).grid(
row=2, column=1, sticky="ew", pady=5
)
self.detail_var = tk.StringVar()
ttk.Label(self.frame, text="Details").grid(row=3, column=0, sticky="w", pady=5, padx=(0, 12))
ttk.Entry(self.frame, textvariable=self.detail_var, width=45).grid(
ttk.Entry(self.frame, textvariable=self.detail_var, width=64).grid(
row=3, column=1, sticky="ew", pady=5
)
@@ -943,8 +948,11 @@ class ReminderDialog(_Dialog):
# instead of whatever size Tk guessed before everything was in place -- the
# base dialog sets resizable(False, False) before this subclass's content
# exists, so an explicit geometry() is needed or the window stays clipped.
# The floor of 700 keeps the dialog comfortably wide even for a short name/
# detail; the user can still grow it further since it's resizable now.
self.update_idletasks()
width, height = self.winfo_reqwidth(), self.winfo_reqheight()
width = max(700, self.winfo_reqwidth())
height = self.winfo_reqheight()
self.geometry(f"{width}x{height}")
self.minsize(width, height)
@@ -984,23 +992,27 @@ class ReminderDialog(_Dialog):
table_frame = ttk.LabelFrame(self.frame, text="Positionen", padding=8)
table_frame.grid(row=row, column=0, columnspan=2, sticky="nsew", pady=(8, 0))
table_frame.columnconfigure(0, weight=1)
table_frame.rowconfigure(0, weight=1)
amount_column_width = 100
self.items_tree = ttk.Treeview(
table_frame, columns=("description", "amount"), show="headings", height=4, selectmode="browse"
)
self.items_tree.heading("description", text="Beschreibung")
self.items_tree.heading("amount", text="Betrag")
self.items_tree.column("description", width=220, anchor="w")
self.items_tree.column("amount", width=100, anchor="w", stretch=False)
self.items_tree.grid(row=0, column=0, sticky="ew")
self.items_tree.column("description", width=320, anchor="w")
self.items_tree.column("amount", width=amount_column_width, anchor="w", stretch=False)
self.items_tree.grid(row=0, column=0, sticky="nsew")
self.items_tree.bind("<<TreeviewSelect>>", lambda _event: self._load_selected_item())
# Edit row lines up under the table's own columns (same 220:100 width ratio as
# "Beschreibung"/"Betrag" above) instead of the fields and buttons being crammed
# into one row.
# Edit row lines up under the table's own columns: "Beschreibung" stretches to
# fill remaining space just like that Treeview column does, while "Betrag" sits
# in a frame pinned to the exact same pixel width as the fixed "amount" column
# above it (Entry's width= is in characters, not pixels, so a fixed-size frame
# is what gets a genuinely exact match instead of an approximate ratio).
edit_row = ttk.Frame(table_frame)
edit_row.grid(row=1, column=0, sticky="ew", pady=(8, 0))
edit_row.columnconfigure(0, weight=22)
edit_row.columnconfigure(1, weight=10)
edit_row.columnconfigure(0, weight=1)
edit_row.columnconfigure(1, weight=0)
self.item_description_var = tk.StringVar()
description_combo = ttk.Combobox(
edit_row,
@@ -1009,8 +1021,11 @@ class ReminderDialog(_Dialog):
)
description_combo.grid(row=0, column=0, sticky="ew", padx=(0, 8))
description_combo.bind("<<ComboboxSelected>>", lambda _event: self._prefill_item_amount())
amount_frame = ttk.Frame(edit_row, width=amount_column_width)
amount_frame.grid(row=0, column=1, sticky="ns")
amount_frame.pack_propagate(False)
self.item_amount_var = tk.StringVar()
ttk.Entry(edit_row, textvariable=self.item_amount_var).grid(row=0, column=1, sticky="ew")
ttk.Entry(amount_frame, textvariable=self.item_amount_var).pack(fill="both", expand=True)
buttons_row = ttk.Frame(table_frame)
buttons_row.grid(row=2, column=0, sticky="w", pady=(8, 0))
+10 -2
View File
@@ -61,13 +61,21 @@ class OptionsDialog(tk.Toplevel):
)
}
self.title("Optionen")
self.geometry("900x650")
self.minsize(760, 540)
self.transient(master.winfo_toplevel())
self.grab_set()
self.resizable(True, True)
self._build_ui()
self.bind("<Escape>", lambda _event: self.destroy())
# A ttk.Notebook sizes itself to its tallest/widest tab, so once every tab
# (including "Mahnungen" with its two tables) is built, the natural required
# size reflects whichever one actually needs the most room -- open at that size
# instead of a static guess that clips a table/buttons at the bottom. The floor
# keeps the window from opening awkwardly small on short content.
self.update_idletasks()
width = max(900, self.winfo_reqwidth())
height = max(650, self.winfo_reqheight())
self.geometry(f"{width}x{height}")
self.minsize(760, 540)
self.after_idle(self._center_on_parent)
def _build_ui(self) -> None: