PyQt5 setText with a signal class - python

I'm trying to update QLineEdit() with setText.
However, that is made through a signal thread class (A)
that is able to send every time signal to a widget class (B) [deputy to change the text].
let see an example:
classB(QWidget):
def __init__(self, parent = None):
super(classB, self).__init__(parent)
self.lineLayout = QGridLayout()
self.textbox = QLineEdit("")
self.lineLayout.addWidget(self.textbox, 0, 0)
self.setLayout(self.lineLayout)
def setInt(self,intVal):
# The shell displays this value perfectly
print(intVal)
# it does not display the change in QWidget that lives in MainWidget
self.textbox.setText("val: " + str(intVal))
def paintEvent(self, event):
painter = QPainter()
painter.begin(self)
While the class A's code:
classA(QThread):
sendVal = QtCore.pyqtSignal(int)
def __init__(self,serial):
QThread.__init__(self)
def do_stuff(self):
cont = 0
while True:
self.sendVal(cont)
cont += 1
So we connect the signal:
class MainWidget(QtWidgets.QWidget):
getClassA = classA()
getClassB = classB()
getClassA.sendVal.connect(getClassB.setInt)
I have observed the following behaviors:
The int signal is perfectly received within the [setInt] function of class B;
The real problem is that everything that happens inside the setInt function stays in setInt. I can not even change a hypothetical class variable (In def init of classB)

Related

How can I create new buttons with buttons and plot them in QGraphicScene with an array in PyQt5

I have an application where I have several button widgets on a QGraphicScene and I am trying to make this button widgets to make new buttons on QGraphicScene when they are clicked.
My code is as follows:
buttons = []
class SeveralButtons(QtWidgets.QWidget):
def __init__(self,id,x,y):
super(SeveralButtons,self).__init__()
self.id = id
self.x = x
self.y = y
self.setGeometry(x,y,1,1)
self.button1 = QtWidgets.QPushButton("B1")
self.button2 = QtWidgets.QPushButton("B2")
self.button1.clicked.connect(self.p1b)
self.severalButtonsLayout = QtWidgets.QGridLayout()
self.severalButtonsLayout.addWidget(self.button1, 0,0,)
self.severalButtonsLayout.addWidget(self.button2, 0,1,)
self.setLayout(self.severalButtonsLayout)
def p1b(self):
ph = SeveralButtons(0,self.x-200,self.y-200)
buttons.append(ph)
UiWindow._scene.addWidget(ph)
And my main class is like this:
class UiWindow(QtWidgets.QMainWindow):
factor = 1.5
def __init__(self, parent=None):
super(UiWindow, self).__init__(parent)
self.setFixedSize(940,720)
self._scene = QtWidgets.QGraphicsScene(self)
self._view = QtWidgets.QGraphicsView(self._scene)
self._view.setDragMode(QtWidgets.QGraphicsView.ScrollHandDrag)
self._view.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self._view.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
self.initButtons()
self.setCentralWidget(self._view)
def initButtons(self):
self.p = SeveralButtons(0,500,500)
buttons.append(self.p)
self._scene.addWidget(self.p)
def updateButtons(self,phrase):
for b in buttons:
if b != buttons[0]:
self._scene.addWidgets(b)
# ADD BUTTON WIDGET IN buttons ARRAY TO THE _scene OBJECT
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = UiWindow()
ui.show()
sys.exit(app.exec_())
As it is shown in here I am trying to update widgets in main window with button click but I get QGraphicsProxyWidget::setWidget: cannot embed widget 0x24ac1a93000; already embedded error.
How can I overcome this problem or what is the sane way to make this work? My main goal in this program is that every button group can create their children button group when clicked. Doing this with classes is way to go or should I stick to methods in main window class when creating recursive widgets?
Thanks in advance.
EDIT:
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1)
...
def b1(self):
sb = SeveralButtons()
buttons.append(sb)
self.b1Signal.emit()
class UiWindow(QtWidgets.QMainWindow):
def __init__():
...
self.sb1 = SeveralButtons()
buttons.append(sb1)
self._scene.addWidget(self.sb1)
self.sb1.b1Signal.connect(self.updateButtons)
def updateButtons():
for b in buttons:
if b != buttons[0]:
self._scene.addWidget(b)
The SeveralButtons class should not be responsible of creating new buttons outside itself.
You should emit that signal and connect it to the function of its parent, which will then create a new instance of the same class and also connect the signal.
class SeveralButtons(QtWidgets.QWidget):
b1Signal = QtCore.pyqtSignal()
def __init__():
super().__init__()
layout = QtWidgets.QHBoxLayout(self)
self.button1 = QtWidgets.QPushButton()
self.button1.clicked.connect(self.b1Signal)
layout.addWidget(self.button1)
class UiWindow(QtWidgets.QMainWindow):
def __init__():
# ...
self.buttons = []
self.addButtons()
def addButtons():
newButtons = SeveralButtons()
newButtons.b1Signal.connect(self.addButtons)
self.buttons.append(newButtons)
self._scene.addWidget(newButtons)

