Multi-Window GUI using Python Traits - python

How can I create two windows using Python traits? Something like
if __name__ == '__main__':
main_gui = MainGUI()
user_input = UserInput()
main_gui.configure_traits()
user_input.configure_traits()
The issue here is that user_input.configure_traits isn't called until main_gui is closed, but since I'd like to have interaction between the two windows this obviously won't do. Is there perhaps a way to mimic a 'close' function within MainGUI and UserInput without actually closing the window, so that the main body of the code can move on? Thanks!

What you are trying to do here is not traithonic.
What you should do is set up the user input window as a child window of the Main window, and use traits' system of event listening to track the user's interaction. A very simple example:
class MainGUI(HasTraits):
GUIWidgetsForMainGUI = Any
user_window = Instance(UserInput)
button = Button('Get User Input')
traits_view=View(Item('GUIWidgetsForMainGUI'),
Item('button'))
def _button_fired(self):
self.user_window.edit_traits()
#on_trait_change('button')
def or_alternately_you_can_use_listeners(self):
self.user_window.edit_traits()
MainGUI().configure_traits()

Related

Why doesn't tkinter free the thread once the window has closed?

I have a class, ClientGUI (make in tkinter), which represents the client's class for my application. In this class, upon pressing a button, a new tkinter window is opened (using a module I imported), which represents a UI for a drawing tool. When I close this drawing tool window, I want it to return a message to the client class that the drawing tool has been closed. The problem is that I receive this message only when I close the main client window as well. The code for all this is below:
This is the code that declares and starts the main client window:
root = Tk()
root.resizable(False, False)
root.title("Client")
root.protocol("WM_DELETE_WINDOW", onX)
clientGui = clientGUI(root) //clientGUI is the client class. It contains all the UI and functionality elements
root.mainloop()
This is the function I use to close it:
def onX():
answer = tkinter.messagebox.askquestion("Quit", "Are you sure you want to quit the application ?")
if answer == "yes":
root.destroy()
In the clientGUI, upon pressing a button, a new window is opened from a module I created, this is the code that starts the new window:
def startDrawingTool(self, username, password):
drawingToolStatus = drawingTool.startTool() // the new window is opened here
print(drawingToolStatus) // This only gets printed when I close the main client window(clientGui)
This is the startTool() function, declared in the drawingTool module I imported:
def startTool():
def onX():
answer = tkinter.messagebox.askquestion("Quit", "Quiting without saving may result in data loss, make sure to always save first. Are you sure you want to quit?")
if answer == "yes":
root.destroy()
root = Tk()
root.resizable(False, False)
root.title("Drawing program")
root.protocol("WM_DELETE_WINDOW", onX)
app = Application(root) // Application is the drawing tool class, contains all the UI elements and functionality
root.mainloop()
return "window closed" // This is how I want to let the main client class know that the drawing tool has been closed
Hopefully, I have provided all the code that you need to get an idea of the application and the issue I am facing. As I already said, I want to know why the "print(drawingToolStatus)" is only printed when the clientGui gets closed and it doesn't in face get printed as soon as I close the drawingTool.
Also, if you know a better way to have the two communicate, I'm open to improvements as this is the first Python application I work on.

Is it possible to edit menu items in runtime in Python using gtk?

I am writing simple app in python, I want to write a menu using PyGtk. The problem is that under "Connect" menu item I want to have a list of avaliable devices which changes during program operation. So far my code for creating menu items is as below:
import gtk
import gobject
class Foo(object):
def __init__(self):
window = gtk.Window(gtk.WINDOW_TOPLEVEL)
gobject.timeout_add(2000, self.AddNewDevice_TEST)
table = gtk.Table(2,1,False)
window.add(table)
menubar = gtk.MenuBar()
self.connectMenu = gtk.Menu()
connectItem = gtk.MenuItem("Connect")
connectItem.set_submenu(self.connectMenu)
dev1 = gtk.MenuItem("device1")
dev1.connect("activate", self.connectToDev)
self.connectMenu.append(dev1)
menubar.append(connectItem)
table.attach(menubar, 0,1,0,1)
window.show_all()
def connectToDev(self, device):
pass
def AddNewDevice_TEST(self):
dev = gtk.MenuItem("device")
dev.connect("activate", self.connectToDev)
self.connectMenu.append(dev)
if __name__=='__main__':
gui = Foo()
gtk.main()
Problem is that when new device appears in my system or it is disconnected I want to add it or remove it from the list under "Connect".
I am able to edit list of devices in menu but after calling gtk.main() I can't make changes any more. Is there any way to do that in runtime?
You can change menu items during runtime in a different thread, e.g. GObject.idle_add(self.connectMenu.remove, dev1) or GObject.idle_add(self.connectMenu.append, dev1). The methods mentioned for GtkMenuShell in the GNOME Developer Documentation might be helpful.
Don't forget to call show() on the MenuItems as mentioned in the answer to this question.

