Gtk.ProgressBar not working in Python - python

I'm trying to use a progress bar in Python and Gtk3, but it doesn't get updated. I have read this documentation and some questions in this forum (mostly for pygtk) and I really don't get it!
I made a code for testing purposes only. When you click a button, it reads the contents of a directory recursively. My intention is to use a progress bar while reading all these files.
This is the whole code:
import os
from gi.repository import Gtk
class MyWindow(Gtk.Window):
"""Progress Bar"""
def __init__(self):
Gtk.Window.__init__(self, title='Progress Bar')
self.set_default_size(300, 75)
self.set_position(Gtk.WindowPosition.CENTER)
self.set_border_width(10)
# read dir contents
mydir = 'Documents'
home_path = os.environ.get('HOME')
dir_path = os.path.join(home_path, mydir)
self.dir_files_list(dir_path)
# create a grid
self.grid = Gtk.Grid(column_homogeneous=True, row_homogeneous=True,
column_spacing=10, row_spacing=10)
self.add(self.grid)
# create a progress bar
self.progressbar = Gtk.ProgressBar()
self.grid.add(self.progressbar)
# create a button
self.button = Gtk.Button(stock=Gtk.STOCK_APPLY)
self.button.connect('clicked', self.on_button_pressed)
self.grid.attach(self.button, 0, 1, 1, 1)
# function to read the dir contents
def dir_files_list(self, dir_path):
self.dir_list = []
for root, dirs, files in os.walk(dir_path):
for fn in files:
f = os.path.join(root, fn)
self.dir_list.append(f)
# function to update the progressbar
def on_button_pressed(self, widget):
self.progressbar.set_fraction(0.0)
frac = 1.0 / len(self.dir_list)
for f in self.dir_list:
new_val = self.progressbar.get_fraction() + frac
print new_val, f
self.progressbar.set_fraction(new_val)
return True
def main():
"""Show the window"""
win = MyWindow()
win.connect('delete-event', Gtk.main_quit)
win.show_all()
Gtk.main()
return 0
if __name__ == '__main__':
main()
Any help from a more experienced programmer is appreciated!

The problem is you are processing the whole directory when you are creating the window. That is even before you show it (in the line with self.dir_files_list(dir_path)). It seems to me you want to call it after pressing the button apply.
There are at least 2 possible solutions: use threads or use iterators. For your specific use case, I think iterator would be enough, besides than simpler and more pythonic. I would recommend threads only when you really need them.
Instead of walking through the whole directory beforehand and the process them later, you can process each file in each directory at a time.
In your example, I would change the method dir_files_list (renamed to walk) and on_button_pressed as:
from gc import collect
from gi.repository import Gtk, GObject
We now need to import also GObject to use idle_add which calls a callback whenever there are no higher priority events pending. Also, once the task is finished, we need to remove the callback to not call it anymore (we need source_remove for that).
I renamed your method dir_files_list as walk because it seems semantically closer to an iterator. When we walk through each directory and file, we will temporarily "return" (using yield). Remember, yield True means that there are pending items to process. yield False means we stop the iteration.
So, the method would be:
def walk(self, dir_path):
self.dir_list = []
for root, dirs, files in os.walk(dir_path):
i = 0.0
self.set_title(os.path.dirname(root))
for fn in files:
i = i + 1.0
f = os.path.join(root, fn)
self.dir_list.append(f)
self.progressbar.set_fraction(i / len(files))
collect()
yield True
yield True
GObject.source_remove(self.id)
yield False
Now, we update the progressbar here. In this particular part, I am updating the progress bar inside each directory. That is, it will be restarted in every sub-directory and the progress bar will restart in each of them. To have an idea of which directory is visiting, set the window title with the current directory. The important part to understand here is yield. You can adapt this to whatever you want to do.
Once we have walked the whole directory, we have to return yield False, but before that we remove the callback (GObject.source_remove(self.id)).
I consider here that self.dir_list is not useful here anymore, but you might have something different in mind.
You might wonder When and how walk is called? That can be set when the button is pressed:
def on_button_pressed(self, button, *args):
homedir = os.path.expanduser('~')
rootdir = os.path.join(homedir, 'Documents')
self.task = self.walk(rootdir)
self.id = GObject.idle_add(self.task.next)
self.task is an iterator, which has the method next() that retrieves the next item from self.task, which is going to be called whenever there are no pending events (with idle_add). We get the id in order to remove the callback once we are done.
You have to remove the line self.dir_files_list(dir_path) from the __init__ method.
One more thing: I called the garbage collector manually (gc.collect()) because it could be useful when processing a large directory (depending on what you are doing with them).

