Последовательные вызовы основного цикла работают со сбоями в ttkbootstrapPython

Программы на Python
Ответить Пред. темаСлед. тема
Anonymous
 Последовательные вызовы основного цикла работают со сбоями в ttkbootstrap

Сообщение Anonymous »

Я пытался разработать приложение, которое получало бы вводимые пользователем данные для подключения к базе данных. После подключения к базе данных приложение получает таблицу, созданную пользователем на основе некоторых параметров. Это было реализовано в рамках объектно-ориентированного подхода. Я создал два объекта этого экземпляра. Вот здесь и возникает проблема. Когда первое окно уничтожается, открывается второе окно, но оно не имеет никаких параметров стиля, используемых при рендеринге окна, которое я передал для создания экземпляра класса в части init класса. Я уже некоторое время пытаюсь его отладить. Но до сих пор не нашел основную причину.
Вот код: -

Код: Выделить всё

import ttkbootstrap as ttkb
from ttkbootstrap.constants import *
from ttkbootstrap.tableview import Tableview
import pyodbc
from typing import *
import pandas as pd
import inspect
import threading

def log_event(event):
print(f"Event: {event.widget} {event.type}")

def print_args(*args, **kwargs):
"""
Print the list of arguments and their types passed to a function.
"""
sig = inspect.signature(print_args)  # Get the signature of the function.
bound_args = sig.bind(*args, **kwargs)  # Bind the arguments to the signature.

# Print the argument name, value, and type.
for name, value in bound_args.arguments.items():
print(f"{name}: {value} ({type(value).__name__})")

def create_form_entry(master, widget: str, label: str, variable, fun=None):
"""Create a single form entry using a frame for each label"""
container = ttkb.Frame(master)
container.pack(fill=X, expand=YES, pady=5)

lbl = ttkb.Label(master=container, text=label.title(), width=10)
lbl.pack(side=LEFT, padx=5, ipadx=10)

if widget == "entry" or widget == "Entry":
ent = ttkb.Entry(master=container, textvariable=variable)
ent.pack(side=LEFT, padx=5, fill=X, expand=YES)
elif widget == "combobox" or widget == "Combobox":
ent = ttkb.Combobox(master=container, textvariable=variable, values=fun, width=20)
ent.pack(side=LEFT, padx=5, ipadx=30, fill=X, expand=YES)
elif widget == "Date" or widget == "date":
ent = ttkb.DateEntry(master=container,dateformat=r"%Y%m%d")
ent.pack(side=LEFT, padx=5, ipadx=30, fill=X, expand=YES)

def create_table(master: ttkb.Window, data: pd.DataFrame):
colors = master.style.colors
container = ttkb.Toplevel(title="Data Validation")
table = Tableview(
master=container,
coldata=list(data.columns.values),
rowdata=list(data.itertuples(index=False, name=None)),
paginated=True,
searchable=True,
bootstyle=LIGHT,
stripecolor=(colors.light, None),
autofit=True,
pagesize=30,
height=30
)
table.pack(fill=BOTH, expand=YES, padx=10, pady=10)
table.autofit_columns()

btn_container = ttkb.Frame(master=container)
btn_container.pack(fill=X, expand=YES, pady=(15, 10))

def on_done():
print("First window destroyed")
master.destroy()
print("After destruction of first window")
done_btn = ttkb.Button(master=btn_container, text="Done", command=on_done)
done_btn.pack(side=RIGHT, padx=5)

cancel_btn = ttkb.Button(master=btn_container, text="Cancel", command=container.destroy)
cancel_btn.pack(side=RIGHT, padx=5)

class DBLoginGUI(ttkb.LabelFrame):
def __init__(self, master, connect_callback: Callable):
super().__init__(master, padding=(15, 5), text="D/B Login")
self.grid(row=0, column=0, padx=5, pady=5, sticky=NSEW)

self.username = ttkb.StringVar()
self.password = ttkb.StringVar()
self.host = ttkb.StringVar()
self.database = ttkb.StringVar()
self.protocol = ttkb.StringVar()
self.port = ttkb.IntVar()
self.driver = ttkb.StringVar()
self.connect_fun: Callable = connect_callback
self.master = master
self.cancel_btn = None
self.connect_btn = ttkb.Button()

def create_gui(self):
"""Creates Labels, Entries and Combo-boxes for the variables.  Also created submit and cancel button"""
create_form_entry(self, "Combobox", "Driver: ", self.driver, pyodbc.drivers())
create_form_entry(self, "Entry", "Username: ", self.username)
create_form_entry(self, "Entry", "Password: ", self.password)
create_form_entry(self, "Entry", "Hostname: ", self.host)
create_form_entry(self, "Entry", "Database: ", self.database)
create_form_entry(self, "Entry", "Protocol: ", self.protocol)
create_form_entry(self, "Entry", "Port: ", self.port)

