Skip to content

Commit b77c895

Browse files
authored
Merge pull request #24 from whellcome/v1.3-dev
1.3.x tkextras, requirements
2 parents 9ee4b12 + 10c4321 commit b77c895

File tree

3 files changed

+26
-250
lines changed

3 files changed

+26
-250
lines changed

README.md

+22
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,28 @@ This tool is ideal for professionals who deal with MS Access databases and need
3434
2. **[Save and Load Configurations (#17)](https://github.com/whellcome/MSAccessToSQL/issues/17)**
3535
- Add functionality to save the current export setup (e.g., database path, selected tables) and reload it in future sessions, enabling quick and consistent operations.
3636

37+
Here’s the "Important" section translated into English:
38+
39+
---
40+
41+
## Install
42+
43+
```bash
44+
git clone https://github.com/whellcome/MSAccessToSQL
45+
cd MSAccessToSQL
46+
pip install -r requirements.txt
47+
```
48+
49+
### Important
50+
51+
This project relies on the `tkextras` module, which enhances functionality for working with the `tkinter` interface. The `tkextras` module, along with other dependencies, is included in the `requirements.txt` file.
52+
53+
**Note:** If the `tkextras` module does not install automatically, you can manually install it using the following command:
54+
55+
56+
```bash
57+
pip install git+https://github.com/whellcome/tkextras.git
58+
```
3759

3860
## Usage
3961

code/export-msaccess-sql.py

+1-250
Original file line numberDiff line numberDiff line change
@@ -1,257 +1,8 @@
11
import tkinter as tk
22
from tkinter import ttk, filedialog, messagebox
33
import webbrowser
4-
import pandas as pd
54
import win32com.client
6-
7-
8-
class WidgetsRender:
9-
def __init__(self, render_params=None, *args, **options):
10-
"""
11-
Initialization of the Frame, description of the main elements
12-
:param render_params: General parameters for the arrangement of elements can be set externally
13-
:param args:
14-
:param options:
15-
"""
16-
super().__init__(*args, **options)
17-
if render_params is None:
18-
render_params = dict(sticky="ew", padx=5, pady=2)
19-
self.__render_params = render_params
20-
21-
def param_prepare(self, pack_params, func="grid"):
22-
pack_params = pack_params if pack_params else {}
23-
united_pack_params = self.__render_params.copy()
24-
if func == "pack":
25-
if "sticky" in united_pack_params:
26-
united_pack_params["anchor"] = united_pack_params.pop("sticky")
27-
elif func == "place":
28-
if "sticky" in united_pack_params:
29-
united_pack_params["anchor"] = united_pack_params.pop("sticky")
30-
united_pack_params.update(pack_params)
31-
return united_pack_params
32-
33-
def rgrid(self, obj:tk.Tk, render_params=None):
34-
"""
35-
Perform element creation and rendering in one command. Without creating a variable unnecessarily.
36-
Combines general parameters for the arrangement of elements and parameters for a specific element.
37-
:param obj: Element to rendering
38-
:param pack_params: Dictionary with element parameters
39-
:return: Rendered element
40-
"""
41-
if obj:
42-
obj.grid(self.param_prepare(render_params,"grid"))
43-
return obj
44-
45-
def rpack(self, obj=None, render_params=None):
46-
if obj:
47-
obj.grid(self.param_prepare(render_params, "pack"))
48-
return obj
49-
50-
def rplace(self, obj=None, render_params=None):
51-
if obj:
52-
obj.grid(self.param_prepare(render_params, "place"))
53-
return obj
54-
55-
56-
class TreeviewDataFrame(WidgetsRender, ttk.Treeview):
57-
58-
def __init__(self, parent, render_params=None, *args, **kwargs):
59-
super().__init__(render_params, parent, *args, **kwargs)
60-
self.df = pd.DataFrame()
61-
self.filtered_df = pd.DataFrame()
62-
self.bind("<Button-1>", self.toggle_cell)
63-
self.svars = {"flag_symbol": {
64-
"check": "✔",
65-
"uncheck": " "
66-
}}
67-
self.svars["flag_values"] = {
68-
self.svars["flag_symbol"]["uncheck"]: self.svars["flag_symbol"]["check"],
69-
self.svars["flag_symbol"]["check"]: self.svars["flag_symbol"]["uncheck"]
70-
}
71-
72-
73-
def column(self, column, option=None, **kw):
74-
"""
75-
Override column method with DataFrame.
76-
"""
77-
result = super().column(column, option=option, **kw)
78-
if column not in self.df.columns:
79-
self.df[column] = ''
80-
return result
81-
82-
def insert(self, parent, index, iid=None, **kw):
83-
"""
84-
Inserts a new row into the Treeview and synchronizes it with the DataFrame.
85-
:param parent: Parent node for Treeview (usually "" for root-level items).
86-
:param index: Position to insert the item.
87-
:param iid: Unique identifier for the row. If None, Treeview generates one.
88-
:param kw: Additional arguments for Treeview insert (e.g., values).
89-
"""
90-
# Use the provided iid or let Treeview generate one
91-
if iid is None:
92-
iid = super().insert(parent, index, **kw) # Automatically generate iid
93-
else:
94-
super().insert(parent, index, iid=iid, **kw)
95-
96-
# Ensure values are provided
97-
values = kw.get("values", [])
98-
99-
# Convert values to a DataFrame-compatible dictionary
100-
new_row = {col: val for col, val in zip(self.cget("columns"), values)}
101-
102-
# Add the new row to the DataFrame, using iid as the index
103-
self.df.loc[iid] = new_row
104-
return iid
105-
106-
def set(self, item, column=None, value=None):
107-
"""
108-
Enhanced set method for synchronization with a DataFrame.
109-
:param item: The item ID (iid) in the Treeview.
110-
:param column: The column name to retrieve or update.
111-
:param value: The value to set; if None, retrieves the current value.
112-
:return: The value as returned by the original Treeview method.
113-
"""
114-
result = super().set(item, column, value)
115-
if item not in self.df.index:
116-
raise KeyError(f"Row with index '{item}' not found in DataFrame.")
117-
is_filtered = True if item in self.filtered_df.index else False
118-
if value is None:
119-
if column is None:
120-
self.df.loc[item] = self.df.loc[item].replace(result)
121-
else:
122-
self.df.loc[item, column] = result
123-
else:
124-
self.df.loc[item, column] = value
125-
if is_filtered:
126-
self.filtered_df.loc[item, column] = value
127-
ind = self.cget("columns").index(column) if not column else 0
128-
self.all_checked_update(ind)
129-
return result
130-
131-
def item(self, item, option=None, **kw):
132-
"""
133-
Override item method with DataFrame.
134-
"""
135-
values = kw.get("values", [])
136-
result = super().item(item, option, **kw)
137-
is_filtered = True if item in self.filtered_df.index else False
138-
if option is None and len(values):
139-
updates = pd.Series(values, index=self.cget("columns"))
140-
self.df.loc[item] = updates
141-
if is_filtered:
142-
self.filtered_df.loc[item] = updates
143-
self.all_checked_update()
144-
return result
145-
146-
def delete(self, *items, inplace=False):
147-
"""
148-
Override delete method with DataFrame..
149-
"""
150-
if inplace:
151-
for item in items:
152-
values = self.item(item, "values")
153-
self.df = self.df[~(self.df[list(self.df.columns)] == values).all(axis=1)]
154-
super().delete(*items)
155-
156-
def flag_inverse(self, value: str) -> str:
157-
flag_values = self.svars["flag_values"]
158-
return flag_values[value]
159-
160-
def toggle_cell(self, event):
161-
"""Handles cell clicks to change flags."""
162-
if self.identify_region(event.x, event.y) != "cell":
163-
return
164-
col_num = int(self.identify_column(event.x).replace("#", "")) - 1
165-
if not col_num:
166-
return
167-
col_name = self.cget("columns")[col_num]
168-
item = self.identify_row(event.y)
169-
current_value = self.set(item, col_name)
170-
self.set(item, col_name, self.flag_inverse(current_value))
171-
self.event_generate("<<TreeToggleCell>>")
172-
173-
def rebuild_tree(self, dataframe=None):
174-
if dataframe is None:
175-
dataframe = self.df
176-
self.delete(*self.get_children())
177-
for index, row in dataframe.iterrows():
178-
self.insert("", "end", iid=index, values=row.to_list())
179-
180-
def filter_by_name(self, keyword=""):
181-
"""Filter rows based on a keyword and update Treeview."""
182-
self.filtered_df = self.df[self.df[self.df.columns[0]].str.contains(keyword, case=False)].copy()
183-
self.rebuild_tree(self.filtered_df)
184-
185-
def filter_event_evoke(self):
186-
"""Filter updated event."""
187-
self.event_generate("<<TreeFilterUpdated>>")
188-
189-
def all_checked_event_evoke(self):
190-
self.event_generate("<<TreeCheckAllUpdated>>")
191-
192-
def is_all_checked(self, column):
193-
df = self.filtered_df if len(self.filtered_df) else self.df
194-
return not len(df[df.iloc[:, column] == self.svars["flag_symbol"]["uncheck"]])
195-
196-
def all_checked_update(self, column = 0):
197-
if column:
198-
self.svars['check_all'][column].set(self.is_all_checked(column))
199-
else:
200-
for i in range(1,len(self.cget("columns"))):
201-
self.svars['check_all'][i].set(self.is_all_checked(i))
202-
self.all_checked_event_evoke()
203-
204-
def filter_widget(self, parent):
205-
widget_frame = ttk.Frame(parent, width=150, borderwidth=1, relief="solid", padding=(2, 2))
206-
self.rgrid(tk.Label(widget_frame, text="Filter by table name:", font=("Helvetica", 9, "bold")),
207-
dict(row=0, column=0, pady=5))
208-
filter_entry = tk.Entry(widget_frame)
209-
self.rgrid(filter_entry, dict(row=0, column=1, padx=5, pady=5, sticky="ew"))
210-
211-
def apply_filter():
212-
self.filter_by_name(filter_entry.get())
213-
if len(self.filtered_df) == len(self.df):
214-
self.filtered_df = pd.DataFrame()
215-
self.filter_event_evoke()
216-
self.all_checked_update()
217-
218-
def clear_filter():
219-
self.rebuild_tree()
220-
filter_entry.delete(0, tk.END)
221-
self.filtered_df = pd.DataFrame()
222-
self.filter_event_evoke()
223-
self.all_checked_update()
224-
225-
self.rgrid(ttk.Button(widget_frame, text="Filter", command=apply_filter),
226-
dict(row=0, column=2, padx=5, pady=5))
227-
self.rgrid(ttk.Button(widget_frame, text="Restore", command=clear_filter),
228-
dict(row=0, column=3, padx=5, pady=5))
229-
return widget_frame
230-
231-
def checkbox_widget(self, parent):
232-
233-
def toggle_all(index: int):
234-
checked = self.svars['check_all'][index].get()
235-
if not checked:
236-
self.svars['check_all'][index].set(False)
237-
for item in self.get_children():
238-
values = list(self.item(item, "values"))
239-
if checked:
240-
values[index] = self.svars['flag_symbol']['check']
241-
else:
242-
values[index] = self.svars['flag_symbol']['uncheck']
243-
self.item(item, values=values)
244-
245-
widget_frame = ttk.Frame(parent, padding=(2, 2))
246-
self.svars["check_all"] = {}
247-
for i, col in enumerate(self.cget("columns")[1:]):
248-
ind = i+1
249-
self.svars["check_all"][ind] = tk.IntVar(value=0)
250-
box_text = f"Check all {self.heading(col)['text'] if self.heading(col)['text'] else col}"
251-
render_params = dict(row=0, column=ind, padx=20)
252-
self.rgrid(ttk.Checkbutton(widget_frame, text=box_text, variable=self.svars["check_all"][ind],
253-
command=lambda k=ind: toggle_all(k)), render_params)
254-
return widget_frame
5+
from tkextras import *
2556

2567

2578
class GetWidgetsFrame(WidgetsRender, ttk.Frame):

requirements.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
tkextras @ git+https://github.com/whellcome/tkextras.git
2+
pandas>=1.3.0
3+
pywin32>=300

0 commit comments

Comments
 (0)