在这一节中,我们介绍Qt中对话框的调用:初始化对话框,显示对话框和与用户交互。我们将会使用在第二章创建的Find,Go-to-Cell对话框和Sort对话框。我们还会创建一个关于(About)对话框。
首先我们看一下Find对话框。我们希望用户能够在Find对话框和Spreadsheet应用程序的主窗口之间自由切换,所以Find对话框应该是非模态的。一个非模态对话框就是在程序运行过程中不依赖其他窗口是否显示的对话框。
创建非模态对话框后,一般要连接自己的信号和槽函数用来响应用户输入。
Find对话框允许用户在表格中查找文本。用户点击了Edit|Find菜单,find槽函数就会调用,弹出Find对话框。这时对话框的有以下三种可能:
1、 第一次调用Find对话框
2、 用户已经调用过Find对话框,但是给关闭了
3、 用户已经调用过Find对话框,且仍然显示
如果Find对话框还不存在,那么创建对话框,连接findNext()和findPrevious()两个信号到Spreadsheet相应的槽函数。当然我们也可以在MainWindow的构造函数中创建,但是在需要的时候再创建可以加快程序的启动时间,而且,如果在程序运行期间没有调用这个对话框,就可以不用创建,这样可以节约内存和时间。
接着我们调用show()和activateWindow()确保窗口是可见的,激活的。单独调用show()是能够显示并激活窗口的。但是如果调用对话框时,Find对话框是可见的,show()就不做任何事情,但调用activateWindow()就有必要了。所以后面几行还可以这样写:
if (findDialog->isHidden()) {
findDialog->show();
} else {
findDialog->activateWindow();
}
接着我们来看Go-to-Cell对话框,它是一个模态对话框。我们需要用户弹出对话框,在切换到程序的其他窗口前关闭它。模式对话框就是弹出后,在关闭之前,它阻止程序的其他消息和其他进程的干扰,也不能切换到其他窗口。我们以前使用过的文件对话框和消息提示对话框都是模态对话框。
使用show()显示的对话框是非模态对话框(除非之前调用setModal()将它设为模态) 。用exec()显示的对话框是模态对话框。如果对话框被接受,QDialog::exec()函数返回true(QDialog::Accepted),否则返回false(QDialog::Rejected)。在第二章创建Go-to-Cell对话框时,我们连接了ok按钮到accept(),cancel按钮连接到了reject()。如果用户点击了Ok按钮,我们就把当前的网格设为编辑框中的值。
函数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数组作为bool型,保存对应每个key的排序顺序。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()
{ Copyright © 2006 Software Inc." Spreadsheet is a small application that "
QMessageBox::about(this, tr("About Spreadsheet"),
tr("Spreadsheet 1.1
"
"
"
"demonstrates QAction, QMainWindow, QMenuBar, "
"QStatusBar, QTableWidget, QToolBar, and many other "
"Qt classes."));
}
到目前为止我们已经使用了几个QMessageBox和QFileDialog的静态函数。这些函数创建一个对话框,进行初始化然后调用exec()显示出来。当然,首先创建QMessageBox或者QFileDialog,然后显式调用exec()或者show()也是可以的,并且一样方便。