对应的博文为:
目录
StarDelegate Class Implementation
StarEditor Class Implementation
StarRating Class Implementation
Possible Extensions and Suggestions
Star Delegate Example
在QListView、QTableView、QTreeView这些控件里面如果要有个性化的item就得使用委托,委托在编辑item的时候让这些个性化的东西放置于最顶部。
这里告诉了一个道理当要自定义数据类型(比如int和QString混合,或者XX:xx这种数据)或者想定制渲染或者编辑存在的数据,就要子类化QAbstractItemDelegate或QItemDelegate(这两个的优劣我就不说了,看名字大家都知道了)。
Star Delegate Example 这个栗子就是来教我们怎么去渲染(我觉得这个应该叫渲染)和编辑一个打分的数据类型(就是用星星表示的)
这个栗子包含了3个类(官方文档就是清楚):
1.StarRating:自定义数据类型类,就是存那几个星星;
2.StarDelegate:继承于QItemDelegate,提供打分的功能,并且就是这个StarDelegate通过继承QItemDelegate实现数据类型(那几个星星)的处理;
3.StarEditor:继承于QWidget,也是被StarDelegate使用的,这吊玩意提供了让用户使用鼠标去编辑“星星”;
StarDelegate Class Definition
class StarDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
StarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {}
void paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
QSize sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const Q_DECL_OVERRIDE;
void setEditorData(QWidget *editor, const QModelIndex &index) const Q_DECL_OVERRIDE;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const Q_DECL_OVERRIDE;
private slots:
void commitAndCloseEditor();
};
关键:重写了公有虚函数,实现了自定义渲染和编辑;
StarDelegate Class Implementation
首先是paint()函数,这个是父类的,目的是让视图重画item:
void StarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data().canConvert<StarRating>()) {
StarRating starRating = qvariant_cast<StarRating>(index.data());
if (option.state & QStyle::State_Selected)
painter->fillRect(option.rect, option.palette.highlight());
starRating.paint(painter, option.rect, option.palette,
StarRating::ReadOnly);
} else {
QStyledItemDelegate::paint(painter, option, index);
}
}
每一个item都会调用这个函数,通过使用model中的QModelIndex对象来表示(估计是可以表示到哪一行调用了这个函数),如果存储的数据类型就是那一坨星星,那就让他进行绘制(源码中可以看到一个if else,说的就是这个),否则使用QItemDelegate进行绘制。这样就确保了StarDelegate更加的灵活。
当item显示的是一坨星星时,选中item时会搞一个背景,然后通过使用StarRating::paint()来画item。
那一坨星星可以存到QVariant里面,这归功于Q_DECLARE_METATYPE()这个宏。
用户开始编辑一个Item的时候就会调用createEditor()
QWidget *StarDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data().canConvert<StarRating>()) {
StarEditor *editor = new StarEditor(parent);
connect(editor, &StarEditor::editingFinished,
this, &StarDelegate::commitAndCloseEditor);
return editor;
} else {
return QStyledItemDelegate::createEditor(parent, option, index);
}
}
如果item里面放了那一坨星星,重写StartEditor这函数并且让editingFinished()信号与commitAndCloseEidtor()型号连接起来,目的是当关闭编辑功能后能更新模型。
关于commitAndCloseEdit()的代码!
void StarDelegate::commitAndCloseEditor()
{
StarEditor *editor = qobject_cast<StarEditor *>(sender());
emit commitData(editor);
emit closeEditor(editor);
}
当用户做完编辑时,就emit commitData()和closeEditor()(这两个信号都来源于QAbstractItemDelegate),这个是为了通知模型正在编辑数据并且通知视图不要让他编辑。
setEditorData()函数当要实现编辑功能的时候创建的,从模型中获取数据并且初始化他!
void StarDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
if (index.data().canConvert<StarRating>()) {
StarRating starRating = qvariant_cast<StarRating>(index.data());
StarEditor *starEditor = qobject_cast<StarEditor *>(editor);
starEditor->setStarRating(starRating);
} else {
QStyledItemDelegate::setEditorData(editor, index);
}
}
此处源码里面编辑的时候调用了setStarRating()这函数!
当完成编辑的时候,就会调用setModelData()这个函数,作用是把editor中的数据给模型:
void StarDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
if (index.data().canConvert<StarRating>()) {
StarEditor *starEditor = qobject_cast<StarEditor *>(editor);
model->setData(index, QVariant::fromValue(starEditor->starRating()));
} else {
QStyledItemDelegate::setModelData(editor, model, index);
}
}
sizeHint()这个函数返回item的最合适的大小!
QSize StarDelegate::sizeHint(const QStyleOptionViewItem &option,
const QModelIndex &index) const
{
if (index.data().canConvert<StarRating>()) {
StarRating starRating = qvariant_cast<StarRating>(index.data());
return starRating.sizeHint();
} else {
return QStyledItemDelegate::sizeHint(option, index);
}
}
StarEditor Class Definition
StarDelegate类在实例化的时候要使用StarEditor,关于StartEditor代码如下:
class StarEditor : public QWidget
{
Q_OBJECT
public:
StarEditor(QWidget *parent = 0);
QSize sizeHint() const Q_DECL_OVERRIDE;
void setStarRating(const StarRating &starRating) {
myStarRating = starRating;
}
StarRating starRating() { return myStarRating; }
signals:
void editingFinished();
protected:
void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE;
void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE;
private:
int starAtPosition(int x);
StarRating myStarRating;
};
这个类通过移动鼠标让用户编辑那一坨星星数据,当鼠标单击一个item后会emiteditingFinished()的信号。
这个类从继承与QWidget,通过重写这些函数来重新实现mouse和paint的事件。startAtPosition()这个函数是一个辅助函数返回鼠标指向的星星的指针(就是指向那个坨星星的哪一个位置)
StarEditor Class Implementation
start的构造函数:
StarEditor::StarEditor(QWidget *parent)
: QWidget(parent)
{
setMouseTracking(true);
setAutoFillBackground(true);
}
通过setMouseTracking这个函数去启动鼠标追踪,即使用户没有点击鼠标,也能监听到鼠标。一般都开打QWidget自动填充的特性用于获取不透明的背景(如果不调用,视图的Editor就会干扰这个编辑器)
下面是关于paintEvent()函数
void StarEditor::paintEvent(QPaintEvent *)
{
QPainter painter(this);
myStarRating.paint(&painter, rect(), this->palette(),
StarRating::Editable);
}
通过这个函数去画星星,这个函数就和StarDelegate差不多一毛一样。
void StarEditor::mouseMoveEvent(QMouseEvent *event)
{
int star = starAtPosition(event->x());
if (star != myStarRating.starCount() && star != -1) {
myStarRating.setStarCount(star);
update();
}
}
在鼠标事件里面调用setStartCount(),通过使用成员myStarRating得到当前光标的位置,并且调用QWidget::update()去让他强制重绘。
void StarEditor::mouseReleaseEvent(QMouseEvent * /* event */)
{
emit editingFinished();
}
当用户释放鼠标,就emit editingFinished()信号
int StarEditor::starAtPosition(int x)
{
int star = (x / (myStarRating.sizeHint().width()
/ myStarRating.maxStarCount())) + 1;
if (star <= 0 || star > myStarRating.maxStarCount())
return -1;
return star;
}
startAtPosition这个函数使用了基础的线性代数去寻找当前光标对应那一坨星星中哪个星星的位置。
StarRating Class Definition
class StarRating
{
public:
enum EditMode { Editable, ReadOnly };
explicit StarRating(int starCount = 1, int maxStarCount = 5);
void paint(QPainter *painter, const QRect &rect,
const QPalette &palette, EditMode mode) const;
QSize sizeHint() const;
int starCount() const { return myStarCount; }
int maxStarCount() const { return myMaxStarCount; }
void setStarCount(int starCount) { myStarCount = starCount; }
void setMaxStarCount(int maxStarCount) { myMaxStarCount = maxStarCount; }
private:
QPolygonF starPolygon;
QPolygonF diamondPolygon;
int myStarCount;
int myMaxStarCount;
};
Q_DECLARE_METATYPE(StarRating)
StarRating类代表了星星的等级(就是那一坨星星有几个组成),不仅如此他还提供了画那一坨星星的能力,这个是通过QPaintDevice实现的,这个一坨星星可以在view上画,或者在编辑器上画。myStarCount这个成员存储了当前有几个星星,myMaxStarCount存储了最多有多少个星星(存在QVariant这个神器里面)
通过Q_DECLARE_METATYPE()这个宏人QVariant知晓StarPating这个类,让QVariant能存储StarPating这个类。
StarRating Class Implementation
构造函数初始化了myStarCount和myMaxStarCount并且构造了一个多边形(就是星星)和钻石(非星星用这个代替)
StarRating::StarRating(int starCount, int maxStarCount)
{
myStarCount = starCount;
myMaxStarCount = maxStarCount;
starPolygon << QPointF(1.0, 0.5);
for (int i = 1; i < 5; ++i)
starPolygon << QPointF(0.5 + 0.5 * std::cos(0.8 * i * 3.14),
0.5 + 0.5 * std::sin(0.8 * i * 3.14));
diamondPolygon << QPointF(0.4, 0.5) << QPointF(0.5, 0.4)
<< QPointF(0.6, 0.5) << QPointF(0.5, 0.6)
<< QPointF(0.4, 0.5);
}
paint()函数在StarRating这个对象的paint device上画了星星。
void StarRating::paint(QPainter *painter, const QRect &rect,
const QPalette &palette, EditMode mode) const
{
painter->save();
painter->setRenderHint(QPainter::Antialiasing, true);
painter->setPen(Qt::NoPen);
if (mode == Editable) {
painter->setBrush(palette.highlight());
} else {
painter->setBrush(palette.foreground());
}
int yOffset = (rect.height() - PaintingScaleFactor) / 2;
painter->translate(rect.x(), rect.y() + yOffset);
painter->scale(PaintingScaleFactor, PaintingScaleFactor);
for (int i = 0; i < myMaxStarCount; ++i) {
if (i < myStarCount) {
painter->drawPolygon(starPolygon, Qt::WindingFill);
} else if (mode == Editable) {
painter->drawPolygon(diamondPolygon, Qt::WindingFill);
}
painter->translate(1.0, 0.0);
}
painter->restore();
}
首先设置了一个画笔(pen)和画刷(brush这两个以后都用英文表示把,用中文太难过了)去画图。mode这个参数可以被设置成可编辑的或者可读的,如果是可编辑的就要用Highligh color去代替Foreground color,从而才能更好的画那一坨星星。
在编辑的时候,通过光标控制星星,没有星星的地方用小方块代替。
sizeHint()函数返回画出来的这坨星星最适合的大小:
QSize StarRating::sizeHint() const
{
return PaintingScaleFactor * QSize(myMaxStarCount, 1);
}
最大也只能画出myMaxStarCount这么多的星星。这个函数会被StarDelegate::sizeHint()与StartEditor::sizeHint()调用。
The main() Function
代码如下:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QTableWidget tableWidget(4, 4);
tableWidget.setItemDelegate(new StarDelegate);
tableWidget.setEditTriggers(QAbstractItemView::DoubleClicked
| QAbstractItemView::SelectedClicked);
tableWidget.setSelectionBehavior(QAbstractItemView::SelectRows);
QStringList headerLabels;
headerLabels << "Title" << "Genre" << "Artist" << "Rating";
tableWidget.setHorizontalHeaderLabels(headerLabels);
populateTableWidget(&tableWidget);
tableWidget.resizeColumnsToContents();
tableWidget.resize(500, 300);
tableWidget.show();
return app.exec();
}
主函数创建了一个QTableWidget和设置了StartDelegate,DoubleClicked和SelectedClicked已经设置好了编辑触发的功能,当星星等级的item被单击选中后,就会打开编辑功能。
populateTableWidget()这个函数填充了QTableWidget的数据
void populateTableWidget(QTableWidget *tableWidget)
{
static const struct {
const char *title;
const char *genre;
const char *artist;
int rating;
} staticData[] = {
{ "Mass in B-Minor", "Baroque", "J.S. Bach", 5 },
...
{ 0, 0, 0, 0 }
};
for (int row = 0; staticData[row].title != 0; ++row) {
QTableWidgetItem *item0 = new QTableWidgetItem(staticData[row].title);
QTableWidgetItem *item1 = new QTableWidgetItem(staticData[row].genre);
QTableWidgetItem *item2 = new QTableWidgetItem(staticData[row].artist);
QTableWidgetItem *item3 = new QTableWidgetItem;
item3->setData(0,
QVariant::fromValue(StarRating(staticData[row].rating)));
tableWidget->setItem(row, 0, item0);
tableWidget->setItem(row, 1, item1);
tableWidget->setItem(row, 2, item2);
tableWidget->setItem(row, 3, item3);
}
}
注意:使用qVariantFromValue把StarRating转化为QVariant
Possible Extensions and Suggestions
在Qt的视图模型框架里面有很多自定义方法,这个例子适合大多数自定义委托和编辑(我估计在国内18k内的Qt开发足够用了,18k外的可能要用别的),这个例子的星星委托和编辑没有尽可能做到以下几点:
1.使用编辑的方式,这种方式要调用QAbstractItemView::edit(),用这个代替编辑触发。这可以支持其他编译器的触发,比如QAbstractItemView::EditTrigger。举个例子,通过鼠标悬移动来改变数据这种效果比点击更好。
2.通过重写QAbstractItemDelegate::editorEvent(),这可以直接实现委托的编辑,而不是创建一个QWidget的子类来实现!