How does the MainDialog() cycle work

I'm trying to understand how the cycle of my "main.py" works. It's based on examples found on the net, about the PySide and Qt Designer, to implement a Python GUI.
The code is:
#***********************************#
# Python Libraries #
#***********************************#
from PySide.QtCore import *
from PySide.QtGui import *
import sys
import time
#***********************************#
# Python files #
#***********************************#
import Gui
from server import *
class MainDialog(QDialog, Gui.Ui_TCPServer):
def __init__(self, parent=None):
super(MainDialog, self).__init__(parent)
self.setupUi(self)
self.connect(self.ConnectBt, SIGNAL("clicked()"), self.ConnectBt_clicked)
self.connect(self.QuitBt, SIGNAL("clicked()"), self.QuitBt_clicked)
self.connect(self.DisconnectBt, SIGNAL("clicked()"), self.DisconnectBt_clicked)
print "NOW HERE\r\n"
def ConnectBt_clicked(self):
self.ConnectBt.setText("Connecting...")
self.server_connect()
print "THEN HERE\r\n"
def QuitBt_clicked(self):
self.close()
def DisconnectBt_clicked(self):
self.ConnectBt.setText("Connect")
self.server_off = ChronoRequestHandler()
self.server_off.finish()
def server_connect(self):
self.server_on = ServerStart()
self.server_on.try_connect()
if __name__ == '__main__':
app = QApplication(sys.argv)
form = MainDialog()
print "HERE\r\n"
form.show()
app.exec_()
print "END\r\n"
When I call the "main.py" I get a print of "NOW HERE" and "THEN HERE". When I press the 'ConnectBt', I get the print of "THEN HERE".
But and after this, where the cycle remains? Does it returns to init, if so, shouldn't I get again the print of "NOW HERE"? Does it returns to main, if so, shouldn't I get the print of "HERE"? Please explain me...
When I press the 'QuitBt' I get the print of "END"... I'm confused!
Thanks.
I think you should get more clear on how object programming and events work.
In the last if-statement (the code on the bottom that runs when you call your script from e.g. terminal) you create an app object instance of QApplication.
After that you create form, instance of MainDialog which is the class you define above (inheriting methods, properties, etc from two classes, QDialog, Gui.Ui_TCPServer).
By doing
form = MainDialog()
you run __init__, print "NOW HERE" and go out of that method. Please check what __init__ does in Python. why-do-we-use-init-in-python-classes
Before the end you call the exec() method of the app instance. This contains a loop so that your interface gathers and processes events. See the documentation on QApplication.exec() below.
When you press the 'ConnectBt' button you call the ConnectBt_clicked() method, which does stuff (connects with the server) and prints "THEN HERE".
In the same way, when you press QuitBt you call QuitBt_clicked(), which closes the connection and lets the code print "END".
I also suggest you read more documentation about the classes you are using. They will explain how come that the different buttons are "linked"/have as callbacks the methods ConnectBt_clicked(), def QuitBt_clicked(), and DisconnectBt_clicked(). The mechanisms by which the buttons trigger these callbacks is kind of implicit in the code implemented in those classes.
QApplication Class Reference: exec_
int QApplication.exec_ ()
Enters the main event loop and waits until exit() is called, then
returns the value that was set to exit() (which is 0 if exit() is
called via quit()).
It is necessary to call this function to start event handling. The
main event loop receives events from the window system and dispatches
these to the application widgets.
Generally, no user interaction can take place before calling exec().
As a special case, modal widgets like QMessageBox can be used before
calling exec(), because modal widgets call exec() to start a local
event loop.
To make your application perform idle processing, i.e., executing a
special function whenever there are no pending events, use a QTimer
with 0 timeout. More advanced idle processing schemes can be achieved
using processEvents().
We recommend that you connect clean-up code to the aboutToQuit()
signal, instead of putting it in your application's main() function.
This is because, on some platforms the QApplication.exec() call may
not return. For example, on the Windows platform, when the user logs
off, the system terminates the process after Qt closes all top-level
windows. Hence, there is no guarantee that the application will have
time to exit its event loop and execute code at the end of the main()
function, after the QApplication.exec() call.
See also quitOnLastWindowClosed, quit(), exit(), processEvents(), and
QCoreApplication.exec().

Create a python tkinter window with no X (close) button

