good evening for all
for a project I have to read and filter a big table (more than 50k lines) using tkinter for this I created a progressbar which is activated while the application calls the filter function in the background.
the problem is that my progress bar does not move i tried with the threading library but nothing changed. does anyone have a solution?
if you also have an idea to insert the lines more quickly in the treeview it would really help me a lot
import tkinter as tk
from tkinter import *
from tkinter import ttk
import numpy as np
import pandas as pd
from threading import *
import time
class Application(tk.Tk):
def __init__(self):
Tk.__init__(self)
self.title("table manipulation")
# I create a random table with 50 rows
self.my_table=pd.DataFrame(np.random.rand(50,4))
#I create a Progressbar
self.progressbar=ttk.Progressbar(self,orient='horizontal',mode='indeterminate',length=280,value=0)
self.progressbar.pack(fill=X)
#I create a single filter for the example
self.filter_frame = Frame(self)
self.filter_frame.pack()
self.create_filter()
#I create a table with tkinter treeview
self.table_frame = Frame(self)
self.table_frame.pack()
self.tree = ttk.Treeview(self.table_frame, show='headings')
self.create_table()
def create_filter(self):
#for example i create one filter
self.my_filter=ttk.Combobox(self.filter_frame,values=[''] +list(set(self.my_table.values.tolist()[0])) , state="readonly")
self.my_filter.pack()
self.my_filter.bind('<<ComboboxSelected>>', self.threading)
def create_table(self):
self.tree["columns"] =self.my_table.columns.values.tolist()
self.tree.pack(expand=TRUE, fill=X)
for i in self.my_table.columns.values.tolist():
self.tree.column(i, anchor="w")
self.tree.heading(i, text=i, anchor="w")
for n in range(len(self.my_table)):
self.tree.insert("", "end", text=n, values=self.my_table.values.tolist()[n])
def filter_function(self,*args):
for n in self.tree.get_children():
time.sleep(1) #to check if my progressbar moves
if self.tree.item(n)['values'][0]!=self.my_filter.get():
self.tree.delete(n)
def threading(self,*args):
self.progressbar.start()
t1=Thread(target=self.filter_function())
t1.start()
self.progressbar.stop()
Application().mainloop()
I found the solution for those who need it
import tkinter as tk
from tkinter import *
from tkinter import ttk
import numpy as np
import pandas as pd
import threading
import time
class Application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.title("table manipulation")
# I create a random table with 10 rows
self.my_table=pd.DataFrame(np.random.rand(10,4))
#I create a Progressbar
self.progressbar=ttk.Progressbar(self,orient='horizontal',mode='indeterminate',length=280,value=0)
self.progressbar.pack()
#I create a single filter for the example
self.filter_frame = tk.Frame(self)
self.filter_frame.pack()
self.create_filter()
#I create a table with tkinter treeview
self.table_frame = tk.Frame(self)
self.table_frame.pack()
self.treeview = ttk.Treeview(self.table_frame, show='headings')
threading.Thread(target=self.create_table() , daemon=True).start()
def create_filter(self):
#for example i create one filter
self.my_filter=ttk.Combobox(self.filter_frame,values=[''] +list(set(self.my_table.values.tolist()[0])) , state="readonly")
self.my_filter.pack()
self.my_filter.bind('<<ComboboxSelected>>', self.threading)
def create_table(self):
self.treeview["columns"] =self.my_table.columns.values.tolist()
self.treeview.pack()
for i in self.my_table.columns.values.tolist():
self.treeview.column(i, anchor="w")
self.treeview.heading(i, text=i, anchor="w")
for n in range(len(self.my_table)):
self.treeview.insert("", "end", text=n, values=self.my_table.values.tolist()[n])
def test(self):
for n in range(200):
time.sleep(1) #to check if my progressbar moves
def filter_function(self,*args):
self.progressbar.start()
for n in self.treeview.get_children():
time.sleep(1) #to check if my progressbar moves
if self.treeview.item(n)['values'][0]!=self.my_filter.get():
self.treeview.delete(n)
self.progressbar.stop()
def threading(self,*args):
threading.Thread(target=self.filter_function , daemon=True).start()
Application().mainloop()
Related
I need help using tkinter in python.
I want to use a while loop inside my class so that it keeps printing in the terminal the content of an entry (box1). But the problem is that if I put a loop in my class the entry wont even be created because the root.mainloop() is after my while loop.
The program:
from tkinter import *
from tkinter import ttk
import tkinter as tk
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.minsize(500,400)
self.configure(bg='#121213')
self.createEntry()
def createEntry(self):
self.name1 = StringVar()
self.box1 = ttk.Entry(self, width=2, textvariable = self.name1, font="Calibri 15")
self.box1.place(x=128, y=31)
while True:
print(self.name1.get())
root=root()
root.mainloop()
If I put the loop after the root.mainloop() it won't start printing the contents of name1 as long as the tkinter file is open. So it will print only the final version of name1 in a loop:
The Code:
from tkinter import *
from tkinter import ttk
import tkinter as tk
class root(Tk):
def __init__(self):
super(root, self).__init__()
self.minsize(500,400)
self.configure(bg='#121213')
self.createEntry()
def createEntry(self):
self.name1 = StringVar()
self.box1 = ttk.Entry(self, width=2, textvariable = self.name1, font="Calibri 15")
self.box1.place(x=128, y=31)
while True:
print(self.name1.get())
root=root()
root.mainloop()
while True:
print(root.name1.get())
Video of my problem
Does anyone have any solution?
How about a solution that does not put constant drain on the processing power?
If you add a callback to StringVar, you can trigger a function every time the variable is changed, and only then. Infinite loops don't work very well with UI applications, as in order to avoid stopping the control flow of the app you have to use things like async, threading etc.
def createEntry(self):
self.name1 = StringVar()
self.name1.trace_add("write", lambda name, index, mode: print(self.name1.get()))
self.box1 = ttk.Entry(self, width=2, textvariable = self.name1, font="Calibri 15")
self.box1.place(x=128, y=31)
This piece of code will make given lambda function trigger every time something is written to name1 variable.
How can I customize the package panastable so that when i press Return in the display, a warning pops up. I tried to bind Return to the function callback, which should create the messagebox. But nothing happens. It should give the warning after I enter new content in the cell and press Return. This is code I'm using:
import pandas as pd
from pandastable import Table
import tkinter as tk
from tkinter import messagebox
class MyTable(Table):
def callback(self, event):
messagebox.showinfo(title="Achtung", message="Achtung")
def showWarning(self):
self.bind('<Return>', self.callback)
top = tk.Tk()
top.geometry("300x1000")
df = pd.DataFrame()
df["column1"] = [1,2,3,4,5]
frame = tk.Frame(top)
frame.pack(fill="both", expand=True)
pt = MyTable(frame, dataframe=df)
pt.show()
pt.focus_set()
pt.showWarning()
button = tk.Button(top, text="Änderungen speichern", command=top.quit)
button.pack()
top.mainloop()
So there is a much better approach to this IMO (because using what is inherited):
import pandas as pd
from pandastable import Table
import tkinter as tk
from tkinter import messagebox
class MyTable(Table):
#staticmethod
def show_info():
messagebox.showwarning(title="Achtung", message="Achtung")
def handleCellEntry(self, row, col):
super().handleCellEntry(row, col)
self.show_info()
root = tk.Tk()
root.geometry("300x1000")
df = pd.DataFrame()
df["column1"] = [1, 2, 3, 4, 6]
frame = tk.Frame(root)
frame.pack(fill="both", expand=True)
pt = MyTable(frame, dataframe=df)
pt.show()
button = tk.Button(root, text="Änderungen speichern", command=root.quit)
button.pack()
root.mainloop()
Using the inherited method handleCellEntry which is called when enter key is pressed while editing the entry, just what you seem to need. You can see in the source code how this method is bound to '<Return>' sequence to that specific entry (so just override it, call the super method (so it does what it is supposed to do) and then your own method (or vice versa but then the entry will be changed only after you close the dialog)).
The solution was binding it the top window. The following code get the wanted result:
import pandas as pd
from pandastable import Table
import tkinter as tk
from tkinter import messagebox
top = tk.Tk()
top.geometry("300x1000")
df = pd.DataFrame()
df["column1"] = [1,2,3,4,5]
frame = tk.Frame(top)
frame.pack(fill="both", expand=True)
def callback(*args):
messagebox.showinfo(title="Achtung", message="Achtung")
top.bind('<Return>', callback)
pt = Table(frame, dataframe=df)
pt.show()
button = tk.Button(top, text="Änderungen speichern", command=top.quit)
button.pack()
top.mainloop()
I have used tkinter and its treeview widget thus far in my project to import and view some data from a csv file. However Im finding its functions limited as to what Im trying to achieve.
I have read in other SO questions that a Pandas data-frame can be imported to Tkinter project and display in the tkinter GUI. I have found some code online # https://gist.github.com/gugat/7cf57eb628f3bb0a3d54b3f8d0023b63 but I cant work out how to migrate this into my existing tkinter code.
import tkinter as tk
from tkinter import *
import tkinter.ttk as tkrttk
from PIL import Image, ImageFont, ImageTk
import csv
from tkinter import filedialog
import pandas as pd
from pandastable import Table, TableModel
root = tk.Tk()
root.geometry("2000x1000")
filepath = (r"C:/Users\James\Desktop\test_data.csv")
root.title('Workshop Manager')
style = tkrttk.Style()
style.configure("Treeview.Heading", foreground='Red', font=('Helvetica', 10))
df = pd.read_csv(filepath)
pt = Table(parent)
class TestApp(Frame):
"""Basic test frame for the table"""
def __init__(self, parent=root):
self.parent = parent
Frame.__init__(self)
self.main = self.master
self.main.geometry('600x400+200+100')
self.main.title('Table app')
f = Frame(self.main)
f.pack(fill=BOTH,expand=1)
df = TableModel.getSampleData()
self.table = pt = Table(f, dataframe=df,
showtoolbar=True, showstatusbar=True)
pt.show()
return
app = TestApp()
root.mainloop()
I get an error NameError name parent is not defined im assuming this pt = Table(parent) is my issue. I have tried pt = Table(root) as I thought this would place it on the tkinter root window. But this didnt work.
Part of your code is from the example used in the document of pandastable, but it is not a good example.
If you just want to show your CSV file using pandastable, below is a simple example:
import tkinter as tk
from pandastable import Table, TableModel
filepath = 'C:/Users/James/Desktop/test_data.csv'
root = tk.Tk()
root.geometry('1600x900+10+10')
root.title('Workshop Manager')
class TestApp(tk.Frame):
def __init__(self, parent, filepath):
super().__init__(parent)
self.table = Table(self, showtoolbar=True, showstatusbar=True)
self.table.importCSV(filepath)
self.table.show()
app = TestApp(root, filepath)
app.pack(fill=tk.BOTH, expand=1)
root.mainloop()
I am new to tkinter GUI programming. I tried to search my problem, but I was unable to formulate the correct question without a description of my problem.
I designed a small GUI - for the example here - with a button and a ScrolledText item.
import tkinter as tk
from tkinter import messagebox as msg
from tkinter.ttk import Notebook
from tkinter import filedialog
import tkinter.scrolledtext as tkscrolled
import do_something as ds
import os
import time
class Fatt(tk.Tk):
def __init__(self):
super().__init__()
# window setup
self.title("Test Gui")
self.geometry("1024x768")
self.resizable(0, 0)
# tab
self.notebook = Notebook(self)
# define tabs
res_avg_tab = tk.Frame(self.notebook)
# group nodal averaging
group_avg = tk.LabelFrame(res_avg_tab, text="Perform nodal averaging of all selected DB files")
group_avg.pack(padx=10, pady=10)
# nodal averaging button
self.avg_button = tk.Button(group_avg, text="Perform Nodal Averaging",
command=self.nodal_avg, bg="lightgrey", fg="black", width=50)
self.avg_button.pack(side=tk.TOP, pady=10, padx=10)
# scrolled log-text window
# group LOG
group_log = tk.LabelFrame(res_avg_tab, text="Result Averaging Output (LOG)")
group_log.pack(padx=10, pady=10)
self.avg_log = tkscrolled.ScrolledText(group_log, bg="white", fg="black", height=13, width=110)
self.avg_log.pack(side=tk.TOP, fill=tk.X, padx=10, pady=10)
# status-bar
self.status_text = tk.StringVar(res_avg_tab)
self.status_text.set("---")
self.status = tk.Label(res_avg_tab, textvar=self.status_text,
bd=1, relief=tk.SUNKEN, anchor=tk.W)
self.status.pack(side=tk.BOTTOM, fill=tk.BOTH)
# add everything to tabs
self.notebook.add(res_avg_tab, text="Average Results")
self.notebook.pack(fill=tk.BOTH, expand=True)
def show_cmb_file_creator(self):
pass
def nodal_avg(self):
sel_dbs = ["file1", "file2", "file3"]
# write file-list to log-window
self.avg_log.insert(tk.INSERT, "\nSelected Files for Nodal Averaging:\n")
for i in sel_dbs:
self.avg_log.insert(tk.INSERT, i+'\n')
self.avg_log.see(tk.END)
# if yes --> average results
if msg.askyesno("Nodal Averaging", "Perform nodal averaging with selected db-results?"):
start = time.time()
self.status_text.set("processing...")
self.config(cursor="wait")
self.avg_log.insert(tk.INSERT, "Start nodal averaging - this may take some time...\n")
class_obj = ds.DoSomething(i, self.avg_log)
for i in sel_dbs:
class_obj.do_something()
end = time.time()
overall_time_str = " Overall Averaging RUNTIME: {0:.2f} sec ({1:.1f} min) ".format(end-start, (end-start)/60.0)
self.avg_log.insert(tk.INSERT, "\n{0:*^80}".format(overall_time_str))
self.avg_log.see(tk.END)
self.status_text.set("---")
self.config(cursor="")
def browse_dir(self):
pass
def copy_to_clipboard(self, text=None):
pass
if __name__=="__main__":
fatt = Fatt()
fatt.mainloop()
The button "avg_button" executes the function "nodal_avg" and the main purpose of this function is to instantiate an external class and run a method.
class_obj = ds.DoSomething(i, self.avg_log)
for i in sel_dbs:
class_obj.do_something()
This class contains the main logic of my software and it contains a lot of print outputs.
import tkinter.scrolledtext as tkscrolled
class DoSomething:
def __init__(self, my_file, outp_print=print):
self.my_file = my_file
self.outp_print = outp_print
# my-print function
# for tkinter-log output
def myprint(self, text):
if self.outp_print==print:
print(text)
elif isinstance(self.outp_print, tkscrolled.ScrolledText):
self.outp_print.insert("end", text+'\n')
else:
print("myprint - ERROR: {0}".format(str(self.outp_print)))
def do_something(self):
for i in range(0,100000):
self.myprint("{0:d} - printed output".format(i))
I would like to print the output of the class/method to the ScrolledText window, but I also like to maintain the classic print functionality. Therefore I use the "myprint" method - which is able to use print or ScrolledText.insert for printing (I do not know if this is a smart approach?!).
If I run the code it basically works - but the ScrolledText window does not update on every print, only when the method in the external class is finished - then the output appears.
So my question is - how can I continuously update my ScrolledText window with my output string?
Thank you very much.
Best Regards
Michael
This question concerns Python's Tkinter.
I first produced this GUI, a simple two-column set of rows in a Labelframe, with an icon on the right:
The above behaviour was correct and expected, based on this following code:
import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk
root = tk.Tk()
icon_colours_fp = r"D:\Dropbox\coding\python\experiments\icon_component.gif"
icon_col = tk.PhotoImage(file=icon_colours_fp)
# icon_col = ImageTk.PhotoImage(Image.open(icon_colours_fp))
tk.Label(root, text="Past").grid(row=0, column=0)
tk.Label(root, text="Today").grid(row=1, column=0)
tk.Label(root, text="Future").grid(row=2, column=0)
_b = ttk.Button(root, image=icon_col)
_b['image'] =icon_col
_b.grid(row=0, column=1)
root.mainloop()
I then re-wrote the code as a class, hoping to produce something similar within a Labelframe:
import tkinter as tk
import tkinter.ttk as ttk
from PIL import Image, ImageTk
class Options(tk.Frame):
def __init__(self, parent):
super().__init__()
main_labelframe = ttk.LabelFrame(parent, text="Test Labelframe")
main_labelframe.pack(fill=tk.BOTH, expand=1)
frame_1 = tk.Frame(main_labelframe)
frame_1_sep = ttk.Separator(main_labelframe, orient=tk.VERTICAL)
frame_2 = tk.Frame(main_labelframe)
frame_1.pack(side=tk.LEFT)
frame_1_sep.pack(side=tk.LEFT, fill=tk.BOTH)
frame_2.pack(side=tk.LEFT)
tk.Label(frame_1, text="Past").grid(row=0, column=0)
tk.Label(frame_1, text="Today").grid(row=1)
tk.Label(frame_1, text="Future").grid(row=2)
icon_colours_fp = r"D:\Dropbox\coding\python\experiments\icon_component.gif"
icon_col = tk.PhotoImage(file=icon_colours_fp)
_b = ttk.Button(frame_2, image=icon_col)
_b['image'] = icon_col
_b.grid(row=0, column=0)
class Gui(tk.Tk):
def __init__(self):
super().__init__()
options = Options(self)
options.pack()
gui = Gui()
gui.mainloop()
The code then failed, in two respects:
The icon fails to appear.
The ttk Button becomes misaligned. (It appears in the centre, whereas by the grid, it should appear at the top.)
The failed code appears as follows:
I have experimented: among others, I changed the geometry manager to .pack(), and changed the parent of ttk.Button, but without success. Would appreciate some pointers as to where I've gone wrong, especially as to the disappearing icon.
You didn't keep a reference to the image. Easiest way here is to change:
icon_col = tk.PhotoImage(file=icon_colours_fp)
b = ttk.Button(frame_2, image=icon_col)
_b['image'] = icon_col
To:
self.icon_col = tk.PhotoImage(file=icon_colours_fp)
b = ttk.Button(frame_2, image=self.icon_col)