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...
Related
I'm running a script coded in python from a scripts menu in a desktop application. It's basically a giant macro that I wrote and added a GUI to. I'm pretty sure the GUI is a really old one that my desktop app uses called dialogKit from MIT.
GitHub still has it here.
The problem is the word "stop" at the very end of the dialog code.
I keep getting a "stop is undefined" message, which I understand, but I've tried everything to close the dialog and if I use exit(), sys.exit(), I don't get an error, but it also closes my entire desktop app.
I need to close the dialog and keep the software open.
The limited dialog documentation for what I'm using can be found here.
(you might have to click on the Dialog section. Their site uses frames.)
class MyDialog:
def __init__(self):
self.d = Dialog(self)
self.d.size = Point(300, 340)
self.d.Center()
self.d.title = "Halftone" #<----- Title of the dialogue
self.d.AddControl(STATICCONTROL, Rect(aIDENT, aIDENT, aIDENT, aIDENT), "frame", STYLE_FRAME)
# more controls and methods..
def on_ok(self, code):
return 1
def on_cancel(self, code):
print "blah"
def Run(self):
return self.d.Run()
d = MyDialog()
if d.Run()!= 1:
stop
I just need a way to change stop to something that 1) will prevent the script from running, and 2) close the dialog without quitting the entire application. This is the functionality of a typical "cancel" button, which is what I want.
Another option is the method called on_cancel(), which I also tried and could get the event itself to work, but still the entire application quits with any kind of exit().
The docs show a method called End(), which claims to terminate the dialog object, but I've tried and failed to get that to work either.
Okay, I'm posting an answer because I think I have a handle on your problem.
Try replacing stop with:
d.d.End()
If that works, you might want to try putting:
self.d.End()
inside of the on_cancel function in your class. That should close the dialogue without closing your program.
I have made a UI window for Autodesk Maya 2015. Every time I run the script within Maya's script editor, it works fine, the UI gets launched and it docks properly on the main Maya window.
However, after closing the UI window and rerunning the script, the window doesn't dock anymore. It gets stuck on the main screen and I have to close the whole software. It seems that the UI can't find the main Maya Window anymore after I close it the first time round. I'm not sure how to fix this. Can anyone give me some advice on how to fix this problem?
Here is my code:
def getMayaWindow():
ptr = apiUI.MQtUtil.mainWindow()
if ptr is not None:
return shiboken.wrapInstance(long(ptr), QtGui.QMainWindow)
class pipeWindow(formClass,baseClass):
def __init__(self, parent=getMayaWindow()):
super(pipeWindow,self).__init__(parent)
self.setupUi(self)
self.setObjectName('pipe_window')
try:
cmds.deleteUI('dockPane')
except:
pass
self.pane = cmds.paneLayout('dockPane', cn='single')
if 'pipeDock' not in cmds.lsUI(ctl=1) :
cmds.dockControl('pipeDock', con=self.pane, area='right',
allowedArea = ['right','left'], label =
"ANMD_Pipeline", w=365)
else:
pass
cmds.control( 'pipe_window', e=True, p=self.pane)
After trying out multiple things, I have managed to resolve this issue. I have used
if cmds.dockControl('pipeDock', q=1, ex=1):
cmds.deleteUI('pipeDock')
Then I have a super(className, self).closeEvent(event) inside a closeEvent function. This did the trick for me. This did the trick for me. Previously I had the cmds.deleteUI within my closeEvent function which did not work, I suspect it was because I did not close the Event itself, so the UI just hides itself whenever I press the cross button instead of deleting it.
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.
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)
Using Win32GUI and Watsup, I'm writing a bit of Python code to automate a search across a database that is accessed through a program that doesn't come with an interface for it. As such, I can take a string from a list and then input it into the search box and press 'lookup'.
However, when the search returns more than 1000 results, the program throws a warning dialog --which is simply a notification of the number of results--which halts the execution of the Python code. I can't get the code to progress past the line where it presses lookup.
At a guess, this would be because it doesn't expect a window or know how to handle a warning--but I don't either, other than manually accepting it. Below is the relevent sample of code, though it's probably not very enlightening. After "clickButton(LookupButton)", the execution halts.
LookupButtonlocation = elemstring.find("Lookup", AuthNameFieldlocation) - 15
#Use Regex search to find handles
number_regex = re.compile(';(\d+);')
AuthNameEdit = int(number_regex.search(elemstring[AuthNameFieldlocation:]).group(1))
LookupButton = int(number_regex.search(elemstring[LookupButtonlocation:]).group(1))
#Input new Author into Edit Field
setEditText(AuthNameEdit, "John Campbell")
#Click lookup button
clickButton(LookupButton)
I'm not a WATSUP user, but I do something very similar using pywinauto - in my case I'm running a number of automated tests that open various 3rd party programs that, in a similar way, throw up inconvenient warning dialogs. It's a bit difficult to deal with dialogs that you don't know about, however if you do know which dialogs appear, but not when they appear, you can start a thread to just deal with those pop-ups. The following is a simple example from what I'm doing, and uses pywinauto but you could adapt the approach for WATSUP:
import time
import threading
class ClearPopupThread(threading.Thread):
def __init__(self, window_name, button_name, quit_event):
threading.Thread.__init__(self)
self.quit_event = quit_event
self.window_name = window_name
self.button_name = button_name
def run(self):
from pywinauto import application, findwindows
while True:
try:
handles = findwindows.find_windows(title=self.window_name)
except findwindows.WindowNotFoundError:
pass #Just do nothing if the pop-up dialog was not found
else: #The window was found, so click the button
for hwnd in handles:
app = application.Application()
app.Connect(handle=hwnd)
popup = app[self.window_name]
button = getattr(popup, self.button_name)
button.Click()
if self.quit_event.is_set():
break
time.sleep(1) #should help reduce cpu load a little for this thread
Essentially this thread is just an infinite loop that looks for a pop-up window by name, and if it finds it, it clicks on a button to close the window. If you have many pop-up windows you can open one thread per popup (bug that's not overly efficient, though). Because it's an infinite loop, I have the thread looking to see if an event is set, to allow me to stop the thread from my main program. So, in the main program I do something like this:
#Start the thread
quit_event = threading.Event()
mythread = ClearPopupThread('Window Popup Title', 'Yes button', quit_event)
# ...
# My program does it's thing here
# ...
# When my program is done I need to end the thread
quit_event.set()
This is not necessarily the only way to deal with your issue, but is a way that's worked for me. Sorry I can't really help you much with dealing with WATSUP (I always found pywinauto a bit easier to use), but I noticed on the WATSUP homepage (http://www.tizmoi.net/watsup/intro.html), Example 2 does something similar without using threads, i.e., looks for a named window and clicks a specific button on that window.