I have a piece of code that does some textual analysis and displays the results in a tkinter window.
Since users can choose to do the same analysis on multiple sources, and this can take quite some time, I would like to display the results as they become available.
However, the tkinter window pops up only after the last result becomes available. From other questions on Stackoverflow, I understand why this does not work (simplified version of my code):
class Output:
def __init__(self):
self.set_main_variables()
def set_main_variables(self):
self.names= []
#and other variables...
def initialize(self):
self.t = Tk()
self.make_frames()
self.populate_frames()
self.t.mainloop()
def update(self):
self.populate_frames()
def populate_frames(self):
<uses the data in self.names to display the results>
output = Output()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
if i == 0:
output.initialize()
else:
output.update()
Therefore, I tried taking the initialize() out of the loop, and creating a button in the window by which the user can update the results (if any are available):
output = Output()
first_results = analyze(s[0])
output.names = first_results
output.initialize()
for i, s in enumerate(sources):
results = analyze(s)
output.names = results
This does not solve the problem, though; the window still pops up only after the last source has been analyzed.
I have read many possible options to deal with this (using after, guiloop, etc.) but I don't see how these could help me in this situation.
Could anyone set me on the right path?
A colleague found the solution to my question.
Eric, in the comments, put me partly on the right path. He proposed using update_idletasks() in the GUI, but that did not work: it opened a blank window, which was filled in with widgets only when the "sources" loop was finished.
The solution was to put update() not inside the GUI, but inside the loop that is sending the updates to the GUI.
A simplified example with working code:
from tkinter import *
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
#self.top.mainloop()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
print(i)
gui.names.append(names[i])
print(gui.names)
gui.updateLabel()
gui.top.update()
#gui.top.update_idletasks()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)
Two notes:
using update_idletasks() works as well, but displays a strange black
border at the bottom of the window until the sources loop is
finished.
calling self.top.mainloop() blocks the window update
I also tried an alternative version, where the user can push a button to manually update the list of results, but this did not function well (the button reacts only when the sources loop is finished):
class SimpleTestClass:
def __init__(self):
self.names = []
self.top = Tk()
self.names_string = StringVar()
self.label = Label(self.top, textvar=self.names_string).pack()
Button(self.top, text="update", command=self.updateLabel).pack()
def updateLabel(self):
self.names_string.set("\n".join(self.names))
def test(gui):
names = ["a", "b", "c"]
sources = [11, 2, 3]
for i, s in enumerate(sources):
gui.names.append(names[i])
print(gui.names)
gui.top.update()
time.sleep(3) # this simulates the program taking /
# some time to do its analysis of the source
s = SimpleTestClass()
test(s)
Related
Honestly, I do not feel like this should be happening, but it is.
self.marketList.bind('<<ListboxSelect>>', self.market_selected)
self.jobsList.bind('<<ListboxSelect>>', self.job_selected)
There really isn't any more interaction between these two functions. When you click on an item in the marketList box, it's supposed to bring up the jobs in the jobsList box. Currently, it is applying the binding two both boxes. When I click on a job entry in the jobsBox, it clears the jobs and my troubleshooting is showing that it's calling market_selected. I'm not sure why this is happening, but it's really messing with what I'm trying to do with it.
How can I ensure that my binding is on only one widget, and won't be applied to multiple widgets?
edit:
I'm told that this isn't enough code to reproduce the error.
This is all the relevant code.
As I said previously, self.market_selected is called when I click on anything in the jobsList
edit #2
I uploaded the entire script.
import MarketWizard
import JobWizard
import SpanWalkerDocuments as swd
import tkinter as tk
from tkinter import *
from tkinter import ttk
import SpanWalkerDocuments as SpanWalker
class SpanWalker:
def __init__(self):
self.root = tk.Tk()
self.root.title('Luke Spanwalker')
self.root.resizable(True, True)
self.MainFrame = Frame(self.root, bg='red')
self.sidebarFrame = Frame(self.root, bg='blue')
self.tabControl = ttk.Notebook(self.sidebarFrame)
self.marketFrame = Frame(self.tabControl)
self.clientFrame = Frame(self.tabControl)
self.jobsFrame = Frame(self.tabControl)
self.polesFrame = Frame(self.MainFrame, height=100, width=50)
self.tabControl.add(self.marketFrame, text="Markets")
self.tabControl.add(self.jobsFrame, text="Jobs")
self.tabControl.add(self.clientFrame, text="Clients")
#self.tabControl.add(self.polesFrame, text="Poles")
self.tabControl.pack(expand=1, fill="both")
self.MainFrame.grid(row=0, column=1)
self.sidebarFrame.grid(row=0, column=0)
#Awesome, our tabbed control is ready.
#Now, we need to make the listBox widgets that will actually display our data.
self.marketList = Listbox(self.marketFrame)
self.jobsList = Listbox(self.jobsFrame)
self.polesList = Listbox(self.polesFrame)
#Binding functions! Yay for binding functions!
self.marketList.bind('<<ListboxSelect>>', self.market_selected)
self.jobsList.bind('<<ListboxSelect>>', self.job_selected)
self.curMarket = ""
self.markets = []
self.jobs = []
self.poles = []
def UpdateMarkets (self):
if len(self.markets) > 0:
self.markets.clear()
self.marketList.delete(0, END)
for mark in swd.Market.objects:
self.markets.append(mark)
for m in range(0, len(self.markets)):
self.marketList.insert(m, self.markets[m].title)
def OpenMarketWizard(self):
mw = MarketWizard.MarketWizard()
mw.RunMarketWizard(self.root)
def market_selected(self, event):
print("Market Selected")
selection = self.marketList.curselection()
selectedMarket = ",".join([self.marketList.get(i) for i in selection])
self.PopulateJobs(selectedMarket)
def PopulateJobs(self, market):
self.jobs.clear()
self.jobsList.delete(0, END)
self.GetJobs(market)
for j in range(0, len(self.jobs)):
self.jobsList.insert(j, self.jobs[j].jobName)
def GetJobs(self, market):
marketJobs = []
jobs = []
if market =="":
return
for j in swd.Job.objects:
jobs.append(j)
print("Market = {0}".format(market))
for i in jobs:
if i.market == market:
self.jobs.append(i)
def job_selected(self, event):
print("Job Selected")
selection = self.jobsList.curselection()
selectedJob = ",".join([self.jobsList.get(i) for i in selection])
print("The selected job is: {0}".format(selectedJob))
def PopulateMarkets(self):
self.marketList.destroy()
for m in range(0, len(self.markets)):
marketList.insert(m, self.markets[m].title)
def OpenJobWizard(self):
jw = JobWizard.JobWizard()
jw.RunJobWizard(self.root)
#We need to pack everything that belongs in our tabbed function.
def marketListDisplay(self, show):
if show == True:
self.marketList.pack(fill="both")
self.UpdateMarkets()
self.newButton=Button(self.marketFrame, text="Open Market Wizard", command=lambda:self.OpenMarketWizard())
self.newButton.pack()
self.refreshButton=Button(self.marketFrame, text="Refresh Markets", command=lambda:self.UpdateMarkets())
self.refreshButton.pack()
else:
self.marketList.forget_pack()
def jobsListDisplay(self, show):
if show==True:
self.jobsList.pack()
self.newButton = Button(self.jobsFrame, text="Create New Job / Open Job Wizard", command=lambda:self.OpenJobWizard())
self.newButton.pack()
else:
self.jobsList.forget_pack()
def polesListDisplay(self, show):
if show==True:
self.polesList.pack()
else:
self.polesList.forget_pack()
#Query, why not put them all in a function?
sp = SpanWalker()
sp.marketListDisplay(True)
sp.polesListDisplay(True)
sp.jobsListDisplay(True)
The answer was given by acw1668. I just added exportselection=0 during the creation process for the listboxes, and it worked perfectly.
Sorry for the confusion, y'all, I've never really asked for help on here before.
Thanks for the help!
I am learning Python and I am starting to learn how to use the TKinter GUI. I am making a small interface that does some simple statistical analysis like STDEV, T-tests, etc. I have this method in a class which basically gets the data to work with.
I want the user to be able enter as many data entries as they want (I mean as long as the computer can handle of course). The problem I am having is -- I think when I use the method .get() on an Entry, None is returned?
I am also using method .trace() of DoubleVar() to trace when the values of the entries are updated using the method shown here:
Python Tkinter update when entry is changed
I thought it made sense but it's not working for me. Whenever I change a box in my TK interface, all the other boxes get changed, but the values used to calculate standard deviation are not the numbers that are being shown on the Entry boxes (which are all the same anyways).
Here is the code:
class StandardDeviation:
"""
Runs standard deivation calculations.
"""
def __init__(self) -> None:
"""
Initializes an instance of the functions!
"""
self.stdev_pop = Button(top_frame,
text="Calculate the population "
"standard deviation of the data set")
self.stdev_pop.bind("<Button-1>", self.show_result_population)
self.stdev_pop.pack()
stdev_samp = Button(top_frame,
text="Calculate the sample "
"standard deviation of the data set")
stdev_samp.bind("<Button-1>", self.show_result_sample)
stdev_samp.pack()
self.data = []
self.enter_data = Button(top_frame, text="Enter data")
self.enter_data.bind("<Button-1>", self.pack_add_entry_button)
self.add_entry = Button(top_frame, text="Add data entry",
command=self.add_new_entry)
self.enter_data.pack()
self.all_entries = {}
self.tracer = DoubleVar()
self.tracer.trace("w", self.update)
def pack_add_entry_button(self, *args) -> None:
"""
Pack the add_entry button.
"""
self.add_entry.pack()
def update(self, *args) -> None:
"""
Update the values of the entries.
"""
global update_in_progress
if update_in_progress:
return
update_in_progress = True
data = [str(self.all_entries[item]) for item in self.all_entries]
self.data = [int(item) for item in data if item.isnumeric()]
update_in_progress = False
def add_new_entry(self):
"""
Add a new entry.
"""
new_entry = Entry(root, textvariable=self.tracer)
new_entry.pack()
new_entry_data = new_entry.get()
self.all_entries[new_entry] = new_entry_data
I'm not sure where I'm wrong here if anyone could help me I'd really appreciate it. Thank you!
There is no way to run the code you posted as the indentation is off and some of the buttons call functions that don't exist so this is a standard trace program that shows how to use the tkinter variable associated with the trace, a StringVar in ths case, to get the contents.
import tkinter
def text_changed(*args):
print(tk_name.get())
top = tkinter.Tk()
tk_name=tkinter.StringVar()
tk_name.set("nothing")
tk_name.trace("w", text_changed)
tkinter.Label(top, textvariable=tk_name).grid(row=0, column=1)
entry_1 = tkinter.Entry(top, textvariable=tk_name)
entry_1.grid(row=1, column=1, sticky="W")
entry_1.focus_set()
top.mainloop()
I'm learning Tkinter by making a GUI for a sudoku solver program (that was arleady built).
On the click of a button, the algorithm that solves the sudoku runs and sometimes it takes some time.
How could I update the sudoku on the screen when the function is called, so that users can see how its running?
I'm working with a gui script separate from the sudoku one, is it correct, in design terms, to have the gui and the logic separate?
Thank in advance
EDIT
This is my code:
Sudoku.py
class Sudoku(object):
def __init__(self):
self.__matrix = [[(0, Status.Guess) for x in range(9)] for y in range(9)]
...
def solveSudoku(self):
...
GUI.py
class App:
def __init__(self, master, su):
self.__sudoku__ = su
self.__root__ = master
self.__entries__ = {}
fsudoku = Frame(master)
fsudoku.grid(row=0)
self.displaysudoku(fsudoku) """grid of entrys"""
tButton = Button(master,text="Solve", command=self.SolveAndDisplay)
...
def refreshSudokuGrid(self):
"""USED AFTER SOLVING A SUDOKU"""
for i in range(1,10):
for j in range(1,10):
val = self.__sudoku__.value(i,j)
self.__entries__[i * 10 +j].delete(0, END)
if (val!= 0):
self.__entries__[i * 10 + j].insert(0, val)
def SolveAndDisplay(self):
self.scanSudoku()
self.__sudoku__.solveSudoku()
self.refreshSudokuGrid()
...
root = Tk()
su = Sudoku()
s = App(root, su)
root.mainloop()
I guess you must be using some loop which solves the sudoku.If this is true:
The place where your function/command for your button is defined,
place the following code at the beginning of the primary loop which solves the sudoku (assuming root is your tkinter window):
root.update()
As such such a method is not fully threadsafe, but should solve the problem for general cases like yours.
Keeping GUI and logic separate is the best practice.
I am running a script with tkinter that captures user input and then opens a second and possibly a third window based on the input. The issue I am having is capturing user input from the third and final window. Each window is divided up into it's own python class on execution.
Here is the code that calls the third window, which executes properly:
test_assign = TestAssign(mylist)
Here is the third window code:
class TestAssign:
def __init__(self, mylist):
self.mylist = mylist
self.selected_values = []
self.master = Tk()
for i in range(len(mylist)):
setattr(self, 'item'+mylist[i], IntVar())
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
ch.pack()
b = Button(master, text='Next', command=self.get_selected_values)
b.pack()
mainloop()
def get_selected_values(self):
for i in range(len(self.mylist)):
if getattr(self, 'item'+self.mylist[i]) == 1:
self.selected_values.append(self.mylist[i])
self.master.destroy()
Control then returns to the call point (at least I believe it does). Where I attempt to print the selected values:
test_assign = TestAssign(mylist)
while not test_assign.selected_values:
pass
print test_assign.selected_values
Everytime execution gets to the print statement it prints an empty list whether there are boxes checked or not. If I call dir(test_assign) for testing purposes, the checkbox attrs are there. Not sure why I am not able to capture it like this.
Can anyone see the flaw in my code?
Two things:
1)
ch = Checkbutton(master, text='item'+mylist[i], variable=getattr(self, 'item'+mylist[i])
and
b = Button(master, text='Next', command=self.get_selected_values)
I think master should be self.master (but honestly, that almost certainly just a copy/pasting error.)
2) The important one:
if getattr(self, 'item'+self.mylist[i]) == 1:
should be
if getattr(self, 'item'+self.mylist[i]).get() == 1:
(you need to call get on your IntVars to read the value.)
Since I'm new to Python I hope this is a blindingly obvious question.
I'm coding a binary clock (i.e. shows 1s and 0s, but will ultimately display graphics of large LEDs)
Here is the code which I have used so far:
#Simple binary clock
#Python 3.3.2
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme= time.strftime("%H",time.localtime()) + time.strftime("%M",time.localtime()) + time.strftime("%S",time.localtime())
print(tme)
hpos=0
for c in tme: #tme set to HHMMSS, iterate through each digit
col=50+hpos*50 #Set column position
b=format(int(c),'04b') #Covert digit to 4 bit binary
vpos=0
for r in b:
row=50+vpos*50
if r=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
label1.place(x=col,y=row)
vpos+=1
hpos+=1
root.after(1000,task) #reschedule event, 1000=1sec
root.after(100,task)
root.mainloop()
The issue is this- after leaving to run the code for about 15 minutes it slows down and grinds to a halt. I've tried it on more than one PC to the same effect, I want this to work on a Raspberry Pi but again it has the same result.
I will eventually make the form fill the screen and use graphics in the label widgets- I'm open to suggestions to solving the solution in a different way.
Thanks in advance for any help you are able to offer.
JJ
The line label1 = Label(root, ...) creates a new window each time and places this on top of the previous instance. So over time you are creating more and more windows which gradually consumes your memory.
Instead, create the label once and place it. Then just update it's text property in the task so that it displays the new value in the same window instance.
Also, you can format time in one call time.strftime("%H%M%S",time.localtime())
Example
from Tkinter import *
import sys,time
class App():
def __init__(self, parent):
parent.title("Title")
parent.geometry("500x500")
self.labels = []
self.parent = parent
x,y = 50,50
for index in range(3):
label = Label(parent, text='0')
label.place(x = x, y = y)
self.labels.append(label)
y += 50
self.parent.after(1000, self.Task)
def Task(self):
t = time.strftime("%H:%M:%S", time.localtime())
print(t)
index = 0
for c in t.split(':'):
b = format(int(c), '04b')
self.labels[index].configure(text=b)
index += 1
self.parent.after(1000, self.Task)
def main():
root = Tk()
app = App(root)
root.mainloop()
if __name__=='__main__':
sys.exit(main())
I've attempted to put in the changes as outlined, I've added a separate widget for each bit, initialized them, placed them on the form.
On changing the text of the widgets the form is not updated. I have looked to see if the label widget has an update method, but apparently not. I must be missing something really obvious here.
I have a slimmed-down version of the code here which only displays the last digit of the seconds, what have I missed?:
#Simple binary clock
from tkinter import *
import time
root=Tk()
root.title("Title")
root.geometry("500x500")
def task():
tme=time.strftime("%S",time.localtime()) #testing on just the seconds
tme=tme[1] #testing, just take last digit of seconds
print(tme)
for c in tme: #tme set to HHMMSS, iterate through each digit
b=format(int(c),'04b') #Covert digit to 4 bit binary
print(b)
if b[0]=="1":
label0=Label(root,text="1")
else:
label0=Label(root,text="0")
if b[1]=="1":
label1=Label(root,text="1")
else:
label1=Label(root,text="0")
if b[2]=="1":
label2=Label(root,text="1")
else:
label2=Label(root,text="0")
if b[3]=="1":
label3=Label(root,text="1")
else:
label3=Label(root,text="0")
root.after(1000,task) #reschedule event, 1000=1sec
label0=Label(root, text="*")
label0.place(x=50,y=50)
label1=Label(root, text="*")
label1.place(x=50,y=100)
label2=Label(root, text="*")
label2.place(x=50, y=150)
label3=Label(root, text="*")
label3.place(x=50,y=200)
root.after(1000,task)
root.mainloop()