how to update pyqt5 GUI label led icons(pixmap)

I have main.py file which contain all threads and dictionary, one is my GUI thread which i have define in main.py file.
now in my gui thread i have define a function to
gui.py
class Ui_MainWindow(object):
def setupUi(self, MainWindow, object_dictionary):
self.closed_led = QtWidgets.QLabel(self.central_widget)
self.closed_led.setGeometry(QtCore.QRect(910, 70, 61, 61))
self.closed_led.setText("")
self.closed_led.setPixmap(QtGui.QPixmap("black.jpg"))
self.closed_led.setScaledContents(True)
self.closed_led.setObjectName("closed_led")
self.update_label(object_dictionary)
self.timer = QTimer()
self.timer.timeout.connect(lambda: self.update_label(object_dictionary))
self.timer.start(1000) # repeat self.update_label every 1 sec
def update_label(self, object_dictionary):
if object_dictionary['fridge_closed'] != 0:
self.closed_led.setPixmap(QtGui.QPixmap("green.jpg"))
print("green")
else:
self.closed_led.setPixmap(QtGui.QPixmap("black.jpg"))
print("black")
but i want this updatelabel to keep checking the if any input is given in dictionary, if fridge_closed = 1 then the led should become green and if fridge_closed = 0 then led should become black automatically. Do i need to use worker thread for this , and if yes then how to assign signal slot.
You could make object_dictionary a member of the Gui class and wrap all edits to the dictionary in a method that emits a signal. When you need to edit the dictionary outside of the class, just use editDictionary() from the class instance.
class MainWindow(QtWidgets.QWidget):
# Signal for when dictionary is changed
objectDictionaryChanged = QtCore.Signal()
def __init__(self):
super(MainWindow, self).__init__()
self.mainLayout = QtWidgets.QVBoxLayout()
self.setLayout(self.mainLayout)
# Object dictionary becomes part of the class
self.object_dictionary = {}
self.closed_led = QtWidgets.QLabel()
# A spinbox that edits the value of 'fridge_closed' in the dictionary
self.dictionarySpinBox = QtWidgets.QSpinBox()
self.dictionarySpinBox.setMinimum(0)
self.dictionarySpinBox.setMaximum(1)
# The connections that handle the changes
self.objectDictionaryChanged.connect(self.update_label)
self.dictionarySpinBox.valueChanged.connect(
lambda: self.editDictionary('fridge_closed', self.dictionarySpinBox.value())
)
self.mainLayout.addWidget(self.closed_led)
self.mainLayout.addWidget(self.dictionarySpinBox)
self.dictionarySpinBox.setValue(1)
def editDictionary(self, key, value):
# All edits to object dictionary should pass through here
self.object_dictionary[key] = value
self.objectDictionaryChanged.emit()
def update_label(self):
state = self.object_dictionary['fridge_closed']
if state is 0:
self.closed_led.setPixmap(QtGui.QPixmap("black.jpg"))
elif state is 1:
self.closed_led.setPixmap(QtGui.QPixmap("green.jpg"))

Qcombobox with Qlabel and signal&slot

