an. Deine Namensschreibweise und die Zeilenlängen halten sich da beispielsweise nicht immer dran.
Sternchenimporte sollte man vermeiden. Dadurch werden Programme unübersichtlich weil man nicht mehr so leicht sieht wo ein Name herkommt. Und es besteht die Gefahr von Namenskollisionen. Die Tk-Anbindung kippt einem beim *-Import mal eben ca. 190 Namen ins Modul.
Was bringt der Präfix `my` bei `MyApp`, `MyTable`, und `myGUI`? Ohne würden die Namen letztlich genau das selbe bedeuten.
Statt eine einzelne Tabellenzeile als Liste mit Widgets in einer Zeilenliste zu speichern, würde ich tatsächlich anfangen GUI und Programmlogik/Datenhaltung zu trennen. Soweit ich das sehe kann man aus einem Gridlayout keine Zeilen löschen. Man müsste also wenn man Datensätze aus dem Modell löscht, das Grid verwerfen und neu erstellen, ohne die gelöschten Datensätze.
Das `row`-Attribut auf dem `MyTable`-Objekt macht keinen Sinn. Das müsste eigentlich eine lokale Variable in `createRow()` sein. Dann hat man auch gar nicht erst das Problem was der Code momentan hat: Jede Zeile hat immer noch die Einträge von allen vorhergehenden weil Du `row` niemals an eine neue Liste bindest. Und das Anhängen an die Zeilenliste ist an der falschen Stelle im Code. Du solltest Dir die Liste mal anschauen.
Das `MyTable` per Gridlayout Widgets im `parent` platziert ist unflexibel und fehleranfällig. Damit rechnet auch niemand. `MyTable` sollte selbst ein Widget sein und zum Beispiel von `Frame` erben.
Statt beim *Aufruf* von `createRow()` einen Dummywert zu übergeben macht es mehr Sinn bei der Methode einen Dummywert als Default zu setzen. Und dann auch eher `None` als eine leere Zeichenkette, denn genau dafür ist der Wert gedacht. Allerdings wäre das gar nicht notwendig wenn Du nicht manuell den Mausklick an die Schaltfläche binden würdest sondern wie vorgesehen die `command`-Option verwenden würdest. Bei dem `bind()` verhalten sich Schaltflächen auch nicht so wie der Benutzer das gewohnt ist, das ist also keine gute Idee.
Die ganzen durchnummerierten Namen in `createHeader()` werden gar nicht benötigt und der Code wäre leichter wartbar wenn man das nicht alles zigmal hinschreibt und leicht anpasst, sondern die Unterschiede als Daten heraus zieht.
Das löschen von Zeilen ist komisch gelöst beziehungsweise funktioniert so nicht. Wenn man den `Checkbutton` wieder deaktiviert würden die meisten Benutzer erwarten das die Zeile dann nicht gelöscht wird. Das macht der Code aber nicht. Wenn man die selbe Zeile mehrfach zum Löschen anklickt wird sie mehrfach zu der Liste hinzugefügt, was zu einem Fehler führen dürfte beim Löschen.
Die Löschmethode hat ein Problem: Sowie ein Element aus der Liste gelöscht wurde dann stimmen die Indexwerte nicht mehr und es wird mit jedem Löschen ”falscher”. Das löschen der zu löschenden Indexe einzeln innerhalb der Schleife ist ineffizient. Da wäre es geschickter am Ende der Schleife diese Datenstruktur einfach an eine neue, leere zu binden. Letztendlich würde man das aber eher so lösen das man in der Löschmethode über die Zeilen iteriert und sich am Status des `Checkbutton` orientiert.
Code: Alles auswählen
#!/usr/bin/env python3
# coding: utf8
import tkinter as tk
from tkinter import ttk
class Table(ttk.Frame):
def __init__(self, parent, rows, columns):
ttk.Frame.__init__(self, parent)
self.row_count = rows
self.column_count = columns
self.table_content = list()
self.row_numbers_to_delete = list()
#
# Call methods to build up initial table.
#
self._create_header()
self.create_row()
def _create_header(self):
helvetica = ('Helvetica', 18)
for text, position, font in [
('Eimer Nr.', (0, 0, 0, 3), None),
('Lebende Tiere', (0, 1, 10, 0), helvetica),
('Tote Tiere', (0, 11, 3, 0), helvetica),
('Erdkröten', (1, 1, 4, 0), None),
('Grasfrösche', (1, 5, 4, 0), None),
('Andere Tiere', (1, 9, 0, 2), None),
('Erdkröten', (1, 11, 0, 2), None),
('Grasfrösche', (1, 12, 0, 2), None),
('Männlich', (2, 1, 0, 0), None),
('Weiblich', (2, 2, 0, 0), None),
('Junge', (2, 3, 0, 0), None),
('Paare', (2, 4, 0, 0), None),
('Männlich', (2, 5, 0, 0), None),
('Weiblich', (2, 6, 0, 0), None),
('Junge', (2, 7, 0, 0), None),
('Paare', (2, 8, 0, 0), None),
]:
row, column, columnspan, rowspan = position
options = dict()
if font:
options['font'] = font
if not (columnspan or rowspan):
options['width'] = 10
label = ttk.Label(self, text=text, relief=tk.RIDGE, **options)
options = {
'row': row,
'column': column,
'sticky': 'we' + ('ns' if rowspan > 1 else '')
}
if columnspan > 1:
options['columnspan'] = columnspan
if rowspan > 1:
options['rowspan'] = rowspan
label.grid(**options)
self.row_count += 3
def create_row(self):
row = list()
for column in range(self.column_count):
if column in [9, 12]:
button = ttk.Button(self, text='+', width=10)
button.grid(column=column, row=self.row_count, sticky='we')
row.append(button)
elif column in [10, 13]:
label = ttk.Label(self, text='Platzhalter', width=10)
label.grid(column=column, row=self.row_count, sticky='we')
row.append(label)
elif column == 14:
variable = tk.IntVar()
button = ttk.Checkbutton(self, variable=variable)
button.grid(
column=column, row=self.row_count, sticky='we', padx=3
)
button.variable = variable
row.append(button)
else:
entry = ttk.Entry(self, width=10)
entry.grid(column=column, row=self.row_count, sticky='we')
row.append(entry)
self.table_content.append(row)
self.row_count += 1
def delete_rows(self):
#
# FIXME Need to redraw the table here.
#
# TODO Replace magical number by constant or introduce an own widget
# for rows.
#
self.table_content = [
row for row in self.table_content if not row[14].variable.get()
]
class App(object):
def __init__(self, parent):
table = Table(parent, 0, 15)
table.grid(row=1, columnspan=14, padx=5, pady=3)
# setup frame at the top
upper_frame = ttk.Frame(parent)
upper_frame.grid(row=0, columnspan=5, padx=5, pady=5, sticky='w')
# setup labels in this frame (Record, etc.)
ttk.Label(
upper_frame,
text='Frühjahrswanderung: XYZ',
font=('Helvetica', 26),
relief=tk.SUNKEN
).grid(row=0, column=0, columnspan=4, padx=7, pady=7)
ttk.Label(upper_frame, text='Record:').grid(
row=1, column=0, sticky='w', padx=7
)
ttk.Label(upper_frame, text='Datum:').grid(
row=2, column=0, sticky='w', padx=7
)
ttk.Label(upper_frame, text='Zeit:').grid(
row=3, column=0, sticky='w', padx=7
)
ttk.Label(upper_frame, text='Temperatur:').grid(
row=4, column=0, sticky='w', padx=7
)
ttk.Label(upper_frame, text='Witterung:').grid(
row=5, column=0, sticky='w', padx=7
)
# setup entries next to the labels, the label here is an exception
# (should not be editable)
ttk.Label(
upper_frame, text='No of the record, this text is replaced later'
).grid(row=1, column=1, sticky='w')
self.date_entry = ttk.Entry(upper_frame, width=15).grid(
row=2, column=1, sticky='w'
)
self.time_entry = ttk.Entry(upper_frame, width=15).grid(
row=3, column=1, sticky='w'
)
self.temperature_entry = ttk.Entry(upper_frame, width=15).grid(
row=4, column=1, sticky='w'
)
self.weather_entry = ttk.Entry(upper_frame, width=15).grid(
row=5, column=1, sticky='w'
)
# setup frame with the main buttons
button_frame = ttk.Frame(parent)
button_frame.grid(row=0, column=5, pady=5, sticky='s')
ttk.Button(
button_frame,
text='Zeile hinzufügen',
width=12,
command=table.create_row
).grid(row=0, column=0, padx=7, pady=2)
ttk.Button(
button_frame,
text='Zeile entfernen',
width=12,
command=table.delete_rows
).grid(row=1, column=0, padx=7, pady=2)
ttk.Button(button_frame, text='Speichern', width=10).grid(
row=0, column=1, padx=7, rowspan=2
)
def main():
root = tk.Tk()
root.config(bg='GREY')
_app = App(root)
root.mainloop()
if __name__ == '__main__':
main()