3-5 使用对话框(Using Dialogs)
在这一节中,我们介绍Qt中对话框的调用:初始化对话框,显示对话框和与用户交互
首先我们看一下Find对话框。我们希望用户能够在Find对话框和Spreadsheet应用程序的主窗口之间自由切换,所以Find对话框应该是无模式的。一个无模式的对话框就是在程序运行过程中不
创建无模式对话框后,一般要连接信号和槽函数用来响应用户输入。
当用户想在表格中查找文本时, Find 对话框就会显示。用户点击了 Edit|Find 菜单, find 槽函数就会调用,弹出 Find 对话框。这时对话框的有以下三种可能:
{
if ( ! findDialog) {
findDialog = new FindDialog( this );
connect(findDialog, SIGNAL (findNext( const QString & ,
Qt::CaseSensitivity)),
spreadsheet, SLOT (findNext( const QString & ,
Qt::CaseSensitivity)));
connect(findDialog, SIGNAL(findPrevious( const QString & ,
Qt::CaseSensitivity)),
spreadsheet, SLOT(findPrevious( const QString & ,
Qt::CaseSensitivity)));
}
findDialog -> show();
findDialog -> activateWindow();
}
1、 第一次调用Find对话框
2、 用户已经调用过,但是给关闭了
3、 用户已经调用过,且仍然显示
如果Find对话框还不存在,那么创建对话框,连接findNext()和findPrevious()两个信号到相应的Spreadsheet槽函数。当然我们也可以在MainWindow的构造函数中创建,但是在需要的时候再创建可以加快程序的启动时间
接着我们调用show()和activateWindow()确保窗口是可见的,激活的。单独调用show()是能够显示并激活窗口的。但是如果调用时,Find对话框是可见的,show()就不做任何事情,调用activateWindow()就有必要了。所以后面几行还可以这样写:
if (findDialog->isHidden()) {
findDialog->show();
} else {
findDialog->activateWindow();
}
接着我们来看Go-to-Cell对话框是一个模式对话框。我们需要用户弹出对话框
使用show()显示的对话框是无模式对话框。用exec()显示的对话框是模式对话框。如果对话框被接受,QDialog::exec()函数返回true(QDialog::Accepted),否则返回false(QDialog::Rejected)。在第二章创建Go-to-Cell对话框时,我们连接了ok按钮到accept(),cancel按钮连接到了reject()。如果用户点击了Ok按钮,我们就把当前的网格设为编辑框中的值。
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/a41954a27d6ad96fa2c2cf816e677448.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/1327ab569c1ae82736693a50b8e33378.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/37c8bf68cdc3cc81759c34160776bc53.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/7ff8d92cded7e0ce15e7ca1acc870052.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6a9c071a08f1dae2d3e1c512000eef41.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/717446ca04a6125dc5b6b54e0fa14ab4.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/0196c3df5ea9e936f21e9932cca91014.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/6810355c2f78c12e91b7997a8e8c583a.gif)
函数QTableWidget::setCurrentCell()需要两个参数:一个行序号和一个列序号。在Spreadsheet程序中,网格A1对应(0,0),B27对应(26,1)。为了从QLineEdit::text()中返回的QString得到行序号,使用QString::mid()函数(得到从mid()中指定的位置到字符串的最后)然后用QString::toInt()得到整数值后减1。至于列序号,我们得到字符串的第一个字符减去字母"A"的unicode的数字值。在创建这个对话框的时候,我们使用了QRegExpValidator确保能够得到正确的格式。
goToCell()和以前创建控件的代码不同,这一次是在栈上创建GoToCellDialog。如果多写一行代码,可以用new和delete实现:
void MainWindow::goToCell()
{
GoToCellDialog *dialog = new GoToCellDialog(this);
if (dialog->exec()) {
QString str = dialog->lineEdit->text().toUpper();
spreadsheet->setCurrentCell(str.mid(1).toInt() - 1,
str[0].unicode() - 'A');
}
delete dialog;
}
在栈上创建对话框是一个常用的编程模式,因为我们使用完这个控件以后就不再需要了,在调用完函数后能够自动析构它。
现在我们看sort对话框。Sort对话框是一个模式对话框,使用户能够按列排序选中的区域。
void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange('A' + range.leftColumn(),
'A' + range.rightColumn());
if (dialog.exec()) {
SpreadsheetCompare compare;
compare.keys[0] =
dialog.primaryColumnCombo->currentIndex();
compare.keys[1] =
dialog.secondaryColumnCombo->currentIndex() - 1;
compare.keys[2] =
dialog.tertiaryColumnCombo->currentIndex() - 1;
compare.ascending[0] =
(dialog.primaryOrderCombo->currentIndex() == 0);
compare.ascending[1] =
(dialog.secondaryOrderCombo->currentIndex() == 0);
compare.ascending[2] =
(dialog.tertiaryOrderCombo->currentIndex() == 0);
spreadsheet->sort(compare);
}
}
在sort()函数中,我们使用了同goToCell()同样的模式:
1、在栈上创建对话框并初始化。
2、用exec()显示对话框。
3、如果用户点击了ok,得到用户在对话框控件中的输入并使用这个字符串。
函数setColumnRange()得到选定的列,下图是一个排序的例子,B列为主排序列,A列为第二排序列,按降序排列。例如使用途中的选定区域,range.leftColumn()得到0,'A'+0='A',range.rightColumn()得到2,'A'+2='C'。
compare对象存储第一,第二,第三排序列和它们排序顺序(在下一章我们将会对SpreadsheetCompare进行定义)。在Spreadsheet::sort()中会使用到这个对象。keys数组存储的是要排列的列序号。例如,如果选定的区域是从C2到E5,列C的位置就是0,ascending数组是每一列排序的顺序。QComboBox::currentIndex()得到当前选定项目的序号,顺序是从0开始。对于第一,第二排序列。还需要用当前值减去"None"项目的值。
sort()函数已经可以工作了,但是有点脆弱。它假定了Sort对话框只能用这种固定的方式实现,有下拉框,需要None项目。如果我们重新设计Sort对话框,我们还要重写这段代码。如果只在一个地方这样调用了,维护一次也就够了。但是如果在多个地方都使用了这个对话框,那么维护这些代码就成了程序员的恶梦。
一个更加强壮的方法是让SortDialog自己创建Spreadsheetcompare对象,这样可以大大减少了MainWindow::sort()的代码。
void MainWindow::sort()
{
SortDialog dialog(this);
QTableWidgetSelectionRange range = spreadsheet->selectedRange();
dialog.setColumnRange('A' + range.leftColumn(),
'A' + range.rightColumn());
if (dialog.exec())
spreadsheet->performSort(dialog.comparisonObject());
}
这样控件之间的耦合度就小多了,如果多次使用了这个对话框,这是一个非常正确的选择。
一个让程序更加强壮的方法是在初始化SortDialog对话框的时候传递Spreadsheet对象的指针,是对话框能够直接操作Spreadsheet。这样SortDialog只是作为一个控件,使SortDialog更加通用,MainWindow::sort()函数也更加简单:
void MainWindow::sort()
{
SortDialog dialog(this);
dialog.setSpreadsheet(spreadsheet);
dialog.exec();
}
这个sort()函数和第一个sort()函数相比:这里调用函数不需要知道对话框的实现细节,也不需给对话框提供任何数据结构。当对话框需要适应数据的实时改变时这样实现很必要。第一种方法调用函数很脆弱,同样如果数据结构改变了,最后一种方法也会失败。
有些程序员坚持用一种方式使用对话框。这样的好处是简单,易于实现,但是同时就失去了其他实现模式的优点。至于到底用那种模式则需要根据实际情况而定。
最后我们实现About对话框。我们也可以象创建Find,Go-to-Cell对话框一样实现一个用户子定义的对话框来显示程序的有关信息,但是由于大多About对话框的样式都是一样的,所以Qt给出了一个简单的解决方案。
void MainWindow::about()
{
QMessageBox::about(this, tr("About Spreadsheet"),
tr("<h2>Spreadsheet 1.1</h2>"
"<p>Copyright © 2006 Software Inc."
"<p>Spreadsheet is a small application that "
"demonstrates QAction, QMainWindow, QMenuBar, "
"QStatusBar, QTableWidget, QToolBar, and many other "
"Qt classes."));
}
调用QMessageBox::about()静态函数可以得到下图这样的About对话框。除了对话框的图标外,这和QMessageBox::warning()显示的对话框很相似。
到目前为止我们已经使用了几个QMessageBox和QFileDialog的静态函数。这些函数创建一个对话框,进行初始化然后调用exec()显示出来。当然,首先创建QMessageBox或者QFileDialog,然后显式调用exec()或者show()也是可以的,并且一样方便。