SQL ウィジェットマッパーの例

SQL Widget Mapper の例では、データベースからフォーム上のウィジェットに情報をマップする方法を示します。

コンボウィジェットマッパーの例では、ウィジェットマッパーと特別な目的のモデルを持つQComboBox ウィジェットの間で名前付きマッピングを使い、モデルの値を選択肢のリストに関連付ける方法を示しました。

ここでも、ほぼ同じユーザインタフェースを持つWindow クラスを作成し、住所が「自宅」、「職場」、「その他」に分類されるようにコンボボックスを提供します。しかし、これらの住所タイプを保持するために別のモデルを使用する代わりに、1つのデータベーステーブルを使用して例データを保持し、もう1つのテーブルを使用して住所タイプを保持します。このようにして、すべての情報を同じ場所に保存します。

ウィンドウ・クラスの定義

このクラスはコンストラクタ、ボタンを最新の状態に保つスロット、モデルをセットアップするプライベート関数を提供します:

class Window : public QWidget
{
    Q_OBJECT

public:
    Window(QWidget *parent = nullptr);

private slots:
    void updateButtons(int row);

private:
    void setupModel();

    QLabel *nameLabel;
    QLabel *addressLabel;
    QLabel *typeLabel;
    QLineEdit *nameEdit;
    QTextEdit *addressEdit;
    QComboBox *typeComboBox;
    QPushButton *nextButton;
    QPushButton *previousButton;

    QSqlRelationalTableModel *model;
    QItemSelectionModel *selectionModel;
    QDataWidgetMapper *mapper;
    int typeIndex;
};

QDataWidgetMapper オブジェクトとユーザーインターフェイスを構成するためのコントロールに加えて、データを保持するためのQStandardItemModelと、各人のデータに適用できる住所の種類に関する情報を保持するためのQStringListModel

ウィンドウ・クラスの実装

Window クラスのコンストラクタが最初に行う処理は、サンプル・データを保持するために使用するモデルをセットアップすることです。これはこの例の重要な部分なので、最初にこれを見ます。

モデルはウィンドウのsetupModel() 関数で初期化されます。ここでは、主キー、名前、住所、typeフィールドを持つ "person "テーブルを含むSQLiteデータベースを作成します。

void Window::setupModel()
{
    QSqlDatabase db = QSqlDatabase::addDatabase("QSQLITE");
    db.setDatabaseName(":memory:");
    if (!db.open()) {
        QMessageBox::critical(0, tr("Cannot open database"),
            tr("Unable to establish a database connection.\n"
               "This example needs SQLite support. Please read "
               "the Qt SQL driver documentation for information how "
               "to build it."), QMessageBox::Cancel);
        return;
    }

    QSqlQuery query;
    query.exec("create table person (id int primary key, "
               "name varchar(20), address varchar(200), typeid int)");
    query.exec("insert into person values(1, 'Alice', "
               "'<qt>123 Main Street<br/>Market Town</qt>', 101)");
    query.exec("insert into person values(2, 'Bob', "
               "'<qt>PO Box 32<br/>Mail Handling Service"
               "<br/>Service City</qt>', 102)");
    query.exec("insert into person values(3, 'Carol', "
               "'<qt>The Lighthouse<br/>Remote Island</qt>', 103)");
    query.exec("insert into person values(4, 'Donald', "
               "'<qt>47338 Park Avenue<br/>Big City</qt>', 101)");
    query.exec("insert into person values(5, 'Emma', "
               "'<qt>Research Station<br/>Base Camp<br/>"
               "Big Mountain</qt>', 103)");

テーブルの各行には、これらのフィールドのデフォルト値を挿入する。住所タイプに対応する住所タイプの値は、別のテーブルに格納されている。

person "テーブルで使用されている識別子と対応する文字列を含む "addresstype "テーブルを作成する:

    query.exec("create table addresstype (id int, description varchar(20))");
    query.exec("insert into addresstype values(101, 'Home')");
    query.exec("insert into addresstype values(102, 'Work')");
    query.exec("insert into addresstype values(103, 'Other')");

    model = new QSqlRelationalTableModel(this);
    model->setTable("person");
    model->setEditStrategy(QSqlTableModel::OnManualSubmit);

    typeIndex = model->fieldIndex("typeid");

    model->setRelation(typeIndex,
           QSqlRelation("addresstype", "id", "description"));
    model->select();
}

person "テーブルの "typeid "フィールドは、"addresstype "テーブルの内容と、QSqlRelationalTableModel のリレーションを介して関連している。この種のモデルは、データベースにデータを格納するために必要なすべての作業を実行し、また、任意のリレーションをそれ自体のモデルとして使用することができる。

