QListView显示多个标签

时间:2017-12-19 20:37:47

标签: python pyside

如何使用PySide在QListView中显示多个标签?如下图所示,我想显示图像的名称以及一些不使用粗体文本的图像的其他信息。

为每个图像显示的第二行文本反映了该类对象的属性,称为'标签'这是用于该图像的描述性词语列表。我最终会添加一个lineedit,用户可以在其中添加其他标签到所选的listview项目。

enter image description here

import os,sys
from PySide import QtGui, QtCore


class AssetItem(object):
    def __init__(self, filepath):
            self._name = ''
            self._extension = ''
            self._tags = []
            self._filepath = ''
            self.filepath = filepath

    @property
    def filepath(self):
        return self._filepath

    @filepath.setter
    def filepath(self, value):
        self._filepath = value
        self.name, self.extension = os.path.splitext(os.path.basename(value))
        self.tags = self.name.split('_')


class AssetItemModel(QtCore.QAbstractListModel):

    def __init__(self, *args, **kwargs):
        QtCore.QAbstractListModel.__init__(self, *args, **kwargs)
        self._items = []
        self._icons = {}
        self._iconSize = QtCore.QSize(96, 96)

    def setIconSize(self, size):
        self._iconSize = size

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

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

    def getItem(self, index):
        row = index.row()
        if index.isValid() and 0 <= row < self.rowCount():
            return self._items[row]

    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()]
            if role == QtCore.Qt.DecorationRole:
                ix_p = QtCore.QPersistentModelIndex(index)
                value = self.get_icon(ix_p)
                if value is None:
                    value = QtGui.QIcon(item.filepath)
                    self._icons[ix_p] = value
                return value
            elif role == QtCore.Qt.SizeHintRole:
                return self._iconSize
            elif role == QtCore.Qt.TextAlignmentRole:
                return QtCore.Qt.AlignLeft
            elif role == QtCore.Qt.DisplayRole:
                return ', '.join(item.tags)

    def get_icon(self, ix):
        if ix in self._icons.keys():
            return self._icons[ix]


class AssetImporterWindow(QtGui.QMainWindow):
    def __init__(self):
        super(AssetImporterWindow, self).__init__()
        self.resize(500, 400)
        self.setWindowTitle('Import New Assets')

        self.ui_asset_viewer = QtGui.QListView()
        self.ui_asset_viewer.setResizeMode(QtGui.QListView.Adjust)
        self.ui_asset_viewer.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.ui_asset_viewer.setIconSize(QtCore.QSize(96, 96))
        self.ui_asset_viewer.setSpacing(5)
        self.ui_asset_viewer.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        # self.ui_asset_viewer.setModel(AssetItemModel())
        sorted_model = QtGui.QSortFilterProxyModel()
        sorted_model.setSourceModel(AssetItemModel())
        self.ui_asset_viewer.setModel(sorted_model)

        self.ui_add_tags = QtGui.QPushButton('Add Tags')
        self.ui_add_tags.clicked.connect(self.add_tags)

        lay = QtGui.QVBoxLayout()
        lay.addWidget(self.ui_asset_viewer)
        lay.addWidget(self.ui_add_tags)

        widget = QtGui.QWidget()
        widget.setLayout(lay)
        self.setCentralWidget(widget)

        self.populate()


    def populate(self):
        root = 'images/'
        for img in os.listdir(root):
            path = os.path.abspath(os.path.join(root, img))
            item = AssetItem(path)
            self.ui_asset_viewer.model().sourceModel().addItem(item)


    def add_tags(self):
        indexes = self.ui_asset_viewer.selectedIndexes()
        for i in indexes:
            if i.isValid():
                item = self.ui_asset_viewer.model().sourceModel().getItem(i)
                item.tags.extend(['Red','Green'])

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

if __name__ == '__main__':
    main()

更新01

建议的答案产生这个结果,它做了我想要的一切,但外观有点奇怪。我认为这是因为它使用HTML文档来显示文本。也许富文本框或qlabel会更好: enter image description here

