init commit

This commit is contained in:
JingweiCui 2025-02-14 15:00:10 +08:00
commit cb33c3b525
7 changed files with 735 additions and 0 deletions

25
CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.16)
project(checkbox_header_table)
#
if(MSVC)
add_compile_options(/utf-8)
endif()
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_AUTOUIC ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets)
add_executable(checkbox_header_table
checkbox_header_table.h
checkbox_header_table.cpp
)
target_link_libraries(checkbox_header_table PRIVATE
Qt6::Widgets
)

143
card_list_widget.cpp Normal file
View File

@ -0,0 +1,143 @@
#include "card_list_widget.h"
#include <QPainter>
#include <QApplication>
#include <QVBoxLayout>
CardItemDelegate::CardItemDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
}
void CardItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
painter->save();
// 获取数据
CardData data = index.data(Qt::UserRole).value<CardData>();
// 绘制卡片背景
QRect rect = option.rect;
rect.adjust(5, 5, -5, -5); // 留出边距
// 如果被选中,绘制不同的背景色
if (option.state & QStyle::State_Selected) {
painter->fillRect(rect, QColor(230, 230, 255));
} else {
painter->fillRect(rect, Qt::white);
}
// 绘制卡片边框
painter->setPen(QPen(Qt::lightGray));
painter->drawRect(rect);
// 设置字体
QFont nameFont = painter->font();
nameFont.setBold(true);
nameFont.setPointSize(10);
QFont normalFont = painter->font();
normalFont.setPointSize(9);
// 计算文本区域
int padding = 10;
QRect nameRect = rect.adjusted(padding, padding, -padding, 0);
nameRect.setHeight(25);
QRect dateRect = nameRect;
dateRect.translate(0, nameRect.height());
QRect descRect = dateRect;
descRect.translate(0, dateRect.height());
descRect.setHeight(40);
// 绘制文本
painter->setFont(nameFont);
painter->setPen(Qt::black);
painter->drawText(nameRect, Qt::AlignLeft | Qt::AlignVCenter, data.name);
painter->setFont(normalFont);
painter->setPen(Qt::darkGray);
painter->drawText(dateRect, Qt::AlignLeft | Qt::AlignVCenter, data.date);
// 绘制描述文本,支持自动换行
painter->drawText(descRect, Qt::AlignLeft | Qt::AlignTop | Qt::TextWordWrap,
data.description);
painter->restore();
}
QSize CardItemDelegate::sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const
{
Q_UNUSED(option);
Q_UNUSED(index);
return QSize(300, 100); // 卡片固定大小
}
MainWindow::MainWindow(QWidget* parent)
: QWidget(parent)
{
setWindowTitle(tr("卡片列表示例"));
resize(400, 600);
// 创建布局
auto layout = new QVBoxLayout(this);
// 创建列表控件
listWidget = new QListWidget(this);
listWidget->setSpacing(5); // 设置卡片间距
listWidget->setViewMode(QListView::ListMode);
listWidget->setItemDelegate(new CardItemDelegate(listWidget));
// 设置样式
listWidget->setStyleSheet(
"QListWidget {"
" background-color: #f0f0f0;"
" border: none;"
"}"
"QListWidget::item {"
" background-color: transparent;"
"}"
"QListWidget::item:selected {"
" background-color: transparent;"
"}"
);
layout->addWidget(listWidget);
// 添加示例数据
for (int i = 1; i <= 10; ++i) {
CardData data{
QString(tr("项目 %1")).arg(i),
QDate::currentDate().toString("yyyy-MM-dd"),
QString(tr("这是项目 %1 的详细描述信息,可以包含多行文本内容。")).arg(i)
};
addCardItem(data);
}
}
void MainWindow::addCardItem(const CardData& data)
{
QListWidgetItem* item = new QListWidgetItem(listWidget);
item->setData(Qt::UserRole, QVariant::fromValue(data));
listWidget->addItem(item);
}
// 注册自定义数据类型
Q_DECLARE_METATYPE(CardData)
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 设置编码和字体
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
QFont font("Microsoft YaHei");
app.setFont(font);
MainWindow window;
window.show();
return app.exec();
}

38
card_list_widget.h Normal file
View File

@ -0,0 +1,38 @@
#ifndef CARD_LIST_WIDGET_H
#define CARD_LIST_WIDGET_H
#include <QWidget>
#include <QListWidget>
#include <QStyledItemDelegate>
// 卡片数据结构
struct CardData {
QString name;
QString date;
QString description;
};
// 自定义委托类来绘制卡片样式
class CardItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit CardItemDelegate(QObject* parent = nullptr);
void paint(QPainter* painter, const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
QSize sizeHint(const QStyleOptionViewItem& option,
const QModelIndex& index) const override;
};
// 主窗口类
class MainWindow : public QWidget {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
private:
QListWidget* listWidget;
void addCardItem(const CardData& data);
};
#endif // CARD_LIST_WIDGET_H

