オーダーフォームの例

注文フォームの例では、ダイアログ内のユーザーによるデータ入力と単純なテンプレートを組み合わせてリッチテキスト文書を生成する方法を示します。

詳細ダイアログの定義

DetailsDialog クラスはQDialog のサブクラスです。verify() スロットを実装し、DetailsDialog の内容を後で確認できるようにしています。これについてはDetailsDialog 実装で詳しく説明します。

class DetailsDialog : public QDialog
{
    Q_OBJECT

public:
    DetailsDialog(const QString &title, QWidget *parent);

public slots:
    void verify();

public:
    QList<QPair<QString, int> > orderItems();
    QString senderName() const;
    QString senderAddress() const;
    bool sendOffers();

private:
    void setupItemsTable();

    QLabel *nameLabel;
    QLabel *addressLabel;
    QCheckBox *offersCheckBox;
    QLineEdit *nameEdit;
    QStringList items;
    QTableWidget *itemsTable;
    QTextEdit *addressEdit;
    QDialogButtonBox *buttonBox;
};

DetailsDialog のコンストラクタはパラメータtitleparent を受け取ります。このクラスは4つのゲッター関数を定義しています:orderItems() senderName()senderAddress()sendOffers() の4つのゲッター関数を定義し、外部からデータにアクセスできるようにします。

このクラス定義には、必須フィールド用の入力ウィジェットnameEditaddressEdit が含まれています。また、QCheckBoxQDialogButtonBox も定義されている。前者は、製品やオファーの情報を受け取るオプションをユーザーに提供し、後者は、使用されるボタンがユーザーのネイティブ・プラットフォームに従って配置されるようにする。さらに、QTableWidgetitemsTable は、注文の詳細を保持するために使用される。

以下のスクリーンショットは、私たちが作成しようとしているDetailsDialog を示しています。

DetailsDialogの実装

DetailsDialog のコンストラクタは、先に定義したフィールドとそれぞれのラベルをインスタンス化します。offersCheckBox のラベルが設定され、setupItemsTable() 関数が呼び出され、itemsTable の設定と入力が行われます。QDialogButtonBox オブジェクト、buttonBox は、OKCancel ボタンとともにインスタンス化される。このbuttonBoxaccepted()rejected() シグナルはDetailsDialogverify()reject() スロットに接続されています。

DetailsDialog::DetailsDialog(const QString &title, QWidget *parent)
    : QDialog(parent)
{
    nameLabel = new QLabel(tr("Name:"));
    addressLabel = new QLabel(tr("Address:"));
    addressLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);

    nameEdit = new QLineEdit;
    addressEdit = new QTextEdit;

    offersCheckBox = new QCheckBox(tr("Send information about products and "
                                      "special offers"));

    setupItemsTable();

    buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok
                                     | QDialogButtonBox::Cancel);

    connect(buttonBox, &QDialogButtonBox::accepted, this, &DetailsDialog::verify);
    connect(buttonBox, &QDialogButtonBox::rejected, this, &DetailsDialog::reject);

QGridLayout は、すべてのオブジェクトをDetailsDialog に配置するために使用されます。

    QGridLayout *mainLayout = new QGridLayout;
    mainLayout->addWidget(nameLabel, 0, 0);
    mainLayout->addWidget(nameEdit, 0, 1);
    mainLayout->addWidget(addressLabel, 1, 0);
    mainLayout->addWidget(addressEdit, 1, 1);
    mainLayout->addWidget(itemsTable, 0, 2, 2, 1);
    mainLayout->addWidget(offersCheckBox, 2, 1, 1, 2);
    mainLayout->addWidget(buttonBox, 3, 0, 1, 3);
    setLayout(mainLayout);

    setWindowTitle(title);
}

setupItemsTable() 関数はQTableWidget オブジェクトitemsTable をインスタンス化し、QStringList オブジェクトitems に基づいて行数を設定する。列の数は2に設定され、"名前 "と "数量 "のレイアウトを提供する。for itemsTablename 項目のフラグはQt::ItemIsEnabled またはQt::ItemIsSelectable に設定される。デモンストレーションのため、quantity 項目は 1 に設定され、itemsTable のすべての項目は数量にこの値を持つ。しかし、これは実行時にセルの内容を編集することで変更できる。

void DetailsDialog::setupItemsTable()
{
    items << tr("T-shirt") << tr("Badge") << tr("Reference book")
          << tr("Coffee cup");

    itemsTable = new QTableWidget(items.count(), 2);

    for (int row = 0; row < items.count(); ++row) {
        QTableWidgetItem *name = new QTableWidgetItem(items[row]);
        name->setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
        itemsTable->setItem(row, 0, name);
        QTableWidgetItem *quantity = new QTableWidgetItem("1");
        itemsTable->setItem(row, 1, quantity);
    }
}

