Модель QTableView с использованием QComboBox

В этом примере ниже у меня есть простой QTableView, который заполняется с помощью AbstractModel. Каждая строка в таблице отображает информацию, относящуюся к объекту класса с именем Актив. У него есть свойство Items, которое содержит список строк. Я хочу знать, как я могу заполнить QTableView полем со списком, отображающим этот список строк для каждой строки.

Во-вторых, когда пользователь изменяет элемент, выбранный в раскрывающемся списке, я хотел бы вызвать событие, чтобы позже использовать его для правильного изменения цвета цветной точки на зеленый или красный в зависимости от свойства объекта, называемого «Статус».

Статус будет указывать, является ли Текущая версия (имеется в виду последний элемент в раскрывающемся списке) выбранным элементом. Если это последний элемент в списке, то есть последний элемент, он будет зеленым, иначе красным.

Свойство «Активно» указывает, какой элемент в раскрывающемся списке выбран в данный момент.

Если статус равен 0, то он устарел, а если статус равен 1, это означает, что используется последняя версия в раскрывающемся списке.

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

import sys
from PySide import QtGui, QtCore


class Asset(object):
    def __init__(self, name, items=None, status=0, active=0):
        self._status = 0
        self._name = ''
        self._items = []
        self._active = active

        self.name = name
        self.items = items if items != None else []
        self.status = status


class AssetModel(QtCore.QAbstractTableModel):

    attr = ["Name", "Options"]


    def __init__(self, *args, **kwargs):
        QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
        self._items = []


    def clear(self):
        self._items = []
        self.reset()


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


    def columnCount(self, index=QtCore.QModelIndex()):
        return len(self.attr)


    def addItem(self, sbsFileObject):
        self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
        self._items.append(sbsFileObject)
        self.endInsertRows()


    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return AssetModel.attr[section]
        return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)


    def getItem(self, index):
        row = index.row()
        if index.isValid() and 0 <= row < self.rowCount():
            return index.data(role=QtCore.Qt.UserRole)
        return None


    def getSelectedItems(self, selection):
        objs = []
        for i, index in enumerate(selection):
            item = self.getItem(index)
            objs.append(item)
        return objs


    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        if 0 <= index.row() < self.rowCount():
            item = self._items[index.row()]
            col = index.column()
            if 0 <= col < self.columnCount():
                if role == QtCore.Qt.DisplayRole:
                    if col == 0:
                        return getattr(item, 'name', '')
                    if col == 1:
                        return (getattr(item, 'items', []))
                elif role == QtCore.Qt.UserRole:
                    if col == 0:
                        return item
                elif role == QtCore.Qt.DecorationRole:
                    if col == 0:
                        status = getattr(item, 'status', 0)

                        col = QtGui.QColor(255,0,0,255)
                        if status == 1:
                            col = QtGui.QColor(255,128,0,255)
                        elif status == 2:
                            col = QtGui.QColor(255,255,0,255)

                        px = QtGui.QPixmap(120,120)
                        px.fill(QtCore.Qt.transparent)
                        painter = QtGui.QPainter(px)
                        painter.setRenderHint(QtGui.QPainter.Antialiasing)
                        px_size = px.rect().adjusted(12,12,-12,-12)
                        painter.setBrush(col)
                        painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
                            QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
                        painter.drawEllipse(px_size)
                        painter.end()

                        return QtGui.QIcon(px)


class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()
        self.resize(400,300)

        # controls
        asset_model = QtGui.QSortFilterProxyModel()
        asset_model.setSortCaseSensitivity(QtCore.Qt.CaseInsensitive)
        asset_model.setSourceModel(AssetModel())

        self.ui_assets = QtGui.QTableView()
        self.ui_assets.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.ui_assets.setModel(asset_model)
        self.ui_assets.verticalHeader().hide()

        main_layout = QtGui.QVBoxLayout()
        main_layout.addWidget(self.ui_assets)
        self.setLayout(main_layout)

        self.unit_test()


    def unit_test(self):
        assets = [
            Asset('Doug', ['v01', 'v02', 'v03'], 0),
            Asset('Amy', ['v10', 'v11', 'v13'], 1),
            Asset('Kevin', ['v11', 'v22', 'v53'], 2),
            Asset('Leslie', ['v13', 'v21', 'v23'], 0)
        ]

        self.ui_assets.model().sourceModel().clear()
        for i, obj in enumerate(assets):
            self.ui_assets.model().sourceModel().addItem(obj)


def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    ex.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

person JokerMartini    schedule 06.02.2018    source источник
comment
Какая связь существует между выбранным значением QComboBox и статусом?   -  person eyllanesc    schedule 06.02.2018
comment
Статус будет указывать, является ли текущая версия, выбранная в раскрывающемся списке, последним элементом в списке. Если это последний элемент в списке, то есть последний элемент, он будет зеленым, иначе красным. Имеет ли это смысл?   -  person JokerMartini    schedule 06.02.2018
comment
Какое значение QComboBox будет выбрано по умолчанию?   -  person eyllanesc    schedule 07.02.2018
comment
По умолчанию это может быть первый элемент. В конце концов я сделаю это необязательным аргументом, который принимается при первом создании объекта.   -  person JokerMartini    schedule 07.02.2018
comment
Вы должны сохранить это состояние, когда оно будет изменено с помощью QComboBox, поэтому у вас должен быть дополнительный атрибут в активе, который является выбранным элементом.   -  person eyllanesc    schedule 07.02.2018
comment
@eyllanesc, так как мне настроить это в коде? Это то, что я не знаю, как сделать.   -  person JokerMartini    schedule 07.02.2018
comment
В своем комментарии вы указываете, что цвет только красный или зеленый, но я вижу оранжевый цвет, пожалуйста, отредактируйте свой вопрос и укажите случаи, например, у меня есть следующие вопросы: меняется ли статус?, если выбор выпадающего списка изменения Что должно измениться в представлении? От чего зависят цвета?   -  person eyllanesc    schedule 07.02.2018


