I want use the autoconnection feature. I am using this example:
http://www.eurion.net/python-snippets/snippet/Connecting%20signals%20and%20slots.html
it works, but I want to create my own signals and own slots, the example using built in signals.
for example, here are a custom signal with a custom slot, but don't works:
import sys
from PyQt4 import QtGui, QtCore
class SignalsAndSlots(QtGui.QWidget):
testSignal = QtCore.pyqtSignal(str,name='testSignal')
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setObjectName('testObject')
self.label = QtGui.QLabel(self)
QtCore.QMetaObject.connectSlotsByName(self)
self.emitSignal()
def emitSignal(self):
self.testSignal.emit('message')
#QtCore.pyqtSlot(str,name='on_testObject_testSignal')
def autoSlot(self,msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
Thanks a lot
Ber is right. This what the pyqt documentation says:
QMetaObject.connectSlotsByName searches recursively for all child objects of the given object [...]
Here is a simple example with custom signals :
import sys
from PyQt4 import QtGui, QtCore
class CustomButton(QtGui.QPushButton):
custom_clicked = QtCore.pyqtSignal(str, name='customClicked')
def mousePressEvent(self, event):
self.custom_clicked.emit("Clicked!")
class SignalsAndSlots(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
layout = QtGui.QHBoxLayout(self)
self.custom_button = CustomButton("Press Me", self)
self.custom_button.setObjectName('customButton')
self.label = QtGui.QLabel("Nothing...", parent=self)
layout.addWidget(self.custom_button)
layout.addWidget(self.label)
QtCore.QMetaObject.connectSlotsByName(self)
#QtCore.pyqtSlot(str, name='on_customButton_customClicked')
def autoSlot(self, msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
But I think you should consider not using the object names. New-style signal connection is way neater. Here is the same application :
import sys
from PyQt4 import QtGui, QtCore
class CustomButton(QtGui.QPushButton):
custom_clicked = QtCore.pyqtSignal(str)
def mousePressEvent(self, event):
self.custom_clicked.emit("Clicked!")
class SignalsAndSlots(QtGui.QWidget):
def __init__(self):
QtGui.QMainWindow.__init__(self)
layout = QtGui.QHBoxLayout(self)
self.custom_button = CustomButton("Press Me", self)
self.custom_button.setObjectName('customButton')
self.label = QtGui.QLabel("Nothing...", parent=self)
layout.addWidget(self.custom_button)
layout.addWidget(self.label)
self.custom_button.custom_clicked.connect(self.on_clicked)
def on_clicked(self, msg):
self.label.setText(msg)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
gui = SignalsAndSlots()
gui.show()
app.exec_()
I investigated a bit into the doc. of QtCore.QMetaObject.connectSlotsByName().
For this, I made an MCVE to collect things not working vs. things working:
#!/usr/bin/python3
import sys
from PyQt5.QtCore import QT_VERSION_STR
from PyQt5.QtCore import QMetaObject, pyqtSlot
from PyQt5.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QPushButton, QWidget
class MainWindow(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
# build GUI
qMain = QWidget()
qVBox = QVBoxLayout()
qBtnPreview1 = QPushButton("Preview 1")
qBtnPreview1.setObjectName("preview1")
qVBox.addWidget(qBtnPreview1)
qBtnPreview2 = QPushButton("Preview 2")
qBtnPreview2.setObjectName("preview2")
qVBox.addWidget(qBtnPreview2)
qBtnPreview3 = QPushButton("Preview 3")
qBtnPreview3.setObjectName("preview3")
qVBox.addWidget(qBtnPreview3)
qBtnPreview4 = QPushButton("Preview 4")
qBtnPreview4.setObjectName("preview4")
qVBox.addWidget(qBtnPreview4)
qBtnPreview5 = QPushButton("Preview 5")
qBtnPreview5.setObjectName("preview5")
qVBox.addWidget(qBtnPreview5)
qBtnPreview6 = QPushButton("Preview 6")
qBtnPreview6.setObjectName("preview6")
qVBox.addWidget(qBtnPreview6)
qMain.setLayout(qVBox)
self.setCentralWidget(qMain)
# install signal handlers
qBtnPreview1.clicked.connect(lambda: print("preview1 clicked."))
qBtnPreview2.clicked.connect(lambda: print("preview2 clicked."))
qBtnPreview3.clicked.connect(lambda: print("preview3 clicked."))
qBtnPreview4.clicked.connect(lambda: print("preview4 clicked."))
qBtnPreview5.clicked.connect(lambda: print("preview5 clicked."))
qBtnPreview6.clicked.connect(lambda: print("preview6 clicked."))
QMetaObject.connectSlotsByName(self)
#pyqtSlot()
def preview1(self):
print("MainWindow.preview1() called.")
#pyqtSlot()
def preview2_clicked(self):
print("MainWindow.preview2_clicked() called.")
#pyqtSlot()
def on_preview3(self):
print("MainWindow.on_preview3() called.")
#pyqtSlot()
def on_preview4_clicked(self):
print("MainWindow.on_preview4_clicked() called.")
#pyqtSlot(name='on_preview5_clicked')
def preview5_clicked(self):
print("MainWindow.preview5_clicked() called.")
def on_preview6_clicked(self):
print("MainWindow.on_preview6_clicked() called.")
if __name__ == '__main__':
print("Qt Version: {}".format(QT_VERSION_STR))
app = QApplication(sys.argv)
# build GUI
qWinMain = MainWindow()
qWinMain.show()
# runtime loop
sys.exit(app.exec_())
Output:
$ ./testQMetaObjectConnectSlotsByName.py
Qt Version: 5.9.3
After clicking each of the six buttons once, I got:
preview1 clicked.
preview2 clicked.
preview3 clicked.
preview4 clicked.
MainWindow.on_preview4_clicked() called.
preview5 clicked.
MainWindow.preview5_clicked() called.
preview6 clicked.
MainWindow.on_preview6_clicked() called.
MainWindow.on_preview6_clicked() called.
Observations:
MainWindow.preview1(), MainWindow.preview2_clicked(), MainWindow.on_preview3() were not connected. They don't follow the required convention of QtCore.QMetaObject.connectSlotsByName().
MainWindow.on_preview4_clicked() is connected by name – properly following the required name convention.
MainWindow.preview5_clicked() is connected as well – by giving the required name by #pyqtSlot(name='on_preview5_clicked').
MainWindow.on_preview6_clicked() is connected as well – even without marking it as slot explicitly. (However, it is called twice for any reason which might be undesirable.)
Explicit connections work in any case and appear more robust to me.
Further readings:
QMetaObject::connectSlotsByName() (Qt5 C++ doc.)
Connecting Slots By Name (PyQt5 doc.)
PyQt5.QtCore.pyqtSlot() (PyQt5 doc.).
Actually, this was my answer to another question (SO: How to display two images in each Qlabel PyQt5) until I realized that QtCore.QMetaObject.connectSlotsByName() unlikely plays a role in that question.
So, I moved it here where it may be more appropriate even although the question is a bit aged.
Related
I would like to know how to implement QProgressBar, which shows the progress of calculation in main thread.
Please refer to below codes.
import sys
from PySide2.QtWidgets import QApplication, QWidget, QPushButton, QVBoxLayout, QProgressBar
from PySide2.QtCore import QThread
class BarThread(QThread):
# Progress Bar UI Definition
def __init__(self):
QThread.__init__(self)
self.window = QWidget()
self.pgsb = QProgressBar()
self.lay = QVBoxLayout()
self.lay.addWidget(self.pgsb)
self.window.setLayout(self.lay)
self.isRun = False
# Thread Function Definition
def run(self):
self.window.show()
while self.isRun:
self.pgsb.setValue(self.percent)
print(self.percent)
if self.percent == 100:
self.isRun = False
class Tool(QWidget):
# Main UI Definition
def __init__(self):
windowWidth = 300
windowHeight = 300
QWidget.__init__(self)
self.setWindowTitle("Example")
self.resize(windowWidth, windowHeight)
self.bt = QPushButton('Numbering')
self.layout = QVBoxLayout()
self.layout.addWidget(self.bt)
self.setLayout(self.layout)
# Main Function Link Definition
self.bt.clicked.connect(self.numbering)
# Main Function Definition
def numbering(self):
bth = BarThread()
bth.start()
bth.isRun = True
for x in range(0,100000):
bth.percent = x/1000
print(x)
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Tool()
widget.show()
sys.exit(app.exec_())
You can copy and paste directly onto your python IDE.
(it needs PySide2. It can be installed with 'pip install pyside2' in your prompt).
This code executes simple numbering, however, this doesn't show numbering progress.
How can I solve this problem? Thank you in advance.
P.S. I'm using Windows 10 with PyCharm.
You have at least the following errors:
You must not modify the GUI from another thread, in your case the run method is executed in another thread but you try to modify the value of the QProgressBar, in addition to displaying a widget which is not allowed. If you want to modify the GUI with the information provided in the execution in the secondary thread you must do it through signals since they are thread-safe
The for loop is the blocking task so it must be executed in another thread.
Considering the above, the solution is:
import sys
from PySide2.QtWidgets import (
QApplication,
QWidget,
QPushButton,
QVBoxLayout,
QProgressBar,
)
from PySide2.QtCore import QThread, Signal
class ProgressWidget(QWidget):
def __init__(self, parent=None):
super(ProgressWidget, self).__init__(parent)
self.pgsb = QProgressBar()
lay = QVBoxLayout(self)
lay.addWidget(self.pgsb)
class BarThread(QThread):
progressChanged = Signal(int)
def run(self):
percent = 0
for x in range(0, 100000):
percent = x / 100
self.progressChanged.emit(percent)
class Tool(QWidget):
"""Main UI Definition"""
def __init__(self, parent=None):
super(Tool, self).__init__(parent)
self.setWindowTitle("Example")
self.resize(300, 300)
self.bt = QPushButton("Numbering")
layout = QVBoxLayout(self)
layout.addWidget(self.bt)
# Main Function Link Definition
self.bt.clicked.connect(self.numbering)
self.bar_thread = BarThread(self)
self.progress_widget = ProgressWidget()
self.bar_thread.progressChanged.connect(self.progress_widget.pgsb.setValue)
# Main Function Definition
def numbering(self):
self.bar_thread.start()
self.progress_widget.show()
def closeEvent(self, event):
super(Tool, self).closeEvent(event)
self.bar_thread.quit()
self.bar_thread.wait()
if __name__ == "__main__":
app = QApplication(sys.argv)
widget = Tool()
widget.show()
sys.exit(app.exec_())
Is there a way to make a loading button out of a QPushButton? I want to do something like https://coreui.io/docs/components/loading-buttons/ but in Qt. Is this possible?
I would like to make a loading/progress bar out of each list widget item like below.
You can use QMovie to load a gif and set it as an icon in a QPushButton, in the following example implement the start and stop method that starts and ends the animation, respectively, and the setGif method that passes the gif path:
from PyQt5 import QtCore, QtGui, QtWidgets
class LoadingButton(QtWidgets.QPushButton):
#QtCore.pyqtSlot()
def start(self):
if hasattr(self, "_movie"):
self._movie.start()
#QtCore.pyqtSlot()
def stop(self):
if hasattr(self, "_movie"):
self._movie.stop()
self.setIcon(QtGui.QIcon())
def setGif(self, filename):
if not hasattr(self, "_movie"):
self._movie = QtGui.QMovie(self)
self._movie.setFileName(filename)
self._movie.frameChanged.connect(self.on_frameChanged)
if self._movie.loopCount() != -1:
self._movie.finished.connect(self.start)
self.stop()
#QtCore.pyqtSlot(int)
def on_frameChanged(self, frameNumber):
self.setIcon(QtGui.QIcon(self._movie.currentPixmap()))
if __name__ == '__main__':
import sys
import random
app = QtWidgets.QApplication(sys.argv)
w = QtWidgets.QWidget()
lay = QtWidgets.QVBoxLayout(w)
for i in range(5):
button = LoadingButton("Install")
button.setGif("loading.gif")
QtCore.QTimer.singleShot(random.randint(3000, 6000), button.start)
QtCore.QTimer.singleShot(random.randint(8000, 12000), button.stop)
lay.addWidget(button)
w.show()
sys.exit(app.exec_())
loading.gif
I am trying to display a loading gif after a button is pressed. This is the code I currently have
import sys
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self)#Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
def gif_display(self):
l = QMovieLabel('loading.gif')
l.show()
class QMovieLabel(QLabel):
def __init__(self, fileName):
QLabel.__init__(self)
m = QMovie(fileName)
m.start()
self.setMovie(m)
def setMovie(self, movie):
QLabel.setMovie(self, movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
run()
I would like to display the gif called "loading.gif" after the button is pressed. Nothing appears after pressing the button and I am unsure of what to do to get the gif to properly appear. The gif is the same size as the screen that I created (240x320).
The problem is that QMovieLabel is a local variable within gif_display so it will be deleted when the function finishes running, so the solution is to avoid deleting it. There are 2 options: make it an attribute of the class or make it a child of the window , I will show the second method since I think it is the one you want:
import sys
from PyQt4 import QtCore, QtGui
class MainWindow (QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow,self).__init__(parent)
self.setGeometry(50,50,240,320)
self.home()
def home(self):
but = QtGui.QPushButton("Example", self) # Creates the brew coffee button
but.clicked.connect(self.gif_display)
but.resize(200,80)
but.move(20,50)
self.show()
#QtCore.pyqtSlot()
def gif_display(self):
l = QMovieLabel('loading.gif', self)
l.adjustSize()
l.show()
class QMovieLabel(QtGui.QLabel):
def __init__(self, fileName, parent=None):
super(QMovieLabel, self).__init__(parent)
m = QtGui.QMovie(fileName)
self.setMovie(m)
m.start()
def setMovie(self, movie):
super(QMovieLabel, self).setMovie(movie)
s=movie.currentImage().size()
self._movieWidth = s.width()
self._movieHeight = s.height()
def run():
app = QtGui.QApplication(sys.argv)
GUI = MainWindow()
sys.exit(app.exec_())
if __name__ == '__main__':
run()
There has been a small problem with a little project of mine using PyQt5. I tried to add a random QWidget (in this example a QPushbutton) to a custom QWidget. However, I don't understand the behavior of the "setParent" function. When I use it outside of the custom QWidget, the QPushButton is displayed. When I use it in a declared function of the custom Widget, the QPushButton is occluded and I have no chance of displaying it outside of adding a layout (which I don't want). Here an example of the source code:
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class customWidget(QWidget):
def __init__(self):
super().__init__()
self.addButton()
def addButton(self):
button = QPushButton('not_showing')
button.setParent(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = QWidget()
button = QPushButton('showing')
button.setParent(w)
button.move(50,50)
w.resize(600,600)
w.move(1000,300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
There is no change, when adding the parent during the initialization of the QPushButton.
When the function addButton exits, the button is removed.
If you want to see the button, try this:
class customWidget(QWidget):
def __init__(self):
super().__init__()
self.addButton()
self.button = None
def addButton(self):
if self.button is None:
self.button = QPushButton('not_showing')
self.button.setParent(self)
You do not have this problem in the main function because this function does not return until the application is stopped.
EDIT: The comment was right, but you also missed some arguments. This will work
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class customWidget(QWidget):
def __init__(self, parent=None):
super(customWidget, self).__init__(parent)
self.addButton()
def addButton(self):
button = QPushButton('not_showing')
button.setParent(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = customWidget()
button = QPushButton('showing')
button.setParent(w)
button.move(50,50)
w.resize(600,600)
w.move(1000,300)
w.setWindowTitle('Simple')
w.show()
sys.exit(app.exec_())
from PyQt4.Qt import Qt, QObject,QLineEdit
from PyQt4 import QtGui, QtCore
import utils
class DirLineEdit(QLineEdit, QtCore.QObject):
"""docstring for DirLineEdit"""
def __init__(self):
super(DirLineEdit, self).__init__()
self.xmlDataObj = utils.ReadWriteCustomPathsToDisk()
self.defaultList = self.xmlDataObj.xmlData().values()
self._pathsList()
def focusInEvent(self, event):
self.completer().complete()
def _pathsList(self):
completerList = QtCore.QStringList()
for i in self.defaultList:
completerList.append(QtCore.QString(i))
lineEditCompleter = QtGui.QCompleter(completerList)
self.setCompleter(lineEditCompleter)
def __dirCompleter(self):
dirModel = QtGui.QFileSystemModel()
dirModel.setRootPath(QtCore.QDir.currentPath())
dirModel.setFilter(QtCore.QDir.AllDirs | QtCore.QDir.NoDotAndDotDot | QtCore.QDir.Files)
dirModel.setNameFilterDisables(0)
completer = QtGui.QCompleter(dirModel, self)
completer.setModel(dirModel)
completer.setCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.setCompleter(completer)
app = QtGui.QApplication(sys.argv)
smObj = DirLineEdit()
smObj.show()
app.exec_()
The above code works except I cannot set the text from the selection I make in the AutoComplete that pops up on focus in event .. Any idea why I am not able to make set text of the selected by completer ?
The problem is that your reimplementation of focusInEvent does not call its baseclass method. You should always do this unless you are certain you want to completely override the default behaviour. In this particular case, it's also important to call the baseclass focusInEvent method before invoking the completer, because that will obviously re-take the focus.
Here's a working demo that fixes the problems in the example code:
from PyQt4 import QtGui, QtCore
class LineEdit(QtGui.QLineEdit):
def __init__(self, strings, parent):
QtGui.QLineEdit.__init__(self, parent)
self.setCompleter(QtGui.QCompleter(strings, self))
def focusInEvent(self, event):
QtGui.QLineEdit.focusInEvent(self, event)
self.completer().complete()
class Window(QtGui.QWidget):
def __init__(self):
QtGui.QWidget.__init__(self)
strings = 'one two three four five six seven eight'.split()
self.edit1 = LineEdit(strings, self)
self.edit2 = LineEdit(strings, self)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit1)
layout.addWidget(self.edit2)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())