orderItems() 関数はitemsTable からデータを抽出し、QList<QPair<QString,int>>の形式で返します。各QPair はアイテムと注文数量に対応しています。

QList<QPair<QString, int> > DetailsDialog::orderItems()
{
    QList<QPair<QString, int> > orderList;

    for (int row = 0; row < items.count(); ++row) {
        QPair<QString, int> item;
        item.first = itemsTable->item(row, 0)->text();
        int quantity = itemsTable->item(row, 1)->data(Qt::DisplayRole).toInt();
        item.second = qMax(0, quantity);
        orderList.append(item);
    }

    return orderList;
}

senderName() 関数は、注文フォームの名前フィールドを格納するために使用されるQLineEdit の値を返すために使用されます。

QString DetailsDialog::senderName() const
{
    return nameEdit->text();
}

senderAddress() 関数は、オーダーフォームの住所を格納するQTextEdit の値を返すために使用される。

QString DetailsDialog::senderAddress() const
{
    return addressEdit->toPlainText();
}

sendOffers() 関数は、true またはfalse の値を返すために使用される。この値は、オーダーフォームの顧客が、会社のオファーやプロモーションに関する詳細情報の受信を希望するかどうかを判断するために使用される。

bool DetailsDialog::sendOffers()
{
    return offersCheckBox->isChecked();
}

verify() 関数は、ユーザーがDetailsDialog に入力した詳細を確認するために使用される、追加実装されたスロットです。 入力された詳細が不完全な場合、QMessageBox が表示され、ユーザーにDetailsDialog を破棄するオプションを提供します。そうでない場合、詳細が受理され、accept() 関数が呼び出されます。

void DetailsDialog::verify()
{
    if (!nameEdit->text().isEmpty() && !addressEdit->toPlainText().isEmpty()) {
        accept();
        return;
    }

    QMessageBox::StandardButton answer;
    answer = QMessageBox::warning(this, tr("Incomplete Form"),
        tr("The form does not contain all the necessary information.\n"
           "Do you want to discard it?"),
        QMessageBox::Yes | QMessageBox::No);

    if (answer == QMessageBox::Yes)
        reject();
}

メインウィンドウの定義

MainWindow クラスはQMainWindow のサブクラスで、openDialog()printFile() の2つのスロットを実装している。また、QTabWidget のプライベート・インスタンスletters も含んでいる。

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow();
    void createSample();

public slots:
    void openDialog();
    void printFile();

private:
    void createLetter(const QString &name, const QString &address,
                      QList<QPair<QString,int> > orderItems,
                      bool sendOffers);

    QAction *printAction;
    QTabWidget *letters;
};

MainWindow の実装

MainWindow コンストラクタはfileMenu と、必要なアクションであるnewActionprintAction をセットアップします。これらのアクションのtriggered() シグナルは、追加実装された openDialog() スロットとデフォルトの close() スロットに接続されています。QTabWidget,letters がインスタンス化され、ウィンドウの中心ウィジェットとして設定されます。

MainWindow::MainWindow()
{
    QMenu *fileMenu = new QMenu(tr("&File"), this);
    QAction *newAction = fileMenu->addAction(tr("&New..."));
    newAction->setShortcuts(QKeySequence::New);
    printAction = fileMenu->addAction(tr("&Print..."), this, &MainWindow::printFile);
    printAction->setShortcuts(QKeySequence::Print);
    printAction->setEnabled(false);
    QAction *quitAction = fileMenu->addAction(tr("E&xit"));
    quitAction->setShortcuts(QKeySequence::Quit);
    menuBar()->addMenu(fileMenu);

    letters = new QTabWidget;

    connect(newAction, &QAction::triggered, this, &MainWindow::openDialog);
    connect(quitAction, &QAction::triggered, this, &MainWindow::close);

    setCentralWidget(letters);
    setWindowTitle(tr("Order Form"));
}

createLetter() 関数は、QTextEditeditor を親として新しいQTabWidget を作成します。この関数は、editor を "埋める "ために、DetailsDialog で取得したパラメータに対応する4つのパラメータを受け取る。

