前言
订单表单示例显示了如何通过将简单模板与用户在对话框中输入的数据相结合来生成富文本文档。
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。 该类定义了四个getter函数:orderItems( ),senderName( ),senderAddress( )和sendOffers( ),以允许从外部访问数据。
类定义包括必填字段的输入小部件,即nameEdit和addressEdit。 此外,还定义了QCheckBox和QDialogButtonBox。 前者为用户提供接收有关产品和报价信息的选项,后者则确保根据用户的本机平台排列使用的按钮。 另外,一个QTableWidget项目表用于保存订单明细。
下面的屏幕快照显示了我们打算创建的DetailsDialog。
DetailsDialog 类实现
DetailsDialog的构造函数实例化先前定义的字段及其各自的标签。 设置offersCheckBox的标签,并调用setupItemsTable( )函数来设置和填充itemsTable。 QDialogButtonBox对象buttonBox用“确定”和“取消”按钮实例化。 该buttonBox的accepted( )和rejected( )信号连接到DetailsDialog中的verify( )和reject( )插槽。
#include "DetailsDialog.h"
DetailsDialog::DetailsDialog(const QString &title, QWidget *parent)
: QDialog(parent)
{
nameLabel = new QLabel("Name:");
addressLabel = new QLabel("Address");
addressLabel->setAlignment(Qt::AlignLeft | Qt::AlignTop);
nameEdit = new QLineEdit;
addressEdit = new QTextEdit;
offersCheckBox = new QCheckBox("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);
}
/******************************************************************************************************
* 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();
}
/******************************************************************************************************
* 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::setnderAddress() const
{
return addressEdit->toPlainText();
}
/******************************************************************************************************
* sendOffers()函数用于返回true或false值,该值用于确定订单中的客户是否希望接收有关公司的优惠和促销的更多信息。
*/
bool DetailsDialog::sendOffers()
{
return offersCheckBox->isChecked();
}
/******************************************************************************************************
* createLetter( )函数创建一个新的QTabWidget,其QTextEdit编辑器作为父级。 该函数接受四个与我们通过DetailsDialog获得的参数相对应的参数,以“填充”编辑器。
*/
void DetailsDialog::setupItemsTable()
{
items << "T-shirt" << "Badge" << "Reference book" << "Coffee cup";
/* 实例化QTableWidget对象itemsTable,并基于QStringList对象items设置行数,该对象保存已排序项目的类型。
* 列数设置为2,提供“名称”和“数量”布局。*/
itemsTable = new QTableWidget(items.count(),2);
QStringList header;
header << "名称" << "数量";
itemsTable->setHorizontalHeaderLabels(header);
itemsTable->setColumnWidth(0,100);
itemsTable->setColumnWidth(1,80);
//使用for循环填充itemTable,将名称项的标志设置为Qt::ItemIsEnabled | Qt::ItemIsSelectable。
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);
}
}
MainWindow 类定义
MainWindow类是QMainWindow的子类,实现了两个插槽-openDialog( )和printFile( )。 它还包含QTabWidget的私有实例(字母)。
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。 这些动作的trigger( )信号连接到另外实现的openDialog( )插槽和默认的close( )插槽。 实例化QTabWidget(字母)并将其设置为窗口的中央窗口小部件。
#include "MainWindow.h"
#include <QtPrintSupport>
MainWindow::MainWindow()
{
QMenu *fileMenu = new QMenu("&File",this);
QAction *newAction = fileMenu->addAction("&New");
newAction->setShortcuts(QKeySequence::New);
printAction = fileMenu->addAction("&Print",this,&MainWindow::printFile);
printAction->setShortcuts(QKeySequence::Print);
printAction->setEnabled(false);
QAction *quitAction = fileMenu->addAction("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("Order Form");
}
/******************************************************************************************************
* 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对象。
* 如果接受对话框中的详细信息,则使用从对话框中提取的参数来调用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( )函数,如下所示:
* 此功能还允许用户使用QTextCursor::hasSelection( )打印选定区域,而不是打印整个文档。
*/
void MainWindow::printFile()
{
QTextEdit *editor = static_cast<QTextEdit*>(letters->currentWidget());
QPrinter printer;
QPrintDialog dialog(&printer, this);
dialog.setWindowTitle(tr("Print Document"));
if (editor->textCursor().hasSelection())
dialog.addEnabledOption(QAbstractPrintDialog::PrintSelection);
if (dialog.exec() != QDialog::Accepted) {
return;
}
editor->print(&printer);
}
/******************************************************************************************************
* createLetter()函数创建一个新的QTabWidget,其QTextEdit编辑器作为父级。
* 该函数接受四个与我们通过DetailsDialog获得的参数相对应的参数,以“填充”编辑器。
*/
void MainWindow::createLetter(const QString &name, const QString &address, QList<QPair<QString, int> > orderItems, bool sendOffers)
{
QTextEdit *editor = new QTextEdit;
QFont font = editor->font();
font.setPointSize(12);
editor->setFont(font);
int tabIndex = letters->addTab(editor,name);
letters->setCurrentIndex(tabIndex);
//然后,我们使用QTextEdit::textCursor()获得编辑器的光标。
QTextCursor cursor(editor->textCursor());
//然后, 使用QTextCursor::Start将光标移动到文档的开头。
cursor.movePosition(QTextCursor::Start);
//回想一下富文本文档的结构,其中框架和表序列总是由文本块分隔,其中一些可能不包含任何信息。
//在订单表单示例中,此部分的文档结构如下所示:
/* 具有referenceFrameFormat格式的框架
block A company
block
block 321 City Street
block
block Industry Park
block
block Another country
*/
// 这是通过以下代码完成的:
// 请注意,topFrame是编辑器的顶级框架,未在文档结构中显示。
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");
cursor.insertBlock();
//然后,我们将光标的位置重新设置为topFrame中的最后一个位置,
//并填写客户的名称(由构造函数提供)和地址-使用基于范围的for循环遍历QString地址。
cursor.setPosition(topFrame->lastPosition());
cursor.insertText(name,textFormat);
const QStringList lines = address.split('\n');
for(const QString &line : lines ) {
cursor.insertBlock();
cursor.insertText(line);
}
//光标现在回到topFrame中,并且以上代码部分的文档结构为:
/*
block Donald
block 47338 Park Avenue
block 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("I would like to place an order for the following items:",textFormat);
cursor.insertBlock();
cursor.insertBlock();
// 现在,文档结构的此部分包含日期,带有bodyFrameFormat的框架以及标准文本。
/*
block
block
block Date: 25 May 2007
block
frame with bodyFrameFormat
block I would like to place an order for the following items:
block
block
*/
// QTextTableFormat对象orderTableFormat用于保存项目的类型和订购的数量。
QTextTableFormat orderTableFormat;
orderTableFormat.setAlignment(Qt::AlignCenter);
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("Product",boldFormat);
cursor = orderTable->cellAt(0,1).firstCursorPosition();
cursor.insertText("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 with orderTableFormat
block Product
block Quantity
block T-shirt
block 4
block Badge
block 3
block Reference book
block 2
block Coffee cup
block 5
*/
// 然后将光标移回topFrame的lastPosition( )并插入更多标准文本。
cursor.setPosition(topFrame->lastPosition());
cursor.insertBlock();
cursor.insertText("Please update my records to take account of the following privacy information:");
// 插入另一个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
block Please update my...
block
offersTable
block I want to receive...
block I do not want to receive...
block X
*/
// 移动光标以插入“ 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);
// 文档结构的底部是:
/*
block
block Sincerely,
block
block
block
block Donald
*/
}
主函数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();
}
总结
订单案例详细地解释了文档框架、表格、文本块的使用方式,挺好的一篇的教程。