PySide (or PyQt) signals and slots basics - python

Consider a simple example like this which links two sliders using signals and slots:
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
vbox.addWidget(sone)
stwo = QSlider(Qt.Horizontal)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())
How would you change this so that the second slider moves in the opposite direction as the first? Slider one would be initialized with these values:
sone.setRange(0,99)
sone.setValue(0)
And slider two would be initialized with these values:
stwo.setRange(0,99)
stwo.setValue(99)
And then the value of stwo would be 99 - sone.sliderPosition.
How would you implement the signal and slot to make this work? I would appreciate a working example that builds on the simple example above.

Your example is a bit broken, because you forgot to set the parent of the layout, and also to save the slider widgets as member attributes to be accessed later... But to answer your question, its really as simple as just pointing your connection to your own function:
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout(self)
self.sone = QSlider(Qt.Horizontal)
self.sone.setRange(0,99)
self.sone.setValue(0)
vbox.addWidget(self.sone)
self.stwo = QSlider(Qt.Horizontal)
self.stwo.setRange(0,99)
self.stwo.setValue(99)
vbox.addWidget(self.stwo)
self.sone.valueChanged.connect(self.sliderChanged)
def sliderChanged(self, val):
self.stwo.setValue(self.stwo.maximum() - val)
Note how sliderChanged() has the same signature as the original setValue() slot. Instead of connecting one widget directly to the other, you connect it to a custom method and then transform the value to what you want, and act how you want (setting a custom value on stwo)

You can connect signals to functions that do things. Your code isn't structured to do that easily and required refactoring, so you can do it the easy way:
stwo.setInvertedAppearance(True)
sone.valueChanged.connect(stwo.setValue)

Here's the way I did it. I added this class which reimplements setValue. (I got the idea from http://zetcode.com/tutorials/pyqt4/eventsandsignals/)
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
Here's the complete code. Is this a good approach?
from PySide.QtCore import *
from PySide.QtGui import *
import sys
class MySlider(QSlider):
def __init__(self):
QSlider.__init__(self, Qt.Horizontal)
def setValue(self, int):
QSlider.setValue(self, 99-int)
class MyMainWindow(QWidget):
def __init__(self):
QWidget.__init__(self, None)
vbox = QVBoxLayout()
sone = QSlider(Qt.Horizontal)
sone.setRange(0,99)
sone.setValue(0)
vbox.addWidget(sone)
stwo = MySlider()
stwo.setRange(0,99)
stwo.setValue(0)
vbox.addWidget(stwo)
sone.valueChanged.connect(stwo.setValue)
self.setLayout(vbox)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = MyMainWindow()
w.show()
sys.exit(app.exec_())

Related

Qt: Dead lock detected - Sender is QPushButton(), receiver is PyQtSlotProxy() - for specific indexes in a QPushButton list