container = ttkb.Frame(self)
container.pack(fill=X, expand=YES, pady=(15, 10))

self.connect_btn = ttkb.Button(
master=container,
text="Connect",
command=self.bind_on_connect,
bootstyle=SUCCESS,
width=6,
)
self.connect_btn.pack(side=RIGHT, padx=5, ipadx=5)
self.connect_btn.focus_set()

self.cancel_btn = ttkb.Button(
master=container,
text="Cancel",
command=self.on_cancel,
bootstyle=DANGER,
width=6,
)
self.cancel_btn.pack(side=RIGHT, padx=5, ipadx=5)

def bind_on_connect(self, callback):
self.connect_btn.bind("", callback)

def on_cancel(self):
self.master.destroy()

class FeedsInput(ttkb.LabelFrame):
def __init__(self, master, opt):
super().__init__(master, padding=(10, 5), text="Feeds Input")
# self.pack(side=TOP, fill=BOTH)
self.grid(row=0,column=1, padx=5, pady=5, sticky=N)

self.param1 = ttkb.StringVar()
self.param2 = ttkb.IntVar()
self.param3 = ttkb.StringVar()
self.param4 = ttkb.StringVar()
self.param5 = ttkb.StringVar()
self.table = ttkb.StringVar()
self.table_opt = opt

self.fetch_btn: ttkb.Button = None

def create_gui(self):
create_form_entry(self, "entry", "Parameter 1: ", self.kritype)
create_form_entry(self, "Date", "Parameter 2: ", self.cob)
create_form_entry(self, "Entry", "Parameter 3: ", self.risk_indicator)
create_form_entry(self, "Entry", "PArameter 4: ", self.src_sys)
create_form_entry(self, "Entry", "Parameter 5: ", self.feed_loc)
create_form_entry(self, "Combobox", "Table: ",self.table, fun=self.table_opt)

container = ttkb.Frame(self)
container.pack(fill=X, expand=YES, pady=(15, 10))

self.fetch_btn = ttkb.Button(
master=container,
text="Fetch",
command=self.bind_on_fetch,
bootstyle=SUCCESS,
width=6,
)
self.fetch_btn.pack(side=RIGHT, padx=5, ipadx=5)
self.fetch_btn.focus_set()

def bind_on_fetch(self, func: Callable = None):

import inspect

args_passed = locals()
print(args_passed)
print("Called function print_args(self, func)")
print_args(self, func)
print(callable(func))
# traceback.print_stack()
caller_frame = inspect.currentframe().f_back.f_back
print(caller_frame)
print(caller_frame.f_back)
print(caller_frame.f_back.f_trace)
print(caller_frame.f_code.co_name)
self.fetch_btn.bind("", func)

class TableView(ttkb.Toplevel):
def __init__(self, master: ttkb.Window, data: pd.DataFrame):
super().__init__(title="Fetched Data")
self.master = master
self.df = data
self.btn_container: ttkb.Frame = None
self.done_btn: ttkb.Button = None
self.cancel_btn: ttkb.Button = None

def create_table(self, done_fun: Callable, cancel_fun:  Callable):
colors = self.master.style.colors
table = Tableview(
master=self,
coldata=list(self.df.columns.values),
rowdata=list(self.df.itertuples(index=False, name=None)),
paginated=True,
searchable=True,
bootstyle=LIGHT,
stripecolor=(colors.dark, None),
autofit=True,
pagesize=38,
height=38
)
table.pack(fill=BOTH, expand=YES, padx=10, pady=10)
table.autofit_columns()

self.btn_container = ttkb.Frame(master=self)
self.btn_container.pack(fill=X, expand=YES, pady=(15, 10))

self.done_btn = ttkb.Button(master=self.btn_container, text="Done", command=done_fun)
self.done_btn.pack(side=RIGHT, padx=10)

self.cancel_btn = ttkb.Button(master=self.btn_container, text="Cancel", command=cancel_fun)
self.cancel_btn.pack(side=RIGHT, padx=10)

class Model:
def __init__(self) -> None:
self.master = ttkb.Window("Fetching Data")
self.master.style.theme_use(themename="solar")
self.db_login = DBLoginGUI(self.master, connect_callback=self.on_connect)
self.db_login.create_gui()
self.db_login.bind_on_connect(self.on_connect)
self.conn: pyodbc.Connection = None
self.cursor : pyodbc.Cursor = None
self.table_opt: List[str] = list()
self.feeds_input: FeedsInput = None
self.conn_string: str = str()
self.table_view: TableView = None