264
card_list_widget.py Normal file
View File

@ -0,0 +1,264 @@
from PySide6.QtWidgets import (QApplication, QWidget, QListWidget, QListWidgetItem,
QStyledItemDelegate, QVBoxLayout, QStyle, QMenu)
from PySide6.QtCore import Qt, QRect, QSize
from PySide6.QtGui import QPainter, QPen, QColor, QFont, QPainterPath, QLinearGradient
import subprocess
import os
from datetime import date
import sys
class CardData:
"""卡片数据结构"""
def __init__(self, name: str, date: str, description: str):
self.name = name
self.date = date
self.description = description
self.activated = False # 添加激活状态标志
class CardItemDelegate(QStyledItemDelegate):
"""自定义委托类来绘制卡片样式"""
def paint(self, painter: QPainter, option, index):
painter.setRenderHint(QPainter.Antialiasing) # 启用抗锯齿
painter.save()
# 获取数据
item = index.data(Qt.UserRole)
if not isinstance(item, CardData):
print(f"Warning: Invalid data type: {type(item)}")
return
# 绘制卡片背景
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)
# 绘制卡片背景
if item.activated: # 激活状态
gradient = QLinearGradient(rect.topLeft(), rect.bottomLeft())
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 = QLinearGradient(rect.topLeft(), rect.bottomLeft())
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.white) # 白色文字
painter.drawText(name_rect, Qt.AlignLeft | Qt.AlignVCenter, item.name)
painter.setFont(normal_font)
painter.setPen(QColor(180, 180, 180)) # 浅灰色文字
painter.drawText(date_rect, Qt.AlignLeft | Qt.AlignVCenter, item.date)
painter.setPen(QColor(160, 160, 160)) # 描述文字颜色
painter.drawText(desc_rect, Qt.AlignLeft | Qt.AlignTop | Qt.TextWordWrap,
item.description)
painter.restore()
def sizeHint(self, option, index):
return QSize(380, 130) # 略微增加高度
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("卡片列表示例")
self.resize(400, 600)
# 设置窗口背景色
self.setStyleSheet("background-color: #1e1e1e;")
# 创建布局
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.Adjust)
self.list_widget.setUniformItemSizes(False)
self.list_widget.setViewMode(QListWidget.ListMode)
self.list_widget.setVerticalScrollMode(QListWidget.ScrollPerPixel)
self.list_widget.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
# 设置委托
delegate = CardItemDelegate(self.list_widget)
self.list_widget.setItemDelegate(delegate)
# 设置样式
self.list_widget.setStyleSheet("""
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;
}
""")
layout.addWidget(self.list_widget)
# 添加示例数据
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)
# 连接信号
self.list_widget.itemDoubleClicked.connect(self.on_item_double_clicked)
self.list_widget.customContextMenuRequested.connect(self.show_context_menu)
self.list_widget.setContextMenuPolicy(Qt.CustomContextMenu)
def add_card_item(self, data: CardData):
"""添加卡片项目"""
item = QListWidgetItem(self.list_widget)
item.setData(Qt.UserRole, data)
# 设置item大小
item.setSizeHint(QSize(380, 120))
# 设置item可选
item.setFlags(Qt.ItemFlag.ItemIsEnabled | Qt.ItemFlag.ItemIsSelectable)
def on_item_double_clicked(self, item):
"""双击处理"""
data = item.data(Qt.UserRole)
if isinstance(data, CardData):
data.activated = not data.activated # 切换激活状态
self.list_widget.viewport().update() # 刷新视图
def show_context_menu(self, position):
"""显示右键菜单"""
item = self.list_widget.itemAt(position)
if not item:
return
menu = QMenu(self)
# 创建动作
send_action = menu.addAction("发送到设备")
edit_action = menu.addAction("修改")
delete_action = menu.addAction("删除")
show_in_explorer_action = menu.addAction("在资源管理器中显示")
# 显示菜单并获取选择的动作
action = menu.exec(self.list_widget.viewport().mapToGlobal(position))
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.UserRole)
print(f"发送到设备: {data.name}")
# TODO: 实现发送到设备的具体逻辑
def edit_item(self, item):
"""编辑项目"""
data = item.data(Qt.UserRole)
print(f"编辑项目: {data.name}")
# TODO: 实现编辑对话框
def delete_item(self, item):
"""删除项目"""
row = self.list_widget.row(item)
self.list_widget.takeItem(row)
def show_in_explorer(self, item):
"""在资源管理器中显示"""
data = item.data(Qt.UserRole)
# 这里假设每个项目都有一个关联的文件路径
# TODO: 需要在CardData中添加文件路径属性
file_path = os.path.abspath(".") # 示例使用当前目录
if os.name == 'nt': # Windows
subprocess.run(['explorer', '/select,', file_path])
elif os.name == 'posix': # macOS 和 Linux
if sys.platform == 'darwin': # macOS
subprocess.run(['open', '-R', file_path])
else: # Linux
subprocess.run(['xdg-open', os.path.dirname(file_path)])
if __name__ == "__main__":
app = QApplication(sys.argv)
# 设置默认字体
font = QFont("Microsoft YaHei", 9)
app.setFont(font)
window = MainWindow()
window.show()
sys.exit(app.exec())