void MainWindow::createLetter(const QString &name, const QString &address,
                              QList<QPair<QString,int> > orderItems,
                              bool sendOffers)
{
    QTextEdit *editor = new QTextEdit;
    int tabIndex = letters->addTab(editor, name);
    letters->setCurrentIndex(tabIndex);

次に、QTextEdit::textCursor ()を使用して、editor のカーソルを取得する。そして、QTextCursor::Start を使ってcursor をドキュメントの先頭に移動します。

    QTextCursor cursor(editor->textCursor());
    cursor.movePosition(QTextCursor::Start);

リッチテキストドキュメントの構造を思い出してください。そこでは、フレームやテーブルのシーケンスは常にテキストブロックで区切られており、そのうちのいくつかは情報を含まないかもしれません。

オーダーフォームの例の場合、この部分の文書構造は以下の表で説明されます:

参照FrameFormatを持つフレーム
ブロックA company
ブロック
ブロック321 City Street
ブロック
ブロックIndustry Park
ブロック
ブロックAnother country

これは以下のコードで実現される:

    QTextFrame *topFrame = cursor.currentFrame();
    QTextFrameFormat topFrameFormat = topFrame->frameFormat();
    topFrameFormat.setPadding(16);
    topFrame->setFrameFormat(topFrameFormat);

    QTextCharFormat textFormat;
    QTextCharFormat boldFormat;
    boldFormat.setFontWeight(QFont::Bold);

    QTextFrameFormat referenceFrameFormat;
    referenceFrameFormat.setBorder(1);
    referenceFrameFormat.setPadding(8);
    referenceFrameFormat.setPosition(QTextFrameFormat::FloatRight);
    referenceFrameFormat.setWidth(QTextLength(QTextLength::PercentageLength, 40));
    cursor.insertFrame(referenceFrameFormat);

    cursor.insertText("A company", boldFormat);
    cursor.insertBlock();
    cursor.insertText("321 City Street");
    cursor.insertBlock();
    cursor.insertText("Industry Park");
    cursor.insertBlock();
    cursor.insertText("Another country");

なお、topFrameeditor の最上位フレームであり、文書構造には表示されていない。

次に、cursor の位置をtopFrame の最後の位置に戻し、顧客の名前(コンストラクタから提供される)と住所を記入する - 範囲ベースのforループを使って、QStringaddress を巡回する。

    cursor.setPosition(topFrame->lastPosition());

    cursor.insertText(name, textFormat);
    const QStringList lines = address.split('\n');
    for (const QString &line : lines) {
        cursor.insertBlock();
        cursor.insertText(line);
    }

cursortopFrame に戻り、上記のコードのドキュメント構造は次のようになります:

ブロックDonald
ブロック47338 Park Avenue
ブロックBig City

間隔を空けるため、insertBlock ()を2回呼び出している。currentDate ()が取得され、表示される。setWidth() を使ってbodyFrameFormat の幅を広げ、その幅で新しいフレームを挿入する。

    cursor.insertBlock();
    cursor.insertBlock();

    QDate date = QDate::currentDate();
    cursor.insertText(tr("Date: %1").arg(date.toString("d MMMM yyyy")),
                      textFormat);
    cursor.insertBlock();

    QTextFrameFormat bodyFrameFormat;
    bodyFrameFormat.setWidth(QTextLength(QTextLength::PercentageLength, 100));
    cursor.insertFrame(bodyFrameFormat);

次のコードは、注文フォームに標準テキストを挿入します。

    cursor.insertText(tr("I would like to place an order for the following "
                         "items:"), textFormat);
    cursor.insertBlock();
    cursor.insertBlock();

文書構造のこの部分には、日付、bodyFrameFormat のフレーム、および標準テキストが含まれるようになりました。

ブロック
ブロック
ブロックDate: 25 May 2007
ブロック
bodyFrameFormatを持つフレーム
ブロックI would like to place an order for the following items:
ブロック
ブロック

QTextTableFormat オブジェクト、orderTableFormat は、商品の種類と注文数量を保持するために使われる。

    QTextTableFormat orderTableFormat;
    orderTableFormat.setAlignment(Qt::AlignHCenter);
    QTextTable *orderTable = cursor.insertTable(1, 2, orderTableFormat);

    QTextFrameFormat orderFrameFormat = cursor.currentFrame()->frameFormat();
    orderFrameFormat.setBorder(1);
    cursor.currentFrame()->setFrameFormat(orderFrameFormat);

orderTable のヘッダーを設定するために、cellAt ()を使用する。

    cursor = orderTable->cellAt(0, 0).firstCursorPosition();
    cursor.insertText(tr("Product"), boldFormat);
    cursor = orderTable->cellAt(0, 1).firstCursorPosition();
    cursor.insertText(tr("Quantity"), boldFormat);

次に、QPair オブジェクトのQList を繰り返し処理し、orderTable に入力します。

    for (int i = 0; i < orderItems.count(); ++i) {
        QPair<QString,int> item = orderItems[i];
        int row = orderTable->rows();

        orderTable->insertRows(row, 1);
        cursor = orderTable->cellAt(row, 0).firstCursorPosition();
        cursor.insertText(item.first, textFormat);
        cursor = orderTable->cellAt(row, 1).firstCursorPosition();
        cursor.insertText(QString("%1").arg(item.second), textFormat);
    }

このセクションの文書構造は以下のようになる:

orderTable orderTableFormat
ブロックProduct
ブロックQuantity
ブロックT-shirt
ブロック4
ブロックBadge
ブロック3
ブロックReference book
ブロック2
ブロックCoffee cup
ブロック5

その後、cursortopFramelastPosition() に戻され、さらに標準的なテキストが挿入される。

    cursor.setPosition(topFrame->lastPosition());

    cursor.insertBlock();
    cursor.insertText(tr("Please update my records to take account of the "
                         "following privacy information:"));
    cursor.insertBlock();

別のQTextTable が挿入され、オファーに関する顧客の希望が表示される。

    QTextTable *offersTable = cursor.insertTable(2, 2);

    cursor = offersTable->cellAt(0, 1).firstCursorPosition();
    cursor.insertText(tr("I want to receive more information about your "
                         "company's products and special offers."), textFormat);
    cursor = offersTable->cellAt(1, 1).firstCursorPosition();
    cursor.insertText(tr("I do not want to receive any promotional information "
                         "from your company."), textFormat);

    if (sendOffers)
        cursor = offersTable->cellAt(0, 0).firstCursorPosition();
    else
        cursor = offersTable->cellAt(1, 0).firstCursorPosition();

    cursor.insertText("X", boldFormat);

この部分の文書構造は

ブロック
ブロックPlease update my...
ブロック
offersTable
ブロックI want to receive...
ブロックI do not want to receive...
ブロックX

cursor 、顧客名とともに「Sincerely」を挿入する。スペーシングのため、さらにブロックが挿入されています。printAction は、注文書が印刷できるようになったことを示すために有効になっています。

    cursor.setPosition(topFrame->lastPosition());
    cursor.insertBlock();
    cursor.insertText(tr("Sincerely,"), textFormat);
    cursor.insertBlock();
    cursor.insertBlock();
    cursor.insertBlock();
    cursor.insertText(name);

    printAction->setEnabled(true);
}