First, the problem is that the above code runs too fast, so you see the progress bar update so quick that it seems its not getting updated. It does that because you're not searching the directory and displaying the result at the same time. When the class __init__ you do the search, and just then when you click the button the list is read and displayed in the progress bar at full speed. I'm pretty sure that if the directory is huge, when you start the script it will take a few seconds for the window to display, and the progress bar will eventually progress in a matter of milliseconds anyway.
Basically the problem you're having is that you're doing everything in the same thread. Gtk is in it's own thread, listening to events from the GUI and updating the GUI. Most of the time, you will be using Gtk.ProgressBar for something that will require some time to complete, and you need to execute both, the GUI thread and the working thread (the one that is doing something), and send updates from the working thread to the GUI thread so it updates the progress bar. If, like the code above, you run everything in the same thread you will end up having this kind of problems, for example when the GUI freezes, that is, it become unresponsive because suddenly the GUI thread is doing some work not GUI-related.
In PyGTK, you had the method gobject.idle_add(function, parameters) so you can communicate from the working thread to the GUI thread, so when the GUI thread is idle it will execute that function with those parameters, for example, to update the Gtk.ProgressBar.
My approach to that problem is implemented here, please note is for PyGTK: https://github.com/carlos-jenkins/nested/blob/master/src/lib/nested/core/gallery/loading.py
Basically, it is a "LoadingWindow" that the whole application share. When you want to start loading something or perform some heavy work you had to subclass the WorkingThread class (example). Then, you just had to call the show() method with a WorkingThread subclass instance as parameter and done. In the WorkingThread subclass, you had to implement the payload() function, that is, the function that does the heavy work. You could directly call from the WorkingThread the pulse method in the LoadingWindow to update the ProgressBar and should not care about Thread communication because that logic is implemented there.
Hope the explanation helps.
EDIT:
I just ported the above to PyGObject, you can find the example here: https://gist.github.com/carlos-jenkins/5358445

Related

How can my program be prevented from freezing?

I have decided to finally work on a project, as I've tried to code in python before, with at least some success. In my project, I am trying to build a menu that lets me "Auto-farm" in a game. It uses 3 modules, namely pynput, pause, and PySimpleGUI.
Whenever I run the code, it runs fine, until I click the button that starts the automation part. It runs completely fine, but I have to force close the GUI prompt that shows up as it just completely stops responding to input until you close it.
How I can make a stop button, and stop my program from freezing up?
I am using 2 files to keep this project slightly more organized, although I don't know if this is the best way to go around doing this. These 2 files are main.py and chand.py.
main.py
import PySimpleGUI as sg
import chand
loop = 0
layout = [[sg.Text("Welcome to RedGrowie's autofarm menu!")], [sg.Button("Chandeliers")]]
window = sg.Window("Autofarming Menu", layout)
while True:
event, values = window.read()
if event == sg.WIN_CLOSED:
break
if event == "Chandeliers":
loop = 1
if loop == 1:
chand.Chandeliers.start(self=chand.Chandeliers)
window.close
chand.py
from pynput.keyboard import Key, Controller
import pause
keyboard = Controller()
start = "0"
class Chandeliers:
def d_press(self):
keyboard.press("d")
pause.milliseconds(70)
keyboard.release("d")
pause.milliseconds(300)
keyboard.release(Key.space)
def space_press(self):
keyboard.press(Key.space)
pause.milliseconds(1)
#keyboard.release(Key.space)
def start(self):
start = "1"
while start == "1":
self.d_press(self)
self.space_press(self)
Your Chandeliers.start function loops indefinitely, so the call from the main loop in main.py never gets returned to. If you want both loops to be running at the same time, you probably need to use threading or some other means of concurrency. Or you might be able to interleave the two loops somehow, depending on the timing requirements for each one.
As a side note, you are using your Chandeliers class in a very odd way. You're never creating an instance of the class, but rather calling the methods it defines as if they were class methods (but with manual passing of the class, in the misleadingly named self argument.
You should probably not do that. Either treat the class as a normal one, and create an instance:
cha = chand.Chandeliers()
chat.start() # and change start to not manually pass self any more
Or you should do away with the unneeded class all together and just make the methods into top-level functions.