124
checkbox_header_table.cpp Normal file
View File

@ -0,0 +1,124 @@
#pragma execution_character_set("utf-8")
#include "checkbox_header_table.h"
#include <QPainter>
#include <QApplication>
#include <QTextCodec>
CheckBoxHeader::CheckBoxHeader(Qt::Orientation orientation, QWidget* parent)
: QHeaderView(orientation, parent)
, isChecked(false)
{
setSectionsClickable(true);
connect(this, &QHeaderView::sectionClicked, this, &CheckBoxHeader::handleSectionClicked);
}
void CheckBoxHeader::paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const
{
painter->save();
if (logicalIndex == 0) {
const int checkboxSize = 15;
checkboxRect = rect;
// 计算复选框位置
int x = rect.x() + 5; // 从左边留出5像素的间距
int y = rect.y() + (rect.height() - checkboxSize) / 2;
// 绘制复选框
painter->setPen(Qt::black);
painter->drawRect(x, y, checkboxSize, checkboxSize);
if (isChecked) {
painter->drawLine(x + 3, y + 7, x + 6, y + 10);
painter->drawLine(x + 6, y + 10, x + 12, y + 4);
}
// 绘制文字
QRect textRect = rect.adjusted(checkboxSize + 10, 0, 0, 0);
painter->drawText(textRect, Qt::AlignVCenter, tr("选择"));
} else {
QHeaderView::paintSection(painter, rect, logicalIndex);
}
painter->restore();
}
void CheckBoxHeader::handleSectionClicked(int logicalIndex)
{
if (logicalIndex == 0 && !checkboxRect.isNull()) {
isChecked = !isChecked;
emit checkBoxClicked(isChecked);
updateSection(0);
}
}
MainWindow::MainWindow(QWidget* parent)
: QMainWindow(parent)
{
setWindowTitle(tr("表头复选框示例"));
resize(400, 300);
// 创建表格
table = new QTableWidget(5, 3, this);
setCentralWidget(table);
// 设置表头
auto header = new CheckBoxHeader(Qt::Horizontal, table);
table->setHorizontalHeader(header);
connect(header, &CheckBoxHeader::checkBoxClicked,
this, &MainWindow::onHeaderCheckBoxClicked);
// 设置表头标题
QStringList headers;
headers << tr("选择") << tr("列1") << tr("列2");
table->setHorizontalHeaderLabels(headers);
// 调整列宽
table->horizontalHeader()->setStretchLastSection(true);
table->horizontalHeader()->resizeSection(0, 80);
// 调整表格样式
table->setShowGrid(true);
table->setAlternatingRowColors(true);
// 填充表格数据
for (int row = 0; row < 5; ++row) {
// 添加复选框
auto checkboxItem = new QTableWidgetItem();
checkboxItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled);
checkboxItem->setCheckState(Qt::Unchecked);
table->setItem(row, 0, checkboxItem);
// 添加其他数据
table->setItem(row, 1, new QTableWidgetItem(
QString(tr("数据 %1-1")).arg(row + 1)));
table->setItem(row, 2, new QTableWidgetItem(
QString(tr("数据 %1-2")).arg(row + 1)));
}
}
void MainWindow::onHeaderCheckBoxClicked(bool checked)
{
for (int row = 0; row < table->rowCount(); ++row) {
if (QTableWidgetItem* item = table->item(row, 0)) {
item->setCheckState(checked ? Qt::Checked : Qt::Unchecked);
}
}
}
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// 设置编码
QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8"));
// 设置默认字体
QFont font("Microsoft YaHei"); // 使用微软雅黑字体
app.setFont(font);
MainWindow window;
window.show();
return app.exec();
}

