SQL 小部件映射器示例

SQL 小部件映射器示例展示了如何使用映射信息来自数据库到报表表单上的小部件。

在《组合小部件映射器示例》中,我们展示了如何使用小部件映射器与具有特定用途的模型的命名映射来关联模型中的值到一组选项。

再次,我们创建了一个具有几乎相同用户界面的 Window 类,提供了一个组合框,以便将地址分类为“家庭”、“工作”或其他。然而,不同于使用单独的模型来存储这些地址类型,我们使用一个数据库表来存储示例数据,另一个表来存储地址类型。以这种方式,我们在同一位置存储所有信息。

窗口类定义

该类提供了一个构造函数,一个更新按钮状态的槽,以及一个设置模型的私有函数。

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() 函数中初始化的。在这里,我们创建了一个包含 "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" 字段通过 QSqlRelationalTableModel 中的关系与 "addresstype" 表的内容相关联。这种模型执行存储数据在数据库中的所有必要工作,并允许任何关系都可以作为模型本身被使用。

在这种情况下,我们为 "person" 表中的 "typeid" 字段定义了一个关系,将其与 "addresstype" 表中的 "id" 字段相关联,并导致无论在用户面前展示 "typeid" 之处都会使用 "description" 字段的内容。(请参阅 QSqlRelationalTableModel::setRelation() 文档的详细信息。)

Window 类的构造函数可以分成三个部分。在第一部分,我们设置了用于存储数据的模型,然后设置了用于用户界面的控件。

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 Qt公司有限公司。包含在此的文档贡献是各自所有者的版权。所提供的文档是在自由软件基金会的GNU自由文档许可证版本1.3条款下授权的。Qt和相应的徽标是芬兰以及/或者是世界上其他国家的Qt公司的商标。所有其他商标属于其各自的拥有者。