I am new to pyqt, and I tried to make an application window that contains a list of buttons that are able to toggle a different window. Since I want the number of these buttons to be of a varying quantity, I created a list of QPushButton elements for iterating over them, creating as many as defined by the length of the list, nevertheless I noticed a very weird behavior :
The following code ...
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
"""
This "window" is a QWidget. If it has no parent,
it will appear as a free-floating window.
"""
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
layout.addWidget(self.label)
self.setLayout(layout)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self,windows):
super().__init__()
self.windows=[]
self.buttons=[]
l=QtWidgets.QVBoxLayout()
for i in range(len(windows)):
window=AnotherWindow()
self.windows.append(window)
button=QtWidgets.QPushButton(f'window {windows[i]}')
print(i," ",button)
self.buttons.append(button)
self.buttons[i].clicked.connect(self.toggle_window,i)
l.addWidget(self.buttons[i])
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
print(len(self.windows))
def toggle_window(self,i):
if self.windows[i].isVisible():
self.windows[i].hide()
else:
self.windows[i].show()
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
windows=[0,1,2,3]
windows=[str(i) for i in windows]
print(windows)
w = MainWindow(windows)
w.show()
app.exec()
produced the following error but only when the 4rth button (window 3) is pressed.
Qt: Dead lock detected while activating a BlockingQueuedConnection: Sender is QPushButton( ... ), receiver is PyQtSlotProxy( ... )
In effort to validate the code, I tried to narrow the list into a linear declaration of a static number of QPushButton instances, indicating that the issue occurs, only when I try to put the buttons on a list. For instance, the following script does not present any similar unpredictable behavior:
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
"""
This "window" is a QWidget. If it has no parent,
it will appear as a free-floating window.
"""
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window % d" % randint(0, 100))
layout.addWidget(self.label)
self.setLayout(layout)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.window0 = AnotherWindow()
self.window1 = AnotherWindow()
self.window2 = AnotherWindow()
self.window3 = AnotherWindow()
l = QtWidgets.QVBoxLayout()
button0 = QtWidgets.QPushButton("window 0")
button0.clicked.connect(self.toggle_window0)
l.addWidget(button0)
button1 = QtWidgets.QPushButton("window 1")
button1.clicked.connect(self.toggle_window1)
l.addWidget(button1)
button2 = QtWidgets.QPushButton("window 2")
button2.clicked.connect(self.toggle_window2)
l.addWidget(button2)
button3 = QtWidgets.QPushButton("window 3")
button3.clicked.connect(self.toggle_window3)
l.addWidget(button3)
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def toggle_window0(self, checked):
if self.window0.isVisible():
self.window0.hide()
else:
self.window0.show()
def toggle_window1(self):
if self.window1.isVisible():
self.window1.hide()
else:
self.window1.show()
def toggle_window2(self):
if self.window2.isVisible():
self.window2.hide()
else:
self.window2.show()
def toggle_window3(self, checked):
if self.window3.isVisible():
self.window3.hide()
else:
self.window3.show()
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
app.exec()
To test it further, I extended the list to a list of random lengths (more than 10), where I reassured that the issue persist for specific indexes each time. For example if I create 20 buttons using the first approach, the same bug appears for - the 4rth, the 12fth and the last index exclusively - but not for the rest of them. I even tested it on a different machine. Having also searched in forums, I could not find a solution.
Do I do anything completely wrong here? Does anyone understands better to indicate why is this happening?
I kindly thank you in advance!
Environment: Ubuntu 22.04
Pyqt version : 1.9 (under conda)
Your problem is the following:
self.buttons[i].clicked.connect(self.toggle_window,i)
You are passing i as second argument to connect and expect the toggle_window function to be called with this argument. This is not happening. In toggle_window, i will always be False. See musicamente's comment regarding what this second argument to connect does.
What you should do instead is connect the button click to a function of your window object. From there, you can of course do a callback to a function of your main window as illustrated below:
import sys
from random import randint
from PyQt5 import QtWidgets
class AnotherWindow(QtWidgets.QWidget):
def __init__(self, parent, i):
super().__init__()
self.parent = parent
self.i = i
layout = QtWidgets.QVBoxLayout()
self.label = QtWidgets.QLabel("Another Window {}".format(i))
layout.addWidget(self.label)
self.setLayout(layout)
def toggle(self):
print("Toggling windows {}".format(self.i))
if self.isVisible():
self.hide()
self.parent.window_toggled(self.i, False)
else:
self.show()
self.parent.window_toggled(self.i, True)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, windows):
super().__init__()
self.windows=[]
self.buttons=[]
l=QtWidgets.QVBoxLayout()
for i,title in enumerate(windows):
window=AnotherWindow(self, i)
self.windows.append(window)
button=QtWidgets.QPushButton(title)
button.clicked.connect(window.toggle)
l.addWidget(button)
self.buttons.append(button)
w = QtWidgets.QWidget()
w.setLayout(l)
self.setCentralWidget(w)
def window_toggled(self, i, visible):
print("Window {} is now {}".format(i, "visible" if visible else "hidden"))
if __name__=="__main__":
app = QtWidgets.QApplication(sys.argv)
windows = ["window {}".format(i) for i in range(12)]
w = MainWindow(windows)
w.show()
app.exec()

I want to know how to use pyqt5 to load the created widget at once

Sorry. I will modify the contents. I would like to load a widget inside def test by pressing Qbutton. Can not you use QStackedWidget to load the widget's configured functions? I've compiled the class and called it, but only a = QLineEdit ('Qline', self). I wonder what should be done to switch widgets.
You can also create a table like html using pyqt5.
import sys
from PyQt5.QtWidgets import *
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.stacked = QStackedWidget(self)
self.FirstpUI()
def FirstpUI(self):
self.btn1 = QPushButton('test1', self)
self.btn1.move(50,50)
self.btn1.clicked.connect(self.btn1_click)
def test(self):
a = QLineEdit('Qline', self)
b = QLineEdit('Qline2', self)
c = QPushButton('button', self)
a.move(0, 0)
b.move(100, 0)
c.move(50,50)
c.clicked.connect(self.btn2_click)
def btn1_click(self):
self.btn1.deleteLater()
self.stacked.addWidget(self.test())
self.stacked.setCurrentIndex(self.stacked.currentIndex()+1)
def btn2_click(self):
QMessageBox.about(self,'hello','hello2')
if __name__ == "__main__":
app = QApplication(sys.argv)
fream = MainWindow()
fream.show()
app.exec_()
May be I don't know what you real want,because I know that little, I think You can use QtDesigner,it's very useful

Using new style signal and slots for combobox?

