Reputation: 467
I want to (in this analogical example) change QLabel's color by QPushButton click or select it from QComboBox representing the selection history. In real code there are more controls than one button, so there is a method setColor(self, color)
that adds new color to the QComboBox as its first item, removes possible duplicates and changes the QLabel's color. This works.
The second option is to choose the color from the history QComboBox. I catch event when an item is pressed
and call the same setColor(self, color)
. This cause a problem.
Problem is when there are colors in QComboBox e.g. ['red', 'blue', 'green']
I choose option 'blue'
and the order of items is changed to ['blue', 'red', 'green']
(I still want this), there is some standard QComboBox behavior which finishes selection and the QComboBox selects 'green'
item. So, QLabel has correct color, but QComboBox does not have the correct text.
How can I overwrite the "standard behavior" and fix this? Please try this example:
import sys
from random import random
from PyQt5.QtWidgets import QApplication, QWidget, QComboBox, QVBoxLayout, QPushButton, QLabel
class MyCombo(QComboBox):
def __init__(self, parent):
super().__init__(parent)
self.view().pressed.connect(self.handleItemPressed)
def handleItemPressed(self, index):
color = self.model().itemData(index)[0]
self.parent().setColor(color)
def removeDuplicates(self):
unique = list()
while len(unique) < self.model().rowCount():
if self.itemText(len(unique)) in unique:
self.removeItem(len(unique))
else:
unique.append(self.itemText(len(unique)))
class Gui(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(300, 200)
lay = QVBoxLayout()
self.cmb = MyCombo(self)
self.btn = QPushButton('Random', self)
self.btn.clicked.connect(self.btnClicked)
self.pnl = QLabel(self)
lay.addWidget(self.cmb)
lay.addWidget(self.btn)
lay.addWidget(self.pnl)
self.setLayout(lay)
def setColor(self, color):
self.cmb.insertItem(0, color)
self.cmb.setCurrentIndex(0)
self.cmb.removeDuplicates()
self.pnl.setStyleSheet('background-color: %s' % color)
def btnClicked(self):
colors = ['red', 'green', 'blue', 'yellow', 'orange']
self.setColor(colors[int(random() * len(colors))])
if __name__ == '__main__':
app = QApplication(sys.argv)
g = Gui()
g.show()
sys.exit(app.exec_())
Upvotes: 0
Views: 589
Reputation: 243965
With a logic similar to the other answer but focusing on the functionality of inserting and removing duplicates, the QComboBox has to do it and only send the information to the window when the item is selected. Also, instead of passing the name, the QColor can be passed as an additional data.
import sys
import random
from PyQt5.QtCore import pyqtSignal, Qt
from PyQt5.QtGui import QColor
from PyQt5.QtWidgets import (
QApplication,
QWidget,
QComboBox,
QVBoxLayout,
QPushButton,
QLabel,
)
class ColorComboBox(QComboBox):
colorChanged = pyqtSignal(str, QColor)
def __init__(self, parent=None):
super().__init__(parent)
self.currentIndexChanged.connect(self.handleCurrentIndexChanged)
def handleCurrentIndexChanged(self, index):
key = self.itemText(index)
color = self.itemData(index)
self.colorChanged.emit(key, color)
def addColor(self, key, color):
index = self.findText(key)
if index != -1:
self.removeItem(index)
self.insertItem(0, key, color)
self.setCurrentIndex(0)
class Gui(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(300, 200)
self.cmb = ColorComboBox()
self.btn = QPushButton("Random")
self.pnl = QLabel()
lay = QVBoxLayout(self)
lay.addWidget(self.cmb)
lay.addWidget(self.btn)
lay.addWidget(self.pnl)
self.btn.clicked.connect(self.btnClicked)
self.cmb.colorChanged.connect(self.updateColor)
def btnClicked(self):
colors = [
("red", QColor("red")),
("green", QColor("green")),
("blue", QColor("blue")),
("yellow", QColor("yellow")),
("orange", QColor("orange")),
]
key, color = random.choice(colors)
self.cmb.addColor(key, color)
def updateColor(self, key, color):
self.pnl.setStyleSheet('background-color: %s' % color.name())
if __name__ == "__main__":
app = QApplication(sys.argv)
g = Gui()
g.show()
sys.exit(app.exec_())
Upvotes: 2
Reputation: 48260
Rearranging items during operations that include setting the current index is never a good thing, unless you're sure about keeping track of the correct index and know the sequence of operations that happen in between.
In your case, the problem is that the pressed
signal of the view is handled before the current index of the combo box is changed, and the result is that when the mouse button is released the current index is wrong also due to the reordering of items.
Your implementation also has another problem: keyboard and mouse wheel navigation will not correctly update the color.
My suggestion is to leave the model unchanged (unless a new color is added), and reorder it only right before showing the popup.
In order to avoid recursion, always remember that you can block the signals of an object by using blockSignals()
.
class MyCombo(QComboBox):
colorChanged = pyqtSignal(str)
def __init__(self, parent):
super().__init__(parent)
self.currentIndexChanged.connect(self.emitChangedColor)
def emitChangedColor(self, index):
if index >= 0:
self.colorChanged.emit(self.itemText(index))
def setColor(self, color):
blocked = self.blockSignals(True)
for index in range(self.count()):
if self.itemText(index) == color:
break
else:
index = 0
self.insertItem(index, color)
self.setCurrentIndex(index)
self.blockSignals(blocked)
def showPopup(self):
if self.currentIndex() > 0:
blocked = self.blockSignals(True)
current = self.currentText()
self.removeItem(self.currentIndex())
self.insertItem(0, current)
self.setCurrentIndex(0)
self.blockSignals(blocked)
super().showPopup()
class Gui(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.resize(300, 200)
lay = QVBoxLayout()
self.cmb = MyCombo(self)
self.cmb.colorChanged.connect(self.setColor)
self.btn = QPushButton('Random', self)
self.btn.clicked.connect(self.btnClicked)
self.pnl = QLabel(self)
lay.addWidget(self.cmb)
lay.addWidget(self.btn)
lay.addWidget(self.pnl)
self.setLayout(lay)
def setColor(self, color):
self.cmb.setColor(color)
self.pnl.setStyleSheet('background-color: %s' % color)
def btnClicked(self):
colors = ['red', 'green', 'blue', 'yellow', 'orange']
self.setColor(colors[int(random() * len(colors))])
Upvotes: 1