Qt-Model/View进阶

高级主题

什么是委托(Delegates)

在QListView、QTableView或QTreeView 中显示数据时,各个item由委托绘制。此外,当用户开始编辑一个item时(例如,通过双击该item),代理会提供一个编辑器控件,该控件在进行编辑时放置在该item的顶部。提供展示(presentation)和编辑(edit)服务的组件称为委托

委托是QAbstractItemDelegate 的子类,Qt提供的QStyledItemDelegate可以处理最常见的数据类型,如int和QString。

当我们想使用一个与众不同的编辑器,或者想把数据显示为图形,我们就可能需要使用委托。

星星评分例子

在此示例中,我们将看到如何实现自定义委托来呈现和编辑“星级”数据类型,该数据类型可以存储诸如“5 星中的 2 颗”之类的值。

该示例由以下类组成:

  • StarRating是自定义数据类型。它存储以星级表示的评级,例如“5 星中的 2 颗”或“6 颗星中的 5 颗”。
  • StarDelegate继承QStyledItemDelegate并提供支持StarRating类型数据
  • StarEditor继承QWidget并用于StarDelegate让用户使用鼠标编辑星级。

StarDelegate定义

class StarDelegate : public QStyledItemDelegate
{
    Q_OBJECT
public:
    using QStyledItemDelegate::QStyledItemDelegate;

    void paint(QPainter *painter, const QStyleOptionViewItem &option,
               const QModelIndex &index) const override;
    QSize sizeHint(const QStyleOptionViewItem &option,
                   const QModelIndex &index) const override;
    QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
                          const QModelIndex &index) const override;
    void setEditorData(QWidget *editor, const QModelIndex &index) const override;
    void setModelData(QWidget *editor, QAbstractItemModel *model,
                      const QModelIndex &index) const override;

private slots:
    void commitAndCloseEditor();
};

所有函数均重写了QStyledItemDelegate的虚函数,以提供自定义渲染和编辑

StarDelegate 类实现

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::EditMode::ReadOnly);
    } else {
        QStyledItemDelegate::paint(painter, option, index);
    }
}

每当视图需要重新绘制item时,都会调用paint()。如果 item 中存储的数据是StarRating,我们自己绘制它;否则,我们让QStyledItemDelegate为我们绘制。这确保了StarDelegate可以处理最常见的数据类型。

option.state & QStyle::State_Selected语句表示当星级评价的item处于被选中的状态时,会绘制被选中的高亮背景(和其他被选中的item一样)。

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;
    }
    return QStyledItemDelegate::createEditor(parent, option, index);
}

当用户开始编辑item时会调用 createEditor() 函数。commitAndCloseEditor实现如下:

void StarDelegate::commitAndCloseEditor()
{
    StarEditor *editor = qobject_cast<StarEditor *>(sender());
    emit commitData(editor);
    emit closeEditor(editor);
}

在创建编辑器时会调用 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);
    }
}

编辑完成后,调用 setModelData() 函数将数据从编辑器提交到模型

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();
    }
    return QStyledItemDelegate::sizeHint(option, index);
}

StarEditor 类定义

class StarEditor : public QWidget
{
    Q_OBJECT
public:
    StarEditor(QWidget *parent = nullptr);

    QSize sizeHint() const override;
    void setStarRating(const StarRating &starRating) {
        myStarRating = starRating;
    }
    StarRating starRating() { return myStarRating; }

signals:
    void editingFinished();

protected:
    void paintEvent(QPaintEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;

private:
    int starAtPosition(int x) const;

    StarRating myStarRating;
};

StarEditor 类实现

StarEditor::StarEditor(QWidget *parent)
    : QWidget(parent)
{
    setMouseTracking(true);
    setAutoFillBackground(true);
}

我们打开QWidget的自动填充背景功能以获得不透明背景。(如果没有调用,视图的背景会穿透到编辑器中。)

void StarEditor::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    myStarRating.paint(&painter, rect(), palette(),
                       StarRating::EditMode::Editable);
}
void StarEditor::mouseMoveEvent(QMouseEvent *event)
{
    const int star = starAtPosition(event->x());

    if (star != myStarRating.starCount() && star != -1) {
        myStarRating.setStarCount(star);
        update();
    }
    QWidget::mouseMoveEvent(event);
}

我们调用 QWidget::update() 来强制重绘。

void StarEditor::mouseReleaseEvent(QMouseEvent *event)
{
    emit editingFinished();
    QWidget::mouseReleaseEvent(event);
}
int StarEditor::starAtPosition(int x) const
{
    const int star = (x / (myStarRating.sizeHint().width()
                           / myStarRating.maxStarCount())) + 1;
    if (star <= 0 || star > myStarRating.maxStarCount())
        return -1;

    return star;
}