更新2

enter image description here

这是解决问题的其他随机尝试。我制作了一个自定义小部件,但我不知道我在这里做了什么。这只是一个想法。我不知道我是否正确地做了,也不确定ListWidget是否是一个很好的解决方案。

import sys
from PySide import QtGui, QtCore

class AssetWidget(QtGui.QWidget):
    def __init__ (self, parent=None, title='Title', subtitle='Subtitle', icon=None):
        super(AssetWidget, self).__init__(parent)
        self.resize(300,300)

        px = QtGui.QPixmap(64,64)
        px.fill(QtGui.QColor(QtCore.Qt.red))
        self.ui_icon = QtGui.QLabel('Icon')
        self.ui_icon.setPixmap(px)

        self.ui_title = QtGui.QLabel(title)
        fnt = self.ui_title.font()
        fnt.setBold(True)
        self.ui_title.setFont(fnt)
        self.ui_subtitle = QtGui.QLabel(subtitle)

        vertical_lay = QtGui.QVBoxLayout()
        vertical_lay.addWidget(self.ui_title)
        vertical_lay.addWidget(self.ui_subtitle)

        main_layout = QtGui.QHBoxLayout()
        main_layout.addWidget(self.ui_icon)
        main_layout.addLayout(vertical_lay, QtCore.Qt.AlignLeft)

        self.setLayout(main_layout)


class exampleQMainWindow (QtGui.QMainWindow):
    def __init__ (self):
        super(exampleQMainWindow, self).__init__()
        self.resize(300,300)
        # Create QListWidget
        self.myQListWidget = QtGui.QListWidget()

        for i in range(5):
            # Create QCustomQWidget
            myQCustomQWidget = AssetWidget()
            myQListWidgetItem = QtGui.QListWidgetItem(self.myQListWidget)
            myQListWidgetItem.setSizeHint(myQCustomQWidget.sizeHint())
            self.myQListWidget.addItem(myQListWidgetItem)
            self.myQListWidget.setItemWidget(myQListWidgetItem, myQCustomQWidget)
        self.setCentralWidget(self.myQListWidget)

app = QtGui.QApplication([])
window = exampleQMainWindow()
window.show()
sys.exit(app.exec_())

1 个答案:

答案 0 :(得分:0)

您必须实现委托,为此我们将使用QTextDocument

class AssetDocument(QtGui.QTextDocument):
    def __init__(self, title, content, font):
        QtGui.QTextDocument.__init__(self)
        cursor = QtGui.QTextCursor(self)
        fmt = QtGui.QTextCharFormat()
        fmt.setFont(font)
        cursor.setCharFormat(fmt)
        self.setPlainText(title+"\n"+", ".join(content))
        cursor.movePosition(QtGui.QTextCursor.Start);
        cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor)
        fmt.setFontWeight(QtGui.QFont.Bold)
        cursor.mergeCharFormat(fmt)

class AssetItemDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        options = QtGui.QStyleOptionViewItemV4(option)
        self.initStyleOption(options,index)
        style = QtGui.QApplication.style() if options.widget is None else options.widget.style()
        name = index.data(AssetItemModel.NameRole)
        tags = index.data(AssetItemModel.TagRole)
        doc = AssetDocument(name, tags, options.font)
        style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options, options.widget)
        painter.save()
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        doc.documentLayout().draw(painter, ctx)
        painter.restore()


class AssetItemModel(QtCore.QAbstractListModel):
    NameRole, TagRole = range(QtCore.Qt.UserRole, QtCore.Qt.UserRole+2)
        [...]
    def data(self, index, role=QtCore.Qt.DisplayRole):
        [...]
            elif role == QtCore.Qt.TextAlignmentRole:
                return QtCore.Qt.AlignLeft
            elif role == AssetItemModel.NameRole:
                return item.name
            elif role == AssetItemModel.TagRole:
                return item.tags

