Python Pyqt5 QPlainTextEdit When user leaves check value - python

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()

Related

Disconnect signals for QGis plugin with dialog created by Qt Designer

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)
...

Is there any way to connect custom Signal(int) with Push-Button directly in PySide2?

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)

Using QThread to display QProgressDialog in PyQt5 [duplicate]

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)

QPushButton toggled connection does not seems to be trigger at the start

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 "+")

pyqt: How to quit a thread properly

I wrote an pyqt gui and used threading to run code which needs a long time to be executed, but I want to have the choice to stop the execution safely. I dont want to use the get_thread.terminate() method. I want to stop the code by a special function (maybe del()). My problem is that, I wrote the code in a own class and just want to abort the class without changing a lot of syntax.
Edit: It was mentioned that one has to pass a flag to the class, which has to be checked constantly. How do I send this flag to the class? Because the flag has to change the value, when one presses the stop button.
Edit 2: My solution so far is, to declerate a global variable with the name running_global. I changed self.get_thread.terminate() to running_global = False and I check constantly in my long_running_prog if the variable has been set False. I think this solution is ugly, so I would be pretty happy if someone has a better idea.
This is my code for the dialog where I start the thread:
class SomeDialog(QtGui.QDialog,
userinterface_status.Ui_status_window):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
"""
:param raster: Coordinates which are going to be scanned.
"""
super(self.__class__, self).__init__() # old version, used in python 2.
self.setupUi(self) # It sets up layout and widgets that are defined
self.get_thread = SomeThread()
# Conencting the buttons
self.start_button.clicked.connect(self.start)
self.stop_button.clicked.connect(self.stop)
self.close_button.clicked.connect(self.return_main)
# Connecting other signals
self.connect(self.get_thread, QtCore.SIGNAL("stop()"), self.stop)
self.connect(self.get_thread, QtCore.SIGNAL("update_status_bar()"), self.update_status_bar)
def return_main(self):
"""
Function is excecuted, when close button is clicked.
"""
print("return main")
self.get_thread.terminate()
self.close()
def start(self):
"""
Starts the thread, which means that the run method of the thread is started.
"""
self.start_button.setEnabled(False)
self.get_thread.start()
def stop(self):
print("Stop programm.")
self.start_button.setEnabled(True)
self.get_thread.quit()
def end(self):
QtGui.QMessageBox.information(self, "Done!", "Programm finished")
def closeEvent(self, event):
"""
This method is called, when the window is closed and will send a signal to the main window to activaete the
window again.
:param event:
"""
self.finished.emit(True)
# close window
event.accept()
In the following class is the code for the thread:
class SomeThread(QtCore.QThread):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
print("del")
self.wait()
def run(self):
self.prog = long_running_prog(self.emit) # Sending from the prog signals
self.prog.run()
self.prog.closeSystem() # Leaving the programm in a safe way.
So if one presses the stop button, the programm should instantly shut down in a save way. Is there a way to abort the class in a save way? For example can I pass a variable to the long_running_prog class which turns True, when one presses the stop button? If somethin like this is possible, could one tell me how?
Thanks for your help in advance
I hope you understand my problem.
Greetings
Hizzy
This is impossible to do unless prog.run(self) would periodically inspect a value of a flag to break out of its loop. Once you implement it, __del__(self) on the thread should set the flag and only then wait.

Categories

Resources