I have a game I'm working on, using text to inform the user what's going on in the game. However, I'm using a Tkinter window to enable input with buttons, for a more professional feel. I also included a few labels, but they're all packing into a new window rather than the one I already made. Don't ask about variable names, but this is the code:
def scan():
action=str('scan')
TSGcontrols.destroy()
return action
def lookaround():
action=str('look around')
TSGcontrols.destroy()
return action
def visoron():
action=str('engage visor')
global firstvisor
if firstvisor==1:
firstvisor=int(0)
print ('The visor has a colour code.')
print ('It displays a wire frame over black, based on sensor data -\nallowing you to see through walls (to a degree).')
print ('\nColours:')
print ('Green = inert object')
print ('Blue = electrical signature')
print ('Red = weapon signature')
print ('Yellow = radiation (the darker the yellow, the deadlier exposure would be)')
print ('White = life (the more grey, the weaker the lifesigns. Dark grey is dead.)')
print ('Purple = unidentified\n')
TSGcontrols.destroy()
return action
def visoroff():
action=str('disengage visor')
TSGcontrols.destroy()
return action
def personbasecreate():
global TSGcontrols
TSGcontrols=tkinter.Tk(screenName='/TSGcontrols',baseName='/TSGcontrols',className='/TSGcontrols')
warning=Label(text='This is only the control panel for TSG.\nThe game\'s responses are output in the Python window.',bg='red')
global location
locationw=Label(text='Location: {0}'.format(location))
controlling=Label(text='You are controlling only yourself.',bg='blue',fg='white')
lookaround=Button(text='Look around',command=lookaround)
visoron=Button(text='Turn visor on',command=visoron)
visoroff=Button(text='Turn visor off',command=visoroff)
scan=Button(text='Scan',command=scan)
warning.pack(parent=TSGcontrols,side='top')
locationw.pack(parent=TSGcontrols,side='top')
controlling.pack(parent=TSGcontrols,side='top')
lookaround.pack(side='left')
scan.pack(side='left')
if visor=='on':
visoroff.pack(parent=TSGcontrols,side='right')
else:
visoron.pack(parent=TSGcontrols,side='right')
groupw.pack(parent=TSGcontrols,side='bottom')
Then later on:
addbutton1 = str('no')
while repeat==str('yes'):
time.sleep(3)
print ('\nChoose your action.')
# Creating the basic window:
personbasecreate()
if addbutton1=='yes':
# Adding the additional function:
leavequarters.pack(in_=personGUI,side='left')
action=str(personGUI.mainloop())
But instead of the widgets appearing in the window called 'TSG Controls', they appear in a new one called 'tk' - so when the window is destroyed to allow the variable 'action' to be processed, it's destroying an empty window and the game crashes because the functions are trying to destroy a window that isn't there, throwing the error:
Exception in Tkinter callback
Traceback (most recent call last):
File "D:\Python\lib\tkinter\__init__.py", line 1489, in __call__
return self.func(*args)
File "D:\Python\TSG.py", line 881, in lookaround
personGUI.destroy()
File "D:\Python\lib\tkinter\__init__.py", line 1849, in destroy
self.tk.call('destroy', self._w)
_tkinter.TclError: can't invoke "destroy" command: application has been destroyed
When the button 'Look around' is clicked twice.
Is there any way to fix this code, or a simpler way to do what I'm trying to accomplish here?
Part of the problem is probably this:
personGUI=tkinter.Tk(className='/TSG Controls')
warning=Label(text='This is only the control panel for TSG.\nThe game\'s responses are output in the Python window.',bg='red')
Notice that you are creating a new root window, but then you create a label without specifying a parent. You should always specify a parent for a widget, and you should always only ever create a single instance of Tk for your entire application. If you need to create more than one window, create your root window once, and then for other windows you need to create instances of tkinter.Toplevel.
Related
I am making a GUI with tkinter that allows me to click a button that will run a port scan. I have a script for a port scan that functions correctly, I have managed to open the port scanner through the button on the GUI but then I receive an error that I otherwise don't receive when running the port scanner alone.
Exception in Tkinter callback
Traceback (most recent call last):
File "C:\Users\Steve\AppData\Local\Programs\Python\Python35-32\lib\tkinter\__init__.py", line 1550, in __call__
return self.func(*args)
File "<string>", line 51, in Scan
NameError: name 'IP_Input' is not defined
My code:
class CallWrapper:
"""Internal class. Stores function to call when some user
defined Tcl function is called e.g. after an event occurred."""
def __init__(self, func, subst, widget):
"""Store FUNC, SUBST and WIDGET as members."""
self.func = func
self.subst = subst
self.widget = widget
def __call__(self, *args):
"""Apply first function SUBST to arguments, than FUNC."""
try:
if self.subst:
args = self.subst(*args)
return self.func(*args) # THIS IS THE ERROR #
except SystemExit:
raise
except:
self.widget._report_exception()
class XView:
"""Mix-in class for querying and changing the horizontal position
of a widget's window."""
def xview(self, *args):
"""Query and change the horizontal position of the view."""
res = self.tk.call(self._w, 'xview', *args)
THIS IS THE CODE FOLLOWING FOR THE LINE 51 ERROR
def Scan():
print ('Scan Called.') #Debugging
IP = str(IP_Input.get(0.0, tkinter.END)) #THIS IS ERROR LINE 51#
print ("IP #Debugging")
Start = int(PortS.get(0.0, tkinter.END))
End = int(PortE.get(0.0, tkinter.END))
TestSocket = socket.socket()
CurrentPort = Start
OpenPorts = 0
print ('Starting scan...')
HowFar = int(CurrentPort/End * 100)
ProgText = HowFar, r'%'
Label1.config(text=('Percentage Done:', ProgText))
The problem is with your exec statement. You're opening your other .py file named port_scanner.py and then calling exec(open("./port scanner.py)).
This just isn't going to work.
Why this doesn't work:
When you do exec(open("path to .py file").read()) exec is of course executing this code, but the problem is that the global variables in this file aren't within the scope.
So, to make this work (which I don't recommend) you'd have to use:
exec(open(path).read(), globals())
From the documentation
If the globals dictionary does not contain a value for the key builtins, a reference to the dictionary of the built-in module builtins is inserted under that key. That way you can control what builtins are available to the executed code by inserting your own builtins dictionary into globals before passing it to exec().
If you really want to call your file this way then you should just use os.system.
Alternative approach:
You really don't need to call your file this way. You now have two instances of Tk() running. If you need another window then a widget is provided for this purpose. It is the Toplevel widget. You can restructure your code to create a Toplevel instance containing the port scanner app on your button click. An example being, create your port scanner app with the Toplevel widget (in your other file if you wish) then import the "app" into your file and on the button click have it initialize the app.
Additional Notes:
You're calling a while loop and if this runs (for any noticeable amount of time) then this is going to block the GUI's main event loop and causing your GUI to "hang".
Your first guess should not be that a part of the widely tested and used python standard library is flawed. The problem is (99.9% of the time)
while True:
print("In your own code.")
I have a simple application that I'm writing using PyQt 4 that includes a MainWindow that consists of a QMdiArea. When the user starts a new session (new experiment), two windows are generated. The user can also select from a list of analysis modules to also run.
I do not want any duplicates of any windows to be allowed (i.e., in the example code below the user can only have one file_tree_window, one data_plot_window, and/or one cell_monitor_window).
So, for example, if the user selects "New Experiment" once, two windows are generated. If he/she then selects "New Experiment" again, those original two windows should be closed and two new ones opened. The same is true for the any of the "Analysis Modules." (i.e. selecting one that is already open will cause the open one to close and a new one to open).
I'm having trouble generating this functionality though.
First off, code:
class MainWindow(QtGui.QMainWindow):
def __init__(self, model):
super().__init__()
self.resize(1400, 800)
self.model = model
menubar = self.menuBar()
new_experiment_action = QtGui.QAction("New Experiment", self)
new_experiment_action.triggered.connect(self.setup_new_experiment)
file_menu = menubar.addMenu("File")
file_menu.addAction(new_experiment_action)
cell_health_action = QtGui.QAction("Cell Health Monitor", self)
cell_health_action.triggered.connect(self.setup_cell_health_window)
analysis_menu = menubar.addMenu("Analysis Modules")
analysis_menu.addAction(cell_health_action)
self.file_tree = None
self.file_tree_window = None
self.data_plot = None
self.data_plot_window = None
self.cell_health = None
self.cell_health_window = None
self.mdi = QtGui.QMdiArea()
self.setCentralWidget(self.mdi)
def setup_new_experiment(self):
self.mdi.closeAllSubWindows()
self.file_tree = FileTree(self.model)
self.file_tree.setMinimumSize(QtCore.QSize(200, 300))
self.data_plot = DataPlot(self.model)
self.data_plot.setMinimumSize(self.size()*.4)
self.file_tree_window = self.mdi.addSubWindow(self.file_tree)
self.file_tree_window.show()
self.data_plot_window = self.mdi.addSubWindow(self.data_plot)
self.data_plot_window.show()
def setup_cell_health_window(self):
if self.cell_health_window:
self.mdi.removeSubWindow(self.cell_health_window)
self.cell_health = CellHealthMonitor(self.model)
self.cell_health.setMinimumSize(self.size()*.3)
self.cell_health_window = self.mdi.addSubWindow(self.cell_health)
self.cell_health_window.show()
This doesn't work in a number of ways:
If I open a "Cell Health Monitor" window, close it, and try to
re-open it, Python crashes
If I have a "Cell Health Monitor" window open, and then try to start a "New Experiment", followed by opening a "Cell Health Monitor" window, Python crashes
I have tried to use remove() instead of close():
i.e.:
def setup_new_experiment(self):
for window in self.mdi.subWindowList():
self.mdi.removeSubWindow(window)
self.file_tree = FileTree(self.model)
self.file_tree.setMinimumSize(QtCore.QSize(200, 300))
self.data_plot = DataPlot(self.model)
self.data_plot.setMinimumSize(self.size()*.4)
self.file_tree_window = self.mdi.addSubWindow(self.file_tree)
self.file_tree_window.show()
self.data_plot_window = self.mdi.addSubWindow(self.data_plot)
self.data_plot_window.show()
def setup_cell_health_window(self):
if self.cell_health_window:
self.mdi.removeSubWindow(self.cell_health_window)
self.cell_health = CellHealthMonitor(self.model)
self.cell_health.setMinimumSize(self.size()*.3)
self.cell_health_window = self.mdi.addSubWindow(self.cell_health)
self.cell_health_window.show()
Depending on the series of actions Python either crashes or I get one of the following two error messages:
(this one is actually just a warning):
QMdiArea::removeSubWindow: window is not inside workspace
or
Traceback (most recent call last):
File "MainWindow.py", line 61, in setup_cell_health_window
self.mdi.removeSubWindow(self.cell_health_window)
RuntimeError: wrapped C/C++ object of type QMdiSubWindow has been deleted
I'm at a loss with regards to what to do differently to remove a subwindow without producing these issues.
Thanks
In my PyQt4 application, there is a functionality that allows users to save a avi file.
To this aim, a saveMovie method has been implemented in the main window:
def saveMovie(self):
""" Let the user make a movie out of the current experiment. """
filename = QtGui.QFileDialog.getSaveFileName(self, "Export Movie", "",
'AVI Movie File (*.avi)')
if filename != "":
dialog = QtGui.QProgressDialog('',
QtCore.QString(),
0, 100,
self,
QtCore.Qt.Dialog |
QtCore.Qt.WindowTitleHint)
dialog.setWindowModality(QtCore.Qt.WindowModal)
dialog.setWindowTitle('Exporting Movie')
dialog.setLabelText('Resampling...')
dialog.show()
make_movie(self.appStatus, filename, dialog)
dialog.close()
My idea is to use a QProgressDialog to show how the video encoding work is proceeding.
Nevertheless, after the selection of the filename, the QFileDialog won't disappear and the entire application stays unresponsive until the make_movie function has completed.
What should I do to avoid this?
Lesson learned: if you have some long-running operations to do -- for example, reading or writing a big file, move them to another thread or they will freeze the UI.
Therefore, I created a subclass of QThread, MovieMaker, whose run method encapsulates the functionality previosly implemented by make_movie:
class MovieMaker(QThread):
def __init__(self, uAppStatus, uFilename):
QtCore.QThread.__init__(self, parent=None)
self.appStatus = uAppStatus
self.filename = uFilename
def run(self):
## make the movie and save it on file
Let's move back to the saveMovie method. Here, I replaced the original call to make_movie with the following code:
self.mm = MovieMaker(self.appStatus,
filename)
self.connect(self.mm, QtCore.SIGNAL("Progress(int)"),
self.updateProgressDialog)
self.mm.start()
Note how I defined a new signal, Progress(int).
Such a signal is emitted by the MovieMaker thread to update the QProgressDialog used to show the user how the movie encoding work is progressing.
I have a GUI designed in glade, using python/gtk in the background.I want to handle the delete event and display a "Are you sure?"-message dialog.I have been trying to handle the delete and destroy events, but failing to do so.any light?
#!/usr/bin/python
import .... stuff
class App:
def __init__(self):
self.gladefile = 'test.glade'
windowname = 'window'# This must match the window name in glade
self.wTree = gtk.glade.XML(self.gladefile, windowname)# object for acessing widgets
dic={
# Also need to set project2's signal tab
'on_window_delete_event':self.on_erro,
'on_window_destroy_event':self.on_erro,
}
self.wTree.signal_autoconnect (dic)
self.op=self.wTree.get_widget('window')
self.op.show()
def on_erro(self,widget,*args):
print 'hello'
app = App()
gtk.main()
This code opens a simple window .On clicking on close button, it prints hello and exits.(I want the window to remain open)
You have to return True in order to stop propagation of the delete event in the callback on_erro as mentioned in the documentation for "delete-event". In your current code, the callback is not returning any boolean value as required by the function, which I am guessing is returning False (Please check the signature for on_window_delete_event callback functions, the return type is boolean)
Hope this helps!
I have extended the UI file resulting from the Plugin builder with Qt Creator.
Just added some checkboxes and a combobox, named layercombo to the form.
The application is named jacktest.py. It uses an intermediate file jackdialog.py (generated from the plugin builder, left unchanged).
Compiled the UI file and the resource file. Then added some code to the plugin and tested this. It's no problem to get the available layer names in a QMessagebox. But how to add these to the combobox ?
Should be simple, but no option succeeds in referencing the combobox.
Error message: AttributeError: jacktest instance has no attribute 'layercombo'.
Result from my latest try:
# run method that performs all the real work
def run(self):
# create and show the dialog
dlg = jacktestDialog()
# show the dialog
dlg.show()
result = dlg.exec_()
for layer in self.iface.legendInterface().layers():
if layer.type() == QgsMapLayer.VectorLayer:
QMessageBox.information( self.iface.mainWindow(), "Info", layer.name())
self.layercombo.Items.Insert(0, layer.name())
# See if OK was pressed
if result == 1:
# do something useful (delete the line containing pass and
# substitute with your code
pass
You are trying to reference the current class (which is not your dialog) when you are setting the layercombo items
Replace:
self.layercombo.Items.Insert(0, layer.name())
with
dlg.ui.layercombo.Items.Insert(0, layer.name())
but you code still won't work correctly as exec_() is blocking and waits until it returns so you are adding items to an invisible dialog.
Try this instead:
# create and show the dialog
dlg = jacktestDialog()
# show the dialog
for layer in self.iface.legendInterface().layers():
if layer.type() == QgsMapLayer.VectorLayer:
QMessageBox.information( self.iface.mainWindow(), "Info", layer.name())
dlg.ui.layercombo.Items.Insert(0, layer.name())
result = dlg.exec_()
Went on in developing a Signal within the run module (code: def run (self):)
QObject.connect(dlg.ui.layercombo,SIGNAL('currentIndexChanged (int)'),self.select_one)
and the code of select_one is:
def select_one(self):
comboindex = dlg.ui.layercombo.currentIndex()
QMessageBox.information(self.iface.mainWindow(), "Info", comboindex)
Error message:
comboindex = dlg.ui.layercombo.currentIndex()
NameError: global name 'dlg' is not defined
Suppose I have to reference dlg as a parameter in the function call, but this is not working until now.