#Login Credentials
self.username: str = str()
self.password: str = str()
self.protocol: str = str()
self.driver: str = str()
self.host: str = str()
self.port: int = int()
self.database: str = str()

def on_connect(self, event=None):
self.username = self.db_login.username.get()
self.password = self.db_login.password.get()
self.driver = self.db_login.driver.get()
self.host = self.db_login.host.get()
self.port = self.db_login.port.get()
self.protocol = self.db_login.protocol.get()
self.database = self.db_login.database.get()

self.conn_string = f'DRIVER={self.driver};' \
f'DATABASE={self.database};' \
f'HOSTNAME={self.host};' \
f'PORT={self.port};' \
f'PROTOCOL={self.protocol};' \
f'UID={self.username};' \
f'PWD={self.password};'

try:
self.conn = pyodbc.connect(self.conn_string)
ttkb.dialogs.Messagebox.show_info("Connected Successfully!", position=(1000, 500))
self.cursor = self.conn.cursor()
self.feeds_input = FeedsInput(self.master, self.get_table())
self.feeds_input.create_gui()
self.feeds_input.bind_on_fetch(self.on_fetch)
except pyodbc.Error as e:
ttkb.dialogs.Messagebox.show_error(f"Error connecting to D/B: {e}", "Connection Error", position=(500, 500))

def get_table(self) ->  List[str]:
self.table_opt = [row.table_name for row in self.cursor.tables(catalog=self.database, tableType="TABLE", schema=self.username.upper())]
return self.table_opt

def on_fetch(self, event=None):
print(event.widget)
print(f"Number of active threads in on_fetch:{threading.active_count()}")
query = f"SELECT * FROM {self.feeds_input.table.get()}"
rows = self.cursor.execute(query).fetchall()
df = pd.DataFrame.from_records(rows, columns=[col[0] for col in self.cursor.description])
# print(df.to_string())
self.table_view = TableView(master=self.master, data=df)
self.table_view.create_table(done_fun=self.on_done, cancel_fun=self.on_cancel)
# create_table(master=self.master, data=df)

def on_done(self, event=None):
self.master.unbind_all("")
print(f"Number of active threads in on_done before master_update:{threading.active_count()}")
self.master.update()
self.table_view.update()
print(f"Number of active threads in on_done before destroy():{threading.active_count()}")
self.table_view.destroy()
self.master.destroy()

def on_cancel(self, event=None): ...

def run(self):
self.master.bind("", log_event)
self.master.mainloop(0)

if __name__ == '__main__':
data1 = Model()
data1.run()

data2 = Model()
data2.run()

Проблемы: -
  • Я добавил функциональность кнопкам с помощью виджета. bind(), а не Button(text="Something", команда=имя_функции). Судя по той отладке, которую я провел, кажется, что когда я использую функцию .bind(), то, если открывается окно верхнего уровня, привязка происходит снова. Но поскольку я явно связал функцию только один раз, другая привязка является произвольной, а функция, вызывающая функциюbind_on_fetch(), вызывается из определения __call__ основного цикла. Я не уверен, что здесь происходит.
  • Теперь, когда открывается окно верхнего уровня, а затем, когда я нажимаю кнопку «Готово», окно верхнего уровня, а также главное окно также уничтожаются. . Но один из виджетов инициирует событие, которое приводит к фоновой ошибке, не обрабатываемой библиотекой tkinter.
Вывод для справки: -

Код: Выделить всё

{'self': , 'func': , 'inspect': }
Called function print_args(self, func)
args: (, ) (tuple)
True


None
__call__
.!feedsinput.!frame7.!button
Number of active threads in on_fetch:1
{'self': , 'func': None, 'inspect': }
Called function print_args(self, func)
args: (, None) (tuple)
False


None
mainloop
Number of active threads in on_done before master_update:1
Number of active threads in on_done before destroy():1
bgerror failed to handle background error.
Original error: can't invoke "event" command: application has been destroyed
Error in bgerror: can't invoke "tk" command: application has been destroyed

Я пытался добавить некоторые функции журналирования, чтобы получить обратную связь о том, какая функция запускает эти вызовы событий, но мне не удалось определить, какая строка в файлах Python tkinter привела к этим триггерам событий. .

Подробнее здесь: https://stackoverflow.com/questions/761 ... kbootstrap
Реклама
Ответить Пред. темаСлед. тема

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

  • Похожие темы
    Ответы
    Просмотры
    Последнее сообщение

Вернуться в «Python»