Using Python 3.5. I have a tkinter form. The user clicks a button to import many files into a Listbox. Another button loops thru the files and reads and extracts data from them.
I have a Label on the form that indicates the status of the loop. The status, for the most part, works as expected except that extra characters are added on the end. I'm not sure where the characters come from. I also print() the same content as the Label back to the screen and the print() displays exactly what it should.
My question is why is my Label not displaying the correct string?
My code, greatly shortened:
class tk_new_db:
def __init__(self, master):
self.master = master # sets 'root' to the instance variable 'master'
self.var_text_2 = StringVar()
self.var_text_2.set('STATUS: Active')
self.label_6 = Label(master, textvariable=self.var_text_2,
font=self.font_10)
self.label_6.grid(row=15, sticky=W, padx=15)
def execute_main(self): # extract data from files
file_num = 0
nf = len(self.listbox_1.get(0, END))
for fr in li:
file_num += 1
print('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.var_text_2.set('STATUS: Extracting Loads from File '
'{} in {}'.format(file_num, nf))
self.master.update_idletasks()
The print() is writing the following:
STATUS: Extracting Loads from File 1 in 5
The Label is writing the following:
STATUS: Extracting Loads from File 1 in 5 nce...
It always adds ' nce...' on the Form.
EDIT:
I do use self.var_text_2 earlier in the program. The ' nce...' looks like it is a fragment of the previous string. I've since tried resetting the variable in two different ways, but I'm still getting the same result.
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.__del__()
self.var_text_2.set('STATUS: Checking .F06 Files for Convergence...')
self.master.update_idletasks()
self.var_text_2.set('')
How do you properly delete the StringVar() for reuse?
The only explanation I can think of is that you're stacking labels on top of each other in the same row and column, so when you add a long string that ends in "nce..." and later update the screen by adding a shorter label in the same grid cell, the trailing text of the longer label underneath is showing through.
The reasons I draw this conclusion are:
this is a common mistake I've seen several times
there are no bugs in tkinter that would cause this
there is nothing in the code that you've shown that could possibly add "nce..." to the end of the string
the fact that you use sticky="w" rather than sticky="ew", which will cause a shorter label to not completely overlay a longer label if placed in the same row and column
I think I can explain. Root.mainloop repeatedly calls root.update. Root.update executes tasks on the 'main' event queue. In brief, it only executes idletasks when the event queue is empty after checking for file events (on non-Windows), window-gui events, and timer events. Idletasks, which are not well documented but seem to primarily be screen update tasks, are never added to the main event queue. It is therefore possible that idletasks will remain undone indefinitely. Root.update_idletasks exists to force execution of idletasks anyway.
When mainloop is running, calling update within a task run by update is usually unnecessary and possibly worse. It is something only for adventurous experts. Hence the warnings you have read (which assume that you are running mainloop).
When mainloop is not running and update is not being called repeatedly, either because you never called mainloop or because you block it with a long running loop, then you likely must call update yourself. What you seem to have discovered is that part of completely handling a StringVar update is a main event and not just an idletask.
I sympathize with your desire to do repeated tasks with a for loop, but doing so currently means taking responsibility for event handling yourself. I hope in the future that it will be possible to have 'async for' (new in 3.5) interoperate with Tk.mainloop, but it is not as easy as I hoped.
In the meanwhile, you could put the body of the for loop into a callback and loop with root.after.
Related
I have a funtion, that parses the log. I am updating the label color and text at different lines within the same funtion. I see that only the last changed color and text only reflecting in the UI. i do not see the intermediate color and text changes.
Below is my code:
def OnclickLogParser(self):
if LogFileName == '':
messagebox.showinfo("Error", "Please select a valid Log")
if LogPathName == '':
messagebox.showinfo("Error", "Please select a valid Log Path")
self.lb_log_status.configure(bg="#08DFE8", fg="#010101", text='Parsing inProgress...')
m_logParser = CAdpBrrLogParser()
m_logReader = CAdpBrrLogReader('mrr', m_logParser)
status = m_logReader.readFile(LogPathName)
if status == True:
self.lb_log_status.configure(bg="#F6F50B", fg="#010101", text='Log Ready')
self.btn_log_start["state"] = "normal"
global m_injector
m_injector = CAdpUdpDataInjector()
you can see that i am changing the color and text of lb_log_status at two different places. The time gap between those two lines would be around 3 -5 secs. But i can not witness the first color change. '
Tkinter must process events regularly to perform any UI operations. If you want to see the color changes here you have to give Tk a chance to process the configuration and paint events that get generated when you re-configure a widget. Your code just immediately starts processing the log and will not process any events until this function exits and you return to the Tk mainloop() which is the event processing method. You would see the same problem if you used a progress bar widget and tried to update the progress during processing.
One way to resolve this is to use the Tk after() method and schedule your processing in chunks that take a limited amount of time per chunk. After reading and processing a chunk call after() again to schedule the next chunk.
Another method is to put the processing on a worker thread and use event_generate() to post events back to the Tk thread to announce progress and completion.
first of all let me start off by saying I started coding around 6 weeks ago in self-study, so it is very likely, that the approach may still be a total mess.
I have a program running (fine for me) in cli, but I want to make it usable for people that faint, when they see plain white text on a black background which they have to operate without a mouse.
Besides my main window, which is running in the background I wanted to have a message window, which displays the information if all necessary files where selected. which is shown below.
files_to_open = {'File_1':'', 'File_2':'', 'File_3':''}
def selectfiles_window():
global message_window
message_window = Tk()
...
content = Label(message_window, text=get_open_file_status_txt(), **txt_status_general)
content.pack(side='top')
button_select_file1 = Button(message_window,text = 'File 1',font = txt_general['font'],command = lambda:(
select_file_action(name='File 1', filetypename='Excel workbook', extension='*.xlsx'),
content.configure(text=get_open_file_status_txt())))
button_select_file1(side='bottom')
message_window.mainloop()
def select_file_action(name, filetypename, extension):
global files_to_open
files_to_open[name] = filedialog.askopenfilename(title = f'Select {name}', filetypes=[(filetypename, extension)])
def get_open_file_status_txt():
global files_to_open
message =f'''
[File_1] is {"NOT SET" if (files_to_open["File_1"] == "") else "SET"}'''
return message
I expected, that the text is updated after the filedialog was closed (which is partly working as expected).
Now what I don't understand: If I click the button to select File_1 and cancel it for the first time, the value for key File_1 is set to (). Any time after that, if I click the button to select File_1 and cancel it, the value for key File_1 is set to ''. If I select a file the path is saved correctly as value (also on the first attempt). If I cancel it is set again to ''.
Anybody an idea about why the value is set to () on the first cancel but afterwards runs as expected?
I would also be grateful for a different solution to update the text, if my approach is totally off.
Thank you and best regards,
Thomas
Turns out, that the function call was not the issue, rather that it is a (strange to me but maybe intended) behavior of filedialog.askopenfilename, which returns an empty tuple if cancel is selected on first call but an empty string on every further canceled calls.
I'm trying to create simple progress status label for my Pyqt5 Python code and update it after every iteration of a loop in which a function does a bunch of stuff. The label I want to be updated is "status_label_counter". The below code only shows the part for creating labels and the exact place where I want to use the functionality I've mentioned.
#initialisation code, etc...
self.status_label_counter = QLabel()
self.status_label_from = QLabel(' from: ')
self.status_label_total = QLabel()
status_hbox = QHBoxLayout()
status_hbox.addStretch()
status_hbox.addWidget(self.status_label_counter)
status_hbox.addWidget(self.status_label_from)
status_hbox.addWidget(self.status_label_total)
status_hbox.addStretch()
#bunch of other code...
def create_ics(self):
counter = 0
self.status_label_total.setText(str(len(self.groups)))
for group in self.groups:
#does a bunch of stuff inside
group_manager.create_calendar_for(self.rows, group, self.term)
counter += 1
#for console output
print('iteration: ', counter)
#trying to update status counter
self.status_label_counter.setText(str(counter))
The problem is that I only see the update of both labels when the loop is done with the nested function. When I click a button that calls for "create_ics" function window becomes inactive for about 5 seconds, I see logs on console with count of iterations, but nothing happens in view.
The view (Qt) is locked in your main thread and never gets its chance to process its event loop and thus redraw itself. If you really want to do it this way, call:
self.status_label_counter.repaint()
After you set the text (and if you have some complex layout measuring call QApplication.processEvents() instead).
However, much better option would be to run your create_ics() function in a separate thread leaving your main thread to deal with the view and Qt's event processing. You can do it either through standard Python's threading module, or using Qt's own QThread: https://nikolak.com/pyqt-threading-tutorial/ .
I am using tkinter for a GUI. I bound an event to an entry like so:
EntryFilePath.bind("<Key>", updateAmountOfPeople)
It works, but the problem is that it only updates when a key other than typing input is being pressed. Backspace triggers it, arrows trigger it, just not letters or numbers. I am looking for this functionality.
Other info that might be important:
PathFileName = StringVar()
EntryFilePath = Entry(topLeftMidFrame, textvariable = PathFileName, width=45)
EntryFilePath.pack(side=TOP, pady=32, padx=10)
How to make it trigger on any key?
EDIT: I found out that this only happens when it just got selected. It needs one of the "other" non [a-Z0-9] keys once, after that it is good to go. This is problematic though, in case people start immediately writing.
EDIT2: It might have to do with it having update delay.
The binding should work for every keypress -- if it's not, you're doing something somewhere else in your code to prevent it from working, or your testing is flawed.
If you want a function to be called whenever the value changes, you might want to consider setting a trace on the variable associated with the entry widget. The trace will fire whenever the value changes, no matter whether it's through keyboard input, pasting with the mouse, etc. It will not call your callback when the user uses the arrow keys or the return key, or any other key that doesn't affect the value.
For example:
def var_callback(*args):
print "the variable has changed:", PathFileName.get()
PathFileName.trace("w", var_callback)
It can be solved by changing
EntryFilePath.bind("<Key>", updateAmountOfPeople)
to
EntryFilePath.bind("<KeyRelease>", updateAmountOfPeople)
Hi
so first of all i made a program that downloads music and displays the percent that has downloaded in a list box.
kind of like this
from Tkinter import *
from urllib2 import *
admin = Tk()
Admin = Tk()
listbox = Listbox(admin, bg="PURPLE")
listbox.pack()
def fores():
chunks = 10000
dat = ''
song = '3 rounds and a sound'
url = 'http://bonton.sweetdarkness.net/music/Blind%20Pilot%20--%203%20Rounds%20and%20A%20Sound.mp3'
down = urlopen(url)
downso = 0
tota = down.info().getheader('Content-Length').strip()
tota = int(tota)
while 1:
a = down.read(chunks)
downso += len(a)
if not a:
break
dat += a
percent = float(downso) / tota
percent = round(percent*100, 1)
listbox.insert(END, percent)
listbox.update()
listbox.delete(0, END)
listbox.insert(END, percent)
listbox.update()
button = Button(Admin, text='Download', command=fores)
button.pack()
button = Button(Admin, text='Download', command=fores)
button.pack()
mainloop()
I wont show you the original program for it is over the limit of the post size.
On my original program if i move the window before i download an mp3 file it downloads less then 3 % and stops and if i then close the window it starts downloading again.
does anyone know why this is or if there is an alternative to displaying the percentage on the Tkinter window?
Please help
and update_idletasks doesent work
The proper widget for displaying a string is a Label. You can change the text at runtime with the configure method:
self.progress = Label(...)
...
self.progress.configure(text="%s%% completed" % percent)
Second, you are creating two root windows - admin and Admin. And strangely, you are putting the listbox in one and the buttons in another. Tk isn't designed to work like that. Third, you need to call the mainloop method of your (single) root window (eg: Admin.mainloop)
Finally, as to your comment that update_idletasks doesn't work -- please define "doesn't work". It will in fact update the display. What it won't do is let you interact with the window while it is running.
I made changes to your code based on the above comments (created only one root, used a Label rather than Listbox, and used update_idletasks and the program ran to completion, downloading the song.
The danger of calling update is this: what if you click the "download" button while you are already downloading? What happens is the next time update is called, that button press will be serviced. In the servicing of that event you'll enter an infinite loop. While that inner infinite loop is running the outer one cannot run. You will have effectively frozen the first download.
The proper solution involves one of (at least) two techniques. One, create a thread to do the downloading, and have it periodically send information back to the main loop so it can update the progress bar. The second is to leverage the already existing infinite loop -- the event loop -- and do your reading of chunks one at a time by placing jobs on the event queue with after.
There are examples on the internet for both approaches.
i use a ttk.Progressbar, all you have to do is associate a variable to it and update that particular variable.
http://docs.python.org/library/ttk.html#progressbar
http://www.tkdocs.com/tutorial/morewidgets.html#progressbar