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
Related
I'm trying to create a gradio User Interface which does the following
on the left panel I have a File control, that allows the selection of a local file (eg. a .csv)
when a file is selected a "Process" button should be made visible
when the "Process" button is pressed, a function is called, reading the contents of the file, and processing it in some ways, resulting in a string
the resulting string is shown in a TextArea in the right column
I'm stuck implementing point 2. I can select the file, but can't make the Process button become visible.
This is my code so far (not yet implementing points 3. a:
import gradio as gr
def file_selected(file_input):
print("yes, file_selected is invoked")
print(process_button)
process_button.visible=True
demo.render()
return process_button
with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Data")
file_input = gr.File(label="Select File")
process_button = gr.Button("Process", visible=False)
with gr.Column(scale=2, min_width=600):
gr.Markdown("### Output")
result_display = gr.TextArea(default="", label="Result", lines=10, visible=False)
file_input.change(fn=file_selected, inputs=file_input, outputs=process_button)
if __name__ == "__main__":
demo.launch()
I see that at file selection the message is printed (and print(process_button) prints "button" so I'm sure this variable is not None), but the button doesn't appear on the page.
edited: fixed some errors not directly related to the problem.
There were many problems with the code (I fixed those not related with the main issue in the original post), but in the end what solved my problem (making the button visible) was that instead to rerender,
def file_selected():
...
process_button.visible=True
demo.render()
I just had to return the process_button.update
def file_selected(file_input):
...
return gr.update(visible=True)
(Actually this was documented in gradio's online docs; sorry, I didn't notice it before)
This is the complete working code:
import gradio as gr
def file_selected(file_input):
print("yes, file_selected is invoked")
print(process_button)
return gr.update(visible=True)
with gr.Blocks() as demo:
with gr.Row():
with gr.Column(scale=1):
gr.Markdown("### Data")
file_input = gr.File(label="Select File")
process_button = gr.Button("Process", visible=False)
with gr.Column(scale=2, min_width=600):
gr.Markdown("### Output")
result_display = gr.TextArea(default="", label="Result", lines=10, visible=False)
file_input.change(fn=file_selected, inputs=file_input, outputs=process_button)
if __name__ == "__main__":
demo.launch()
I work in GPG key generation programm with Python-GnuGP lib (just to make my life easier). I have used PyQt for the UI and to get values as well as two classes (one for the main up and the other for the dialog) to gen my key. The problem is that when I execute the GenButton I get the following errors in terminal:
Key-Type: RSA
Key-Length: 4096
Passphrase: 1202
Name-Real: TestUser
Name-Email: test#gmail.com
Expire-Date: 2019-3-3
%commit
key not created
Here is my code: I tested it with build in values and all worked fine, but when I pass values from the QlineEdit boxes I get the above error.
class GenKeyDialog(QtGui.QDialog, Ui_GPGGenerateWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self.gpg = gnupg.GPG(homedir='keys')
self.gpg.encoding = 'utf-8'
self.GenButton.clicked.connect(self.genkey)
def genkey(self):
type = "RSA"
length = 4096
name = str(self.NameEdit.text())
email = str(self.EmailEdit.text())
if str(self.PassEdit.text()) == str(self.RPassEdit.text()):
passs = str(self.PassEdit.text())
else:
print "passwords do not match"
return
expire = str(self.ExpireEdit.text())
input_data = self.gpg.gen_key_input(key_type=type, key_length=length, name_real=name, name_email=email, passphrase=passs, expire_date=expire)
print input_data
key = self.gpg.gen_key(input_data)
print key
class Main_window_ex(QMainWindow, Ui_MainWindow):
def createPGP(self):
dialog = GenKeyDialog()
ret = dialog.exec_()
def __init__(self, parent = None):
"""
Default Constructor. It can receive a top window as parent.
"""
Screenshot:
You are implementing Main_window_ex.__init__ as an empty function. When instances of that class are initialised, nothing will happen.
I suspect that what you want is to have the Main_window_ex instances initialise exactly like its parent class. If that's what you want, don't implement a specific Main_window_ex.__init__.
You would have learned this when you worked through the Python tutorial, specifically its chapter on classes and inheritance. Hopefully this is a reminder :-)
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.
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 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.