I have a standard QProgressDialog with a cancel button. If/When the user clicks the cancel button, I don't want the dialog to immediately hide, instead I would prefer to disable the cancel button and perform some clean-up work, and then close the QProgressDialog once I'm sure this work is complete.
How to I intercept the current function?
From the docs it seems like I should be overwriting:
PySide.QtGui.QProgressDialog.cancel()
Resets the progress dialog. PySide.QtGui.QProgressDialog.wasCanceled()
becomes true until the progress dialog is reset. The progress dialog
becomes hidden.
I've tried subclassing this method but it doesn't even seem to be called when I click the cancel button.
To disable the button of the dialog you have to get a reference to it. Since it is a basic QPushButton, you can use findChild():
dialog = QProgressDialog(self)
cancelButton = dialog.findChild(QPushButton)
cancelButton.setEnabled(False)
Consider that disabling a button that would never get enabled is annoying from the UX point of view, so a better choice would be to not show it at all, and setCancelButton() explains how to do it:
If nullptr is passed, no cancel button will be shown.
In python terms, nullptr means None:
dialog = QProgressDialog(self)
dialog.setCancelButton(None)
Unfortunately, this won't prevent the user to cancel the dialog by closing it or by pressing Esc.
This is valid for any QDialog, and, to properly avoid that, subclassing is the better choice: you need to prevent rejecting the dialog (the Esc key) and the close event. While they have similar results, they are handled in different ways.
Overriding reject() (and doing nothing) prevents any action that would trigger a rejection (cancelling), including pressing Esc.
Overriding the closeEvent() requires an extra step: you have to ensure that the event is spontaneous() (triggered by the system - normally, the user presses the close button of the window), and eventually ignore that. This is necessary as you might need to call close() or accept() to actually close the dialog upon completing the process.
class NonStopProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setCancelButton(None)
def reject(self):
pass
def closeEvent(self, event):
if event.spontaneous():
event.ignore()
Note that there is no direct way to know if the spontaneous close event is directly triggered by the user (trying to close the window), or the system (while shutting down).
Also note that if you do need to close the dialog programmatically, you either call accept(), or you call the base implementation, which allows you to get the proper return value from the dialog's reject():
def rejectNoMatterWhat(self):
super().reject()
Finally, if, for any reason, you still need the cancel button, you have to disconnect its signals.
In general, this might do the work:
dialog = QProgressDialog(self)
cancelButton = dialog.findChild(QPushButton)
cancelButton.disconnect()
But the above would disconnect any signal to any slot, and there are some cases for which this should be avoided.
We know from the sources that the clicked signal is actually connected to the canceled() slot, so a better solution would be to do the following instead:
dialog = QProgressDialog(self)
cancelButton = dialog.findChild(QPushButton)
cancelButton.clicked.disconnect(self.canceled)
Since you may need to be notified about that in the parent/main class, a more appropriate solution would be to create a custom signal in the subclass used above:
class NonStopProgressDialog(QtWidgets.QProgressDialog):
userCancel = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
cancelButton = self.findChild(QPushButton)
cancelButton.clicked.disconnect(self.canceled)
cancelButton.clicked.connect(
lambda: cancelButton.setEnabled(False))
cancelButton.clicked.connect(self.userCancel)
def reject(self):
pass
def closeEvent(self, event):
if event.spontaneous():
event.ignore()
class SomeWindow(QtWidgets.QWidget):
def showProgress(self):
self.progressDialog = NonStopProgressDialog(self)
self.progressDialog.userCancel.connect(self.stopSomething)
# ...
def stopSomething(self):
self.progressDialog.setCancelButtonText('Please wait')
# do something...
Related
I'm using PyQt5 to create a program. I created 3 Radio Buttons, but when I check the first button and check the second button after that. The program will run both of the functions which are connected to these buttons. How I can make it only run the function which is connected to that button. Thanks.
def __init__(self):
super(Program, self).__init__()
self.ui = Ui_APIManager()
self.ui.setupUi(self)
self.show()
self.ui.add_btn.toggled.connect(self.start)
self.ui.check_btn.toggled.connect(self.start)
self.ui.delete_btn.toggled.connect(self.start)
def start(self):
if self.ui.add_btn.isChecked():
self.ui.third_lbl.setEnabled(True)
self.ui.first_lbl.setText('Tool name')
self.ui.second_lbl.setText('ID')
self.ui.third_lbl.setText('Username')
self.ui.action_btn.clicked.connect(self.add_user)
elif self.ui.check_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.check_user)
elif self.ui.delete_btn.isChecked():
self.ui.first_lbl.setText('Type of search')
self.ui.second_lbl.setText('Keyword')
self.ui.third_lbl.setEnabled(False)
self.ui.action_btn.clicked.connect(self.delete_user)
Qt signals can have multiple slots attached to them. Every time you click a button the start function is adding another connection to the action_button.clicked signal.
You will need to disconnect any existing slots from the signal first to achieve the desired behaviour. You can disconnect everything from self.ui.action_btn all at once by calling its disconnect() function.
Rather than trying to reassign the roles of the GUI elements you have created, you would be better off creating separate widgets containing the elements for each checkbox state and switching between them. You might find QStackedWidget useful.
Is there a way to lock docks in pyqtgraph so that the user cannot move them around?
I'm using a small touchscreen to display a pyqtgraph application with multiple docks. It is very easy for the user to accidentally move a dock. When that happens the screen becomes unusable because of the size. I would like to prevent the user from moving the docks.
However, the user must still be able to choose between docks (i.e. treat them like a tab widget).
Just to be clear, I want to prevent a dock from being detached and I want to prevent the dock from being drug to a new location.
Thanks,
Chris
I managed to disable the ability to detach and drag docks by overriding the Dock class' methods.
Dragging a dock moves it to another location. So I overrode all of the 'drag' event handlers with methods that do nothing (i.e. a no-op).
Double-clicking on a dock's label will cause the dock to detach. So, I overrode the dock's label's double-click event handler with a no-op.
Replace Dock with MyDock in your code. UPDATE: I added code to override the drag methods for the DockArea too because I was still able to move DockAreas around.
Here is the code:
##
# This class is used to eliminate a standard Dock class' ability to detach and
# move (i.e. dragging this Dock will have no effect)
#
class MyDock(Dock):
def __init__(self, name, area=None, size=(10, 10), widget=None, hideTitle=False, autoOrientation=True):
# Initialize the baseclass
#
Dock.__init__(self, name, area, size, widget, hideTitle, autoOrientation)
# Override the label's double click event. Normally double clicking
# the dock's label will cause it to detach into it's own window.
#
self.label.mouseDoubleClickEvent=self.noopEvent
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pass
def noopEvent(self,ev):
pass
class MyDockArea(DockArea):
def dragEventEnter(self, ev):
pass
def dragMoveEvent(self, ev):
pass
def dragLeaveEvent(self, ev):
pass
def dragDropEvent(self, ev):
pas
I have created a ui from scratch using the commands within Maya documentation.
The following function that I have wrote applies in two scenerios:
When the user has clicked onto another button - Import, in which it will do as what it was written in the code then close it off with the following function (see readFile function)
When user has clicked onto the button where it close the UI without running anything.
In my script, to cater the above two scenarios, I wrote as the following where closeWindow is catered to Scenario1 and cancelWindow is catered to Scenario2
def ui(self):
...
cancelButton = cmds.button( label='Cancel', command=self.cancelWindow, width=150, height=35)
def closeWindow(self, *args):
cmds.deleteUI(self.window, window=True)
def cancelWindow(self, *args):
cmds.delete(camSel[0])
cmds.deleteUI(self.window, window=True)
def readFile(self, *args):
...
self.closeWindow()
As such, is it possible to create some sort of signal like those in PyQt (clicked(), returnPressed() etc) by combining the above 2 (automated + manual), seeing that the deleteUI command usage is the same?
Default Maya UI provides only callbacks, not signals. You can create a sort of 'pseudo signal' by calling an event handler object instead of a function. In that scenario the button only knows 'I fired the button event' and the handler can call as many functions as needed.
class Handler(object):
def __init__(self):
self.handlers = []
def add_handler (self, func):
self.handlers.append(func)
def __call__(self, *args, **kwargs):
for eachfunc in handler:
eachfunc(*args, **kwargs)
hndl = Handler()
hndl.add_handler(function1) # do some ui work...
hndl.add_handler(function2) # do some scene work...
hndl.add_handler(function3) # do something over network, etc....
b = cmds.button('twoFunctions', c = Hndl)
In a large complex UI this is a nice way to keep minor things like button higlights and focus changes separated out from important stuff like changing the scene. In your application it's almost certainly overkill. You've only sharing 1 line between close and cancel, that's not too bad :)
Heres' more background on on pseudo-events in maya gui.
You can also use Maya's QT directly to get at the close event... Again, seems like overkill. More here
I have designed a gtk3 layout with Glade, including some comboboxtext widgets.
The bookings_tour_selector ComboBoxText has the changed signal connected, so when user selects an option, this is detected. That part works fine.
Now the problem, when I make the call: bookings_tour_selector.remove_all() the changed signal is triggered once for every single item being removed. That's not the expected behaviour. I expect it to not trigger the signal at all.
How to prevent this signal to be triggered when removing items?
Just add conditional in your callback, i.e:
def on_changed_combobox (self, widget):
if self.bookings_tour_selector.get_active () != -1:
#do whatever you want when combo box changed
#if not, it simply, does nothing
I have created a PyGTK application that shows a Dialog when the user presses a button.
The dialog is loaded in my __init__ method with:
builder = gtk.Builder()
builder.add_from_file("filename")
builder.connect_signals(self)
self.myDialog = builder.get_object("dialog_name")
In the event handler, the dialog is shown with the command self.myDialog.run(), but this only works once, because after run() the dialog is automatically destroyed. If I click the button a second time, the application crashes.
I read that there is a way to use show() instead of run() where the dialog is not destroyed, but I feel like this is not the right way for me because I would like the dialog to behave modally and to return control to the code only after the user has closed it.
Is there a simple way to repeatedly show a dialog using the run() method using gtkbuilder? I tried reloading the whole dialog using the gtkbuilder, but that did not really seem to work, the dialog was missing all child elements (and I would prefer to have to use the builder only once, at the beginning of the program).
[SOLUTION] (edited)
As pointed out by the answer below, using hide() does the trick. I first thought you still needed to catch the "delete-event", but this in fact not necessary. A simple example that works is:
import pygtk
import gtk
class DialogTest:
def rundialog(self, widget, data=None):
self.dia.show_all()
result = self.dia.run()
self.dia.hide()
def destroy(self, widget, data=None):
gtk.main_quit()
def __init__(self):
self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
self.window.connect("destroy", self.destroy)
self.dia = gtk.Dialog('TEST DIALOG', self.window,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
self.dia.vbox.pack_start(gtk.Label('This is just a Test'))
self.button = gtk.Button("Run Dialog")
self.button.connect("clicked", self.rundialog, None)
self.window.add(self.button)
self.button.show()
self.window.show()
if __name__ == "__main__":
testApp = DialogTest()
gtk.main()
Actually, read the documentation on Dialog.run(). The dialog isn't automatically destroyed. If you hide() it when the run() method exits, then you should be able to run() it as many times as you want.
Alternatively, you can set the dialog to be modal in your builder file, and then just show() it. This will achieve an effect that's similar, but not quite the same as run() - because run() creates a second instance of the main GTK loop.
EDIT
The reason you are getting a segmentation fault if you don't connect to the delete-event signal is that you are clicking the close button twice. Here is what happens:
You click "Run Dialog", this calls the dialog's run() method.
The modal dialog appears, and starts its own main loop.
You click the close button. The dialog's main loop exits, but since run() overrides the normal behavior of the close button, the dialog is not closed. It is also not hidden, so it hangs around.
You wonder why the dialog is still there and click the close button again. Since run() is not active anymore, the normal behavior of the close button is triggered: the dialog is destroyed.
You click "Run Dialog" again, which tries to call the run() method of the destroyed dialog. Crash!
So if you make sure to hide() the dialog after step 3, then everything should work. There's no need to connect to the delete-event signal.
Your dialog should only need to run once. Assuming a menu item triggers the dialog, the code should look something like this:
def on_menu_item_clicked(self, widget, data=None):
dialog = FunkyDialog()
response = dialog.run()
if response = gtk.RESPONSE_OK:
// do something with the dialog data
dialog.destroy()
dialog.run() is a blocking main-loop that returns when the dialog send a response. This is normally done via the Ok and Cancel buttons. When this happens, the dialog is finished and needs to be destroyed.
To show the dialog repeatedly, the user should follow the same workflow (in the example above, that would be clicking on a menu item). The dialog is responsible, in __init__, for setting itself up. If you hide() the dialog, you have the problem of communicating with that dialog so it stays up-to-date with the rest of the application even when it's hidden.
One of the reasons some people want to "run the dialog repeatedly" is because the user has entered invalid information, and you want to give the user the opportunity to correct it. This must be dealt with in the dialog's response signal handler. The order of events in a dialog is:
User physically pushes the Ok button
Dialog sends the response gtk.RESPONSE_OK (-5)
Dialog calls the handler for the response signal
Dialog calls the handler for the Ok button
Dialog run() method returns the response
To prevent steps 4 and 5 from happening, the response handler must suppress the response signal. This is achieved as follows:
def on_dialog_response(self, dialog, response, data=None:
if response == gtk.RESPONSE_OK:
if data_is_not_valid:
# Display an error message to the user
# Suppress the response
dialog.emit_stop_by_name('response')
I just spent some time figuring this out. Re-fetching the same object from a builder will not create a new instance of the object, but only return a reference to the old (destroyed) object. If you create a new builder instance, however, and load your file into the new builder, it will create a new instance.
So my dialog creation function looks something like this:
def create():
builder = gtk.Builder()
builder.add_from_file('gui/main.ui')
dlg = builder.get_object('new_dialog')
def response_function(dialog, response_id):
... do stuff ...
dialog.destroy()
dlg.connect('response', response_function)
dlg.show_all()
Note that I am not blocking for a response with run() in this case because I'm using twisted, but it should be equivalent.