Qt5再入門: モデルデータ 追加と削除
本シリーズでは、QStandardItemModelのサブクラス、BookmarkModelを利用して、ブックマークを管理しています。モデルは、ビューui_->bookmarksViewと連携しています。
今回は、ブックマークモデルに新しいブックマークを追加したり、不要なブックマークを削除するコードを勉強します。
スロットの作成
モデルにブックマークデータを追加したり、削除したりするコードは、UIから選択したアクションに直結するので、ここではスロット関数として宣言します。
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include "bookmarkmodel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
/**
* @brief メインウィンドウクラス
*/
class MainWindow : public QMainWindow
{
Q_OBJECT
public slots:
// ...
/**
* @brief ブックマークを追加
*/
void appendBookmark();
/**
* @brief ブックマークを削除
*/
void removeBookmark();
// ...
};
#endif // MAINWINDOW_H
「public slots:」として書かれたエリアに、appendBookmarkとremoveBookmarkメソッドを定義しています。
続いて追加メソッドの実装です。
// mainwindow.cpp
// ...
void MainWindow::appendBookmark() {
// 空の行を追加
pModel_->appendRow(
QList<QStandardItem*>()
<< new QStandardItem()
<< new QStandardItem()
<< new QStandardItem()
);
ui_->statusbar->showMessage(
tr(R"(Added a bookmark.)")
);
}
// ...
コメントにもあるように、新規で追加するブックマークの中身は空の文字列なので、QStandardItemオブジェクトを3つ作って、モデルのappendRowメソッドで追加します。
続いて削除メソッドです。
// mainwindow.cpp
// ...
void MainWindow::removeBookmark() {
// テーブル内を指定していなければ中止する。
QModelIndex index = ui_->bookmarksView->currentIndex();
if (!index.isValid()) {
ui_->statusbar->showMessage("Aborted.");
return;
}
// 選択されているブックマークを取得
Bookmark bookmark = pModel_->bookmark(index.row());
// 確認ダイアログを表示して、実行を確認する。
QMessageBox::StandardButton selected = QMessageBox::question(
this,
tr(R"(Deleting a bookmark "%1")")
.arg(bookmark.label()),
tr(R"(Are you sure you want to delete a bookmark "%1"?)")
.arg(bookmark.label())
);
// OKでなければ中止する。
if (selected != QMessageBox::Yes) {
ui_->statusbar->showMessage("Aborted.");
return;
}
// 指定インデックスの行を削除
pModel_->removeRow(index.row());
ui_->statusbar->showMessage(
tr(R"(Deleted a bookmark "%1".)")
.arg(bookmark.label())
);
}
// ...
削除メソッドでは、「何を削除対象とするか」が明示されていないといけません。GUIアプリケーションなので、目で見てわかる選択状態が望ましいのはいうまでもありません。では、それをプログラム上ではどう識別するか・・・。
ビューQTableViewの親クラス「 QAbstractItemView」には、どのアイテムが選択されているかがわかる「currentIndex」メソッドが提供されています。これは、ビュー上でマウスやカーソルなどで選ばれているアイテムのインデックスクラス「QModelIndex」を返します。
QModelIndexクラスは、モデルアイテムの位置状態を表すクラスです。行位置、列位置、親アイテム、兄弟アイテムなどの情報を提供します。
削除メソッドが呼ばれた時点で、どのアイテムが選択されているかを示すcurrentIndexを呼び出し、返ってきたQModelIndexが有効なインデックスかどうかをテストします。フォーカスがあたっていないなどの理由で、QModelIndexが無効な場合があります。その場合、削除対象もわかりませんから、その時点で削除プロセスを中止します。
めでたくインデックスが有効だったら、行位置を利用してブックマークデータを取得します。そしてその内容を、本当に削除していいのかユーザーに確認します。対話ダイアログにはQMessageBox::questionを使います。
QMessageBoxは、QDialogから派生したサブクラスで、メッセージとボタン、オプションとして既定のアイコンを指定できるシンプルなモーダルダイアログUIです。ユーザに情報を提供してOKボタンを押したり、質問を提供して「はい」「いいえ」などの回答を得たりできますが、複雑な情報を求めることはできません。その場合はQDialogのサブクラスを定義します。
QMessageBoxクラスのオブジェクトに、メッセージやボタンを一つ一つ定義する方法もありますが、静的メソッドを使って一行で済ますこともできます。QMessageBoxには、about、critical、warning、information、questionの5つの静的メソッドがあります。
基本的に、メッセージとボタン、既定アイコンで構成され、question以外はOKボタンのみ、questionはYesとNoボタンが標準構成になります。
例えば、エラーメッセージを通知したい場合はcritical、2択の質問にはquestion、という風に使います。about以外はボタンを選択することも可能です。questionならYes/Noの代わりにOK/Cancelを使う、といったことができます。
ユーザーがYesを選択したら、いよいよブックマークの削除です。選択位置の行番号を使ってモデルのremoveRowメソッドを呼び出せば完了です。
ブックマーク追加と削除のアクション
アクション(QAction)は、100%コードだけでも定義できますが、MainWindowをUIファイルで編集していたら、Qt Designerで定義した方がいいかもしれません。というのも、メニューのサブメニューはアクションとして定義されるし、独立したアクションを後でサブメニュー化することもできるからです。
以下はQt Designer画面です。
画面下のウィジェットがアクションエディタで、次がそのアップです。
actionAppend_Bookmarkを開くと、次のようなダイアログが表示されます。
Qt Designer上からアクションエディタで、ブックマークを追加するアクション、削除するアクションを準備しておきます。
アクションのシグナルをスロットに接続
ユーザーがブックマークを追加したい、削除したいという意思をアクションという形で具現化できました。次はこれを実際のプロセスに接続してあげます。
MainWindowのコンストラクタで、アクションのシグナルを目的のスロットに接続します。
// mainwindow.cpp
// ...
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui_(new Ui::MainWindow)
{
// UIのセットアップ
ui_->setupUi(this);
// ...
// ブックマーク追加アクション時、ブックマーク追加メソッド実行
connect(
ui_->actionAppend_Bookmark, &QAction::triggered,
this, &MainWindow::appendBookmark
);
// ブックマーク削除アクション時、ブックマーク削除メソッド実行
connect(
ui_->actionRemove_a_bookmark, &QAction::triggered,
this, &MainWindow::removeBookmark
);
// ...
}
// ...
コンテキストメニュー
GUI画面で右クリックすると、マウスでポイントした先だけで有効なメニューを開くことができることがあります。これを「コンテキストメニュー」と呼びます。Qtウィジェットではこのシステムがデフォルトで組み込まれていて、すぐに使えるようになります。数パターンあるコンテキストメニューのうち、そのウィジェットに登録されているアクションを集めてコンテキストメニュー化する方法を紹介します。
まず、コンテキストメニューを表示したいウィジェット、この場合ui_->bookmarkViewにメニュー化したいアクションを追加します。追加には「addAction」メソッドを使います。
// mainwindow.cpp
// ...
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui_(new Ui::MainWindow)
{
// UIのセットアップ
ui_->setupUi(this);
// ...
// ブックマーク追加アクションをビューに追加
ui_->bookmarksView->addAction(ui_->actionAppend_Bookmark);
// ブックマーク削除アクションをビューに追加
ui_->bookmarksView->addAction(ui_->actionRemove_a_bookmark);
// (続く)
続いて、ビューのコンテキストメニューポリシーをQt::ActionsContextMenuに設定します。
// (続き)
// コンテキストメニューポリシーを設定(Qt Designerでも設定可)
ui_->bookmarksView->setContextMenuPolicy(Qt::ActionsContextMenu);
// ...
}
それでは、ここまでの設定が動作するか、デバッグモードで確認してみます。なお、デバッグの際にはNotesクライアントのプログラムディレクトリにパスを通しておきます。
起動直後は、ブックマークはまっさらです。
ブックマークエリア内で右クリックをしてみます。
「ブックマークを追加」「ブックマークを削除」というメニューの選択肢が出てきました。これが「アクションのコンテキストメニュー化」です。なお、英文だったアクション名が日本語になっているのは、翻訳機能が働いているからです。翻訳機能は別記事で紹介します。
このまま「ブックマークを追加」を選択すると、空のブックマークが追加されます。
空の場所をダブルクリックすると編集状態になります。
編集を終えてEnterを押すと確定します。Tabを押すと次の欄に移動します。
次に、ブックマークを削除してみます。今追加したブックマークの項目の上で右クリックすると、さきほどのコンテキストメニューが表示されます。
この図では、1行目のラベルが選択状態になっています。ここで「ブックマークを削除」を選択すると、QMessageBox::questionのダイアログが表示されます。
Yesボタンをクリックすると、以下のようにブックマークが削除されます。
まとめ
今回は、ユーザーのアクションをコンテキストメニューから受け取り、それをモデルデータに伝えてビジネスロジックを実行する方法を学びました。Qtが持っているシグナル/スロット、コンテキストメニュー、モデル/ビューを使っているので、C++で作るGUIアプリケーションの煩わしさが感じられないのではないでしょうか。また、この恩恵がMacOS、Linuxでも同じソースコードで動かせるのには頭が下がる思いです。
さて、ここまで作成したブックマーク編集機能ですが、このままだとアプリケーションを終了した時点で、ブックマークデータはなくなってしまいます。次回は、以前紹介したQSettingsの機能を用いて、アプリケーション終了時にブックマークデータをレジストリに保存し、起動時にブックマークデータを復元する方法を学びます。