Slow Tmatic's GUI issue #195
Replies: 5 comments
-
Since the application was primarily developed for Linux, I agree that less attention has been paid to GUI performance on Windows and MacOS. I think Linux works well. As for other OSes, I agree that Tkinter is a big disappointment. Maybe Treeview is the right solution. I would not rush to conclusions yet until practical steps have been taken. |
Beta Was this translation helpful? Give feedback.
-
The code below inserts 100 rows into the Treeview. My laptop achieves about 150,000 rows per second. This looks promising. from tkinter import ttk
import tkinter as tk
root = tk.Tk()
root.geometry("1000x200")
rows = []
for n in range(100):
row = [
"col1_"+str(n),
"col2_"+str(n),
"col3_"+str(n),
"col4_"+str(n),
"col5_"+str(n),
"col6_"+str(n),
"col7_"+str(n),
"col8_"+str(n),
"col9_"+str(n),
]
rows.append(row)
names = [
"EMI",
"SYMB",
"CURRENCY",
"TIMEFR",
"CAPITAL",
"STATUS",
"VOL",
"PNL",
"POS",
]
columns = [num for num in range(1, len(names)+1)]
tree = ttk.Treeview(root, columns=columns, show="headings")
vsb = tk.Scrollbar(root, orient="vertical")
vsb.config(command=tree.yview)
tree.config(yscrollcommand=vsb.set)
root.grid_rowconfigure(0, weight=1)
root.grid_columnconfigure(0, weight=1)
tree.grid(row=0, column=0, sticky="NSEW")
vsb.grid(row=0, column=1, sticky="NS")
# define headers
for num, name in enumerate(names, start=1):
tree.heading(num, text=name)
tree.column(num, anchor=tk.CENTER, width=50)
# insert rows
for person in rows:
tree.insert("",0 , values=person)
root.mainloop() |
Beta Was this translation helpful? Give feedback.
-
This test code demonstrates how to set column widths relative to their fillings. from tkinter import *
from tkinter import ttk
import tkinter as tk
root = Tk()
root.geometry("{}x{}".format(1000, 500))
root.update_idletasks()
now_width = root.winfo_width()
frame = tk.Frame(root)
frame.grid(row=0, column=0, sticky="NSWE")
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
class AutoScrollbar(tk.Scrollbar):
def set(self, low, high):
if float(low) <= 0.0 and float(high) >= 1.0:
self.tk.call("grid", "remove", self)
else:
self.grid()
tk.Scrollbar.set(self, low, high)
rows = []
for n in range(2):
rows.append((n, n, str(n*10)+"_"+str(n*10), n, n, str(n*1000000)+"_"+str(n*1000000), n, n, str(n*1000)+"_"+str(n*1000)))
columns = ("name1", "name2", "name3", "name4", "name5", "long long long col", "name7", "name8", "name9")
tree = ttk.Treeview(columns=columns, show="headings")
tree.grid(row=0, column=0, sticky="NSEW")
vsb = AutoScrollbar(frame, orient="vertical")
vsb.config(command=tree.yview)
vsb.grid(row=0, column=1, sticky="NS")
tree.config(yscrollcommand=vsb.set)
for col in columns:
tree.heading(col, text=col)
for row in rows:
tree.insert("", END, values=row)
# This part of code must run every time the tree widget executes insert() or delete() method
col_width = [5, 5, 5, 5, 5, 5, 5, 5, 5]
# 5 is a minimum relative width of a column
for row in rows:
for cell in row:
i = row.index(cell)
l = len(str(cell))
if l > col_width[i]:
col_width[i] = l
sum_width = 0
for width in col_width:
sum_width += width
for col in columns:
#print(col_width[columns.index(col)], sum_width, col_width[columns.index(col)] / sum_width * now_width)
tree.column(col, minwidth=-now_width, width=int(col_width[columns.index(col)] / sum_width * now_width))
# This part of code must run every time the tree widget executes insert() or delete() method
root.mainloop() |
Beta Was this translation helpful? Give feedback.
-
Please observe the new grid scheme based on the ttk.Treeview widgets. This scheme is vertically adaptive. import platform
from tkinter import *
import tkinter as tk
from tkinter import ttk
rows = []
for n in range(10):
rows.append(("word1", n, str(n*10)+"_"+str(n*10), n, n, str(n*100)+"_"+str(n*100), n, n, str(n*1000)+"_"+str(n*1000)))
columns = ("name", "age", "email", "name1", "age1", "email1", "name2", "age2", "email2")
class AutoScrollbar(tk.Scrollbar):
def set(self, low, high):
if float(low) <= 0.0 and float(high) >= 1.0:
self.tk.call("grid", "remove", self)
else:
self.grid()
tk.Scrollbar.set(self, low, high)
class Variables:
root = tk.Tk()
root.title("Tmatic")
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
if screen_width > 1440:
window_ratio = 0.7
adaptive_ratio = 0.9
elif screen_width > 1366:
window_ratio = 0.72
adaptive_ratio = 0.95
elif screen_width > 1280:
window_ratio = 0.75
adaptive_ratio = 0.95
elif screen_width > 1024:
window_ratio = 0.8
adaptive_ratio = 1
else:
window_ratio = 1
adaptive_ratio = 1
window_width = int(screen_width * window_ratio)
window_height = screen_height
root.geometry("{}x{}".format(window_width, window_height))
if platform.system() == "Windows":
ostype = "Windows"
elif platform.system() == "Darwin":
ostype = "Mac"
else:
ostype = "Linux"
# Main frame. Always visible
frame_left = tk.Frame()
frame_left.grid(row=0, column=0, sticky="NSWE")
root.grid_columnconfigure(0, weight=1)
# Adaptive frame to the right. Always blank; forgotten when the window is narrowed
frame_right = tk.Frame(width=200, bg="green")
frame_right.grid(row=0, column=1, sticky="NSWE")
root.grid_columnconfigure(1, weight=0)
root.grid_rowconfigure(0, weight=1)
# Top state frame: trading on/off, time
frame_state = tk.Frame(frame_left)
frame_state.grid(row=0, column=0, sticky="NSWE")
frame_left.grid_columnconfigure(0, weight=1)
frame_left.grid_rowconfigure(0, weight=0)
label_trading = tk.Label(frame_state, text="frame_state")
label_trading.pack(fill="both", expand="yes")
title_color = label_trading["background"]
# Paned window: up - information field, down - the rest interface
pw_info_rest = tk.PanedWindow(
frame_left, orient=tk.VERTICAL, sashrelief="raised", bd=0
)
pw_info_rest.grid(row=1, column=0, sticky="NSEW")
frame_left.grid_rowconfigure(1, weight=1)
# Information field
frame_info = tk.Frame(pw_info_rest)
frame_info.pack(fill="both", expand="yes")
# Information widget
if ostype == "Mac":
text_info = tk.Text(
frame_info,
highlightthickness=0,
)
else:
text_info = tk.Text(
frame_info,
bg="gray98",
highlightthickness=0,
)
# This technical frame contains most frames and widgets
frame_rest1 = tk.Frame(pw_info_rest)
frame_rest1.pack(fill="both", expand="yes")
pw_info_rest.add(frame_info)
pw_info_rest.add(frame_rest1)
pw_info_rest.bind(
"<Configure>", lambda event: resize_row(event, Variables.pw_info_rest, 9)
)
# One or more exchages is put in this frame
frame_market = tk.Frame(frame_rest1)
frame_market.grid(row=0, column=0, sticky="NSEW")
frame_rest1.grid_columnconfigure(0, weight=1)
label_test_market = tk.Label(frame_market, text="frame_market", bg="blue")
label_test_market.pack(fill="both", expand="yes")
# This technical PanedWindow contains orderbook, positions, orders, trades, fundings, results, currencies, robots
pw_rest2 = tk.PanedWindow(
frame_rest1, orient=tk.VERTICAL, sashrelief="raised", bd=0, sashwidth=0, height=1
)
pw_rest2.grid(row=0, column=1, sticky="NSEW")
frame_rest1.grid_columnconfigure(1, weight=1)
frame_rest1.grid_rowconfigure(0, weight=1)
# This technical frame contains orderbook, positions, orders, trades, fundings, results
frame_rest3 = tk.Frame(pw_rest2)
frame_rest3.pack(fill="both", expand="yes")
# Frame for the order book
frame_orderbook = tk.Frame(frame_rest3)
frame_orderbook.grid(row=0, column=0, sticky="NSEW")
frame_rest3.grid_columnconfigure(0, weight=1)
# This technical PanedWindow contains positions, orders, trades, fundings, results
pw_rest4 = tk.PanedWindow(
frame_rest3, orient=tk.VERTICAL, sashrelief="raised", bd=0, sashwidth=0, height=1
)
pw_rest4.grid(row=0, column=1, sticky="NSWE")
frame_rest3.grid_columnconfigure(1, weight=1)
frame_rest3.grid_rowconfigure(0, weight=1)
# Frame for instruments and their positions
frame_position = tk.Frame(pw_rest4)
frame_position.pack(fill="both", expand="yes")
# Paned window: up - orders, down - trades, fundings, results
pw_orders_trades = tk.PanedWindow(
pw_rest4, orient=tk.VERTICAL, sashrelief="raised", bd=0, height=1
)
pw_orders_trades.pack(fill="both", expand="yes")
# Orders frame
frame_orders = tk.Frame(pw_orders_trades)
frame_orders.pack(fill="both", expand="yes")
label_test_orders = tk.Label(frame_orders, text="frame_orders", bg="red")
label_test_orders.pack(fill="both", expand="yes")
# Notebook tabs: Trades / Funding / Results
if ostype == "Mac":
notebook = ttk.Notebook(pw_orders_trades, padding=(-9, 0, -9, -9))
else:
notebook = ttk.Notebook(pw_orders_trades, padding=0)
style = ttk.Style()
style.configure("TNotebook", borderwidth=0, background="gray90")
style.configure("TNotebook.Tab", background="gray90")
style.map("TNotebook.Tab", background=[("selected", title_color)])
notebook.pack(expand=1, fill="both")
# Trades frame
frame_trades = ttk.Frame(notebook)
frame_trades.pack(fill="both", expand="yes")
# Funding frame
frame_funding = tk.Frame(notebook)
frame_funding.pack(fill="both", expand="yes")
# Results frame
frame_results = tk.Frame(notebook)
frame_results.pack(fill="both", expand="yes")
notebook.add(frame_trades, text="Trades")
notebook.add(frame_funding, text="Funding")
notebook.add(frame_results, text="Results")
pw_orders_trades.add(frame_orders)
pw_orders_trades.add(notebook)
pw_orders_trades.bind(
"<Configure>", lambda event: resize_row(event, Variables.pw_orders_trades, 2)
)
pw_rest4.add(frame_position)
pw_rest4.add(pw_orders_trades)
pw_rest4.bind(
"<Configure>", lambda event: resize_row(event, Variables.pw_rest4, 5)
)
# Paned window: up - currencies (account), down - robots
pw_account_robo = tk.PanedWindow(
pw_rest2, orient=tk.VERTICAL, sashrelief="raised", bd=0, height=1
)
pw_account_robo.pack(fill="both", expand="yes")
# Frame for currencies (account)
frame_account = tk.Frame(pw_account_robo)
frame_account.pack(fill="both", expand="yes")
# Frame for the robots table
frame_robots = tk.Frame(pw_account_robo)
frame_robots.pack(fill="both", expand="yes")
pw_account_robo.add(frame_account)
pw_account_robo.add(frame_robots)
pw_account_robo.bind(
"<Configure>", lambda event: resize_row(event, Variables.pw_account_robo, 2)
)
pw_rest2.add(frame_rest3)
pw_rest2.add(pw_account_robo)
pw_rest2.bind(
"<Configure>", lambda event: resize_row(event, Variables.pw_rest2, 1.4)
)
tree_orderbook = ttk.Treeview(frame_orderbook, columns=columns, show="headings")
tree_orderbook.grid(row=0, column=0, sticky="NSEW")
for item in rows:
tree_orderbook.insert("", END, values=item)
for col in columns:
tree_orderbook.heading(col, text="col" + str(columns.index(col)))
tree_orderbook.column(col, minwidth=20, width=20)
scroll_orderbook = AutoScrollbar(frame_orderbook, orient="vertical")
scroll_orderbook.config(command=tree_orderbook.yview)
scroll_orderbook.grid(row=0, column=1, sticky="NS")
tree_orderbook.config(yscrollcommand=scroll_orderbook.set)
frame_orderbook.grid_columnconfigure(0, weight=1)
frame_orderbook.grid_columnconfigure(1, weight=0)
frame_orderbook.grid_rowconfigure(0, weight=1)
tree_position = ttk.Treeview(frame_position, columns=columns, show="headings")
tree_position.grid(row=0, column=0, sticky="NSEW")
for item in rows:
tree_position.insert("", END, values=item)
for col in columns:
tree_position.heading(col, text="col" + str(columns.index(col)))
tree_position.column(col, minwidth=20, width=20)
scroll_position = AutoScrollbar(frame_position, orient="vertical")
scroll_position.config(command=tree_position.yview)
scroll_position.grid(row=0, column=1, sticky="NS")
tree_position.config(yscrollcommand=scroll_position.set)
frame_position.grid_columnconfigure(0, weight=1)
frame_position.grid_columnconfigure(1, weight=0)
frame_position.grid_rowconfigure(0, weight=1)
tree_account = ttk.Treeview(frame_account, columns=columns, show="headings")
tree_account.grid(row=0, column=0, sticky="NSEW")
for item in rows:
tree_account.insert("", END, values=item)
for col in columns:
tree_account.heading(col, text="col" + str(columns.index(col)))
tree_account.column(col, minwidth=20, width=20)
scroll_account = AutoScrollbar(frame_account, orient="vertical")
scroll_account.config(command=tree_account.yview)
scroll_account.grid(row=0, column=1, sticky="NS")
tree_account.config(yscrollcommand=scroll_account.set)
frame_account.grid_columnconfigure(0, weight=1)
frame_account.grid_columnconfigure(1, weight=0)
frame_account.grid_rowconfigure(0, weight=1)
tree_robots = ttk.Treeview(frame_robots, columns=columns, show="headings")
tree_robots.grid(row=0, column=0, sticky="NSEW")
for item in rows:
tree_robots.insert("", END, values=item)
for col in columns:
tree_robots.heading(col, text="col" + str(columns.index(col)))
tree_robots.column(col, minwidth=20, width=20)
scroll_robots = AutoScrollbar(frame_robots, orient="vertical")
scroll_robots.config(command=tree_robots.yview)
scroll_robots.grid(row=0, column=1, sticky="NS")
tree_robots.config(yscrollcommand=scroll_robots.set)
frame_robots.grid_columnconfigure(0, weight=1)
frame_robots.grid_columnconfigure(1, weight=0)
frame_robots.grid_rowconfigure(0, weight=1)
def resize_row(event, pw, ratio):
pw.paneconfig(pw.panes()[0], height=pw.winfo_height() // ratio)
Variables.root.mainloop() |
Beta Was this translation helpful? Give feedback.
-
Tmatic with its last 24.5.1 release has got the new, much faster design based on ttk.Treeview() widgets. |
Beta Was this translation helpful? Give feedback.
-
Whilst the GUI generally works stable and correct, this moment there is a serious problem concerning GUI performance. Slow filling of ListBoxTable class under MacOS as well as unacceptable performance of tk.PanedWindow() widget under Windows and MacOS. The complicated GridTable and ListBoxTable structure shows lags when resizing the Tmatic's window. Linux looks better but far from perfection anyway.
Possible solution. ListBoxTable class uses tk.Listbox() set of widgets and GridTable class uses and tk.Label() set of widgets are put together by grid() method. Those tricky compounds perhaps could be replaced by ttk.Treeview() widget. The ttk.Treeview() is fast, but does it satisfy the Tmatic's GUI needs?
Beta Was this translation helpful? Give feedback.
All reactions