Я ищу информацию о том, как go создать горизонтальную доску в стиле Kanban, используя PySide2. Мое приложение - это файловый браузер, в котором вы выбираете элемент из папки QTreeView
слева, а в правом окне отображаются карточки. С правой стороны карты я в тупике.
Вот моя цель:
Моя текущая реализация wip использует QTreeView
для отображения карт - это близко, но не совсем то, что я ищу. В настоящее время делегат dr aws родители в качестве псевдо заголовков, а дети в виде карточек. Как вы можете видеть ниже, одной из проблем использования QTreeView
является то, что дочерние элементы перечислены по вертикали, а не по моему предпочтительному горизонтальному списку.
Моя текущая реализация wip:
У меня есть несколько идей о том, как go об этом:
- Используйте
setIndexWidget()
, чтобы добавить QListView
под каждым псевдо заголовком родительский элемент. Я не уверен, является ли это предполагаемым использованием этого метода или как получить данные модели для заполнения списка. - Заменить или покрыть
QListView
на QWidget
, который динамически заполняется данными модели из выбора папки просмотра. Этот виджет создает QLabels
и QlistViews
для каждого элемента заголовка из модели. Я чувствую, что все усложняется, и решение, модифицирующее существующее представление, вероятно, будет лучше в долгосрочной перспективе. - Используйте другое представление, о котором я не знаю!
Любые мысли о том, как go о создании этого? Вертикальный Kanban виджет существует? Спасибо!
Кроме того, вот некоторые другие просмотры, которые я пробовал:
список
столбец просмотр
Пример кода:
import os
import sys
import collections
from PySide2 import QtWidgets, QtGui, QtCore
class MainWidget(QtWidgets.QWidget):
def __init__(self, model_data):
super(MainWidget, self).__init__()
self.setMinimumSize(600, 500)
# Model
self.model_data = model_data
self.folder_model = QtGui.QStandardItemModel()
self._fill_model(model_data)
# Folder view
self.folders_view = QtWidgets.QTreeView()
self.folders_view.setModel(self.folder_model)
self.folders_view.expandAll()
self.folders_view.setItemsExpandable(False)
self.folders_view.setVerticalScrollMode(QtWidgets.QAbstractItemView.ScrollPerPixel)
# Files Delegate
self.files_delegate = FilesItemDelegate()
self.folders_view.setItemDelegate(self.files_delegate)
# Layout
self.main_layout = QtWidgets.QHBoxLayout()
self.main_layout.addWidget(self.folders_view)
self.main_layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.main_layout)
def _fill_model(self, value, parent=None):
if isinstance(value, collections.abc.Mapping):
for key, val in sorted(value.items()):
if key == 'meta_data':
pass
else:
item = QtGui.QStandardItem(key)
item.setData(val['meta_data']['name'], QtCore.Qt.DisplayRole)
item.setData(val['meta_data']['item_type'], QtCore.Qt.UserRole + 1)
try: # special data for major items
item.setData(val['meta_data']['major_number'], QtCore.Qt.UserRole)
except KeyError:
pass
try: # special data for minor items
item.setData(val['meta_data']['comment'], QtCore.Qt.UserRole)
except KeyError:
pass
try: # Add row under a parent item
parent.appendRow(item)
self._fill_model(value=val, parent=item)
except AttributeError: # Add first item to model
self.folder_model.appendRow(item)
self._fill_model(value=val, parent=item)
class FilesItemDelegate(QtWidgets.QStyledItemDelegate):
def __init__(self, parent=None):
super(FilesItemDelegate, self).__init__(parent)
def sizeHint(self, option, index):
if index.data(role=QtCore.Qt.UserRole + 1) == 'major':
size = super(FilesItemDelegate, self).sizeHint(option, index)
size.setWidth(option.rect.width())
size.setHeight(50)
return size
elif index.data(role=QtCore.Qt.UserRole + 1) == 'minor':
size = super(FilesItemDelegate, self).sizeHint(option, index)
if option.state & QtWidgets.QStyle.State_Selected:
width = 250
elif option.state & QtWidgets.QStyle.State_MouseOver:
width = 250
else:
width = 200
size.setWidth(width)
size.setHeight(200)
return size
else:
return super(FilesItemDelegate, self).sizeHint(option, index)
def paint(self, painter, option, index):
# Rect
rect_item = option.rect
# Background
painter.setPen(QtCore.Qt.NoPen)
# File component
if index.data(role=QtCore.Qt.UserRole + 1) == 'major':
# Rects:
rect_header_icon = QtCore.QRect(
rect_item.left() + 7,
rect_item.top(),
50,
rect_item.height() - 3)
rect_header_name = QtCore.QRect(
rect_header_icon.right() + 8,
rect_item.top(),
rect_item.width() - rect_header_icon.width(),
rect_item.height() * .666)
rect_header_major = QtCore.QRect(
rect_header_icon.right() + 8,
rect_header_name.bottom(),
rect_item.width() - rect_header_icon.width(),
rect_item.height() * .333)
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(QtGui.QColor('#00000000'))
elif option.state & QtWidgets.QStyle.State_MouseOver:
painter.setBrush(QtGui.QColor('#00000000'))
else:
painter.setBrush(QtGui.QColor('#00000000'))
painter.drawRect(option.rect)
# Icon
painter.save()
painter.setBrush(QtGui.QColor('#262626'))
painter.drawRect(rect_header_icon)
painter.restore()
# Primary Title
painter.setPen(QtGui.QColor(100, 100, 100))
# Name
name_index = index.data(role=QtCore.Qt.DisplayRole)
name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
painter.setFont(name_font)
QtWidgets.QApplication.style().drawItemText(painter, rect_header_name, QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
QtWidgets.QApplication.palette(), True,
name_index)
# Major number
name_index = index.data(role=QtCore.Qt.UserRole)
name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
painter.setFont(name_font)
QtWidgets.QApplication.style().drawItemText(painter, rect_header_major,
QtCore.Qt.AlignLeft | QtCore.Qt.AlignVCenter,
QtWidgets.QApplication.palette(), True,
name_index)
# Cards
elif index.data(role=QtCore.Qt.UserRole + 1) == 'minor':
# Rects
rect_icon_target = QtCore.QRect(
rect_item.left(),
rect_item.top(),
rect_item.width(),
rect_item.height() - int(rect_item.height()/2)
)
rect_data_target = QtCore.QRect(
rect_item.left(),
rect_icon_target.bottom(),
rect_item.width(),
rect_item.height() - int(rect_item.height()/2)
)
pad_data_target = 15
rect_name_target = QtCore.QRect(
rect_item.left() + pad_data_target,
rect_icon_target.bottom() + pad_data_target,
rect_item.width() - pad_data_target,
rect_data_target.height()/2 - pad_data_target
)
rect_comment_target = QtCore.QRect(
rect_item.left() + pad_data_target,
rect_name_target.bottom() + pad_data_target,
rect_item.width() - pad_data_target,
rect_data_target.height() - pad_data_target
)
# Image half
painter.save()
path = QtGui.QPainterPath()
path.addRect(rect_icon_target)
painter.setBrush(QtGui.QColor(90, 90, 90))
painter.drawPath(path)
painter.restore()
# Data half
painter.save()
path = QtGui.QPainterPath()
path.setFillRule(QtCore.Qt.WindingFill)
path.addRect(rect_data_target)
if option.state & QtWidgets.QStyle.State_Selected:
painter.setBrush(QtGui.QColor(0, 149, 119))
elif option.state & QtWidgets.QStyle.State_MouseOver:
painter.setBrush(QtGui.QColor(100, 100, 100))
else:
painter.setBrush(QtGui.QColor(67, 67, 67))
painter.drawPath(path.simplified())
painter.restore()
# Primary Title
painter.setPen(QtGui.QColor(255, 255, 255))
name_index = index.data(role=QtCore.Qt.DisplayRole)
name_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
painter.setFont(name_font)
QtWidgets.QApplication.style().drawItemText(painter, rect_name_target, QtCore.Qt.AlignLeft,
QtWidgets.QApplication.palette(), True,
name_index)
# Comment
painter.setPen(QtGui.QColor(255, 255, 255))
comment_index = index.data(role=QtCore.Qt.UserRole)
comment_font = QtGui.QFont("Segoe UI", 13, QtGui.QFont.DemiBold | QtGui.QFont.NoAntialias)
painter.setFont(comment_font)
QtWidgets.QApplication.style().drawItemText(painter, rect_comment_target, QtCore.Qt.AlignLeft,
QtWidgets.QApplication.palette(), True,
comment_index)
else:
return super(FilesItemDelegate, self).paint(painter, option, index)
def launch():
model_data = {
'low_poly': {
'001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'hey'}},
'002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'you'}},
'003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'guyyyyys'}},
'004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "i'll"}},
'005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'be'}},
'006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'back'}},
'meta_data': {'item_type': 'major', 'name': 'low_poly', 'major_number': '001'}},
'high_poly': {
'001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'as'}},
'002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'you'}},
'003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'wish'}},
'004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "lok"}},
'005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'tar'}},
'006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'ogar'}},
'meta_data': {'item_type': 'major', 'name': 'high_poly', 'major_number': '002'}},
'no_poly': {
'001': {'meta_data': {'name': '001', 'item_type': 'minor', 'comment': 'this'}},
'002': {'meta_data': {'name': '002', 'item_type': 'minor', 'comment': 'is'}},
'003': {'meta_data': {'name': '003', 'item_type': 'minor', 'comment': 'my'}},
'004': {'meta_data': {'name': '004', 'item_type': 'minor', 'comment': "boomstick"}},
'005': {'meta_data': {'name': '005', 'item_type': 'minor', 'comment': 'good'}},
'006': {'meta_data': {'name': '006', 'item_type': 'minor', 'comment': 'bye'}},
'meta_data': {'item_type': 'major', 'name': 'high_poly', 'major_number': '003'}},
'meta_data': {'item_type': 'asset', 'name': 'asset1'}
}
try:
os.environ["QTWEBENGINE_CHROMIUM_FLAGS"] = "--enable-logging --log-level=3"
os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" # High dpi setting
app = QtWidgets.QApplication(sys.argv)
except:
pass
window = MainWidget(model_data)
window.show()
try:
sys.exit(app.exec_())
except:
pass
return window
if __name__ == "__main__":
launch()