Model/View模块中Delegate的扩展:持久Delegate(二)

接着上一篇,我们来实现一个任意类型的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,因此代理的界面就可以千变万化,做成我们想要的任何样子。

至于美化部分,我仅仅加了靠近底部的分割线,处理了图片边框和“详情”按钮,仅仅是为了展示可以做美化效果。实际开发中,自然要更加精细一些。

H&A
发布了7 篇原创文章 · 获赞 4 · 访问量 838

猜你喜欢

转载自blog.csdn.net/qq_34305316/article/details/90314763