[...]

class AssetImporterWindow(QtGui.QMainWindow):
    def __init__(self):
        [..]
        self.ui_asset_viewer = QtGui.QListView()
        self.ui_asset_viewer.setItemDelegate(AssetItemDelegate())
        [...]

您的代码存在的另一个问题是,在添加新信息时它没有更新,这是因为您没有通知模型存在更改,因此您必须使用dataChanged信号:

def add_tags(self):
    indexes = self.ui_asset_viewer.selectedIndexes()
    for i in indexes:
        if i.isValid():
            item = self.ui_asset_viewer.model().sourceModel().getItem(i)
            item.tags.extend(['Red','Green'])
            self.ui_asset_viewer.model().sourceModel().dataChanged.emit(i, i)

完整代码:

import os,sys
from PySide import QtGui, QtCore

class AssetItem(object):
    def __init__(self, filepath):
            self._name = ''
            self._extension = ''
            self._tags = []
            self._filepath = ''
            self.filepath = filepath

    @property
    def filepath(self):
        return self._filepath

    @filepath.setter
    def filepath(self, value):
        self._filepath = value
        self.name, self.extension = os.path.splitext(os.path.basename(value))
        self.tags = self.name.split('_')


class AssetItemModel(QtCore.QAbstractListModel):
    NameRole, TagRole = range(QtCore.Qt.UserRole, QtCore.Qt.UserRole+2)
    def __init__(self, *args, **kwargs):
        QtCore.QAbstractListModel.__init__(self, *args, **kwargs)
        self._items = []
        self._icons = {}
        self._iconSize = QtCore.QSize(96, 96)

    def setIconSize(self, size):
        self._iconSize = size

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

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

    def getItem(self, index):
        row = index.row()
        if index.isValid() and 0 <= row < self.rowCount():
            return self._items[row]

    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()]
            if role == QtCore.Qt.DecorationRole:
                ix_p = QtCore.QPersistentModelIndex(index)
                value = self.get_icon(ix_p)
                if value is None:
                    value = QtGui.QIcon(item.filepath)
                    self._icons[ix_p] = value
                return value
            elif role == QtCore.Qt.SizeHintRole:
                return self._iconSize
            elif role == QtCore.Qt.TextAlignmentRole:
                return QtCore.Qt.AlignLeft
            elif role == AssetItemModel.NameRole:
                return item.name
            elif role == AssetItemModel.TagRole:
                return item.tags

    def get_icon(self, ix):
        if ix in self._icons.keys():
            return self._icons[ix]

class AssetDocument(QtGui.QTextDocument):
    def __init__(self, title, content, font):
        QtGui.QTextDocument.__init__(self)
        cursor = QtGui.QTextCursor(self)
        fmt = QtGui.QTextCharFormat()
        fmt.setFont(font)
        cursor.setCharFormat(fmt)
        self.setPlainText(title+"\n"+", ".join(content))
        cursor.movePosition(QtGui.QTextCursor.Start);
        cursor.movePosition(QtGui.QTextCursor.EndOfLine, QtGui.QTextCursor.KeepAnchor)
        fmt.setFontWeight(QtGui.QFont.Bold)
        cursor.mergeCharFormat(fmt)

class AssetItemDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        options = QtGui.QStyleOptionViewItemV4(option)
        self.initStyleOption(options,index)
        style = QtGui.QApplication.style() if options.widget is None else options.widget.style()
        name = index.data(AssetItemModel.NameRole)
        tags = index.data(AssetItemModel.TagRole)
        doc = AssetDocument(name, tags, options.font)
        style.drawControl(QtGui.QStyle.CE_ItemViewItem, options, painter)
        ctx = QtGui.QAbstractTextDocumentLayout.PaintContext()
        textRect = style.subElementRect(QtGui.QStyle.SE_ItemViewItemText, options, options.widget)
        painter.save()
        painter.translate(textRect.topLeft())
        painter.setClipRect(textRect.translated(-textRect.topLeft()))
        doc.documentLayout().draw(painter, ctx)
        painter.restore()


