I am connecting a QPushButton in which it will either hide/ show the widgets within a Frame.
I loaded/ created my GUI using the .ui method.
For this QPushButton, I have set and checked the attribute setChecked.
class MyWindow(QtGui.QWidget):
def __init__(self):
...
# self.informationVisBtn, `setChecked` and `setCheckable` field is checked in the .ui file
self.informationVisBtn.toggled.connect(self.setInfoVis)
def setInfoVis(self):
self.toggleVisibility(
self.informationVisBtn.isChecked()
)
def toggleVisibility(self, value):
if value:
self.uiInformationFrame.show()
self.informationVisBtn.setText("-")
else:
self.uiInformationFrame.hide()
self.informationVisBtn.setText("+")
While loading my code on the first try, I noticed that the informationVisBtn, while it is checked, the frame is being shown but the text did not gets set to - and instead it remains as a + as set in my .ui file.
Unless in the __init__(), if I add in setInfoVis() before setting the connection, only will the text be populated correctly.
Does the use of toggled not trigger the state at the start? Appreciate in advance for any replies.
The signal is emited when there is a change of state and notify the slots that are connected up to that moment. When a new slot is connected, it will only be notified if there is a change of status after the connection so it is always advisable to update the status as signals. On the other hand it is not necessary to create setInfoVis() method since toggled transmits the state information.
class MyWindow(QtGui.QWidget):
def __init__(self):
super(MyWindow, self).__init__()
# ...
self.informationVisBtn.toggled.connect(self.toggleVisibility)
# update the state it has since the connection
# was made after the state change
self.toggleVisibility(
self.informationVisBtn.isChecked()
)
#QtCore.pyqtSlot(bool)
def toggleVisibility(self, value):
self.uiInformationFrame.setVisible(value)
self.informationVisBtn.setText("-" if value else "+")
Related
I'm working on a plugin for QGis 3.x. The UI was created with Qt Designer and the plugin code boilerplate by Plugin builder.
I do have 3 main files in my project :
my_plugin.py with the main MyPlugin class and initGui(), unload() and run() methods
my_plugin_dialog.py with a simple MyPluginDialog class, that inherits from QtWidgets.QDialog and the FORM_CLASS based on my .ui designer file. The class only contains __init__() method, itself calling self.setupUi()
an UI file created by Qt Designer my_plugin_dialog_base.ui
I encounter some issues related to duplicate signals when starting/closing the plugin dialog.
The most similar discussion on SO is the following : https://gis.stackexchange.com/questions/137160/qgis-plugin-triggers-function-twice
Yet, my problem is that my dialog object is created within the run() method (see below), so I can't access to my UI elements in the initGui() nor unload() methods.
So how can I disconnect signals, or define them so that closing the plugin also disconnects all my signals ?
Here is an excerpt of my_plugin.py content (created by Plugin Builder, except the last line related to connecting the signal and the slot) :
def initGui(self):
"""Create the menu entries and toolbar icons inside the QGIS GUI."""
icon_path = ':/plugins/my_plugin/icon.png'
self.add_action(
icon_path,
text=self.tr(u'Start My Plugin'),
callback=self.run,
parent=self.iface.mainWindow())
# will be set False in run()
self.first_start = True
def run(self):
"""Run method that performs all the real work"""
# Create the dialog with elements (after translation) and keep reference
# Only create GUI ONCE in callback, so that it will only load when the plugin is started
if self.first_start == True:
self.first_start = False
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start) # connect signal to slot
And my_plugin_dialog.py:
FORM_CLASS, _ = uic.loadUiType(os.path.join(
os.path.dirname(__file__), 'small_etl_dialog_base.ui'))
class MyPluginDialog(QtWidgets.QDialog, FORM_CLASS):
def __init__(self, parent=None):
"""Constructor."""
super(MyPluginDialog, self).__init__(parent)
# Set up the user interface from Designer through FORM_CLASS.
# After self.setupUi() you can access any designer object by doing
# self.<objectname>, and you can use autoconnect slots - see
# http://qt-project.org/doc/qt-4.8/designer-using-a-ui-file.html
# #widgets-and-dialogs-with-auto-connect
self.setupUi(self)
Maybe there is a proper way to access dialog objects inside the initGui() method ?
This is happening because you connect your signal every time the run method is called. However, you only create your dialog once guarded by a Boolean variable.
The solution is to only connect your signal when you create the dialog, so something like this would work better:
def run(self):
...
if self.first_start == True:
self.first_start = False
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start)
...
Also, note that you do not necessarily need the self.first_start boolean guard. You could always check whether self.dlg is not None instead.
So, you could tidy this up a bit by something like this:
def run(self):
...
if not self.dlg:
self.dlg = MyPluginDialog()
self.dlg.push_button_start.clicked.connect(self.on_pb_start)
...
I want to connect a trigger generated from PushButton to my custom signal with an argument(Signal(int)).
It is possible to connect a signal without an argument to the Button. Currently, I am creating an extra slot to emit the Signal(int) triggered from Signal() and Button. Is there any simple way to this?
class GUI(QObject):
sig_x = Signal()
sig_y = Signal(int)
def __init__(self,parent = None):
super(GUI,self).__init__(parent)
self.value = 10
self.button = QPushButton("Click me")
self.button.clicked.connect(self.sig_x)
#self.button.clicked.connect(self.sig_y(self.value)) want to do something like this
self.sig_x.connect(self.pass_args)
self.sig_y.connect(self.final_function)
#connection slot for sig_x
def pass_args(self):
self.sig_y.emit(self.value)
#demo connection slot for sig_y
def final_function(self,passed_value):
print("passed value is " + str(passed_value))
The reason this won't work:
self.button.clicked.connect(self.sig_y(self.value))
is because connect() needs to be passed a function (or, more precisely anything that can be called). But written like this, you're not passing it the signal itself, you're calling the signal and passing in the result.
One solution is a lambda function. A lambda is a way to define a function inline; this way you define a new function right there, in the same line where you pass it to connect.
self.button.clicked.connect(lambda x: self.sig_y(self.value))
You can see some more context of how this works in this quesiton, even though the question itself is about getting a slightly more complex version to work.
This is all assuming that you need this signal structure for reasons not immediately evident in your example. If not, you could simply connect the button clicked signal directly to your final_function, with no need for custom signals.
class GUI(QObject):
def __init__(self, parent=None):
super().__init__(parent)
self.value = 10
self.button = QPushButton("Click me")
self.button.clicked.connect(self.final_function)
def final_function(self, state):
print(f"My value is {self.value}.")
And even then, if you need a signal for notifying other things, you could also add that to the method:
def final_function(self, state):
print(f"My value is {self.value}.")
self.sig_y.emit(self.value)
This question already has an answer here:
Progress Bar Does not Render Until Job is Complete
(1 answer)
Closed 2 years ago.
I am using PyQt5 to write an app that manages Sales Orders. When creating an Order or deleting itI want to display a marqee style progress dialog to indicate that the app is working. I have visited a lot of posts where the answer involved using QThread.I have tried to implement it but it seems I am missing something. This is my threading class.
class Worker(QThread):
finished = Signal()
def run(self):
self.x = QProgressDialog("Please wait..",None,0,0)
self.x.show()
def stop(self):
self.x.close()
In the Main window's init I create self.worker=Worker()
Now the code for deleting an entry is for example:
msg = MsgBox("yn", "Delete Order", "Are you sure you want to delete this order?") # Wrapper for the QMessageBox
if msg == 16384:
self.worker.start() ## start the worker thread, hoping to start the progress dialog
session.delete(order) ##delete order from db
session.commit() ##commit to db
self.change_view("Active", 8) ##func. clean up the table.
self.worker.finished.emit() ##emit the finished signal to close the progress dialog
The result is no progress dialog being displayed. The gui just freezes for a second or two and then the entry deletes without any progress dialog being displayed.
Sorry my code is quite long so I couldn't include it all here, I just wanted to see if I got something terribly wrong.
There are two main problems with your code:
GUI elements (everything inherited or related to a QWidget subclass) must be created and accessed only from the main Qt thread.
assuming that what takes some amount of time is the delete/commit operations, it's those operation that must go in the thread while showing the progress dialog from the main thread, not the other way around.
Also, consider that QThread already has a finished() signal, and you should not overwrite it.
This is an example based on your code:
class Worker(QThread):
def __init__(self, session, order):
super.__init__()
self.session = session
self.order = order
def run(self):
self.session.delete(self.order)
self.session.commit()
class Whatever(QMainWindow):
def __init__(self):
super().__init__()
# ...
self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
def deleteOrder(self, session, order):
msg = MsgBox("yn", "Delete Order",
"Are you sure you want to delete this order?")
if msg == MsgBox.Yes: # you should prefer QMessageBox flags
self.worker = Worker(session, order)
self.worker.started(self.progressDialog.show())
self.worker.finished(self.deleteCompleted)
self.worker.start()
def deleteCompleted(self):
self.progressDialog.hide()
self.change_view("Active", 8)
Since the progress dialog should stay open while processing, you should also prevent the user to be able to close it. To do that you can install an event filter on it and ensure that any close event gets accepted; also, since QProgressDialog inherits from QDialog, the Esc key should be filtered out, otherwise it will not close the dialog, but would reject and hide it.
class Whatever(QMainWindow):
def __init__(self):
super().__init__()
# ...
self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
self.progressDialog.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.progressDialog:
# check for both the CloseEvent *and* the escape key press
if event.type() == QEvent.Close or event == QKeySequence.Cancel:
event.accept()
return True
return super().eventFilter(source, event)
I have a big app and in some QPlaintextEdits.
I need to check the input when the user ends with the input he wants.
If check fails input should be updated.
I suppose it is something with focus, but I've failed find the right solution.
Sample of code i want to achieve:
self.plainTextEdit_2.focusOutEvent(lambda:self.check_input(Dialog14)) #### or something like this
def check_input(self,Dialog14):
if int(self.plainTextEdit_2.toPlainText()) > num_pages:
self.plainTextEdit_2.setPlainText(str(num_pages))
The problem is that you cannot connect to a QTextEdit's focusOutEvent. It is not a signal but an event handler. You must override the class and emit a signal from the event handler.
self.plainTextEdit = CustomTextEdit()
self.plainTextEdit.focus_out.connect(lambda:self.check_input(Dialog14))
class CustomTextEdit(QtWidgets.QTextEdit):
focus_out = QtCore.pyqtSignal()
def __init__(self):
super().__init__()
def focusOutEvent(self, event):
super().focusOutEvent(event)
self.focus_out.emit()
Inside a custom button class I have a signal which emits when is dropped something into it. Here the relevant method:
class CustomButton
linked = QtCore.pyqtSignal()
...
def dropEvent(self, e):
print e.source().objectName()
print self.objectName()
# set the drop action as LinkAction
e.setDropAction(QtCore.Qt.LinkAction)
# tell the QDrag we accepted it
e.accept()
#Emit linked signal with the drag object's name as parameter
self.linked.emit( e.source().objectName() )
return QtGui.QPushButton.dropEvent(self, QtGui.QDropEvent(QtCore.QPoint(e.pos().x(), e.pos().y()), e.possibleActions(), e.mimeData(), e.buttons(), e.modifiers()))
In otherhand, outside the class, in the main application I'm creating a slot, and a way to connect it to the signal.
#The slot (actually is just a python callable)
def on_link(self):
input = self.sender().objectName()[4:]
print input
#I need to print the name of the other object emitted as str parameter in the signal....
#Instance of custom button
custom_button.linked.connect( lambda: on_link( custom_button ) )
At this point I already know that I can get the sender() of the signal, however, I don't know how to get the parameter of self.linked.emit( e.source().objectName() ). I just know that first I have to change first this: linked = QtCore.pyqtSignal(str), but don't know how to write the connection or the slot and retrieve the e.source().objectName() in the emit signal.
The current design of the slot looks very confusing. At first glance, it looks like an instance method, but it is actually just a module-level function with a fake self parameter.
I would suggest something simpler, and more explicit, like this:
class CustomButton(QtGui.QPushButton):
linked = QtCore.pyqtSignal(str, str)
def dropEvent(self, event):
...
self.linked.emit(self.objectName(), event.source().objectName())
return QtGui.QPushButton.dropEvent(self, event)
def on_link(btn_name, src_name):
print btn_name, src_name
custom_button.linked.connect(on_link)
An alternative design would be to send the objects, instead of their names:
linked = QtCore.pyqtSignal(object, object)
...
self.linked.emit(self, event.source())
def on_link(button, source):
print button.objectName(), source.objectName()