41
checkbox_header_table.h Normal file
View File

@ -0,0 +1,41 @@
#ifndef CHECKBOX_HEADER_TABLE_H
#define CHECKBOX_HEADER_TABLE_H
#include <QMainWindow>
#include <QTableWidget>
#include <QHeaderView>
class CheckBoxHeader : public QHeaderView {
Q_OBJECT
public:
explicit CheckBoxHeader(Qt::Orientation orientation, QWidget* parent = nullptr);
signals:
void checkBoxClicked(bool checked);
protected:
void paintSection(QPainter* painter, const QRect& rect, int logicalIndex) const override;
private slots:
void handleSectionClicked(int logicalIndex);
private:
bool isChecked;
mutable QRect checkboxRect;
};
class MainWindow : public QMainWindow {
Q_OBJECT
public:
explicit MainWindow(QWidget* parent = nullptr);
private slots:
void onHeaderCheckBoxClicked(bool checked);
private:
QTableWidget* table;
};
#endif // CHECKBOX_HEADER_TABLE_H

100
checkbox_header_table.py Normal file
View File

@ -0,0 +1,100 @@
from PySide6.QtWidgets import (QApplication, QMainWindow, QTableWidget,
QTableWidgetItem, QHeaderView)
from PySide6.QtCore import Qt, Signal
from PySide6.QtGui import QPainter
import sys
class CheckBoxHeader(QHeaderView):
checkbox_clicked = Signal(bool) # 创建信号用于传递复选框状态
def __init__(self, orientation, parent=None):
super().__init__(orientation, parent)
self.is_checked = False
self.checkbox_rect = None
self.setSectionsClickable(True)
self.sectionClicked.connect(self.handle_section_clicked)
def paintSection(self, painter: QPainter, rect, logical_index):
painter.save()
if logical_index == 0: # 在第一列绘制复选框
checkbox_size = 15
self.checkbox_rect = rect
# 计算复选框位置,稍微向左偏移
x = rect.x() + 5 # 从左边留出5像素的间距
y = rect.y() + (rect.height() - checkbox_size) // 2
# 绘制复选框
painter.setPen(Qt.black)
painter.drawRect(x, y, checkbox_size, checkbox_size)
if self.is_checked:
painter.drawLine(x + 3, y + 7, x + 6, y + 10)
painter.drawLine(x + 6, y + 10, x + 12, y + 4)
# 绘制文字
text_rect = rect.adjusted(checkbox_size + 10, 0, 0, 0) # 文字位置在复选框右侧
painter.drawText(text_rect, Qt.AlignVCenter, "选择")
else:
# 其他列正常绘制
super().paintSection(painter, rect, logical_index)
painter.restore()
def handle_section_clicked(self, logical_index):
if logical_index == 0 and self.checkbox_rect:
self.is_checked = not self.is_checked
self.checkbox_clicked.emit(self.is_checked)
self.updateSection(0)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("表头复选框示例")
self.resize(400, 300)
# 创建表格
self.table = QTableWidget(5, 3)
self.setCentralWidget(self.table)
# 设置表头
header = CheckBoxHeader(Qt.Horizontal, self.table)
self.table.setHorizontalHeader(header)
header.checkbox_clicked.connect(self.on_header_checkbox_clicked)
# 设置表头标题
self.table.setHorizontalHeaderLabels(["选择", "列1", "列2"])
# 调整列宽
self.table.horizontalHeader().setStretchLastSection(True) # 最后一列自动拉伸
self.table.horizontalHeader().resizeSection(0, 80) # 设置第一列宽度为80像素
# 调整表格样式
self.table.setShowGrid(True) # 显示网格线
self.table.setAlternatingRowColors(True) # 交替行颜色
# 填充表格数据
for row in range(5):
# 添加复选框
checkbox_item = QTableWidgetItem()
checkbox_item.setFlags(Qt.ItemIsUserCheckable | Qt.ItemIsEnabled)
checkbox_item.setCheckState(Qt.Unchecked)
self.table.setItem(row, 0, checkbox_item)
# 添加其他数据
self.table.setItem(row, 1, QTableWidgetItem(f"数据 {row+1}-1"))
self.table.setItem(row, 2, QTableWidgetItem(f"数据 {row+1}-2"))
def on_header_checkbox_clicked(self, checked):
# 处理表头复选框点击事件
for row in range(self.table.rowCount()):
item = self.table.item(row, 0)
if item:
item.setCheckState(Qt.Checked if checked else Qt.Unchecked)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec())