订单表单示例
“订单表单”示例展示了如何通过将简单模板与用户在对话框中输入的数据相结合,来生成富文本文档。

DetailsDialog 定义
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 的构造函数接受参数title 和parent 。该类定义了四个获取函数:orderItems() 、senderName() 、senderAddress() 和sendOffers() ,以便从外部访问数据。
类定义中包含了必填字段的输入控件:nameEdit 和addressEdit 。此外,还定义了QCheckBox 和QDialogButtonBox ;前者用于向用户提供接收产品和优惠信息的选项,后者则确保所使用的按钮布局符合用户所在平台的原生规范。另外,QTableWidget (即itemsTable )用于存储订单详情。
下图所示即为我们计划创建的DetailsDialog 。

DetailsDialog 的实现
DetailsDialog 的构造函数会实例化先前定义的字段及其相应的标签。设置offersCheckBox 的标签,并调用setupItemsTable() 函数来初始化并填充itemsTable 。QDialogButtonBox 对象buttonBox 通过OK 和Cancel 按钮进行实例化。该buttonBox 的accepted() 和rejected() 信号分别连接到DetailsDialog 中的verify() 和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 循环填充itemsTable ,并将name 项的标志设置为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 定义
MainWindow 类是QMainWindow 类的子类,实现了两个槽——openDialog() 和printFile() 。它还包含一个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 以及所需的动作newAction 和printAction 。这些动作的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() 函数创建一个新的QTabWidget ,并将其父容器设置为QTextEdit (editor )。该函数接受四个参数,这些参数与通过DetailsDialog 获取的参数相对应,用于“填充”editor 。
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);回顾富文本文档的结构:帧和表格的序列总是由文本块分隔,其中某些文本块可能不包含任何信息。
以“订单表单示例”为例,该部分的文档结构如下表所示:
| 带有referenceFrameFormat 的框架 | |
| 块 | A company |
| 块 | |
| 文本块 | 321 City Street |
| 块 | |
| 块 | Industry Park |
| block | |
| 块 | 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");请注意,topFrame 是editor 的顶级框架,且不会显示在文档结构中。
随后,我们将cursor 的位置恢复到它在topFrame 中的最后位置,并填入客户姓名(由构造函数提供)和地址——使用基于范围的for循环遍历QString 和address 。
cursor.setPosition(topFrame->lastPosition());
cursor.insertText(name, textFormat);
const QStringList lines = address.split('\n');
for (const QString &line : lines) {
cursor.insertBlock();
cursor.insertText(line);
}cursor 现已恢复至topFrame 中,上述代码段的文档结构如下:
| block | Donald |
| block | 47338 Park Avenue |
| 代码块 | Big City |
出于间距考虑,我们调用两次insertBlock()。获取并显示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 的框架,以及标准文本。
| 块 | |
| block | |
| 块block | Date: 25 May 2007 |
| block | |
| 带有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);我们使用cellAt() 来设置orderTable 的头部信息。
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 |
| block | Quantity |
| 块 | T-shirt |
| 块 | 4 |
| 块 | Badge |
| 块 | 3 |
| 块 | Reference book |
| 块 | 2 |
| 块 | Coffee cup |
| 块 | 5 |
随后,将cursor 移回topFrame 的lastPosition()中,并插入更多标准文本。
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);该部分的文档结构如下:
| 块 | |
| block | Please update my... |
| block | |
offersTable | |
| block | I want to receive... |
| 块 | I do not want to receive... |
| 块 | X |
将“cursor ”移动至适当位置,插入“此致”以及客户姓名。为调整间距,插入了更多分隔块。启用“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 ,并将尺寸设置为640x480像素,然后调用show() 函数和createSample() 函数。
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.resize(640, 480);
window.show();
window.createSample();
return app.exec();
}© 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.