I have a Qgroupbox which contains Qcombobox with Qlabels, I want to select a value from Qcombobox and display the value as Qlabel. I have the complete code, even I do print value before and after within function every thing works as it should, Only display setText wont set text to Qlabel and update it.
Current screen
What I want
I've corrected signal code, when Qgroupbox in it Qcombobox appears or value would be changed, self.activation.connect(......) would emit an int of the index. to ensure that would work I print it-value inside the def setdatastrength(self, index), see figure below indeed it works, then argument would be passed to function self.concreteproperty.display_condata(it) would be called and do a print of value inside def display_condata(self, value) to make sure about value passing, as shown figure below, it does work. This line code self.con_strength_value.setText(fmt.format(L_Display))
wont assign value to Qlabel.
The script
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class secondtabmaterial(QtWidgets.QWidget):
def __init__(self, parent=None):
super(secondtabmaterial, self).__init__(parent)
self.concretewidgetinfo = ConcreteStrengthInFo()
Concrete_Group = QtWidgets.QGroupBox(self)
Concrete_Group.setTitle("&Concrete")
Concrete_Group.setLayout(self.concretewidgetinfo.grid)
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45"
,"C40/50","C45/55","C50/60","C55/67","C60/75","C70/85",
"C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40",
"45","50","55","60","70","80","90"]
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
concretestrength_lay = QtWidgets.QHBoxLayout(self)
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
concretestrength_lay.addWidget(con_strength)
concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignRight)
self.setLayout(concretestrength_lay)
#QtCore.pyqtSlot(int)
def display_condata(self, value):
try:
L_Display = str(value)
print("-------- After ------")
print(L_Display, type(L_Display))
fmt = "{}mm"
self.con_strength_value.setText(fmt.format(L_Display))
except ValueError:
print("Error")
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
self.concretestrengthbox.activated.connect(self.setdatastrength)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
hbox.addWidget(self.concretestrengthbox)
self.grid = QtWidgets.QGridLayout()
self.grid.addLayout(hbox, 0, 0)
self.grid.addWidget(self.concreteproperty, 1, 0)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
self.concreteproperty.display_condata(it)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = secondtabmaterial()
w.show()
sys.exit(app.exec_())
Above code is corrected and final. Now it works as it should.
I think the issue is that your receiving slot doesn't match any of the available .activated signals.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot()
def setdatastrength(self):
index = self.currentIndex()
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
The QComboBox.activated signal emits either an int of the index, or a str of the selected value. See documentation.
You've attached it to setdatastrength which accepts doesn't accept any parameters (aside from self, from the object) — this means it doesn't match the signature of either available signal, and won't be called. If you update the definition to add the index value, and accept a single int it should work.
self.activated.connect(self.setdatastrength)
#QtCore.pyqtSlot(int) # add the target type for this slot.
def setdatastrength(self, index):
it = self.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
After the update — the above looks now to be fixed, although you don't need the additional index = self.currentIndex() in setdatastrength it's not doing any harm.
Looking at your code, I think the label is being updated. The issue actually is that you can't see the label at all. Looking at the init for ConcreteProperty
class ConcreteProperty(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteProperty, self).__init__(parent)
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.concretestrength_lay = QtWidgets.QHBoxLayout()
fctd = "\nfcd\n\nfctd\n\nEc"
con_strength = QtWidgets.QLabel(fctd)
self.con_strength_value = QtWidgets.QLabel(" ")
self.concretestrength_lay.addWidget(con_strength)
self.concretestrength_lay.addWidget(self.con_strength_value, alignment=QtCore.Qt.AlignLeft)
The reason the changes are not appearing is that you create two ConcreteProperty objects, one in ConcreteStrengthInfo and one in ConcreteStrengthComboBox. Updates to the combo box trigger an update of the ConcreteProperty attached to the combobox, not the other one (they are separate objects). The visible ConcreteProperty is unaffected.
To make this work, you need to move the signal attachment + the slot out of the combo box object. The following is a replacement for the two parts —
class ConcreteStrengthComboBox(QtWidgets.QComboBox):
def __init__(self, parent = None):
super(ConcreteStrengthComboBox, self).__init__(parent)
self.addItems(["C12/15","C16/20","C20/25","C25/30","C30/37","C35/45","C40/50","C45/55",
"C50/60","C55/67","C60/75","C70/85","C80/95","C90/105"])
self.setFont(QtGui.QFont("Helvetica", 10, QtGui.QFont.Normal, italic=False))
self.compressive_strength = ["12","16","20","25","30","35","40","45","50","55",
"60","70","80","90"]
class ConcreteStrengthInFo(QtWidgets.QWidget):
def __init__(self, parent=None):
super(ConcreteStrengthInFo, self).__init__(parent)
hbox = QtWidgets.QHBoxLayout()
concrete_strength = QtWidgets.QLabel("Concrete strength: ")
hbox.addWidget(concrete_strength)
self.concreteproperty = ConcreteProperty()
self.concretestrengthbox = ConcreteStrengthComboBox()
hbox.addWidget(self.concretestrengthbox)
self.concretestrengthbox.activated.connect(self.setdatastrength)
self.vlay = QtWidgets.QVBoxLayout()
self.vlay.addLayout(hbox)
self.vlay.addLayout(self.concreteproperty.concretestrength_lay)
#QtCore.pyqtSlot(int)
def setdatastrength(self, index):
it = self.concretestrengthbox.compressive_strength[index]
print(it)
self.concreteproperty.display_condata(it)
This works for me locally.

