スターデリゲートの例

Star Delegate の例は、それ自身を描画し、編集をサポートするデリゲートの作成方法を示している。

The Star Delegate Example

QListView,QTableView,QTreeView にデータを表示するとき、個々の項目はデリゲートによって描画されます。また、ユーザが項目の編集を開始するとき(例えば、項目をダブルクリックする)、デリゲートは、編集が行われている間、項目の上に配置されるエディタ・ウィジェットを提供します。

デリゲートはQAbstractItemDelegate のサブクラスです。Qt はQAbstractItemDelegate を継承し、最も一般的なデータ型(特にintQString )を扱うQStyledItemDelegate を提供しています。カスタムのデータ型をサポートする必要がある場合や、既存のデータ型に対するレンダリングや編集をカスタマイズしたい場合は、QAbstractItemDelegateQStyledItemDelegate をサブクラス化することができます。デリゲートの詳細については「デリゲート・クラス」を、Qtのモデル/ビュー・アーキテクチャ(デリゲートを含む)について高レベルの入門が必要な場合は「モデル/ビュー・プログラミング」を参照してください。

この例では、"1 out of 5 stars "のような値を格納することができる "star rating "データタイプをレンダリングし、編集するためのカスタムデリゲートを実装する方法を見ていきます。

この例は、以下のクラスで構成されています:

  • StarRating はカスタムデータ型です。5つ星のうち2つ "や "6つ星のうち5つ "のような、星で表現された評価を格納します。
  • StarDelegate QStyledItemDelegate を継承し、 のサポートを提供します( ですでに扱われているデータ型に加えて)。StarRating QStyledItemDelegate
  • StarEditor QWidget を継承し、 によって、ユーザーがマウスを使って星の評価を編集できるようにするために使用されます。StarDelegate

StarDelegate の動作を示すために、QTableWidget にデータを入れ、デリゲートをインストールする。

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 クラスの実装

paint() 関数はQStyledItemDelegate から再実装され、ビューがアイテムを再描画する必要があるときはいつでも呼び出されます:

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

この関数は、モデルからQModelIndex オブジェクトで表される各アイテムに対して 1 回呼び出されます。アイテムに格納されているデータがStarRating の場合は、私たち自身がペイントします。そうでない場合は、QStyledItemDelegate にペイントさせます。これにより、StarDelegate が最も一般的なデータ型を扱えるようになります。

アイテムがStarRating の場合、アイテムが選択されていれば背景を描画し、StarRating::paint() を使ってアイテムを描画します。

StartRating starrating.h にある () マクロのおかげで、 にデータを格納することができます。これについては後で詳しく説明する。Q_DECLARE_METATYPE QVariant

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

アイテムがStarRating の場合、StarEditor を作成し、そのeditingFinished() シグナルをcommitAndCloseEditor() スロットに接続し、エディターが閉じたときにモデルを更新できるようにします。

以下は、commitAndCloseEditor() の実装です:

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

ユーザが編集を終えると、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() 関数は、編集が終了したときにエディタからモデルへデータをコミットするために呼び出されます:

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() 関数はアイテムの優先サイズを返します:

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

この呼び出しを単にStarRating に転送します。

StarEditor クラス定義

StarDelegate を実装する際に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;
};

このクラスは、エディタ上でマウスを動かすことで、StarRating を編集できるようにします。ユーザがエディタをクリックすると、editingFinished() シグナルを送出します。

protected関数はQWidget を再実装したもので、マウスイベントとペイントイベントを処理します。private 関数starAtPosition() はマウスポインタの下にある星の番号を返すヘルパー関数です。

StarEditor クラスの実装

コンストラクタから始めよう:

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

ウィジェットのmouse tracking を有効にして、ユーザーがマウス・ボタンを押したままでもカーソルを追えるようにします。また、QWidgetauto-fill background 機能をオンにして、不透明な背景を取得します。(この呼び出しがないと、ビューの背景がエディタを通して光ってしまいます)。

paintEvent() 関数はQWidget から再実装されています:

void StarEditor::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    myStarRating.paint(&painter, rect(), palette(),
                       StarRating::EditMode::Editable);
}

StarDelegate を実装したときと同じように、単純にStarRating::paint() を呼び出して星を描画します。

void StarEditor::mouseMoveEvent(QMouseEvent *event)
{
    const int star = starAtPosition(event->position().toPoint().x());

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

マウス・イベント・ハンドラでは、現在のカーソル位置を反映させるためにプライベート・データ・メンバmyStarRatingsetStarCount() を呼び出し、再描画を強制するためにQWidget::update() を呼び出します。

void StarEditor::mouseReleaseEvent(QMouseEvent *event)
{
    emit editingFinished();
    QWidget::mouseReleaseEvent(event);
}

ユーザがマウスボタンを離すと、editingFinished() シグナルを発信します。

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

starAtPosition() 関数は、基本的な線形代数を使って、どの星がカーソルの下にあるかを調べる。

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 クラスは、評価を星の数で表します。データを保持するだけでなく、QPaintDevice (この例ではビューまたはエディタ)上に星をペイントすることもできます。メンバ変数myStarCount には現在のレーティングが格納され、myMaxStarCount には最高レーティング(通常は5)が格納されます。

Q_DECLARE_METATYPE() マクロは、StarRating 型をQVariant に既知にし、StarRating 値をQVariant に格納できるようにします。

StarRating クラスの実装

コンストラクタはmyStarCountmyMaxStarCount を初期化し、星と菱形の描画に使用するポリゴンを設定します:

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 オブジェクトの星をペイント・デバイスにペイントします:

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

まず、描画に使用するペンとブラシを設定します。mode パラメーターには、Editable またはReadOnly を指定します。mode が編集可能な場合は、WindowText の色の代わりにHighlight の色を使用して星を描きます。

それから星を描く。Edit モードの場合、レーティングが最高レーティングより低い場合は、星の代わりに菱形を描画します。

sizeHint() 関数は、星を描く領域の好ましいサイズを返す:

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

好ましいサイズは、星の最大数を描くのに十分なサイズです。この関数はStarDelegate::sizeHint()StarEditor::sizeHint() の両方から呼び出されます。

main() 関数

以下はプログラムの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();
}

main() 関数はQTableWidget を作成し、その上にStarDelegate を設定します。DoubleClickedSelectedClickededit triggers として設定され、星の評価項目が選択されたときにシングルクリックでエディタが開かれるようになっています。

populateTableWidget() 関数はQTableWidget をデータで満たします:

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

StarRatingQVariant に変換するためにQVariant::fromValue を呼び出していることに注目してください。

可能な拡張と提案

Qtのモデル/ビュー・フレームワークをカスタマイズする方法はたくさんあります。この例で使われているアプローチは、ほとんどのカスタムデリゲートとエディタに適しています。スターデリゲートとスターエディターで使用されていない可能性の例は以下の通りです:

  • エディットトリガーに頼るのではなく、QAbstractItemView::edit()を呼び出すことで、プログラムでエディタを開くことができます。これは、QAbstractItemView::EditTrigger enumによって提供されるもの以外のエディットトリガーをサポートするために使用することができます。例えば、Star Delegateの例では、マウスでアイテムの上にカーソルを置くと、エディタがポップアップします。
  • QAbstractItemDelegate::editorEvent() を再実装することで、QWidget のサブクラスを作成する代わりに、デリゲートに直接エディタを実装することができます。

プロジェクト例 @ code.qt.io

©2024 The Qt Company Ltd. ここに含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。