Creating Toplevel widgets that are thread safe

I'm trying to learn how to use the thread module. I followed along with the instructions here: http://effbot.org/zone/tkinter-threads.htm
My hope is the test script will:
Print out the "count" every two seconds
Show a pop-up dialog window (also every 2 seconds)
The pop-ups should be allowed to accumulate (if I don't click "OK" for a while, there should be
multiple pop-ups)
However, when I run this script it will freeze the main window and after a while crash. I think I'm not implementing the thread module correctly.
Could someone please have a look and point out what I'm doing wrong?
Here is what I've tried so far:
from Tkinter import *
import thread
import Queue
import time
class TestApp:
def __init__(self, parent):
self.super_Parent = parent
self.main_container = Frame(parent)
self.main_container.pack()
self.top_frame = Frame(self.main_container)
self.top_frame.pack(side=TOP)
self.bottom_frame = Frame(self.main_container)
self.bottom_frame.pack(side=TOP)
self.text_box = Text(self.top_frame)
self.text_box.config(height=20, width=20)
self.text_box.pack()
self.queue = Queue.Queue()
self.update_me()
def show_popup(self):
self.my_popup = Toplevel(self.main_container)
self.my_popup.geometry('100x100')
self.popup_label = Label(self.my_popup, text="Hello!")
self.popup_label.pack(side=TOP)
self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy)
self.pop_button.pack(side=TOP)
def write(self, line):
self.queue.put(line)
def update_me(self):
try:
while 1:
line = self.queue.get_nowait()
if line is None:
self.text_box.delete(1.0, END)
else:
self.text_box.insert(END, str(line))
self.text_box.see(END)
self.text_box.update_idletasks()
except Queue.Empty:
pass
self.text_box.after(100, self.update_me)
def pipeToWidget(input, widget):
widget.write(input)
def start_thread():
thread.start_new(start_test, (widget,))
def start_test(widget):
count = 0
while True:
pipeToWidget(str(count) + "\n", widget)
count += 1
time.sleep(2)
widget.show_popup()
root = Tk()
widget = TestApp(root)
start_button = Button(widget.bottom_frame, command=start_thread)
start_button.configure(text="Start Test")
start_button.pack(side=LEFT)
root.title("Testing Thread Module")
root.mainloop()
I can't reproduce your problem, but I can see why it would happen.
You're using the queue to pass messages from the background thread to the main thread for updating text_box, which is correct. But you're also calling widget.show_popup() from the background thread, which means it creates and displays a new Toplevel in the background thread. That's not correct.
All UI code must run in the same thread—not all UI code for each top-level window, all UI code period. On some platforms, you may get away with running each window in its own thread (or even free-threading everything), but that isn't supposed to work, and definitely will crash or do improper things on some platforms. (Also, that single UI thread has to be the initial thread on some platforms, but that isn't relevant here.)
So, to fix this, you need to do the same dance for creating the popups that you do for updating the textbox.
The obvious way to do that is to move the widget.show_popup() to the loop in update_me(). If you want it to happen 2 seconds after the textbox updates, just add self.top_frame.after(2000, self.show_popup) to the method.
But I'm guessing you're trying to teach yourself how to have multiple independent updating mechanisms, so telling you "just use a single update queue for everything" may not be a good answer. In that case, just create two queues, and a separate update method servicing each queue. Then, do your pipeToWidget, sleep 2 seconds, then pipeToPopup.
Another way around this is to use mtTkinter. It basically does exactly what you're doing, but makes it automatic, pushing each actual Tk GUI call onto a queue to be run later by the main loop. Of course your objects themselves have to be thread-safe, and this also means that you have to deal with the GUI calls from one thread getting interleaved with calls from another thread. But as long as neither of those is a problem (and they don't seem to be in your case), it's like magic.
If you want to know why this is freezing and/or crashing for you on Win7 and not for me on OS X 10.8… well, you really need to look into a mess of Tcl, C, and Python code, and also at how each thing is built. And, unless it's something simple (like your Tk build isn't free-threaded), it wouldn't tell you much anyway. The code isn't supposed to work, and if it seems to work for me… that probably just means it would work every time until the most important demo of my career, at which point it would fail.

