from PySide6.QtWidgets import (QWidget, QListWidget, QStyledItemDelegate, QApplication, QVBoxLayout, QMenu, QListWidgetItem, QStyle) from PySide6.QtCore import Qt, QSize, QRect, QPoint from PySide6.QtGui import (QPainter, QPainterPath, QColor, QLinearGradient, QPen, QFont, QIcon) from dataclasses import dataclass import sys from datetime import date import os import subprocess @dataclass class CardData: name: str date: str description: str activated: bool = False class CardItemDelegate(QStyledItemDelegate): def __init__(self, parent=None): super().__init__(parent) def paint(self, painter: QPainter, option, index): painter.setRenderHint(QPainter.RenderHint.Antialiasing) painter.save() # 获取数据 data = index.data(Qt.ItemDataRole.UserRole) # 绘制卡片背景 rect = option.rect rect.adjust(8, 4, -8, -4) # 创建圆角路径 path = QPainterPath() path.addRoundedRect(rect, 8, 8) # 绘制阴影 shadow_color = QColor(0, 0, 0, 30) for i in range(5): shadow_rect = rect.adjusted(0, i, 0, i) shadow_path = QPainterPath() shadow_path.addRoundedRect(shadow_rect, 8, 8) painter.fillPath(shadow_path, shadow_color) # 绘制卡片背景 gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft()) if data.activated: gradient.setColorAt(0, QColor(40, 70, 45)) gradient.setColorAt(1, QColor(45, 80, 50)) painter.fillPath(path, gradient) painter.setPen(QPen(QColor(60, 180, 90), 2)) elif option.state & QStyle.State_Selected: gradient.setColorAt(0, QColor(45, 45, 55)) gradient.setColorAt(1, QColor(55, 55, 65)) painter.fillPath(path, gradient) painter.setPen(QPen(QColor(70, 130, 180), 2)) else: painter.fillPath(path, QColor(35, 35, 40)) painter.setPen(QPen(QColor(60, 60, 65))) painter.drawPath(path) # 设置字体 name_font = QFont("Microsoft YaHei", 10) name_font.setBold(True) normal_font = QFont("Microsoft YaHei", 9) # 计算文本区域 padding = 15 name_rect = rect.adjusted(padding, padding, -padding, 0) name_rect.setHeight(25) date_rect = QRect(name_rect) date_rect.translate(0, name_rect.height()) desc_rect = QRect(date_rect) desc_rect.translate(0, date_rect.height()) desc_rect.setHeight(40) # 绘制文本 painter.setFont(name_font) painter.setPen(Qt.GlobalColor.white) painter.drawText(name_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.name) painter.setFont(normal_font) painter.setPen(QColor(180, 180, 180)) painter.drawText(date_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter, data.date) painter.setPen(QColor(160, 160, 160)) painter.drawText(desc_rect, Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop | Qt.TextFlag.TextWordWrap, data.description) painter.restore() def sizeHint(self, option, index): return QSize(380, 130) class MainWindow(QWidget): def __init__(self): super().__init__() self.setup_ui() self.setup_style() # 添加示例数据 for i in range(1, 11): data = CardData( name=f"项目 {i}", date=date.today().strftime("%Y-%m-%d"), description=f"这是项目 {i} 的详细描述信息,可以包含多行文本内容。这是一个较长的描述,用于测试换行效果。" ) self.add_card_item(data) def setup_ui(self): self.setWindowTitle("卡片列表示例") self.resize(400, 600) layout = QVBoxLayout(self) layout.setContentsMargins(10, 10, 10, 10) layout.setSpacing(0) self.list_widget = QListWidget(self) self.list_widget.setSpacing(10) self.list_widget.setResizeMode(QListWidget.ResizeMode.Adjust) self.list_widget.setUniformItemSizes(False) self.list_widget.setViewMode(QListWidget.ViewMode.ListMode) self.list_widget.setVerticalScrollMode(QListWidget.ScrollMode.ScrollPerPixel) self.list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) delegate = CardItemDelegate(self.list_widget) self.list_widget.setItemDelegate(delegate) # 连接信号 self.list_widget.itemDoubleClicked.connect(self.on_item_double_clicked) self.list_widget.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu) self.list_widget.customContextMenuRequested.connect(self.show_context_menu) layout.addWidget(self.list_widget) def setup_style(self): self.setStyleSheet(""" QWidget { background-color: #1e1e1e; } QListWidget { background-color: #1e1e1e; border: none; outline: none; } QListWidget::item { background-color: transparent; padding: 4px; } QListWidget::item:selected { background-color: transparent; } QScrollBar:vertical { border: none; background: #1e1e1e; width: 8px; margin: 0px; } QScrollBar::handle:vertical { background: #404040; min-height: 20px; border-radius: 4px; } QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0px; } QScrollBar::add-page:vertical, QScrollBar::sub-page:vertical { background: none; } QMenu { background-color: #2d2d2d; border: 1px solid #404040; padding: 5px; } QMenu::item { background-color: transparent; padding: 6px 25px; border-radius: 4px; color: #ffffff; } QMenu::item:selected { background-color: #404040; } QMenu::separator { height: 1px; background: #404040; margin: 5px 0px; } """) def add_card_item(self, data: CardData): item = QListWidgetItem(self.list_widget) item.setData(Qt.ItemDataRole.UserRole, data) item.setSizeHint(QSize(380, 120)) item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable) def on_item_double_clicked(self, item): # 先将所有项目设置为未激活状态 for row in range(self.list_widget.count()): current_item = self.list_widget.item(row) data = current_item.data(Qt.ItemDataRole.UserRole) data.activated = False current_item.setData(Qt.ItemDataRole.UserRole, data) # 设置当前点击项目为激活状态 data = item.data(Qt.ItemDataRole.UserRole) data.activated = True item.setData(Qt.ItemDataRole.UserRole, data) self.list_widget.viewport().update() def show_context_menu(self, pos: QPoint): item = self.list_widget.itemAt(pos) if not item: return menu = QMenu(self) send_action = menu.addAction("发送到设备") menu.addSeparator() edit_action = menu.addAction("修改") delete_action = menu.addAction("删除") menu.addSeparator() show_in_explorer_action = menu.addAction("在资源管理器中显示") # 设置图标 send_action.setIcon(QIcon.fromTheme("document-send")) edit_action.setIcon(QIcon.fromTheme("document-edit")) delete_action.setIcon(QIcon.fromTheme("document-delete")) show_in_explorer_action.setIcon(QIcon.fromTheme("folder")) action = menu.exec(self.list_widget.viewport().mapToGlobal(pos)) if not action: return if action == send_action: self.send_to_device(item) elif action == edit_action: self.edit_item(item) elif action == delete_action: self.delete_item(item) elif action == show_in_explorer_action: self.show_in_explorer(item) def send_to_device(self, item): data = item.data(Qt.ItemDataRole.UserRole) print(f"发送到设备: {data.name}") def edit_item(self, item): data = item.data(Qt.ItemDataRole.UserRole) print(f"编辑项目: {data.name}") def delete_item(self, item): row = self.list_widget.row(item) self.list_widget.takeItem(row) def show_in_explorer(self, item): path = os.getcwd() if sys.platform == 'win32': subprocess.run(['explorer', '/select,', os.path.normpath(path)]) elif sys.platform == 'darwin': subprocess.run(['open', '-R', path]) else: subprocess.run(['xdg-open', os.path.dirname(path)]) if __name__ == '__main__': app = QApplication(sys.argv) # 设置字体 font = QFont("Microsoft YaHei", 9) app.setFont(font) window = MainWindow() window.show() sys.exit(app.exec())