ドリルダウンの例
このドリルダウンの例では、QSqlRelationalTableModel およびQDataWidgetMapper クラスを使用して、データベースからデータを読み込み、変更を送信する方法を示します。

サンプルアプリケーションを実行すると、ユーザーは各アイテムに対応する画像をクリックすることで、そのアイテムに関する情報を取得できます。アプリケーションはデータを表示する情報ウィンドウをポップアップ表示し、ユーザーが説明文や画像を変更できるようにします。ユーザーが変更内容を送信すると、メインビューが更新されます。
このサンプルは3つのクラスで構成されています:
ImageItemは、画像を表示するために使用されるカスタムグラフィックアイテムクラスです。Viewは、ユーザーがさまざまなアイテムを閲覧できるようにするメインのアプリケーションウィジェットです。InformationWindowは、要求された情報を表示し、ユーザーがそれを変更して、変更内容をデータベースに送信できるようにするクラスです。
まず、InformationWindow クラスを確認し、データベースからデータを読み取り、変更する方法を学びます。次に、メインアプリケーションウィジェットであるView クラスと、関連するImageItem クラスについて解説します。
InformationWindowクラスの定義
InformationWindow クラスは、QWidget を継承したカスタムウィジェットです:
class InformationWindow : public QDialog
{
Q_OBJECT
public:
InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent = nullptr);
int id() const;
Q_SIGNALS:
void imageChanged(int id, const QString &fileName);情報ウィンドウを作成する際、コンストラクタに関連するアイテムID、モデルへのポインタ、および親オブジェクトを渡します。モデルポインタを使用してウィンドウにデータを設定し、親パラメータは基底クラスに渡します。IDは後で参照できるように保存されます。
ウィンドウが作成されると、指定された場所に関する情報が要求された際に、パブリック関数 `id() ` を使用してそのウィンドウを検索します。また、ユーザーが変更内容をデータベースに送信した際、ID を使用してメインアプリケーションウィジェットを更新します。つまり、ユーザーが関連する画像を変更するたびに、ID とファイル名をパラメータとして含むシグナルを発行します。
private Q_SLOTS:
void revert();
void submit();
void enableButtons(bool enable);ユーザーが一部のデータを変更できるようにしているため、変更を元に戻したり送信したりする機能を提供する必要があります。「enableButtons() 」スロットは、必要に応じて各種ボタンを有効・無効にするための便宜上の機能として用意されています。
private:
void createButtons();
int itemId;
QString displayedImage;
QComboBox *imageFileEditor = nullptr;
QLabel *itemText = nullptr;
QTextEdit *descriptionEditor = nullptr;
QPushButton *closeButton = nullptr;
QPushButton *submitButton = nullptr;
QPushButton *revertButton = nullptr;
QDialogButtonBox *buttonBox = nullptr;
QDataWidgetMapper *mapper = nullptr;
};createButtons() 関数もまた、コンストラクタを簡略化するために提供された便利な関数です。前述の通り、後で参照できるようにアイテムIDを保存します。また、imageChanged() シグナルをいつ発行すべきかを判断できるよう、現在表示されている画像ファイルの名前も保存します。
情報ウィンドウは、QLabel クラスを使用して項目の名前を表示します。関連付けられた画像ファイルはQComboBox インスタンスを使用して表示され、説明文はQTextEdit を使用して表示されます。さらに、このウィンドウには、データフローの制御やウィンドウの表示/非表示を切り替えるための3つのボタンがあります。
最後に、マッパーを宣言します。QDataWidgetMapper クラスは、データモデルの特定の部分とウィジェットとの間のマッピングを提供します。このマッパーを使用して、指定されたデータベースからデータを抽出するとともに、ユーザーがデータを変更した際にはデータベースを更新します。
InformationWindow クラスの実装
コンストラクタは、アイテムID、データベースポインタ、親ウィジェットの3つの引数を受け取ります。データベースポインタは、実際には、データベーステーブルに対して(外部キーをサポートした)編集可能なデータモデルを提供するQSqlRelationalTableModel オブジェクトへのポインタです。
InformationWindow::InformationWindow(int id, QSqlRelationalTableModel *items,
QWidget *parent)
: QDialog(parent)
{
QLabel *itemLabel = new QLabel(tr("Item:"));
QLabel *descriptionLabel = new QLabel(tr("Description:"));
QLabel *imageFileLabel = new QLabel(tr("Image file:"));
createButtons();
itemText = new QLabel;
descriptionEditor = new QTextEdit;まず、データベースに含まれるデータを表示するために必要な各種ウィジェットを作成します。ほとんどのウィジェットは単純な方法で作成されますが、画像ファイル名を表示するコンボボックスについては以下の点に注意してください:
imageFileEditor = new QComboBox;
imageFileEditor->setModel(items->relationModel(1));
imageFileEditor->setModelColumn(items->relationModel(1)->fieldIndex("file"));この例では、アイテムに関する情報は「items」というデータベーステーブルに格納されています。モデルを作成する際、このテーブルと、利用可能な画像ファイルの名前を含む 2 つ目のデータベーステーブル「images」との間に、外部キーを使用して関係を確立します。その方法については、View クラスを解説する際に改めて説明します。 このような関係を作成する理由は、ユーザーが事前に定義された画像ファイルのみから選択できるようにするためです。
「images」データベーステーブルに対応するモデルは、QSqlRelationalTableModel のrelationModel()関数を通じて利用でき、引数として外部キー(この場合は「imagefile」列の番号)を指定する必要があります。QComboBox のsetModel()関数を使用して、コンボボックスが「images」モデルを使用するように設定します。また、このモデルには2つの列(「itemid」と「file」)があるため、QComboBox::setModelColumn()関数を使用して、どの列を表示するかを指定します。
mapper = new QDataWidgetMapper(this);
mapper->setModel(items);
mapper->setSubmitPolicy(QDataWidgetMapper::ManualSubmit);
mapper->setItemDelegate(new QSqlRelationalDelegate(mapper));
mapper->addMapping(imageFileEditor, 1);
mapper->addMapping(itemText, 2, "text");
mapper->addMapping(descriptionEditor, 3);
mapper->setCurrentIndex(id);次に、マッパーを作成します。QDataWidgetMapper クラスを使用すると、ウィジェットをアイテムモデルのセクションにマッピングすることで、データ連動型ウィジェットを作成できます。
addMapping() 関数は、指定されたウィジェットとモデルの指定されたセクションとの間にマッピングを追加します。マッパーの向きが水平(デフォルト)の場合、セクションはモデル内の列となり、それ以外の場合は行となります。setCurrentIndex() 関数を呼び出し、指定されたアイテム ID に関連付けられたデータでウィジェットを初期化します。現在のインデックスが変更されるたびに、すべてのウィジェットがモデルの内容に基づいて更新されます。
また、マッパーの送信ポリシーを `QDataWidgetMapper::ManualSubmit` に設定します。これは、ユーザーが明示的に送信を要求するまで、データベースにデータが送信されないことを意味します(もう一つの選択肢は `QDataWidgetMapper::AutoSubmit` で、対応するウィジェットがフォーカスを失った際に変更内容を自動的に送信します)。 最後に、マッパービューがアイテムに対して使用するアイテムデリゲートを指定します。QSqlRelationalDelegate クラスは、デフォルトのデリゲートとは異なり、他のテーブルへの外部キーであるフィールド(例えば、当例の「items」テーブル内の「imagefile」など)に対してコンボボックス機能を有効にするデリゲートです。
connect(descriptionEditor, &QTextEdit::textChanged, this, [this]() { enableButtons(true); });
connect(imageFileEditor, &QComboBox::currentIndexChanged, this, [this]() { enableButtons(true); });
QFormLayout *formLayout = new QFormLayout;
formLayout->addRow(itemLabel, itemText);
formLayout->addRow(imageFileLabel, imageFileEditor);
formLayout->addRow(descriptionLabel, descriptionEditor);
QVBoxLayout *layout = new QVBoxLayout;
layout->addLayout(formLayout);
layout->addWidget(buttonBox);
setLayout(layout);
itemId = id;
displayedImage = imageFileEditor->currentText();
setWindowFlags(Qt::Window);
enableButtons(false);
setWindowTitle(itemText->text());
}最後に、エディタ内の「変更があった」というシグナルを、カスタムスロットenableButtons に接続し、ユーザーが変更を送信するか元に戻すかを選択できるようにします。enableButtons スロットを接続するにはラムダ式を使用する必要があります。これは、そのシグネチャがQTextEdit::textChanged やQComboBox::currentIndexChanged と一致しないためです。
すべてのウィジェットをレイアウトに追加し、後で参照できるようアイテムIDと表示される画像ファイル名を保存し、ウィンドウのタイトルと初期サイズを設定します。
なお、ウィジェットが実際にはウィンドウシステムフレームとタイトルバーを持つウィンドウであることを示すために、Qt::Window ウィンドウフラグも設定しています。
int InformationWindow::id() const
{
return itemId;
}ウィンドウが作成されると、メインアプリケーションが終了するまでそのウィンドウは削除されません(つまり、ユーザーが情報ウィンドウを閉じた場合でも、ウィンドウは非表示になるだけです)。 このため、各項目に対して複数の `InformationWindow ` オブジェクトを作成することは避け、ユーザーが特定の場所に関する情報を要求した際に、その場所にウィンドウがすでに存在するかどうかを判断できるように、パブリック関数 `id() ` を提供しています。
void InformationWindow::revert()
{
mapper->revert();
enableButtons(false);
}ユーザーがRevert ボタンをクリックするたびに、revert() スロットがトリガーされます。
QDataWidgetMapper::ManualSubmit のサブミットポリシーを設定しているため、ユーザーが明示的にすべてを送信することを選択しない限り、ユーザーの変更はモデルに書き戻されません。とはいえ、QDataWidgetMapper のrevert()スロットを使用することで、エディタウィジェットをリセットし、すべてのウィジェットにモデルの現在のデータを再表示させることができます。
void InformationWindow::submit()
{
QString newImage(imageFileEditor->currentText());
if (displayedImage != newImage) {
displayedImage = newImage;
emit imageChanged(itemId, newImage);
}
mapper->submit();
mapper->setCurrentIndex(itemId);
enableButtons(false);
}同様に、ユーザーがSubmit ボタンを押して変更を送信することを決定するたびに、submit() スロットがトリガーされます。
QDataWidgetMapper のsubmit()スロットを使用して、マッピングされたウィジェットからのすべての変更をモデル、つまりデータベースに送信します。マッピングされた各セクションについて、アイテムデリゲートはウィジェットから現在の値を読み取り、それをモデルに設定します。 最後に、モデルの submit()関数が呼び出され、モデルに対して、キャッシュされているデータをすべて永続ストレージに送信すべきであることを通知します。
なお、データを送信する前に、以前に保存された `displayedImage ` 変数を参照として、ユーザーが別の画像ファイルを選択したかどうかを確認します。現在のファイル名と保存済みのファイル名が異なる場合、新しいファイル名を保存し、`imageChanged() ` シグナルを発行します。
void InformationWindow::createButtons()
{
closeButton = new QPushButton(tr("&Close"));
revertButton = new QPushButton(tr("&Revert"));
submitButton = new QPushButton(tr("&Submit"));
closeButton->setDefault(true);
connect(closeButton, &QPushButton::clicked, this, &InformationWindow::close);
connect(revertButton, &QPushButton::clicked, this, &InformationWindow::revert);
connect(submitButton, &QPushButton::clicked, this, &InformationWindow::submit);createButtons() 関数は、利便性のため、つまりコンストラクタを簡略化するために提供されています。
Close ボタンをデフォルトボタン(ユーザーがEnter を押した際に押されるボタン)に設定し、そのclicked()シグナルをウィジェットのclose()スロットに接続します。前述の通り、ウィンドウを閉じてもウィジェットは非表示になるだけで、削除されるわけではありません。また、Submit ボタンとRevert ボタンを、それぞれ対応するsubmit() スロットとrevert() スロットに接続します。
buttonBox = new QDialogButtonBox(this);
buttonBox->addButton(submitButton, QDialogButtonBox::AcceptRole);
buttonBox->addButton(revertButton, QDialogButtonBox::ResetRole);
buttonBox->addButton(closeButton, QDialogButtonBox::RejectRole);
}QDialogButtonBox クラスは、現在のウィジェットスタイルに適したレイアウトでボタンを表示するウィジェットです。この情報ウィンドウのようなダイアログは、通常、そのプラットフォームのインターフェースガイドラインに準拠したレイアウトでボタンを表示します。 当然のことながら、プラットフォームごとにダイアログのレイアウトは異なります。QDialogButtonBox を使用することで、ユーザーのデスクトップ環境に適したレイアウトを自動的に適用してボタンを追加することができます。
ダイアログのボタンのほとんどは、特定の役割に従っています。Submit およびRevert ボタンには、reset という役割を割り当てています。これは、ボタンを押すとフィールドがデフォルト値(この場合はデータベースに含まれる情報)にリセットされることを示しています。 「reject 」ロールは、ボタンをクリックするとダイアログが破棄されることを示します。一方、情報ウィンドウを非表示にするだけであるため、ユーザーが加えた変更は、ユーザーが明示的に元に戻すか送信するまで保持されます。
void InformationWindow::enableButtons(bool enable)
{
revertButton->setEnabled(enable);
submitButton->setEnabled(enable);
}enableButtons() スロットは、ユーザーが表示されたデータを変更するたびに、ボタンを有効にするために呼び出されます。同様に、ユーザーが変更を送信することを選択した際、現在のデータがデータベースに保存されたことを示すために、ボタンは無効化されます。
これでInformationWindow クラスの実装は完了です。サンプルアプリケーションでこれをどのように使用しているかを見てみましょう。
ビュークラスの定義
View クラスはメインのアプリケーションウィンドウを表し、QGraphicsView を継承しています:
class View : public QGraphicsView
{
Q_OBJECT
public:
View(const QString &items, const QString &images, QWidget *parent = nullptr);
protected:
void mouseReleaseEvent(QMouseEvent *event) override;
private Q_SLOTS:
void updateImage(int id, const QString &fileName);QGraphicsView クラスは、画像の表示に使用するGraphics View Frameworkの一部です。画像がクリックされた際に適切な情報ウィンドウを表示してユーザーの操作に応答できるようにするため、QGraphicsView のmouseReleaseEvent()関数を再実装しています。
コンストラクタには、2つのデータベーステーブルの名前を指定する必要があることに注意してください。1つはアイテムの詳細情報を格納するテーブル、もう1つは利用可能な画像ファイルの名前を格納するテーブルです。また、ユーザーがアイテムに関連付けられた画像を変更するたびに発せられるInformationWindow のimageChanged() シグナルを捕捉するために、updateImage() のプライベートスロットも用意しています。
private:
void addItems();
InformationWindow *findWindow(int id) const;
void showInformation(ImageItem *image);
QGraphicsScene *scene;
QList<InformationWindow *> informationWindows;addItems() 関数は、コンストラクタの処理を簡略化するために提供されている便利な関数です。これは1回だけ呼び出され、さまざまなアイテムを作成してビューに追加します。
一方、findWindow() 関数は頻繁に使用されます。これはshowInformation() 関数から呼び出され、指定されたアイテムに対してウィンドウがすでに作成されているかどうかを判定します(InformationWindow オブジェクトを作成するたびに、その参照をinformationWindows リストに格納します)。後者の関数は、さらに私たちのカスタムmouseReleaseEvent() 実装から呼び出されます。
QSqlRelationalTableModel *itemTable;
};最後に、QSqlRelationalTableModel ポインタを宣言します。前述の通り、QSqlRelationalTableModel クラスは、外部キーをサポートする編集可能なデータモデルを提供します。QSqlRelationalTableModel クラスを使用する際には、以下の点に留意する必要があります。テーブルには主キーが宣言されていなければならず、このキーは他のテーブルへの参照を含んではなりません。つまり、外部キーであってはなりません。 また、リレーショナルテーブルに、参照先テーブルに存在しない行を参照するキーが含まれている場合、その無効なキーを含む行はモデルを通じて公開されない点にも注意してください。参照整合性を維持するのは、ユーザーまたはデータベースの責任です。
Viewクラスの実装
コンストラクタでは、オフィスの詳細を含むテーブルと、利用可能な画像ファイルの名前を含むテーブルの両方の名前が要求されますが、実際に作成する必要があるのは「items」テーブル用のQSqlRelationalTableModel オブジェクトのみです:
View::View(const QString &items, const QString &images, QWidget *parent)
: QGraphicsView(parent)
{
itemTable = new QSqlRelationalTableModel(this);
itemTable->setTable(items);
itemTable->setRelation(1, QSqlRelation(images, "itemid", "file"));
itemTable->select();その理由は、アイテムの詳細を含むモデルが一度作成されれば、QSqlRelationalTableModel のsetRelation()関数を使用して、利用可能な画像ファイルへのリレーションを作成できるためです。この関数は、指定されたモデルのカラムに対して外部キーを作成します。 キーは、キーが参照するテーブル名、キーがマッピングされるフィールド、およびユーザーに表示されるフィールドによって構成された、渡されたQSqlRelation オブジェクトによって指定されます。
tableの設定は、モデルが操作するテーブルを指定するだけであることに注意してください。つまり、モデルにデータを格納するには、モデルのselect()関数を明示的に呼び出す必要があります。
scene = new QGraphicsScene(this);
scene->setSceneRect(0, 0, 465, 365);
setScene(scene);
addItems();
setMinimumSize(470, 370);
setMaximumSize(470, 370);
QLinearGradient gradient(QPointF(0, 0), QPointF(0, 370));
gradient.setColorAt(0, QColor("#868482"));
gradient.setColorAt(1, QColor("#5d5b59"));
setBackgroundBrush(gradient);
}次に、ビューの内容、つまりシーンとそのアイテムを作成します。ラベルは通常の `QGraphicsTextItem ` オブジェクトですが、画像は `QGraphicsPixmapItem` から派生した `ImageItem ` クラスのインスタンスです。これについては、後ほど `addItems() ` 関数を解説する際に改めて触れます。
最後に、メインアプリケーションウィジェットのサイズ制約とウィンドウタイトルを設定します。
void View::addItems()
{
int itemCount = itemTable->rowCount();
int imageOffset = 150;
int leftMargin = 70;
int topMargin = 40;
for (int i = 0; i < itemCount; i++) {
QSqlRecord record = itemTable->record(i);
int id = record.value("id").toInt();
QString file = record.value("file").toString();
QString item = record.value("itemtype").toString();
int columnOffset = ((i % 2) * 37);
int x = ((i % 2) * imageOffset) + leftMargin + columnOffset;
int y = ((i / 2) * imageOffset) + topMargin;
ImageItem *image = new ImageItem(id, QPixmap(":/" + file));
image->setData(0, i);
image->setPos(x, y);
scene->addItem(image);
QGraphicsTextItem *label = scene->addText(item);
label->setDefaultTextColor(QColor("#d7d6d5"));
QPointF labelOffset((120 - label->boundingRect().width()) / 2, 120.0);
label->setPos(QPointF(x, y) + labelOffset);
}
}addItems() 関数は、メインアプリケーションウィンドウの作成時に一度だけ呼び出されます。データベーステーブルの各行について、まずモデルのrecord()関数を使用して対応するレコードを取得します。QSqlRecord クラスは、データベースレコードの機能と特性をカプセル化しており、フィールドの追加や削除、およびフィールド値の設定や取得をサポートしています。QSqlRecord::value() 関数は、指定された名前またはインデックスを持つフィールドの値を、QVariant オブジェクトとして返します。
各レコードについて、ラベルアイテムと画像アイテムを作成し、それらの位置を計算してシーンに追加します。画像アイテムは、ImageItem クラスのインスタンスによって表現されます。 カスタムアイテムクラスを作成する必要がある理由は、アイテムのホバーイベントを捕捉し、マウスカーソルが画像上にホバーしているときにアイテムをアニメーションさせたいからです(デフォルトでは、どのアイテムもホバーイベントを受け付けません)。詳細については、Graphics View FrameworkのドキュメントおよびGraphics Viewのサンプルを参照してください。
void View::mouseReleaseEvent(QMouseEvent *event)
{
if (QGraphicsItem *item = itemAt(event->position().toPoint())) {
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item))
showInformation(image);
}
QGraphicsView::mouseReleaseEvent(event);
}ユーザーの操作に応答するために、QGraphicsView のmouseReleaseEvent()イベントハンドラを再実装します。ユーザーが画像アイテムのいずれかをクリックすると、この関数はプライベート関数showInformation() を呼び出し、関連する情報ウィンドウを表示します。
Graphics View Frameworkは、指定された `QGraphicsItem ` インスタンスが特定の型であるかどうかを判定するための `qgraphicsitem_cast()` 関数を提供しています。なお、イベントが当方の画像アイテムのいずれにも関連していない場合は、そのイベントを基底クラスの実装に委譲することに注意してください。
void View::showInformation(ImageItem *image)
{
int id = image->id();
if (id < 0 || id >= itemTable->rowCount())
return;
InformationWindow *window = findWindow(id);
if (!window) {
window = new InformationWindow(id, itemTable, this);
connect(window, &InformationWindow::imageChanged,
this, &View::updateImage);
window->move(pos() + QPoint(20, 40));
window->show();
informationWindows.append(window);
}
if (window->isVisible()) {
window->raise();
window->activateWindow();
} else
window->show();
}showInformation() 関数には、引数としてImageItem オブジェクトが渡され、まずそのアイテムのアイテムIDを抽出することから処理を開始します。
次に、その位置に対して情報ウィンドウがすでに作成されているかどうかを判断します。指定された位置に対応するウィンドウが存在しない場合は、アイテムID、モデルへのポインタ、および親として当ビューをInformationWindow のコンストラクタに渡して、ウィンドウを作成します。 なお、情報ウィンドウのimageChanged() シグナルを、このウィジェットのupdateImage() スロットに接続してから、適切な位置を指定し、既存のウィンドウのリストに追加することに注意してください。指定された位置にウィンドウが存在し、かつそのウィンドウが表示されている場合、そのウィンドウがウィジェットスタックの最前面に持ち上げられ、アクティブになるようにします。 非表示の場合は、show()スロットを呼び出すことで同様の結果が得られます。
void View::updateImage(int id, const QString &fileName)
{
QList<QGraphicsItem *> items = scene->items();
while(!items.empty()) {
QGraphicsItem *item = items.takeFirst();
if (ImageItem *image = qgraphicsitem_cast<ImageItem *>(item)) {
if (image->id() == id){
image->setPixmap(QPixmap(":/" +fileName));
image->adjust();
break;
}
}
}
}updateImage() スロットは、アイテムIDと画像ファイル名を引数として受け取ります。このスロットは画像アイテムをフィルタリングし、指定されたアイテムIDに対応する画像を、渡された画像ファイルで更新します。
InformationWindow *View::findWindow(int id) const
{
for (auto window : informationWindows) {
if (window && (window->id() == id))
return window;
}
return nullptr;
}findWindow() 関数は、単に既存のウィンドウのリストを検索し、指定されたアイテムIDに一致するウィンドウへのポインタを返します。ウィンドウが存在しない場合は、nullptr を返します。
最後に、独自に実装したImageItem クラスを簡単に見てみましょう:
ImageItemクラスの定義
ImageItem クラスは、画像アイテムのアニメーションを容易にするために提供されています。このクラスはQGraphicsPixmapItem を継承し、そのホバーイベントハンドラを再実装しています:
class ImageItem : public QObject, public QGraphicsPixmapItem
{
Q_OBJECT
public:
enum { Type = UserType + 1 };
ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent = nullptr);
int type() const override { return Type; }
void adjust();
int id() const;
protected:
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
void hoverLeaveEvent(QGraphicsSceneHoverEvent *event) override;
private Q_SLOTS:
void setFrame(int frame);
void updateItemPosition();
private:
QTimeLine timeLine;
int recordId;
double z;
};カスタムアイテム用にType 列挙型値を宣言し、type()を再実装します。これは、qgraphicsitem_cast()を安全に使用できるようにするためです。さらに、関連する位置を特定できるパブリック関数id() と、元の画像ファイルに関係なく画像アイテムに優先サイズを確実に適用するために呼び出せるパブリック関数adjust() を実装します。
アニメーションは、QTimeLine クラスとイベントハンドラ、およびプライベートスロットsetFrame() を組み合わせて実装されています。画像アイテムは、マウスカーソルがホバーすると拡大し、カーソルが境界線を離れると元のサイズに戻ります。
最後に、この特定のレコードに関連付けられているアイテムIDとz値を保存します。Graphics View Frameworkでは、アイテムのz値によってアイテムスタック内での位置が決まります。同じ親アイテムを共有している場合、z値の高いアイテムはz値の低いアイテムの上に描画されます。 また、必要に応じてビューを更新するためのupdateItemPosition() 関数も用意しています。
ImageItemクラスの実装
ImageItem クラスは、実際にはいくつかの追加機能を備えたQGraphicsPixmapItem に過ぎません。つまり、コンストラクタの引数のほとんど(ピクマップ、親、シーン)を基底クラスのコンストラクタに渡すことができます。
ImageItem::ImageItem(int id, const QPixmap &pixmap, QGraphicsItem *parent)
: QGraphicsPixmapItem(pixmap, parent)
{
recordId = id;
setAcceptHoverEvents(true);
timeLine.setDuration(150);
timeLine.setFrameRange(0, 150);
connect(&timeLine, &QTimeLine::frameChanged, this, &ImageItem::setFrame);
connect(&timeLine, &QTimeLine::finished, this, &ImageItem::updateItemPosition);
adjust();
}次に、後で参照できるよう ID を保存し、この画像アイテムがホバーイベントを受け付けるようにします。ホバーイベントは、現在マウスが捕捉されているアイテムがない場合に発生します。これらは、マウスカーソルがアイテム内に入ったとき、アイテム内で移動したとき、およびカーソルがアイテムから離れたときに送信されます。 前述したように、Graphics View Frameworkのアイテムは、デフォルトではいずれもホバーイベントを受け付けません。
QTimeLine クラスは、アニメーションを制御するためのタイムラインを提供します。そのduration プロパティは、タイムラインの合計継続時間をミリ秒単位で保持します。デフォルトでは、タイムラインは最初から最後まで一度だけ実行されます。QTimeLine::setFrameRange()関数はタイムラインのフレームカウンターを設定し、タイムラインの実行中は、フレームが切り替わるたびにframeChanged()シグナルが発信されます。 アニメーションの再生時間とフレーム範囲を設定し、タイムラインのframeChanged()およびfinished()シグナルを、プライベートスロットであるsetFrame() およびupdateItemPosition() に接続します。
最後に、adjust() を呼び出して、アイテムが優先サイズになるようにします。
void ImageItem::hoverEnterEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Forward);
if (z != 1.0) {
z = 1.0;
updateItemPosition();
}
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}
void ImageItem::hoverLeaveEvent(QGraphicsSceneHoverEvent * /*event*/)
{
timeLine.setDirection(QTimeLine::Backward);
if (z != 0.0)
z = 0.0;
if (timeLine.state() == QTimeLine::NotRunning)
timeLine.start();
}マウスカーソルが画像アイテムに進入または離脱するたびに、対応するイベントハンドラがトリガーされます。まず、タイムラインの方向を設定し、アイテムをそれぞれ拡大または縮小させます。次に、アイテムの z 値が期待される値にまだ設定されていない場合は、それを変更します。
ホバー進入イベントの場合、アイテムが拡大し始めたらすぐに他のすべてのアイテムの上に表示されるようにするため、直ちにアイテムの位置を更新します。一方、ホバー離脱イベントの場合は、同じ結果を得るために実際の更新を遅らせます。 ただし、アイテムを構築した際に、タイムラインの `finished()` シグナルを `updateItemPosition() ` スロットに接続していたことを覚えておいてください。これにより、アニメーションが完了すると、アイテムはアイテムスタック内で正しい位置に配置されます。最後に、タイムラインがまだ実行されていない場合は、それを開始します。
void ImageItem::setFrame(int frame)
{
adjust();
QPointF center = boundingRect().center();
setTransform(QTransform::fromTranslate(center.x(), center.y()), true);
setTransform(QTransform::fromScale(1 + frame / 300.0, 1 + frame / 300.0), true);
setTransform(QTransform::fromTranslate(-center.x(), -center.y()), true);
}タイムラインが実行中になると、アイテムのコンストラクタで作成した接続により、現在のフレームが変更されるたびにsetFrame() スロットがトリガーされます。このスロットがアニメーションを制御し、画像アイテムを段階的に拡大または縮小させます。
まず、adjust() 関数を呼び出し、アイテムが元のサイズから開始されるようにします。次に、(frame パラメータを使用して)アニメーションの進行状況に応じた倍率でアイテムを拡大・縮小します。 なお、デフォルトでは、変形はアイテムの左上隅を基準に行われます。ここではアイテムの中心を基準に変形させたいので、アイテムを拡大・縮小する前に座標系を平行移動させる必要があります。
最終的に、以下の便利関数だけが残ります:
void ImageItem::adjust()
{
setTransform(QTransform::fromScale(120.0 / boundingRect().width(),
120.0 / boundingRect().height()));
}
int ImageItem::id() const
{
return recordId;
}
void ImageItem::updateItemPosition()
{
setZValue(z);
}adjust() 関数は、変換行列を定義して適用し、ソース画像のサイズにかかわらず、画像アイテムが希望のサイズで表示されるようにします。id() 関数は単純なもので、アイテムを識別できるようにするために用意されているだけです。updateItemPosition() スロットでは、QGraphicsItem::setZValue()関数を呼び出し、アイテムのelevationを設定します。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.