文書構造の下部は

ブロック
ブロックSincerely,
ブロック
ブロック
ブロック
ブロックDonald

createSample() 関数は説明のために使用され、注文書のサンプルを作成します。

void MainWindow::createSample()
{
    DetailsDialog dialog("Dialog with default values", this);
    createLetter("Mr. Smith", "12 High Street\nSmall Town\nThis country",
                 dialog.orderItems(), true);
}

openDialog() 関数はDetailsDialog オブジェクトを開きます。dialog の詳細が受理されると、dialog から抽出されたパラメータを使用してcreateLetter() 関数が呼び出されます。

void MainWindow::openDialog()
{
    DetailsDialog dialog(tr("Enter Customer Details"), this);

    if (dialog.exec() == QDialog::Accepted) {
        createLetter(dialog.senderName(), dialog.senderAddress(),
                     dialog.orderItems(), dialog.sendOffers());
    }
}

注文書を印刷するために、以下に示すようにprintFile() 関数が含まれています:

void MainWindow::printFile()
{
#if defined(QT_PRINTSUPPORT_LIB) && QT_CONFIG(printdialog)
    QTextEdit *editor = static_cast<QTextEdit*>(letters->currentWidget());
    QPrinter printer;

    QPrintDialog dialog(&printer, this);
    dialog.setWindowTitle(tr("Print Document"));
    if (editor->textCursor().hasSelection())
        dialog.setOption(QAbstractPrintDialog::PrintSelection);
    if (dialog.exec() != QDialog::Accepted) {
        return;
    }

    editor->print(&printer);
#endif
}

この関数では、文書全体を印刷する代わりに、QTextCursor::hasSelection() で選択した領域を印刷することもできます。

main() 関数

main() 関数はMainWindow をインスタンス化し、show() 関数とcreateSample() 関数を呼び出す前に、そのサイズを 640x480 ピクセルに設定します。

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    MainWindow window;
    window.resize(640, 480);
    window.show();
    window.createSample();
    return app.exec();
}

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

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