What is the simplest way of monitoring when a wxPython frame has been resized?

I want to know when a frame has been resized, so I can save the size and remember it the next time the application launches. Here is my on_resize method:
def on_resize(self, event):
logic.config_set('main_frame_size',
(event.Size.width, event.Size.height))
event.Skip()
And it's bound like this:
self.Bind(wx.EVT_SIZE, self.on_resize)
The problem is performance. For safety, my logic module saves the config file every time a setting changes, and writing the config file every time the resize event fires is way too performance taxing.
What would be the best/easiest way of monitoring for when the user is done resizing the frame?
Update
My config_set function:
def config_set(key, value):
"""Set a value to the config file."""
vprint(2, 'Setting config value: "{}": "{}"'.format(key, value))
config[key] = value
# Save the config file.
with open(config_file_path, 'w') as f:
pickle.dump(config, f)
You could handle EVT_IDLE which is triggered when the event queue is empty:
wx.IdleEvent: This class is used for EVT_IDLE events, which are generated and sent when the application becomes idle. In other words, the when the event queue becomes empty then idle events are sent to all windows (by default) and as long as none of them call RequestMore then there are no more idle events until after the system event queue has some normal events and then becomes empty again.
The process of resizing or moving a window should keep the event queue jammed so it won't become empty (and trigger the idle event) until the resizing/moving is done.
Set a dirty flag in EVT_SIZE and check it in the EVT_IDLE handler. If the flag is set, save the new size and reset the flag:
import wx
class Frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self,None)
self.resized = False # the dirty flag
self.Bind(wx.EVT_SIZE,self.OnSize)
self.Bind(wx.EVT_IDLE,self.OnIdle)
def OnSize(self,event):
self.resized = True # set dirty
def OnIdle(self,event):
if self.resized:
# take action if the dirty flag is set
print "New size:", self.GetSize()
self.resized = False # reset the flag
app = wx.PySimpleApp()
frame = Frame().Show()
app.MainLoop()
EVT_SIZE may also be triggered when restoring a minimized window (the window size remains the same). If you want to cut down on unnecessary saves, you may want to check if the size is actually different before you save it to the config (you could keep track of it in a variable containing the last saved size).
You may want to add EVT_MOVE to keep track of the window position.
You could start a timer and have it check for changes every so often, kind of like the auto-save in Microsoft Word. Or you could set some kind of flag when EVT_SIZING or EVT_SIZE occurs and then bind to EVT_LEAVE_WINDOW as you'll almost certainly leave the window when you're done resizing. Thus when that event fires, you check the flag that was set and if it is set, you save and reset the flag.
On windows, you can save the configuration in the registry, which results in no performance hit when the window is resized.
On other OS's, where there is no registry, I guess you need to use a file. However, I am surprised that even this gives the kind of performance penalty that you would notice.
Are you sure that whatever poor performance you are seeing is due to this? ( Perhaps your redrawing code is slow? )
I would think that any modern OS would look after such a small file write without getting in your way. Perhaps it is Python problem?
I urge you to look into the above questions first. However, to answer your actual question:
The way to do this is to save the window size in a variable, and only write it to a file when your application quits.
Took a look at the code you just posted. I am not a python expert, but it looks like you are re-opening the file on every update. If so, no wonder it is slow!
Keep the file open all the time.
Only write the file when your app quits.
You might also take a look at the wxWidget wxConfig class.
You definitely shouldn't be saving the window geometry on every resize, it should be normally only done when the frame is closed. If you want extra safety (but, honestly, how do you manage to crash in Python?), you can also call the same function you call on frame close from a EVT_TIMER handler. But window geometry is hardly a critical resource so I don't think there is ever any real need to do this.