QThread does not update view with events

On menu I can trigger:
def on_git_update(self):
update_widget = UpdateView()
self.gui.setCentralWidget(update_widget)
updateGit = UpdateGit()
updateGit.progress.connect(update_widget.on_progress)
updateGit.start()
then I have:
class UpdateView(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
vbox = QVBoxLayout()
self.pbar = QProgressBar()
vbox.addWidget(self.pbar)
vbox.addStretch(1)
self.setLayout(vbox)
def on_progress(self, value):
self.pbar.setValue(int(value * 100))
class UpdateGit(QThread):
progress = pyqtSignal(float)
def __del__(self):
self.wait()
def run(self):
for i in range(10):
self.progress.emit(i / 10)
sleep(.5)
The app freezes during the processing, afaik it should work as it is in a thread using signals.
Also, it works as expected with the app updating every step when I run it in debug mode via pycharm.
How is my thread set up incorrectly?
A variable created in a function only exists until the function exists, and this is what happens with updateGit, in the case of update_widget when it is set as centralwidget it has a greater scope since Qt handles it. The solution is to extend the scope of the thread by making it a member of the class.
def on_git_update(self):
update_widget = UpdateView()
self.gui.setCentralWidget(update_widget)
self.updateGit = UpdateGit()
self.updateGit.progress.connect(update_widget.on_progress)
self.updateGit.start()

Disconnecting a PyQt Signal in a conditional

I am having some troubles with a mouse event in PyQt. This is the code:
class A(QMainWindow):
var = None
def __init__(self):
QMainWindow.__init__(self)
#Here I draw a matplotlib figure
self.figure_canvas = FigureCanvas(Figure())
layout.addWidget(self.figure_canvas, 10)
self.axes = self.figure_canvas.figure.add_subplot(211)
#I created a toolbar for the figure and I added a QPushButton
self.btn_selection_tool = QPushButton()
self.navigation_toolbar.addWidget(self.btn_selection_tool)
self.connect(self.btn_selection_tool, SIGNAL("clicked()"), self.B)
def B(self):
if self.var == 1:
cid = self.figure_canvas.mpl_connect("press_button_event", self.C)
def C(self, event):
x = xdata.event
#I draw a line every time I click in the canvas
def D(self):
#Here I tried to call this method and disconnect the signal
self.figure_canvas.mpl_disconnect(cid)
The problem is that I can not disconnect the signal of the mouse event using:
self.figure_canvas.mpl_disconnect(cid)
Nothing happens, I keep drawing a line with every click I make. The mouse event is still connected.
How can I disconnect the signal? maybe using another QPushButton?
Are you storing the connection somewhere? You might need to store the in a variable to disconnect it properly:
class A(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
self.cid = None
def B(self):
if self.var == 1:
self.cid = self.figure_canvas.mpl_connect("press_button_event", self.C)
def D(self):
if self.cid is not None:
self.figure_canvas.mpl_disconnect(self.cid)

Categories