refactored entry mask to own window, split windows and config from main app file

This commit is contained in:
2025-04-20 23:34:41 +02:00
parent 7ef1fca873
commit dc86d8ce01
3 changed files with 223 additions and 197 deletions

View File

@@ -2,174 +2,13 @@ import csv
import os import os
import sys import sys
import tkinter as tk import tkinter as tk
from tkinter import font, filedialog
from configparser import NoSectionError, NoOptionError from configparser import NoSectionError, NoOptionError
from configparser import ConfigParser
from tkinter import messagebox from tkinter import messagebox
from tkinter import ttk from tkinter import ttk
import json import json
from config import Config
def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel): from windows import SettingsWindow, EditRecord, Window, show_error
messagebox.showwarning(
title=message_title,
message=message,
parent=parent
)
class Window(tk.Toplevel):
def __init__(self, root: tk.Tk):
super().__init__(root)
self.root = root
self.protocol("WM_DELETE_WINDOW", self.close_window)
def close_window(self):
self.destroy_window()
def destroy_window(self):
self.root.update()
self.destroy()
class InsertRecord(Window):
def __init__(self, root: tk.Tk):
super().__init__(root)
self.title("Adresse einfügen")
edit_frame = tk.Frame(self.root)
edit_frame.pack(side=tk.TOP, fill=tk.X)
tk.Label(edit_frame, text="Aktiv").grid(row=0, column=0)
tk.Label(edit_frame, text="Firma").grid(row=0, column=1)
tk.Label(edit_frame, text="Name").grid(row=0, column=2)
tk.Label(edit_frame, text="Strasse").grid(row=0, column=3)
tk.Label(edit_frame, text="Plz/Ort").grid(row=0, column=4)
edit_aktiv = tk.Checkbutton(edit_frame, variable=self.aktiv, onvalue="x", offvalue="")
edit_aktiv.grid(row=1, column=0)
edit_firma = tk.Entry(edit_frame, textvariable=self.firma)
edit_firma.grid(row=1, column=1)
edit_name = tk.Entry(edit_frame, textvariable=self.name)
edit_name.grid(row=1, column=2)
edit_strasse = tk.Entry(edit_frame, textvariable=self.strasse)
edit_strasse.grid(row=1, column=3)
edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort)
edit_plz_ort.grid(row=1, column=4)
tk.Button(edit_frame, text="Update", command=self.update_record, width=button_width).grid(row=1, column=5)
tk.Button(self, text="Abbrechen", command=self.close_window).pack()
class SettingsWindow(Window):
def __init__(self, root: tk.Tk):
super().__init__(root)
self.geometry(f"500x330+{self.root.winfo_x() + 20}+{self.root.winfo_y() + 20}")
self.config = Config()
self.json_file = tk.StringVar()
self.csv_file = tk.StringVar()
title = font.Font(family='Ubuntu Mono', size=20, weight=font.BOLD)
tk.Label(self, text="Einstellungen", font=title).pack(pady=20)
path_frame = tk.Frame(self)
path_frame.pack(pady=(10, 40))
tk.Label(path_frame, text="Datenpfad JSON Datei").grid(row=0, column=0)
tk.Entry(path_frame, textvariable=self.json_file, width=50).grid(row=1, column=0)
tk.Button(path_frame, text="Pfad", command=lambda: self.set_json_path(self.json_file.get())).grid(row=1,
column=1)
tk.Label(path_frame, text="Datenpfad CSV Export Datei").grid(row=2, column=0)
tk.Entry(path_frame, textvariable=self.csv_file, width=50).grid(row=3, column=0)
tk.Button(path_frame, text="Pfad", command=lambda: self.set_csv_path(self.csv_file.get())).grid(row=3, column=1)
bottom_frame = tk.Frame(self)
bottom_frame.pack()
tk.Button(bottom_frame, text="Ok", command=self.ok).pack(side=tk.LEFT)
tk.Button(bottom_frame, text="Abbrechen", command=self.cancel).pack(side=tk.LEFT)
self.load_config()
def set_json_path(self, initial_dir: str = ""):
initial_dir = "~/" if initial_dir == "" else initial_dir
new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad JSON Datei")
new_path = initial_dir if len(new_path) == 0 else new_path
self.json_file.set(new_path)
def set_csv_path(self, initial_dir: str = ""):
initial_dir = "~/" if initial_dir == "" else initial_dir
new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad CSV Datei")
new_path = initial_dir if len(new_path) == 0 else new_path
self.csv_file.set(new_path)
def load_config(self):
try:
self.json_file.set(self.config.get("json", "path"))
except NoSectionError:
self.config.add_section("json")
self.config.set("json", "path", "")
except NoOptionError:
self.config.set("json", "path", "")
try:
self.csv_file.set(self.config.get("csv", "path"))
except NoSectionError:
self.config.add_section("csv")
self.config.set("csv", "path", "")
except NoOptionError:
self.config.set("csv", "path", "")
def ok(self):
if self.json_file.get() == "" or self.csv_file.get() == "":
show_error(message_title="Fehlerhafte Konfiguration",
message="Pfad für JSON oder CSV Datei fehlt",
parent=self)
return
self.config.set("json", "path", self.json_file.get())
self.config.set("csv", "path", self.csv_file.get())
self.close_window()
def cancel(self):
self.close_window()
class Config:
parser: ConfigParser
def __init__(self):
"""
Config parser reading config.ini
Attributes:
self.parser: ConfigParser holding list of sections and options
self.__filename: Path and name to the config file
"""
self.parser = ConfigParser()
self.config_file_name = "config.ini"
self.root_path = os.path.dirname(os.path.abspath(__file__))
self.config_file = os.path.join(self.root_path, self.config_file_name)
self._load()
def _save(self):
with open(self.config_file, 'w') as outfile:
self.parser.write(outfile)
def _load(self):
self.parser.read(self.config_file)
def add_section(self, section):
self._load()
self.parser.add_section(section)
self._save()
def set(self, section: str, option: str, value: str):
self._load()
self.parser.set(section, option, value)
self._save()
def get(self, section: str, option: str):
self._load()
return self.parser.get(section, option)
class Application: class Application:
@@ -220,13 +59,6 @@ class Application:
tk.Button(top_frame, text="Quit", command=self.on_close, width=button_width).pack(side=tk.RIGHT) tk.Button(top_frame, text="Quit", command=self.on_close, width=button_width).pack(side=tk.RIGHT)
tk.Button(top_frame, text="Settings", command=self.show_settings, width=button_width).pack(side=tk.RIGHT) tk.Button(top_frame, text="Settings", command=self.show_settings, width=button_width).pack(side=tk.RIGHT)
self.aktiv = tk.StringVar()
self.firma = tk.StringVar()
self.name = tk.StringVar()
self.strasse = tk.StringVar()
self.plz_ort = tk.StringVar()
data_frame = tk.Frame(self.root, bg="teal") data_frame = tk.Frame(self.root, bg="teal")
data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True) data_frame.pack(side=tk.TOP, fill=tk.BOTH, expand=True)
@@ -244,6 +76,8 @@ class Application:
scrollbar.pack(side=tk.LEFT, fill=tk.Y) scrollbar.pack(side=tk.LEFT, fill=tk.Y)
self.table.bind("<ButtonRelease-1>", self.mouse_click) self.table.bind("<ButtonRelease-1>", self.mouse_click)
self.table.bind("<Return>", self.mouse_click_double)
self.table.bind("<Double-1>", self.mouse_click_double)
self._load_json_file() self._load_json_file()
@@ -275,12 +109,10 @@ class Application:
self.root.destroy() self.root.destroy()
def show_settings(self): def show_settings(self):
settings = self.open_window(SettingsWindow) settings = self.open_window_settings()
settings.wait_window() settings.wait_window()
def insert_record(self): def insert_record(self):
if self.current_record is not None:
self.clear_entry_fields()
values = [ values = [
"x", "x",
"Firma", "Firma",
@@ -288,7 +120,9 @@ class Application:
"Strasse", "Strasse",
"Plz/Ort", "Plz/Ort",
] ]
self.table.insert('', 'end', values=values) last_iid = self.table.get_children()[-1]
next_iid = int(last_iid) + 1
self.table.insert('', 'end', iid=next_iid, values=values)
self._save_json_file() self._save_json_file()
def delete_record(self): def delete_record(self):
@@ -300,24 +134,19 @@ class Application:
"Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"): "Willst du diesen Eintrag wirklich löschen?\nDies kann nicht rückgängig gemacht werden"):
self.table.delete(self.current_record) self.table.delete(self.current_record)
self.clear_entry_fields()
self.deselect_tree() self.deselect_tree()
self._save_json_file() self._save_json_file()
def update_record(self): def update_record(self, data: list):
if self.current_record is None: if self.current_record is None:
return return
values = { values = {}
0: self.aktiv.get(), for idx, value in enumerate(data):
1: self.firma.get(), values[str(idx)] = value.get()
2: self.name.get(),
3: self.strasse.get(),
4: self.plz_ort.get(),
}
for key, value in values.items(): for key, value in values.items():
self.table.set(self.current_record, key, value) self.table.set(self.current_record, key, value)
self.clear_entry_fields()
self._save_json_file() self._save_json_file()
def toggle_active(self): def toggle_active(self):
@@ -331,24 +160,23 @@ class Application:
new_active = "x" if active == "" else "" new_active = "x" if active == "" else ""
self.table.set(record_id, "0", new_active) self.table.set(record_id, "0", new_active)
self.clear_entry_fields()
self._save_json_file() self._save_json_file()
def clear_entry_fields(self):
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for entry in entry_var_list:
entry.set("")
self.current_record = None
def deselect_tree(self): def deselect_tree(self):
while len(self.table.selection()) > 0: while len(self.table.selection()) > 0:
self.table.selection_remove(self.table.selection()[0]) self.table.selection_remove(self.table.selection()[0])
self.current_record = None
def mouse_click(self, event): def mouse_click(self, event):
self.current_record = self.table.focus()
region = self.table.identify("region", event.x, event.y) region = self.table.identify("region", event.x, event.y)
match region: match region:
case "heading": case "heading":
self.click_on_header(event) self.click_on_header(event)
def mouse_click_double(self, event):
region = self.table.identify("region", event.x, event.y)
match region:
case "cell": case "cell":
self.click_on_cell() self.click_on_cell()
@@ -373,14 +201,11 @@ class Application:
case _: case _:
print(column) print(column)
self.sort_order = not self.sort_order self.sort_order = not self.sort_order
self.clear_entry_fields()
def click_on_cell(self): def click_on_cell(self):
self.current_record = self.table.focus() self.current_record = self.table.focus()
values = self.table.item(self.current_record, "values") values = self.table.item(self.current_record, "values")
entry_var_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort] self.open_window_edit_records(values)
for i in range(len(values)):
entry_var_list[i].set(values[i])
def _load_json_file(self): def _load_json_file(self):
try: try:
@@ -443,11 +268,21 @@ class Application:
for item in self.table.get_children(): for item in self.table.get_children():
self.table.delete(item) self.table.delete(item)
def open_window(self, child: type[Window]) -> Window: def open_window_settings(self):
window = child(self.root) window = SettingsWindow(self, self.root)
window.wm_transient(self.root) window.wm_transient(self.root)
window.wait_visibility()
window.grab_set() window.grab_set()
return window return window
def open_window_edit_records(self, data):
window = EditRecord(self, self.root, data)
window.wm_transient(self.root)
window.wait_visibility()
window.grab_set()
if __name__ == '__main__': if __name__ == '__main__':
Application() Application()