I have two lines using the old SIGNAL and SLOT style..
combobox.emit(SIGNAL("activated(int)"), combobox.currentIndex())
combobox.emit(SIGNAL("activated(const QString &)"), combobox.currentText())
I was wondering what the new style would look like. I'm new to python and I don't have much experience with signals and slots. Is there a really good resource floating around that covers this? The documentation didn't really help me understand what was going on.
The solution is to indicate the type of argument of the signal that is being emitted:
combo.activated[type].connect(someSlot)
Example:
class Widget(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.setLayout(QVBoxLayout())
combo = QComboBox(self)
self.layout().addWidget(combo)
combo.addItems(["item1", "item2", "item3"])
combo.activated[int].connect(self.onActivatedIndex)
combo.activated[str].connect(self.onActivatedText)
#pyqtSlot(int)
def onActivatedIndex(self, index):
print(index)
#pyqtSlot(str)
def onActivatedText(self, text):
print(text)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())

Uppercase input in QLineEdit python way

I had drawn up an UI using the QT Designer but found out that there are no parameters for me to set QLineEdit inputs to be uppercase.
After doing some online searching, I have only seen a very few handful of results that cater to my needs, however all are coded in Qt. Example, this link
And so, are there ways for me to do this in the pythonic way?
The simplest way would be to use a validator.
This will immediately uppercase anything the user types, or pastes, into the line-edit:
from PyQt4 import QtCore, QtGui
class Validator(QtGui.QValidator):
def validate(self, string, pos):
return QtGui.QValidator.Acceptable, string.upper(), pos
# for old code still using QString, use this instead
# string.replace(0, string.count(), string.toUpper())
# return QtGui.QValidator.Acceptable, pos
class Window(QtGui.QWidget):
def __init__(self):
super(Window, self).__init__()
self.edit = QtGui.QLineEdit(self)
self.validator = Validator(self)
self.edit.setValidator(self.validator)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.edit)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.setGeometry(500, 300, 300, 100)
window.show()
sys.exit(app.exec_())
Try this,
I believe this serves your purpose. I won't call it much pythonic. More like PyQt override.
#minor code edit
from PyQt4 import QtGui
import sys
#===============================================================================
# MyEditableTextBox-
#===============================================================================
class MyEditableTextBox(QtGui.QLineEdit):
#|-----------------------------------------------------------------------------|
# Constructor
#|-----------------------------------------------------------------------------|
def __init__(self,*args):
#*args to set parent
QtGui.QLineEdit.__init__(self,*args)
#|-----------------------------------------------------------------------------|
# focusOutEvent :-
#|-----------------------------------------------------------------------------|
def focusOutEvent(self, *args, **kwargs):
text = self.text()
self.setText(text.__str__().upper())
return QtGui.QLineEdit.focusOutEvent(self, *args, **kwargs)
#|--------------------------End of focusOutEvent--------------------------------|
#|-----------------------------------------------------------------------------|
# keyPressEvent
#|-----------------------------------------------------------------------------|
def keyPressEvent(self, event):
if not self.hasSelectedText():
pretext = self.text()
self.setText(pretext.__str__().upper())
return QtGui.QLineEdit.keyPressEvent(self, event)
#|--------------------End of keyPressEvent-------------------------------------|
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
w = QtGui.QWidget()
lay = QtGui.QHBoxLayout()
w.setLayout(lay)
le1 = MyEditableTextBox()
lay.addWidget(le1)
le2 = MyEditableTextBox()
lay.addWidget(le2)
w.show()
sys.exit(app.exec_())
Hey I know I am kind of late, but I hope this might help some one else like me who spent some time searching for this
Mycase:
I was trying to convert only the first letter to capital and this is what I ended up with and it worked (just a beginner in python so if you can make this more pythonic please let me know)
In the defining function: line_edit_object.textChanged.connect(lambda:auto_capital(line_edit_object))
the function auto_capital:
def auto_capital(line_edit_object):
edit=line_edit_object
text=edit.text()
edit.setText(text.title())
this shall fix every issue. Feel free to make it more pythonic.
I am also late but after contemplating on this question I think this is some sort of pythonic way of accomplishing it in PyQt5:
class CustomInput(QLineEdit):
def __init__(self):
super().__init__()
self.textChanged.connect(self.text_changed)
def text_changed(self):
if self.text().isupper():
return
self.setText(self.text().upper())

Strange behavior of QProgressBar with PyQt4

I have this sample of code:
import sys
import time
from PyQt4.QtGui import *
from PyQt4.QtCore import *
class Bar(QDialog):
def __init__(self, parent=None):
super(Bar, self).__init__()
self.pbar = QProgressBar(self)
self.pbar.setValue(0)
layout = QHBoxLayout()
layout.addWidget(self.pbar)
self.setLayout(layout)
def main(self):
for value in range(1, 100):
time.sleep(1)
print value
self.pbar.setValue(value)
app = QApplication(sys.argv)
form = Bar()
form.show()
form.main()
app.exec_()
I expect progressbar's value to increased by 1 every second.
Instead, although that all values printed on the screen, the progressbar shows only some of them. Also, the bar appears just when value == 5. I know how to achieve the appropriate result with QBasicTimer(), but why this one does not work? Did i make a stupid mistake?
Try adding a
QApplication.processEvents()
just after print value (this should force the UI to update).

Categories