Разделитель элементов в сочетании с моделью

#python #pyqt #pyqt5 #qcombobox

Вопрос:

У меня есть выпадающее меню, которое содержит тепловые карты и названия сплошных цветов. Он также имеет опцию » Пользовательский…», которая при выборе откроет палитру цветов. Я хотел бы отделить элемент » Пользовательские…» от шестнадцатеричных цветов.

введите описание изображения здесь

Если модель не используется, легко добавить разделитель:

 if self.count() > 1:
    self.insertSeparator(self.count()-1)
 

Как я могу вставить разделитель при заполнении комбинации с помощью модели?

 import sys
from PyQt5 import QtCore, QtWidgets, QtGui


HEATMAPS = [
    'viridis', 'inferno', 'ocean', 'hot', 'terrain', 'nipy_spectral',
]

COLORS = [
    '#ffffff', '#00ff00', '#0000ff', '#ff0000', '#ffff00',
]


class IconModel(QtCore.QAbstractListModel):

    def __init__(self, items=None, parent=None):
        super().__init__(parent=parent)

        if not items:
            self._items = []
        else:
            self._items = items

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self._items)

    def data(self, index, role):
        if not index.isValid():
            return None

        row = index.row()

        if role == QtCore.Qt.DisplayRole:
            return self._items[row]
        elif role == QtCore.Qt.DecorationRole:
            item = self._items[row]

            if item[0] != '#':
                return None
            else:
                h = item.lstrip('#')
                rgb = tuple(int(h[i:i 2], 16) for i in (0, 2, 4))

                color = QtGui.QColor(*rgb)

                pixmap = QtGui.QPixmap(16, 16)
                pixmap.fill(color)
                icon = QtGui.QIcon(pixmap)

                return icon


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    choices = [*HEATMAPS, 'Custom...', *COLORS]
    model = IconModel(choices)

    combo_box = QtWidgets.QComboBox()
    combo_box.setModel(model)

    def on_current_index_changed(index):
        text = combo_box.itemText(index)
        data = combo_box.itemData(index, QtCore.Qt.UserRole)
        print(index, text, data, flush=True)

    combo_box.currentIndexChanged[int].connect(on_current_index_changed)
    combo_box.show()
    sys.exit(app.exec_())

 

Ответ №1:

Вы должны реализовать метод setData() и insertRow() модели, так insertSeparator() как вставляете данные (пустая строка и нулевой QIcon). Например, для этого можно использовать QStandardItemModel.

 import sys
from PyQt5 import QtCore, QtWidgets, QtGui


HEATMAPS = [
    "viridis",
    "inferno",
    "ocean",
    "hot",
    "terrain",
    "nipy_spectral",
]

COLORS = [
    "#ffffff",
    "#00ff00",
    "#0000ff",
    "#ff0000",
    "#ffff00",
]


class ColorDelegate(QtWidgets.QStyledItemDelegate):
    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        value = index.data()
        if value.startswith("#"):
            option.features |= QtWidgets.QStyleOptionViewItem.HasDecoration
            
            pixmap = QtGui.QPixmap(option.decorationSize)
            pixmap.fill(QtGui.QColor(value))
            option.icon = QtGui.QIcon(pixmap)


if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    model = QtGui.QStandardItemModel()
    for choice in HEATMAPS   ["Custom ..."]   COLORS :
      item = QtGui.QStandardItem(choice)
      model.appendRow(item)


    combo_box = QtWidgets.QComboBox()
    combo_box.setModel(model)
    combo_box.setItemDelegate(ColorDelegate(model))
    combo_box.insertSeparator(len(HEATMAPS))
    combo_box.insertSeparator(len(HEATMAPS)   2)

    def on_current_index_changed(index):
        text = combo_box.itemText(index)
        data = combo_box.itemData(index, QtCore.Qt.UserRole)
        print(index, text, data, flush=True)

    combo_box.currentIndexChanged[int].connect(on_current_index_changed)
    combo_box.show()
    sys.exit(app.exec_())
 

С другой стороны, модель не нужна, так как логика значка может быть обработана делегатом:

 app = QtWidgets.QApplication(sys.argv)

combo_box = QtWidgets.QComboBox()
combo_box.addItems(HEATMAPS   ["Custom..."]   COLORS)
combo_box.setItemDelegate(ColorDelegate(combo_box))
combo_box.insertSeparator(len(HEATMAPS))
combo_box.insertSeparator(len(HEATMAPS)   2)

def on_current_index_changed(index):
    text = combo_box.itemText(index)
    data = combo_box.itemData(index, QtCore.Qt.UserRole)
    print(index, text, data, flush=True)

combo_box.currentIndexChanged[int].connect(on_current_index_changed)
combo_box.show()
sys.exit(app.exec_())
 

Если вы хотите отобразить значок на дисплее комбинации, то решение может изменить метод данных модели QStandardItem, но более эффективным вариантом является создание значка и установка его при создании QStandardItem:

 app = QtWidgets.QApplication(sys.argv)

model = QtGui.QStandardItemModel()
for choice in HEATMAPS   ["Custom ..."]:
    item = QtGui.QStandardItem(choice)
    model.appendRow(item)

for color in COLORS:
    item = QtGui.QStandardItem(choice)
    pixmap = QtGui.QPixmap(16, 16)
    pixmap.fill(QtGui.QColor(color))
    icon = QtGui.QIcon(pixmap)
    item.setIcon(icon)
    model.appendRow(item)

combo_box = QtWidgets.QComboBox()
combo_box.setModel(model)
combo_box.insertSeparator(len(HEATMAPS))
combo_box.insertSeparator(len(HEATMAPS)   2)

def on_current_index_changed(index):
    text = combo_box.itemText(index)
    data = combo_box.itemData(index, QtCore.Qt.UserRole)
    print(index, text, data, flush=True)

combo_box.currentIndexChanged[int].connect(on_current_index_changed)
combo_box.show()
sys.exit(app.exec_())
 

Комментарии:

1. Здесь много хорошего, спасибо. Как я могу сделать так, чтобы украшение отображалось в тексте комбинированного отображения? Украшение присутствует при выпадении, но только текст отображается после выбора.