1. 概述
在展示小批量数据方面,QListWidget是一个非常实用的部件,但默认的QListWidget只能展示文本,功能比较局限。在很多情况下,我们需要在列表中使用其它控件来对数据进行操作,比如复选框和菜单等,这时就需要扩展QListWidget了,这一节我们来聊聊两种扩展QListWidget的方法:
-
使用delegate扩展
-
使用setItemWidget扩展
作为例子,我们使用两种方法各自实现一个带下拉菜单的QListWidget
2. delegate
delegate是用于自定义模型/视图架构(list/table/tree等)中数据项的呈现和编辑方式的工具,它具有以下作用:
-
自定义外观:可以完全控制数据项在视图中的外观,包括自定义字体、颜色、背景、图标等各种视觉元素
-
自定义编辑功能:允许创建自定义的编辑组件,以替代默认的编辑方式
-
优化交互
-
可以响应各种用户交互事件,如鼠标点击、悬停、键盘输入等,并根据这些事件执行特定的操作。
-
可以优化数据项的选择和导航行为。例如,可以实现特殊的选择效果,或者在特定情况下自动滚动视图以确保用户关注的数据项可见。
-
而delegate的使用有固定的步骤:
-
继承delegate基类:QListWidget通常使用QStyledItemDelegate或QItemDelegate
-
重写关键接口:比如paint接口用于绘制,editorEvent接口用于编辑响应
-
设置delegate:这一步比较简单,就是将delegate设置到视图,使其生效
基于以上信息,我们创建一个SongItemDelegate类,头文件如下:
// songitemdelegate.h
#pragma once
#include <QStyledItemDelegate>
class SongItemDelegate : public QStyledItemDelegate {
Q_OBJECT
public:
explicit SongItemDelegate(QObject *parent = nullptr) : QStyledItemDelegate(parent) {}
virtual ~SongItemDelegate() {}
protected:
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option,
const QModelIndex &index) override;
private:
void showMenu(QAbstractItemModel *model, const QModelIndex &index);
private:
int m_itemHeight = 32;
};
源文件如下:
#include "songitemdelegate.h"
#include <QAction>
#include <QMenu>
#include <QMouseEvent>
#include <QPainter>
void SongItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const {
QRect rc = option.rect;
// todo: draw other things
// ...
painter->drawText(rc.width() - 48, rc.top(), 48, rc.height(), Qt::AlignCenter, "...");
}
QSize SongItemDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const {
QSize size = QStyledItemDelegate::sizeHint(option, index);
size.setHeight(m_itemHeight);
return size;
}
bool SongItemDelegate::editorEvent(QEvent *event, QAbstractItemModel *model,
const QStyleOptionViewItem &option, const QModelIndex &index) {
QMouseEvent *ev = static_cast<QMouseEvent *>(event);
if (QEvent::MouseButtonPress != ev->type()) {
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
QRect rc = option.rect;
QRect menuRect(rc.width() - 48, rc.top(), 48, rc.height());
if (menuRect.contains(ev->pos())) {
showMenu(model, index);
}
return QStyledItemDelegate::editorEvent(event, model, option, index);
}
void SongItemDelegate::showMenu(QAbstractItemModel *model, const QModelIndex &index) {
QMenu *menu = new QMenu;
// todo: add action
// ...
bool value = model->data(index, Qt::UserRole + 1).toBool();
model->setData(index, !value, Qt::UserRole + 1);
model->dataChanged(index, index);
menu->popup(QCursor::pos());
}
delegate的优点是绘制速度快,缺点是难以应对复杂ui,所以简单的ui定制delegate是最佳选择。
3. setItemWidget
如果要在QListWidget中绘制复杂ui,比如有多列,每列有不同的部件,各个不同部件又有不同的响应,那使用setItemWidget就是比较好的选择了。
其使用步骤如下:
-
继承QWidget,绘制自定义UI
-
QListWidget新增item
-
在新增的item上调用setItemWidget
示例代码如下:
// customitem.h
#pragma once
class CustomItem : public QWidget {
Q_OBJECT
// custom code
// ...
}
// use CustomItem
QListWidgetItem *bgItem = new QListWidgetItem;
ui->list->addItem(bgItem);
CustomItem *fgItem = new CustomItem;
ui->list->setItemWidget(bgItem, fgItem);
此处需要注意设置QListWidgetItem高度与CustomItem高度匹配,否则显示会错乱。
另外此种方法不适用大批量数据的展示,会有性能问题。
PS: 代码已经开源在github:linqiaqun/music-player: A cross platform music player (github.com) 欢迎star/fork/issue