Tkinter Keyboard Binds

I'm working on an interface using Tkinter and the canvas widget, and so far have found answers to issues I have had from others questions and the answers posted, but I am stumped on this one.
I have several keyboard binds in the class where my GUI elements are created, and they all work fine when the program is started. The binds looks something like this:
self.canvas.get_tk_widget().bind("<Control-o>",self.flash_open)
and are within the __init__ function of the class. As of yesterday, I initialized this class
to start the program, then waited for the user to select open from a menu, which then opened (among other things) a tkmessagebox
self.specfilename =askopenfilename(filetypes=[("spec", "")],initialdir= self.pathname)
With this filename I am able to retrieve my required variable names from a certain filetype (inconsequential to the problem). Today I modified the __init__ function to call the open function when the program starts. Since nothing else can be done until this file is opened, it would make sense to open it first thing. Once the file is selected and the Tkmessagebox is closed, the root window is active, but none of the keyboard binds work. My functions still work using the menu/buttons assigned to them, just not the binds. I have tried binding the shortcuts to the root, with the same result, and am now thinking it may be an issue with the order I am calling them
def __init__(self):
...
self.openfile() #calls the tkmessagebox
self.root.mainloop() #starts gui
I had actually run into this issue before, where a toplevel() instance was closed/destroyed and disabled the binds of the parent window. There isn't any error message to speak of, the binds just don't do anything. I should also mention I have tried to focus on the root window again using
self.openfile()
self.root.mainloop()
self.root.focus_set()
I got around it before by using the wm_withdraw() and wm_deiconify() functions to simply hide the child window, then close it after the program is complete. This fix is a little more difficult to apply in this case however. If anyone can shed some light on the cause of the problem I'd appreciate it.
Edit:
I've written up a runable code segment to show exactly what my issue is.
import os
from tkFileDialog import askopenfilename
from Tkinter import *
class Start:
def __init__(self):
self.root = Tk()
self.root.title('Binding Troubles')
menubar = Menu(self.root)
#add items and their commands to the menubar
filemenu = Menu(menubar, tearoff=0)
filemenu.add_command(label="Do work", command=self.do_work)
filemenu.add_command(label="Open File",command=self.openfile)
menubar.add_cascade(label="File", menu=filemenu)
#bind control-o to perform the do work function
self.root.bind("<Control-o>",self.flash_do_work)
self.root.bind("<Control-O>",self.flash_do_work)
#add the menubar to the GUI
self.root.config(menu=menubar)
#initially open a tkdialog to open a file
self.openfile()#comment out this line to make the bind work
self.root.focus()#also tried self.root.focus_set()
self.root.mainloop()
def flash_do_work(self,event):
#indirect tie to the do_work() function, I'm don't know a
#proper way to make functions handle calls from both events and non-events
self.do_work()
def openfile(self):
#gets current path
self.pathname = os.getcwd()
#Requests filename using a tkdialog
self.filename =askopenfilename(initialdir= self.pathname)
print self.filename
def do_work(self):
#placeholder for actual function; shows whether the bind is working or not
print "work"
Start()
The bind will work if self.openfile() is removed from __init__, and used only from the menu
Another Edit: I've updated the example again, giving a menu option to run the openfile() function. I noticed that if openfile() is called in __init__, the bind will not work. But if next the openfile function is called again, this time manually from the menu, the bind will start working again. Not exactly sure what to take from this. Also, my apologies for the post getting so long.
Change
self.openfile()
to
self.root.after(1, self.openfile)
This moves the call to askopenfilename into the main event loop. Having it outside the main event loop is somehow clobbering your event bindings.
I had this kind of problem a couple of times and it took quite a while until I found a solution I was comfortable with. As #Steven Rumbalski suggests I tried with delaying the application, which works but seems shaky.
Then I found the functions for waiting until something is complete, in this case wait_visibility(widget). This will delay execution until the widget is visible, which seems to be the thing to be waiting for. Try this:
self.root.wait_visibility(self.root) # Wait for root to be displayed
self.openfile()
Now; I'm not sure why this is so, and it seems that there may be differences depending on platform: Tkinter window event . This has nevertheless worked for me on Windows10 and Python 3.10.5.