根据鼠标x轴坐标计算星级。后面的判断语句是当x坐标为负数,或者计算的星级大于最大星级时返回-1,此时StarEditor::mouseMoveEvent内只会调用QWidget::mouseMoveEvent。

StarRating 定义

class StarRating
{
public:
    enum class 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类表示评分星级,包含当前星级和最大星级,此外它还能绘制星星。

StarRating 实现

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);
}

设置星级,初始化用于绘制星星(starPolygon)和菱形(diamondPolygon)的多边形。

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);
    painter->setBrush(mode == EditMode::Editable ?
                          palette.highlight() :
                          palette.windowText());

    const 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 == EditMode::Editable)
            painter->drawPolygon(diamondPolygon, Qt::WindingFill);
        painter->translate(1.0, 0.0);
    }

    painter->restore();
}

其中PaintingScaleFactor是个常数,源码中为20,用于控制星星显示的大小。

QSize StarRating::sizeHint() const
{
    return PaintingScaleFactor * QSize(myMaxStarCount, 1);
}

返回星星绘制区域的大小

main()函数

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);
    tableWidget.setHorizontalHeaderLabels({"Title", "Genre", "Artist", "Rating"});

    populateTableWidget(&tableWidget);

    tableWidget.resizeColumnsToContents();
    tableWidget.resize(500, 300);
    tableWidget.show();

    return app.exec();
}

主函数创建了一个QTableWidget,设置委托为StarDelegate,并设置了编辑触发的方式
populateTableWidget填充表格,用于demo效果展示。

void populateTableWidget(QTableWidget *tableWidget)
{
    static constexpr struct {
        const char *title;
        const char *genre;
        const char *artist;
        int rating;
    } staticData[] = {
        { "Mass in B-Minor", "Baroque", "J.S. Bach", 5 },
    ...
        { nullptr, nullptr, nullptr, 0 }
    };

    for (int row = 0; staticData[row].title != nullptr; ++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);
    }
}

总结

如果想要自定义item的样式,可以通过视图的setItemDelegate()方法,使用自定义委托替换默认委托。通过创建一个继承自QStyledItemDelegate的类来编写自定义委托,对没有编辑功能的item,我们只需重写两个方法:

  1. void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index)。绘制item
  2. QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index)。告知item的大小

如果要提供item编辑能力,还需要重写以下方法:

  1. QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index)。提供编辑器
  2. void setEditorData(QWidget *editor, const QModelIndex &index)。初始化编辑器内数据
  3. void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index)。将编辑器内数据保存到model中

参考链接:

  1. https://doc.qt.io/qt-5/qtwidgets-itemviews-stardelegate-example.html
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在QML中,TextField不直接支持Model,但您可以通过结合其他组件或自定义属性来实现与Model的交互。下面是一种常见的方法: 1. 首先,您需要在QML中使用ListView或Repeater等组件来显示和编辑Model的数据。 2. 创建一个自定义的属性(例如,名为"modelData")来保存Model中的数据,并将它绑定到TextField的text属性上。 ```qml TextField { id: textField text: modelData onTextChanged: { // 更新Model中的数据 } } ``` 3. 在QML中,通过信号和槽机制或直接调用C++方法来更新Model中的数据。 ```qml Button { text: "Update" onClicked: { // 调用C++方法更新Model中的数据 } } ``` 4. 在C++中,您可以使用QObject派生类作为Model的包装类,在该类中实现与Model的交互逻辑。 ```cpp class ModelWrapper : public QObject { Q_OBJECT Q_PROPERTY(QString modelData READ getModelData WRITE setModelData NOTIFY modelDataChanged) public: QString getModelData() const; void setModelData(const QString& newData); signals: void modelDataChanged(); private: QString m_modelData; }; ``` 5. 将ModelWrapper对象注册到QML上下文中,并在C++中实现相应的读取和写入方法。 ```cpp int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQmlApplicationEngine engine; ModelWrapper modelWrapper; engine.rootContext()->setContextProperty("modelWrapper", &modelWrapper); // ... return app.exec(); } ``` 这样,您就可以在QML中使用TextField并与Model进行交互了。通过将TextField的text属性绑定到自定义属性上,并通过信号和槽机制或调用C++方法来更新Model中的数据。请根据您的具体需求扩展和修改这个示例。如有任何问题,请随时提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

mrbone11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值