告别枯燥列表!用Qt QListView打造一个带右键菜单和自定义样式的待办事项应用
用Qt QListView构建高交互性待办事项应用实战每次看到那些千篇一律的待办事项应用总让人提不起使用的兴趣。作为一个Qt开发者我们完全有能力打造一个既美观又实用的个性化工具。今天我们就用QListView这个看似简单实则强大的控件来实现一个支持自定义样式、右键菜单操作和动态交互的专业级待办事项应用。1. 项目基础搭建与环境准备1.1 创建Qt Widgets应用首先使用Qt Creator新建一个Widgets Application项目。确保勾选了Generate form选项这样我们可以使用Qt Designer来设计界面。在.pro文件中确认已包含以下模块QT widgets对于主窗口我们只需要一个QListView控件作为核心组件。在UI设计器中拖拽一个QListView到主窗口并设置objectName为todoListView。同时添加一个QPushButton用于新增事项命名为addButton。1.2 基础数据模型设置QListView的强大之处在于其与模型/视图架构的完美结合。我们使用QStringListModel作为基础数据模型// 在MainWindow类声明中添加 private: QStringListModel *todoModel; QStringList todoItems; // 在构造函数中初始化 MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) { setupUi(this); todoItems 购买食材 完成项目报告 健身30分钟; todoModel new QStringListModel(this); todoModel-setStringList(todoItems); ui-todoListView-setModel(todoModel); }2. 实现核心交互功能2.1 自定义项渲染与编辑默认的QListView项显示可能过于简单我们可以通过委托(Delegate)来实现更丰富的表现形式class TodoItemDelegate : public QStyledItemDelegate { public: explicit TodoItemDelegate(QObject *parent nullptr) : QStyledItemDelegate(parent) {} void paint(QPainter *painter, const QStyleOptionViewItem option, const QModelIndex index) const override { // 自定义绘制逻辑 } QSize sizeHint(const QStyleOptionViewItem option, const QModelIndex index) const override { return QSize(option.rect.width(), 40); // 统一项高度 } }; // 在MainWindow构造函数中添加 ui-todoListView-setItemDelegate(new TodoItemDelegate(this));2.2 右键菜单功能实现右键菜单是提升应用交互性的关键。我们需要实现添加、删除和标记完成等功能// 在MainWindow类声明中添加 private slots: void showContextMenu(const QPoint pos); void addTodoItem(); void removeTodoItem(); void toggleComplete(); private: QMenu *contextMenu; // 在构造函数中初始化菜单 contextMenu new QMenu(this); QAction *addAction contextMenu-addAction(添加事项); QAction *removeAction contextMenu-addAction(删除事项); QAction *completeAction contextMenu-addAction(标记完成); connect(addAction, QAction::triggered, this, MainWindow::addTodoItem); connect(removeAction, QAction::triggered, this, MainWindow::removeTodoItem); connect(completeAction, QAction::triggered, this, MainWindow::toggleComplete); ui-todoListView-setContextMenuPolicy(Qt::CustomContextMenu); connect(ui-todoListView, QListView::customContextMenuRequested, this, MainWindow::showContextMenu); // 实现菜单显示 void MainWindow::showContextMenu(const QPoint pos) { QModelIndex index ui-todoListView-indexAt(pos); if (index.isValid()) { contextMenu-exec(ui-todoListView-mapToGlobal(pos)); } }3. 高级样式定制与视觉效果3.1 使用QSS美化列表Qt样式表(QSS)可以让我们轻松实现专业的外观效果/* 基础列表样式 */ QListView { background: #f5f5f5; border: 1px solid #ddd; border-radius: 4px; padding: 5px; show-decoration-selected: 1; outline: none; } /* 项的一般状态 */ QListView::item { background: white; border: 1px solid #e0e0e0; border-radius: 3px; margin: 2px; padding: 8px; } /* 项的悬停状态 */ QListView::item:hover { background: #f0f7ff; border: 1px solid #cce0ff; } /* 项的选中状态 */ QListView::item:selected { background: #e6f2ff; border: 1px solid #99c2ff; color: #0066cc; } /* 已完成事项的特殊样式 */ QListView::item[completedtrue] { color: #888; text-decoration: line-through; background: #f8f8f8; }在代码中应用样式表QFile styleFile(:/styles/todo.qss); styleFile.open(QFile::ReadOnly); QString styleSheet QLatin1String(styleFile.readAll()); ui-todoListView-setStyleSheet(styleSheet);3.2 添加动画效果为了提升用户体验我们可以为操作添加简单的动画效果// 在MainWindow类中添加 #include QPropertyAnimation void MainWindow::addTodoWithAnimation(const QString text) { int row todoModel-rowCount(); todoModel-insertRow(row); QModelIndex index todoModel-index(row); todoModel-setData(index, text); // 创建动画 QPropertyAnimation *animation new QPropertyAnimation(ui-todoListView-viewport(), geometry); animation-setDuration(300); QRect startRect ui-todoListView-visualRect(index); startRect.setHeight(0); animation-setStartValue(startRect); animation-setEndValue(ui-todoListView-visualRect(index)); animation-start(QAbstractAnimation::DeleteWhenStopped); }4. 数据持久化与高级功能4.1 实现数据保存与加载一个实用的待办事项应用需要能够保存数据void MainWindow::saveTodos() { QSettings settings(MyCompany, TodoApp); settings.setValue(todoItems, todoItems); } void MainWindow::loadTodos() { QSettings settings(MyCompany, TodoApp); todoItems settings.value(todoItems).toStringList(); if (!todoItems.isEmpty()) { todoModel-setStringList(todoItems); } }4.2 添加拖拽排序功能增强用户体验的另一个方式是支持拖拽重新排序// 在MainWindow构造函数中添加 ui-todoListView-setDragEnabled(true); ui-todoListView-setAcceptDrops(true); ui-todoListView-setDropIndicatorShown(true); ui-todoListView-setDragDropMode(QAbstractItemView::InternalMove); // 重写模型的dropMimeData方法 bool TodoListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex parent) { if (action Qt::IgnoreAction) return true; // 实现自定义拖拽逻辑 return QAbstractListModel::dropMimeData(data, action, row, column, parent); }4.3 实现分类与过滤对于复杂的待办事项分类功能非常实用class TodoFilterProxyModel : public QSortFilterProxyModel { public: explicit TodoFilterProxyModel(QObject *parent nullptr) : QSortFilterProxyModel(parent), m_filterCompleted(false) {} void setFilterCompleted(bool filter) { m_filterCompleted filter; invalidateFilter(); } protected: bool filterAcceptsRow(int sourceRow, const QModelIndex sourceParent) const override { if (m_filterCompleted) { QModelIndex index sourceModel()-index(sourceRow, 0, sourceParent); return !index.data(Qt::UserRole 1).toBool(); // UserRole1存储完成状态 } return true; } private: bool m_filterCompleted; }; // 使用方式 TodoFilterProxyModel *proxyModel new TodoFilterProxyModel(this); proxyModel-setSourceModel(todoModel); ui-todoListView-setModel(proxyModel);