Consider this:
import wx, time
# STEP 1: Setup Window
class Window(wx.Frame):
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Bobby the Window', size=(300,200))
panel = wx.Panel(self)
self.Bind(wx.EVT_CLOSE, self.closewindow)
wx.StaticText(panel, -1, "Yo user, the program is busy doing stuff!", (50,100), (200,100))
def closewindow(self, event):
self.Destroy()
# STEP 2: Show Window (run in background, probably with threading, if that is the best way)
if __name__=='__main__':
app = wx.PySimpleApp()
frame = Window(parent=None,id=-1)
frame.Show()
app.MainLoop()
# STEP 3: Do work
time.sleep(5)
# STEP 4: Close Window, and show user evidence of work
## *Update window to say: "I am done, heres what I got for you: ^blah blah info!^"*
exit()
My questions are:
In step 4, how do I change the text in the window (esp. if it is in a thread)?
And in step 2, how do I run the window in the background, but still be able to communicate with it? (to update the text and such)
This is similar to my question about how to run a cli progress bar and work at the same time, except with gui windows.
I know that to change StaticText, I would do 'text.SetLabel("BLAH!")', but how would I communicate that with the window class if it is running in the background?
Update:
This thread was also some help.
If you need to execute a long running process, then you will have to use some type of threading or perhaps the multiprocessing module. Otherwise you will block the GUI's main loop and make it unresponsive. You will also need to use wxPython's threadsafe methods to communicate with the GUI from the thread. They are wx.CallAfter, wx.CallLater and wx.PostEvent.
You can read more about wxPython and threads at the following:
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
Related
In my wxPython GUIs, the wx.BusyInfo widget no longer works. I'm working on OSX, and I recently upgraded to El Capitan.
This simple code below doesn't work anymore with either of the wx versions that I have available ('3.0.2.0' or '2.9.2.4'). As far as I can tell, wx.BusyInfo simply no longer shows up. Unfortunately, I don't know exactly when the widget stopped appearing.
class MyFrame(wx.Frame):
def __init__(self, parent, title):
super(MyFrame, self).__init__(parent, size=(450, 350))
self.panel = wx.Panel(self)
btn = wx.Button(self.panel, wx.ID_ANY, "Do thing")
self.Bind(wx.EVT_BUTTON, self.do_thing)
self.Centre()
self.Show()
def do_thing(self, event):
wait = wx.BusyInfo('Please wait...')
time.sleep(5)
del wait
Any ideas on the cause or solution to this problem?
It looks like something may have changed with respect to when the paint events for the busy info window are processed. What you are seeing is simply that the paint event is not being delivered until after your sleep is done. If you give it a chance to be painted before you block with your busyness (such as calling wx.Yield(True) before) then you should see it working like with earlier versions of OSX. Better yet, if you can organize your busy task so it periodically yields then the system can do things like keep the busy info panel updated and show a real busy cursor instead of the spinning beachball.
I tested the suggested "Yield" workaround.
I also tried using "WindowDisabler", but that didn't work.
My band-aid fix was to refresh the wx.BusyInfo itself, instead of updating it.
For example (in Python):
busy=wx.BusyInfo("Loading corresponding data.")
#then do some work.
busy=wx.BusyInfo("Processing data for display.") #instead of busy.UpdateLabel("text")
#then do some different work.
#work done, time to let the BusyInfo object go.
del busy
I had a python console script that I wanted to add a basic status window to, so without knowing much about pyqt I added a window. If I started pyqt from my main thread, it blocked everything else, so I started it from another thread instead. It's been running fine like this for months, but I just noticed a warning (not sure how I missed it before):
WARNING: QApplication was not created in the main() thread. I'm wondering what problems this might cause.
This is a slimmed down version of the code I'm using, just updating the window titlebar:
from PyQt4 import QtGui, QtCore
import threading
import sys
from time import sleep
class MainWidget(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWidget, self).__init__(parent)
self.setWindowTitle(statusLine)
self.timer = QtCore.QBasicTimer()
self.timer.start(500, self)
def updateWindow(self):
self.setWindowTitle(statusLine)
def timerEvent(self, event):
if event.timerId() == self.timer.timerId():
self.updateWindow()
else:
super(MainWidget, self).timerEvent(event)
def startWindow():
app = QtGui.QApplication(sys.argv)
mw = MainWidget()
mw.show()
app.exec_()
if __name__ == '__main__':
global statusLine
statusLine = 'foo'
threadWindow = threading.Thread(target=startWindow)
threadWindow.start()
sleep(2) # process lots of data
statusLine = 'bar'
# keep doing stuff and updating statusLine
Edit: it looks like I don't get the warning with this simplified sample; instead, I seem to only get it if I start up multiple other python threads before the one that starts pyQt. However the question still stands: what's wrong with doing this?
I would say that since users interact with the GUI there is some danger that people kill the GUI without actually killing the main program, this can lead to:
Problems because another instance gets started leading to resource leakage, clashes, etc. &
Problems because the __main__ tries to update the GUI which no longer exists.
It seem to be generally considered best practice in programs with GUIs, whether QT or WX, to have the GUI as the __main__ and have child threads that do any background, computationally intensive, processing. Of course it is still a very good idea to explicitly kill any child threads in your OnExit method(s).
I have a python Gtk application, with the GUI designed in Glade. It has a "scan" feature which will scan the network for a few seconds and then report its results to the user. During the scanning I want a popup window to appear stealing the focus from the parent until scanning is done.
I use a threading.Lock to synchronize the GUI and the scan thread, which makes the popup to last exactly the right time I want (see scanLock.acquire() ). It seems straightforward to me to implement something like a show() and hide() call before and after the scanLock.acquire(). I did use waitPopupShow and waitPopupHide instead of just calling the window.show() and window.hide() because I also may want to set the Label in the popup or start/stop the GtkSpinner. Here is some code from the GUI class:
def scan(self):
sT = scannerThread(self,self.STagList)
self.dataShare.threadsList.append(sT)
sT.start() # start scanning
self.waitPopupShow('Scanning... Please Wait')
self.scanLock.acquire() # blocks here until scan is finished
self.waitPopupHide()
def waitPopupShow(self, msg): # shows a GtkSpinner until the semaphore is cleared
self.waitDialogLabel.set_text(msg)
self.waitDialogBox.show_all()
self.waitDialog.show()
self.waitDialogSpinner.start()
def waitPopupHide(self):
# how to get the handle to the spinner and stop it?
self.waitDialogSpinner.stop()
self.waitDialog.hide()
def getAll(self):
# GUI
self.builder = Gtk.Builder()
self.builder.add_from_file(path to main GUI)
# ... getting stuff from a first glade file
# getting stuff from the waitDialog glade file
self.builder.add_from_file(path to waitDialog GUI)
self.waitDialog = self.builder.get_object("waitDialog") # GtkWindow
self.waitDialogBox = self.builder.get_object("waitDialogBox") # GtkBox
self.waitDialogLabel = self.builder.get_object("waitDialogLabel") # GtkLabel
self.waitDialogSpinner = self.builder.get_object("waitDialogSpinner") # GtkSpinner
self.waitDialog.hide()
I'm trying hardly since a couple of days to show a dialog with a label and a Gtk.Spinner. The best I obtain at the moment is to have the window showing up with no content. Please note that the self.waitDialog.hide() right after getting it with self.builder.get_object is needed because I set the property of the waitDialog Gtkwindow to Visibile. If I stop with the debugger before .hide() the waitDialog shows up perfectly. Afterwards its broken.
This is the waitDialog GUI file: http://pastebin.com/5enDQg3g
So my best guess is that I'm dooing something wrong, and I could find nothing on creating a new Gtk window over the main one, only basic examples and dialogs. A pointer to the documentation saying a bit about this would be a good starting point...
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)
I'm having a problem, where I wish to run several command line functions from a python program using a GUI. I don't know if my problem is specific to PyQt4 or if it has to do with my bad use of python code.
What I wish to do is have a label on my GUI change its text value to inform the user which command is being executed. My problem however, arises when I run several commands using a for loop. I would like the label to update itself with every loop, however, the program is not updating the GUI label with every loop, instead, it only updates itself once the entire for loop is completed, and displays only the last command that was executed.
I am using PyQt4 for my GUI environment. And I have established that the text variable for the label is indeed being updated with every loop, but, it is not actually showing up visually in the GUI.
Is there a way for me to force the label to update itself? I have tried the update() and repaint() methods within the loop, but they don't make any difference.
I would really appreciate any help.
Thank you.
Ronny.
Here is the code I am using:
# -*- coding: utf-8 -*-
import sys, os
from PyQt4 import QtGui, QtCore
Gui = QtGui
Core = QtCore
# ================================================== CREATE WINDOW OBJECT CLASS
class Win(Gui.QWidget):
def __init__(self, parent = None):
Gui.QWidget.__init__(self, parent)
# --------------------------------------------------- SETUP PLAY BUTTON
self.but1 = Gui.QPushButton("Run Commands",self)
self.but1.setGeometry(10,10, 200, 100)
# -------------------------------------------------------- SETUP LABELS
self.label1 = Gui.QLabel("No Commands running", self)
self.label1.move(10, 120)
# ------------------------------------------------------- SETUP ACTIONS
self.connect(self.but1, Core.SIGNAL("clicked()"), runCommands)
# ======================================================= RUN COMMAND FUNCTION
def runCommands():
for i in commands:
win.label1.setText(i) # Make label display the command being run
print win.label1.text() # This shows that the value is actually
# changing with every loop, but its just not
# being reflected in the GUI label
os.system(i)
# ======================================================================== MAIN
# ------------------------------------------------------ THE TERMINAL COMMANDS
com1 = "espeak 'senntence 1'"
com2 = "espeak 'senntence 2'"
com3 = "espeak 'senntence 3'"
com4 = "espeak 'senntence 4'"
com5 = "espeak 'senntence 5'"
commands = (com1, com2, com3, com4, com5)
# --------------------------------------------------- SETUP THE GUI ENVIRONMENT
app = Gui.QApplication(sys.argv)
win = Win()
win.show()
sys.exit(app.exec_())
The label gets updated all right, but the GUI isn't redrawn before the end of your loop.
Here's what you can do about it:
Move your long-running loop to a secondary thread, drawing the GUI is happening in the main thread.
Call app.processEvents() in your loop. This gives Qt the chance to process events and redraw the GUI.
Break up your loop and let it run using a QTimer with a timeout of 0.
Using a thread is the best option, but involves quite a bit more work than just calling processEvents. Doing it with a timer is the old fashioned way and is not recommanded anymore. (see the documentation)
You have a basic misunderstanding of how such a GUI works. A Qt GUI has to run in an event loop of its own. Your loop runs instead, and the GUI can't do its work between the executions of your loop. That is, while your for loop is running the GUI code doesn't get CPU time and won't update.
You can set up a timer with an event, and execute your code in handlers of this event a set amount of time - this will solve your problem.
Or you can just call repaint() it update the GUI instantly.