43
src/config.py Normal file
View File

@@ -0,0 +1,43 @@
import os
from configparser import ConfigParser
class Config:
parser: ConfigParser
def __init__(self):
"""
Config parser reading config.ini
Attributes:
self.parser: ConfigParser holding list of sections and options
self.__filename: Path and name to the config file
"""
self.parser = ConfigParser()
self.config_file_name = "config.ini"
self.root_path = os.path.dirname(os.path.abspath(__file__))
self.config_file = os.path.join(self.root_path, self.config_file_name)
self._load()
def _save(self):
with open(self.config_file, 'w') as outfile:
self.parser.write(outfile)
def _load(self):
self.parser.read(self.config_file)
def add_section(self, section):
self._load()
self.parser.add_section(section)
self._save()
def set(self, section: str, option: str, value: str):
self._load()
self.parser.set(section, option, value)
self._save()
def get(self, section: str, option: str):
self._load()
return self.parser.get(section, option)

148
src/windows.py Normal file
View File

@@ -0,0 +1,148 @@
import tkinter as tk
from configparser import NoSectionError, NoOptionError
from tkinter import font, filedialog, messagebox
from config import Config
def show_error(message_title: str, message: str, parent: tk.Tk | tk.Toplevel):
messagebox.showwarning(
title=message_title,
message=message,
parent=parent
)
class Window(tk.Toplevel):
def __init__(self, parent, root: tk.Tk):
super().__init__(root)
self.parent = parent
self.root = root
self.protocol("WM_DELETE_WINDOW", self.close_window)
self.bind("<Escape>", self.close_window)
def close_window(self, event = None):
self._destroy_window()
def _destroy_window(self):
self.root.update()
self.destroy()
class EditRecord(Window):
def __init__(self, parent, root: tk.Tk, data: tuple):
super().__init__(parent, root)
self.bind("<Return>", self._update)
self.data = data
button_width = 8
self.title("Adresse bearbeiten")
self.aktiv = tk.StringVar()
self.firma = tk.StringVar()
self.name = tk.StringVar()
self.strasse = tk.StringVar()
self.plz_ort = tk.StringVar()
self.field_list = [self.aktiv, self.firma, self.name, self.strasse, self.plz_ort]
for argument, field in zip(self.data, self.field_list):
field.set(argument)
edit_frame = tk.Frame(self)
edit_frame.pack(side=tk.TOP, fill=tk.X, pady=20, padx=20)
tk.Label(edit_frame, text="Aktiv", anchor=tk.W).grid(row=0, column=0)
tk.Label(edit_frame, text="Firma").grid(row=0, column=1)
tk.Label(edit_frame, text="Name").grid(row=0, column=2)
tk.Label(edit_frame, text="Strasse").grid(row=0, column=3)
tk.Label(edit_frame, text="Plz/Ort").grid(row=0, column=4)
edit_aktiv = tk.Checkbutton(edit_frame, variable=self.aktiv, onvalue="x", offvalue="")
edit_aktiv.grid(row=1, column=0)
edit_firma = tk.Entry(edit_frame, textvariable=self.firma)
edit_firma.grid(row=1, column=1)
edit_name = tk.Entry(edit_frame, textvariable=self.name)
edit_name.grid(row=1, column=2)
edit_strasse = tk.Entry(edit_frame, textvariable=self.strasse)
edit_strasse.grid(row=1, column=3)
edit_plz_ort = tk.Entry(edit_frame, textvariable=self.plz_ort)
edit_plz_ort.grid(row=1, column=4)
button_frame = tk.Frame(self)
button_frame.pack(side=tk.TOP, pady=10)
tk.Button(button_frame, text="Save", command=self._update, width=button_width).pack(side=tk.LEFT)
tk.Button(button_frame, text="Abbrechen", command=self.close_window, width=button_width).pack(side=tk.LEFT)
def _update(self, event = None):
self.parent.update_record(self.field_list)
self.close_window()
class SettingsWindow(Window):
def __init__(self, parent, root: tk.Tk):
super().__init__(parent, root)
self.geometry(f"500x330+{self.root.winfo_x() + 20}+{self.root.winfo_y() + 20}")
self.config = Config()
self.json_file = tk.StringVar()
self.csv_file = tk.StringVar()
title = font.Font(family='Ubuntu Mono', size=20, weight=font.BOLD)
tk.Label(self, text="Einstellungen", font=title).pack(pady=20)
path_frame = tk.Frame(self)
path_frame.pack(pady=(10, 40))
tk.Label(path_frame, text="Datenpfad JSON Datei").grid(row=0, column=0)
tk.Entry(path_frame, textvariable=self.json_file, width=50).grid(row=1, column=0)
tk.Button(path_frame, text="Pfad", command=lambda: self.set_json_path(self.json_file.get())).grid(row=1,
column=1)
tk.Label(path_frame, text="Datenpfad CSV Export Datei").grid(row=2, column=0)
tk.Entry(path_frame, textvariable=self.csv_file, width=50).grid(row=3, column=0)
tk.Button(path_frame, text="Pfad", command=lambda: self.set_csv_path(self.csv_file.get())).grid(row=3, column=1)
bottom_frame = tk.Frame(self)
bottom_frame.pack()
tk.Button(bottom_frame, text="Ok", command=self.ok).pack(side=tk.LEFT)
tk.Button(bottom_frame, text="Abbrechen", command=self.cancel).pack(side=tk.LEFT)
self.load_config()
def set_json_path(self, initial_dir: str = ""):
initial_dir = "~/" if initial_dir == "" else initial_dir
new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad JSON Datei")
new_path = initial_dir if len(new_path) == 0 else new_path
self.json_file.set(new_path)
def set_csv_path(self, initial_dir: str = ""):
initial_dir = "~/" if initial_dir == "" else initial_dir
new_path = filedialog.askdirectory(initialdir=initial_dir, title="Datenpfad CSV Datei")
new_path = initial_dir if len(new_path) == 0 else new_path
self.csv_file.set(new_path)
def load_config(self):
try:
self.json_file.set(self.config.get("json", "path"))
except NoSectionError:
self.config.add_section("json")
self.config.set("json", "path", "")
except NoOptionError:
self.config.set("json", "path", "")
try:
self.csv_file.set(self.config.get("csv", "path"))
except NoSectionError:
self.config.add_section("csv")
self.config.set("csv", "path", "")
except NoOptionError:
self.config.set("csv", "path", "")
def ok(self):
if self.json_file.get() == "" or self.csv_file.get() == "":
show_error(message_title="Fehlerhafte Konfiguration",
message="Pfad für JSON oder CSV Datei fehlt",
parent=self)
return
self.config.set("json", "path", self.json_file.get())
self.config.set("csv", "path", self.csv_file.get())
self.close_window()
def cancel(self):
self.close_window()