Update a Tkinter text widget as it's written rather than after the class is finished

I'm in a bind, since this is being written on a classified machine I am unable to copy+paste here. Being somewhat a novice, my approach is probably unorthodox.
I have a GUI written in Tkinter with several buttons. Each button is linked to a class that, in effect, runs a short script. When the button is clicked, I inititalize a class log_window which is simply a Tkinter text widget. I then create a global variable linking log to the log_window I just created, and as the script runs I pipe sys.stdout/stderr to log (I created a write method specifically for this). Everything is kosher, except that the log_window text widget doesn't update with my piped stdout until after the class calling it is finished. However, if I simply print within the class, it will print in the order it is called.
Example
import Tkinter
from Tkinter import *
import time
class log_window:
def __init__(self,master):
self.textframe = Tkinter.Frame(master)
self.text = Text(self.textframe)
self.text.pack()
self.textframe.pack()
def write(self,text):
self.text.insert(END,text)
class some_func1: # This effectively waits 5 seconds then prints both lines at once
def __init__(self,master):
log.write("some text")
time.sleep(5)
log.write("some text")
class some_func2: # This prints the first object, waits 5 seconds, then prints the second
def __init__(self,master):
print "some text"
time.sleep(5)
print "some text"
if __name__ == '__main__':
global log
root = Tk()
log = log_window(root)
root.after(100,some_func1, root)
root.after(100,some_func2, root)
root.mainloop()
Sorry if my example is a little bit muffed, but I think it makes the point. The piping I do is through Popen and some system calls, but they aren't part of the issue, so I only highlighted what, I presume, is the LCD of the issue.
I don't know the details of Tkinter's concurrency, but fiddling around reveals that if you put
master.update_idletasks()
after each call to log.write, it updates on cue. You could give log a .flush() method to do that (like file handles have), or you could just make log.write call it after writing.
When you call sleep it causes your whole GUI to freeze. You must remember that your GUI runs an event loop, which is an infinite loop that wraps all your code. The event loop is responsible for causing widgets to redraw when they are changed. When a binding is fired it calls your code from within that loop, so as long as your code is running, the event loop can't loop.
You have a couple of choices. One is to call update_idletasks after adding text to the widget. This lets the event loop service "on idle" events -- things that are schedule to run when the program isn't doing anything else. Redrawing the screen is one such event, and there are others as well.
The other option is to run your functions in a thread or separate process. Because Tkinter isn't thread safe, these other threads or processes can't directly communicate with the GUI. What they must do is push a message onto a queue, and then your main (GUI) thread must poll the queue and pull messages off. It would be easy to build this code into your log class, and polling the queue can be done using the event loop -- just write a method that pulls messages off the queue and inserts them into the widget, the calls itself using after a few hundred milliseconds later.
You have to update your widget content by adding self.text.update() after self.text.insert(END,text)

Categories

Resources