Ответы (1)


У вас есть 2 задачи:

  • Сделайте вашу модель доступной для редактирования, потому что при использовании поля со списком вы должны редактировать значения, кроме того, вы должны реализовать новые роли для доступа ко всем свойствам актива, для этого измените класс актива:

class Asset(object):
    def __init__(self, name, items=[], active=0):
        self.active = active
        self.name = name
        self.items = items

    @property
    def status(self):
        return self.active == len(self.items) - 1

Чтобы сделать редактируемую модель, вы должны реализовать метод setData() и включить флаг Qt.ItemIsEditable:

class AssetModel(QtCore.QAbstractTableModel):
    attr = ["Name", "Options"]
    ItemsRole = QtCore.Qt.UserRole + 1
    ActiveRole = QtCore.Qt.UserRole + 2

    def __init__(self, *args, **kwargs):
        QtCore.QAbstractTableModel.__init__(self, *args, **kwargs)
        self._items = []

    def flags(self, index):
        fl = QtCore.QAbstractTableModel.flags(self, index)
        if index.column() == 1:
            fl |= QtCore.Qt.ItemIsEditable
        return fl

    def clear(self):
        self.beginResetModel()
        self._items = []
        self.endResetModel()

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

    def columnCount(self, index=QtCore.QModelIndex()):
        return len(self.attr)

    def addItem(self, sbsFileObject):
        self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount())
        self._items.append(sbsFileObject)
        self.endInsertRows()

    def headerData(self, section, orientation, role=QtCore.Qt.DisplayRole):
        if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
            return AssetModel.attr[section]
        return QtCore.QAbstractTableModel.headerData(self, section, orientation, role)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return None
        if 0 <= index.row() < self.rowCount():
            item = self._items[index.row()]
            col = index.column()
            if role == AssetModel.ItemsRole:
                return getattr(item, 'items')

            if role == AssetModel.ActiveRole:
                return getattr(item, 'active')

            if 0 <= col < self.columnCount():
                if role == QtCore.Qt.DisplayRole:
                    if col == 0:
                        return getattr(item, 'name', '')
                    if col == 1:
                        return getattr(item, 'items')[getattr(item, 'active')]
                elif role == QtCore.Qt.DecorationRole:
                    if col == 0:
                        status = getattr(item, 'status')
                        col = QtGui.QColor(QtCore.Qt.red) if status else QtGui.QColor(QtCore.Qt.green)
                        px = QtGui.QPixmap(120, 120)
                        px.fill(QtCore.Qt.transparent)
                        painter = QtGui.QPainter(px)
                        painter.setRenderHint(QtGui.QPainter.Antialiasing)
                        px_size = px.rect().adjusted(12, 12, -12, -12)
                        painter.setBrush(col)
                        painter.setPen(QtGui.QPen(QtCore.Qt.black, 4,
                                                  QtCore.Qt.SolidLine,
                                                  QtCore.Qt.RoundCap,
                                                  QtCore.Qt.RoundJoin))
                        painter.drawEllipse(px_size)
                        painter.end()

                        return QtGui.QIcon(px)

    def setData(self, index, value, role=QtCore.Qt.EditRole):
        if 0 <= index.row() < self.rowCount():
            item = self._items[index.row()]
            if role == AssetModel.ActiveRole:
                setattr(item, 'active', value)
                return True
        return QtCore.QAbstractTableModel.setData(self, index, value, role)
  • Используйте делегат, для этого вы должны перезаписать методы createEditor(), setEditorData() и setModelData(), где мы создали QComboBox, обновили выбор QComboBox информацией о модели и обновили модель выбором QComboBox. Мы также используем paint(), чтобы сделать QComboBox постоянным.

class AssetDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        if isinstance(self.parent(), QtGui.QAbstractItemView):
            self.parent().openPersistentEditor(index)
        QtGui.QStyledItemDelegate.paint(self, painter, option, index)

    def createEditor(self, parent, option, index):
        combobox = QtGui.QComboBox(parent)
        combobox.addItems(index.data(AssetModel.ItemsRole))
        combobox.currentIndexChanged.connect(self.onCurrentIndexChanged)
        return combobox

    def onCurrentIndexChanged(self, ix):
        editor = self.sender()
        self.commitData.emit(editor)
        self.closeEditor.emit(editor, QtGui.QAbstractItemDelegate.NoHint)

    def setEditorData(self, editor, index):
        ix = index.data(AssetModel.ActiveRole)
        editor.setCurrentIndex(ix)

    def setModelData(self, editor, model, index):
        ix = editor.currentIndex()
        model.setData(index, ix, AssetModel.ActiveRole)

Затем мы устанавливаем делегата и передаем его в качестве родителя QTableView, чтобы его можно было сохранить автоматически:

self.ui_assets.setItemDelegateForColumn(1, AssetDelegate(self.ui_assets))

Полный код можно найти по следующей ссылке.

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

person eyllanesc    schedule 07.02.2018