class AssetImporterWindow(QtGui.QMainWindow):
    def __init__(self):
        super(AssetImporterWindow, self).__init__()
        self.resize(500, 400)
        self.setWindowTitle('Import New Assets')

        self.ui_asset_viewer = QtGui.QListView()
        self.ui_asset_viewer.setItemDelegate(AssetItemDelegate())
        self.ui_asset_viewer.setResizeMode(QtGui.QListView.Adjust)
        self.ui_asset_viewer.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
        self.ui_asset_viewer.setIconSize(QtCore.QSize(96, 96))
        self.ui_asset_viewer.setSpacing(5)
        self.ui_asset_viewer.setSelectionMode(QtGui.QAbstractItemView.ExtendedSelection)
        # self.ui_asset_viewer.setModel(AssetItemModel())
        sorted_model = QtGui.QSortFilterProxyModel()
        sorted_model.setSourceModel(AssetItemModel())
        self.ui_asset_viewer.setModel(sorted_model)

        self.ui_add_tags = QtGui.QPushButton('Add Tags')
        self.ui_add_tags.clicked.connect(self.add_tags)

        lay = QtGui.QVBoxLayout()
        lay.addWidget(self.ui_asset_viewer)
        lay.addWidget(self.ui_add_tags)

        widget = QtGui.QWidget()
        widget.setLayout(lay)
        self.setCentralWidget(widget)
        self.populate()


    def populate(self):
        root = '/home/eyllanesc/Pictures/'
        for img in os.listdir(root):
            path = os.path.abspath(os.path.join(root, img))
            item = AssetItem(path)
            self.ui_asset_viewer.model().sourceModel().addItem(item)


    def add_tags(self):
        indexes = self.ui_asset_viewer.selectedIndexes()
        for i in indexes:
            if i.isValid():
                item = self.ui_asset_viewer.model().sourceModel().getItem(i)
                item.tags.extend(['Red','Green'])
                self.ui_asset_viewer.model().sourceModel().dataChanged.emit(i, i)

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

if __name__ == '__main__':
    main()

另一种解决方案:

使用自定义小部件:

class AssetWidget(QtGui.QWidget):
    def __init__ (self, title='Title', subtitle='Subtitle', icon=None):
        super(AssetWidget, self).__init__()
        self.resize(300,300)
        if icon:
            px = icon.pixmap(64, 64)
        else:
            px = QtGui.QPixmap(64,64)
            px.fill(QtGui.QColor(QtCore.Qt.red))

        self.ui_icon = QtGui.QLabel(self)
        self.ui_icon.setPixmap(px)

        self.ui_title = QtGui.QLabel(title, self)
        fnt = self.ui_title.font()
        fnt.setBold(True)
        self.ui_title.setFont(fnt)
        self.ui_subtitle = QtGui.QLabel(subtitle, self)

        vertical_lay = QtGui.QVBoxLayout()
        vertical_lay.addWidget(self.ui_title)
        vertical_lay.addWidget(self.ui_subtitle)

        main_layout = QtGui.QHBoxLayout(self)
        main_layout.addWidget(self.ui_icon)
        main_layout.addLayout(vertical_lay, QtCore.Qt.AlignLeft)

class AssetItemDelegate(QtGui.QStyledItemDelegate):
    def paint(self, painter, option, index):
        r = option.rect
        painter.save()
        painter.translate(r.topLeft())
        icon = index.data(QtCore.Qt.DecorationRole)
        name = index.data(AssetItemModel.NameRole)
        tags = index.data(AssetItemModel.TagRole)
        widget = AssetWidget(name, ", ".join(tags), icon)
        widget.resize(r.size())
        widget.render(painter, QtCore.QPoint(), QtGui.QRegion(), QtGui.QWidget.DrawChildren)
        painter.restore()