接着上一篇,我们来实现一个任意类型的Widget持久代理。
首先,我们实现一个自定义Model,为了其通用性,我定义了一个QVaraint的List Model。当然这仅仅是最简单的实现,如有需要可以自己添加 加入、删除行列等操作。
#ifndef QVARIANTLISTMODEL_H
#define QVARIANTLISTMODEL_H
#include <QAbstractListModel>
#include <QVariant>
class QVariantListModel : public QAbstractListModel
{
public:
QVariantListModel();
void setOriginData(QList<QVariant> &data);
int rowCount(const QModelIndex &parent) const;
int columnCount(const QModelIndex &parent) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
private:
QList<QVariant> m_data;
};
#endif // QVARIANTLISTMODEL_H
#include "QVariantListModel.h"
#include <QDebug>
QVariantListModel::QVariantListModel()
{
}
void QVariantListModel::setOriginData(QList<QVariant> &data)
{
m_data = data;
}
int QVariantListModel::rowCount(const QModelIndex &parent) const
{
return m_data.count();
}
int QVariantListModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
QVariant QVariantListModel::data(const QModelIndex &index, int role) const
{
if(index.isValid() && (role == Qt::DisplayRole || role == Qt::EditRole ))
{
int row = index.row();
if(row >= 0 && row < m_data.count())
{
return m_data[row];
}
}
return QVariant();
}
bool QVariantListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if(index.isValid() && (role == Qt::DisplayRole || role == Qt::EditRole ))
{
int row = index.row();
if(row >= 0 && row < m_data.count())
{
m_data[row] = value;
emit dataChanged(index,index);
qDebug() << "datachanged index";
return true;
}
}
return false;
}
和常见的自定义Model类似,不再赘述。
接下来,定义底层数据结构和对应的Widget。
#ifndef FORM_H
#define FORM_H
#include <QWidget>
#include <QLabel>
namespace Ui {
class Form;
}
//model底层数据,
struct DataType
{
QString Picture;
QString Name;
QString Author;
QString Description;
uint click,download,collection,comment;
};
//为了能直接使用QVariantListModel,将其指针申明为 可转变为QVaraint类型
using pDataType = DataType *;
Q_DECLARE_METATYPE(pDataType)
class Form : public QWidget
{
Q_OBJECT
public:
explicit Form(QWidget *parent = 0);
~Form();
//用于delegate中的读写数据的接口
void SetData(const DataType &data);
DataType &GetData();
signals:
//主动保存数据的信号,必不可少,需要在delegate中链接信号槽
void WriteToModel();
protected:
void paintEvent(QPaintEvent *event);
private slots:
void on_pushButton_more_clicked();
private:
Ui::Form *ui;
DataType m_data;
};
#endif // FORM_H
#include "form.h"
#include "ui_form.h"
#include <QApplication>
#include <QPainter>
Form::Form(QWidget *parent) :
QWidget(parent),
ui(new Ui::Form)
{
ui->setupUi(this);
ui->label_cover->setMinimumWidth(60);
ui->label_cover->setMaximumWidth(100);
ui->label_cover->setScaledContents(true);
this->setObjectName("Form");
this->setAutoFillBackground(true);
//因为是动态加入现有widget体系的,因此当前窗口的qss不起作用,需要重写paint函数
this->setStyleSheet("#Form{border-bottom-style: outset;"
"border-bottom-width: 1px;"
"border-bottom-color:lightblue;}"
"#label_cover{"
"padding:3px;"
"border:1px solid gray;"
"}"
"#textEdit{border-style: none;}"
"#pushButton_more{"
"margin-right:15px;"
"border-style: none;"
"color:red;"
"}"
);
}
Form::~Form()
{
delete ui;
}
//当自定义界面类继承QWidget后,qss样式表对其无效的情况下,需要重写paintEvent函数。
void Form::paintEvent(QPaintEvent *event)
{
Q_UNUSED(event);
QStyleOption styleOpt;
styleOpt.init(this);
QPainter painter(this);
style()->drawPrimitive(QStyle::PE_Widget, &styleOpt, &painter, this);
}
void Form::SetData(const DataType &data)
{
m_data = data;
ui->label_cover->setPixmap(QPixmap(m_data.Picture));
ui->label_name->setText(m_data.Name);
ui->label_author->setText("作者:"+m_data.Author);
ui->textEdit->setText(m_data.Description);
ui->label_click->setText(QString("点击:%1").arg(m_data.click));
ui->label_download->setText(QString("下载:%1").arg(m_data.download));
ui->label_collection->setText(QString("收藏:%1").arg(m_data.collection));
ui->label_comment->setText(QString("评论:%1").arg(m_data.comment));
}
DataType &Form::GetData()
{
m_data.Description = ui->textEdit->toPlainText();
return m_data;
}
void Form::on_pushButton_more_clicked()
{
emit WriteToModel();
}
model和widget窗口都有了,接下来,我们就自定义用Form类为编辑器的Delegate。
#ifndef ANYWIDGETDELEGATEEXAMPLE_H
#define ANYWIDGETDELEGATEEXAMPLE_H
#include "QPersistentStyledItemDelegate.h"
#include "form.h"
#include <QStandardItemModel>
class AnyWidgetDelegateExample :public QPersistentStyledItemDelegate
{
public:
AnyWidgetDelegateExample();
/*
* 由于持久代理会一直显示编辑器,因此重载paint毫无意义(既不必)
* 且由于QPersistentStyledItemDelegate使用paint做了一些工作,因此不能重载(也不能)
*/
//void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
//创建一个任意widget来作为编辑器,由于我们继承自 “持久代理类”,因此编辑器讲一直打开
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
//数据从model到编辑器
void setEditorData(QWidget *editor, const QModelIndex &index) const;
//数据从编辑器到model,如果只读,则不需要实现
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
};
#endif // ANYWIDGETDELEGATEEXAMPLE_H
#include "AnyWidgetDelegateExample.h"
#include <QStandardItem>
#include <QStandardItemModel>
#include <QDebug>
AnyWidgetDelegateExample::AnyWidgetDelegateExample()
{
}
QSize AnyWidgetDelegateExample::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
{
return QSize(option.rect.width(),160);
}
QWidget *AnyWidgetDelegateExample::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
if(!index.isValid())
return nullptr;
QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
if(model == nullptr)
return nullptr;
Form *editor = new Form(parent);
QVariant variant = model->data(index);
if (variant.canConvert<pDataType>()) {
pDataType p = variant.value<pDataType>();
editor->SetData(*p);
}
//普通的delegate在close的时候会讲数据写入到model
//我们由于代理窗口一直开启,因此需要主动告诉delegate去保存数据
//updateModelData是QPersistentStyledItemDelegate中实现的槽,
//它会去调用setModelData函数,因此我们也必需重写setModelData
//如果只读,则不需要调用,setModelData函数也不必重写
connect(editor,SIGNAL(WriteToModel()),this,SLOT(updateModelData()));
return editor;
}
void AnyWidgetDelegateExample::setEditorData(QWidget *editor, const QModelIndex &index) const
{
if(!index.isValid() || editor == nullptr)
return;
QAbstractItemModel *model = const_cast<QAbstractItemModel *>(index.model());
if(model == nullptr)
return;
Form *form = static_cast<Form *>(editor);
QVariant variant = model->data(index);
if (variant.canConvert<pDataType>()) {
pDataType p = variant.value<pDataType>();
form->SetData(*p);
}
}
void AnyWidgetDelegateExample::setModelData(QWidget *editor, QAbstractItemModel *absmodel, const QModelIndex &index) const
{
if(!index.isValid() || editor == nullptr || absmodel == nullptr)
return;
QAbstractItemModel *model = static_cast<QAbstractItemModel *>(absmodel);
if(model == nullptr)
return;
QVariant variant = model->data(index);
if (variant.canConvert<pDataType>()) {
pDataType p = variant.value<pDataType>();
Form *form = static_cast<Form *>(editor);
(*p) = form->GetData();
model->setData(index,variant);
qDebug() << "rewrite data";
}
}
在上述自定义Delegate中,我们实现了从Model读取数据的setEditorData和写入Model的setModelData。可以看出,和定义一个常见的代理没有太大区别。
需要注意的是:由于编辑器一直显示,因此我们不知道什么时候将数据写入到Model,因此需要连接信号槽到QPersistentStyledItemDelegate中定义的槽函数:
void updateModelData();
而在form中,我们是在点击 “详情” 按钮时,发出了信号连接到上述的槽中的。点击该按钮后,就会将数据写入到model中,我们在上述代码中,仅仅修改了书籍的描述信息(只是为了示范一下功能,因此简单处理),写入model后,基于Model/View的逻辑,和该Model关联的所有View都会重新加载改数据。
下面就是我们的示例:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "AnyWidgetDelegateExample.h"
#include "QVariantListModel.h"
#include <QScrollBar>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QList<QVariant> datas;
for(int i = 0;i < 5;i++)
{
DataType *d = new DataType;
d->Picture = "cover.jpg";
d->Author = "托马斯•皮凯蒂 (Thomas Piketty)";
d->Name = "21世纪资本论";
d->Description = " 《21世纪资本论》是未来十年内最重要的一部经济著作,《21世纪资本论》可以媲美马克思的《资本论》,《21世纪资本论》在美国亚马逊销售排行榜第一名。《21世纪资...";
d->click = 4356;
d->download = 1453;
d->collection = 125;
d->comment = 45;
datas.append(QVariant::fromValue(d));
}
QVariantListModel *model = new QVariantListModel;
model->setOriginData(datas);
ui->listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->listView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->listView->setModel(model);
ui->listView->setItemDelegate(new AnyWidgetDelegateExample);
ui->listView_normal->setEditTriggers(QAbstractItemView::NoEditTriggers);
ui->listView_normal->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
ui->listView_normal->setModel(model);
ui->listView_normal->setItemDelegate(new AnyWidgetDelegateExample);
}
上述代码中,将一个model关联到了两个View,就是为了展示Widget Delegate的编辑功能。
效果图:
可以看到,我们实现了我们想要的效果,而Form可以是任意Widget,因此代理的界面就可以千变万化,做成我们想要的任何样子。
至于美化部分,我仅仅加了靠近底部的分割线,处理了图片边框和“详情”按钮,仅仅是为了展示可以做美化效果。实际开发中,自然要更加精细一些。