I'm writing a 'wizard' type Python Tkinter GUI that collects information from the user and then performs several actions based on the user's entries: file copying, DB updates, etc. The processing normally takes 30-60 seconds and during that time, I want to:
Provide the user with text updates on the activity and progress
Prevent the user from closing the app until it's finished what it's doing
I started on the route of having the text updates appear in a child window that's configured to be trainsient and using wait_window to pause the main loop until the activities are done. This worked fine for other custom dialog boxes I created which have OK/cancel buttons that call the window's destroy method. The basic approach is:
def myCustomDialog(parent,*args):
winCDLG = _cdlgWin(parent,*args)
winCDLG.showWin()
winCDLG.dlgWin.focus_set()
winCDLG.dlgWin.grab_set()
winCDLG.dlgWin.transient(parent)
winCDLG.dlgWin.wait_window(winCDLG.dlgWin)
return winCDLG.userResponse
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.userResponse = ''
def showWin(self):
#Tkinter widgets and geometry defined here
def _btnOKClick(self):
#self.userResponse assigned from user entry/entries on dialog
self.dlgWin.destroy()
def _btnCancelClick(self):
self.dlgWin.destroy()
However this approach isn't working for the new monitor-and-update dialog I want to create.
First, because there's no user-initiated action to trigger the copy/update activities and then the destroy, I have to put them either in showWin, or in another method. I've tried both ways but I'm stuck between a race condition (the code completes the copy/update stuff but then tries to destroy the window before it's there), and never executing the copy/update stuff in the first place because it hits the wait_window before I can activate the other method.
If I could figure out a way past that, then the secondary problem (preventing the user from closing the child window before the work's done) is covered by the answers below.
So... is there any kind of bandaid I could apply to make this approach work the way I want? Or do I need to just scrap this because it can't work? (And if it's the latter, is there any way I can accomplish the original goal?)
self.dlgWin.overrideredirect(1) will remove all of the buttons (make a borderless window). Is that what you're looking for?
As far as I know, window control buttons are implemented by the window manager, so I think it is not possible to just remove one of them with Tkinter (I am not 100% sure though). The common solution for this problem is to set a callback to the protocol WM_DELETE_WINDOW and use it to control the behaviour of the window:
class _cdlgWin():
def __init__(self,parent,*args):
self.parent = parent
self.dlgWin = tk.Toplevel()
self.dlgWin.protocol('WM_DELETE_WINDOW', self.close)
self.userResponse = ''
def close(self):
tkMessageBox.showwarning('Warning!',
'The pending action has not finished yet')
# ...

tk destroy() and grid_forget() don't always work

i'm hoping anyone can help me out here. i'm having an issue with a tkinter gui i built. the issue only happens in windows. My GUI creates a results frame with some labels in it, when it's time to calculate something else, the user clicks on the "newPort" button and that button is supposed to remove the results frame and set to False some instance attributes internal to the calculation. The issue i'm having, which is apparent only in windows is that sometimes the results frame, and its descendant labels don't disappear every time. Sometimes they do, sometimes they don't. The instance variable is correctly set to False but the widgets are still visible on the main GUI. The GUI also contains a couple checkboxes and radiobuttons but they don't impact the creation of the results frame nor its expected destruction. I have not been able to pin point a pattern of actions the user takes before clicking on the newPort button which causes the frame and labels to not get destroyed. This happens when i freeze my app with py2exe, as well as running the app from the python interpreter within the eclipse IDE. I have not tried running the app from the python interpreter directly (i.e. without the IDE) and this problem does not happen on my Mac when i run the app using the eclipse python interpreter. Thanks very much all! My code looks like this:
import Tkinter as TK
class widget(object):
def __init__(self,parent=None):
self.parent = TK.Frame(parent)
self.parent.grid()
self.frame = TK.Frame(self.parent)
self.frame.grid()
newLedger = TK.Button(self.parent,command=self.newPort).grid()
self.calcButton = TK.Button(self.frame,command=self.showResults)
self.calcButton.grid()
self.calcVariable = True
def newPort(self):
self.calcVariable = False
try:
self.second.grid_forget()
self.first.grid_forget()
self.resultsFrame.grid_forget()
self.second.destroy()
self.first.destroy()
self.resultsFrame.destroy()
except:
raise
self.frame.update_idletasks()
def showResults(self):
self.resultsFrame = TK.Frame(self.frame)
self.resultsFrame.grid()
self.first = TK.Label(self.resultsFrame,text='first')
self.first.grid()
self.second = TK.Label(self.resultsFrame,text='second')
self.second.grid()
if __name__ == '__main__':
root = TK.Tk()
obj = widget(root)
root.mainloop()
You don't need to destroy or call grid_forget on the labels, and you don't need to call grid_forget on the resultsFrame; when you destroy the resultsFrame it will cause all off its children to be destroyed, and when these widgets are destroyed they will no longer be managed by grid.
The only way I can get widgets to not be destroyed is if I click on the "calc" button twice in a row without clicking on the "new" button in-between. I'm doing this by running your program from the command line.

Categories