この場合、"person "テーブルの "typeid "フィールドに対して、"addresstype "テーブルの "id "フィールドと関連付けるリレーションを定義し、"typeid "がユーザーに表示される場所であればどこでも、"description "フィールドの内容が使用されるようにしている。(詳細はQSqlRelationalTableModel::setRelation()のドキュメントを参照)。

Window クラスのコンストラクタは、3つの部分に分けて説明できる。最初の部分では、データを保持するためのモデルを設定し、次にユーザー・インターフェースに使用するウィジェットを設定します:

Window::Window(QWidget *parent)
    : QWidget(parent)
{
    setupModel();

    nameLabel = new QLabel(tr("Na&me:"));
    nameEdit = new QLineEdit();
    addressLabel = new QLabel(tr("&Address:"));
    addressEdit = new QTextEdit();
    typeLabel = new QLabel(tr("&Type:"));
    typeComboBox = new QComboBox();
    nextButton = new QPushButton(tr("&Next"));
    previousButton = new QPushButton(tr("&Previous"));

    nameLabel->setBuddy(nameEdit);
    addressLabel->setBuddy(addressEdit);
    typeLabel->setBuddy(typeComboBox);

typeid "フィールドに設定したリレーションに基づいて、メインモデルからコンボボックスのモデルを取得します。コンボボックスのsetModelColumn ()の呼び出しは、モデル内のフィールドの中から表示するフィールドを選択します。

このアプローチは、コンボボックス用にモデルをセットアップするという点で、コンボウィジェットマッパーの例で使われたアプローチと似ていることに注意してください。しかし、この場合、別個にモデルを作成するのではなく、QSqlRelationalTableModel のリレーションに基づいてモデルを取得します。

次に、各入力ウィジェットをモデルのフィールドに関連付けるウィジェットマッパーをセットアップします:

    QSqlTableModel *relModel = model->relationModel(typeIndex);
    typeComboBox->setModel(relModel);
    typeComboBox->setModelColumn(relModel->fieldIndex("description"));

    mapper = new QDataWidgetMapper(this);
    mapper->setModel(model);
    mapper->setItemDelegate(new QSqlRelationalDelegate(this));
    mapper->addMapping(nameEdit, model->fieldIndex("name"));
    mapper->addMapping(addressEdit, model->fieldIndex("address"));
    mapper->addMapping(typeComboBox, typeIndex);

コンボボックスについては、setupModel() 関数からモデル内のフィールドのインデックスをすでに知っています。コンボボックスについては、QSqlRelationalDelegate 関数からモデル内のフィールドのインデックスをすでに知っています。 をマッパーと入力ウィジェットの間のプロキシとして使い、モデル内の「typeid」値とコンボボックスのモデル内の値を一致させ、コンボボックスに整数値ではなく説明を入力します。

その結果、ユーザはコンボボックスから項目を選択することができ、関連する値がモデルに書き戻されます。

コンストラクタの残りの部分では、接続とレイアウトを設定します:

    connect(previousButton, &QPushButton::clicked,
            mapper, &QDataWidgetMapper::toPrevious);
    connect(nextButton, &QPushButton::clicked,
            mapper, &QDataWidgetMapper::toNext);
    connect(mapper, &QDataWidgetMapper::currentIndexChanged,
            this, &Window::updateButtons);

    QGridLayout *layout = new QGridLayout();
    layout->addWidget(nameLabel, 0, 0, 1, 1);
    layout->addWidget(nameEdit, 0, 1, 1, 1);
    layout->addWidget(previousButton, 0, 2, 1, 1);
    layout->addWidget(addressLabel, 1, 0, 1, 1);
    layout->addWidget(addressEdit, 1, 1, 2, 1);
    layout->addWidget(nextButton, 1, 2, 1, 1);
    layout->addWidget(typeLabel, 3, 0, 1, 1);
    layout->addWidget(typeComboBox, 3, 1, 1, 1);
    setLayout(layout);

    setWindowTitle(tr("SQL Widget Mapper"));
    mapper->toFirst();
}

念のため、updateButtons() スロットの実装を示します:

void Window::updateButtons(int row)
{
    previousButton->setEnabled(row > 0);
    nextButton->setEnabled(row < model->rowCount() - 1);
}

まとめと参考文献

コンボボックスのために別のモデルを使い、ウィジェットマッパーのために特別なデリゲートを使うことで、ユーザーに選択肢のメニューを提示することができます。選択肢はユーザーのデータと同じデータベースに保存されますが、別のテーブルに保持されます。このアプローチを使うことで、データベースの機能を適切に使いながら、後で完全なレコードを再構築することができます。

SQLモデルを使わない場合でも、複数のモデルを使ってユーザーに選択肢を提示することは可能です。これはコンボ・ウィジェット・マッパーの例でカバーされている。

サンプルプロジェクト @ code.qt.io

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