New菜单项或者单击工具栏上的New按钮时,就会调用newFile()槽。如果存在还没有被保存的信息,okToC_qt添加open动作">
赞
踩
在这一节中,将实现那些能够让File菜单项正常工作并且能够对最近打开文件进行管理的槽函数和私有函数。
void MainWindow::newFile()
{
if (okToContinue()) {
spreadsheet->clear();
setCurrentFile("");
}
}
当用户点击File->New菜单项或者单击工具栏上的New按钮时,就会调用newFile()槽。
如果存在还没有被保存的信息,okToContinue()私有函数就会弹出对话框:“Do you wantto save your changes?”。如果用户选择Yes或者No(保存文档应该选择Yes),这个函数会返回true;如果用户选择Cancel,它就返回false。
Spreadsheet::clear()函数会清空电子制表软件中的全部单元格和公式。
setCurentFile()私有函数会更新窗口的标题,以说明正在编辑的是一个没有标题的文档,它还会设置curFile私有变量并且更新最近打开文件的列表。
bool MainWindow::okToContinue() { //QMessageBox::warning(parent, title, message, buttons); if (isWindowModified()) { int r = QMessageBox::warning(this, tr("Spreadsheet"), tr("The document has been modified.\n" "Do you want to save your changes?"), QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); if (r == QMessageBox::Yes) { return save(); } else if (r == QMessageBox::Cancel) { return false; } } return true; }
在okToContinue()函数中,会检测windowModifed属性的状态。如果该属性的值是true,就显示一个如下图所示的消息框。这个消息框包含一个Yes按钮、一个No按钮和一个Cancel按钮。QMessageBox::Default 说明 Yes 为默认的按钮,QMessageBox::Escape 说明按键 Esc 和 Cancel 按钮等效。
QMessageBox提供了许多标准按钮,并且会自动尝试着让其中的一个成为默认的确认按钮(在用户按下Enter键时会得到激活) ,一个成为默认的退出按钮(在用户按下Esc时会得到激活)。选择一些特殊的按钮作为默认的确认按钮和退出按钮也是有可能的,用户还可以自定义按钮中将要显示的文本内容。
咋一看,QMessageBox::warning() 看起来有些复杂,实际是很简单明了的。
QMessageBox::warning(parent, title, message, button0, button1, …)
除了warning()之外,QMessageBox还提供了information()、question()和critical()函数,它们每一个都有自己特定的图标。
void MainWindow::open()
{
if (okToContinue()) {
QString fileName = QFileDialog::getOpenFileName(this,
tr("Open Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (!fileName.isEmpty())
loadFile(fileName);
}
}
open()槽对File->Open做出响应。就像newFile()一样,它首先调用okToContiniue()函数来处理任何没有被保存的变化。然后它使用方便的QFileDialog::getOpenFileName()静态函数从用户那里获得一个新的文件名。这个函数会弹出一个文件对话框,让用户选择一个文件,并且返回这个文件名——或者,如果用户单击了Cancel按钮,则返回一个空字符串。
传递给QFileDialog::getOpenFileName()函数的第一个参数是它的父窗口部件。用于对话框和其他窗口部件的这种父子对象关系意义并不相同。对话框通常都拥有自主权,但是如果它有父对象,那么在默认情况下,它就会居中放到父对象上。一个子对话框也会共用它的父对象的任务栏。
第二个参数是这个对话框应当使用的标题。
第三个参数告诉它应当从哪一级目录开始,在这个例子中就是当前目录。
第四个参数指定了文件过滤器。文件过滤器(filter)由一个描述文本和一个通配符组成。如果除了要支持Spreadsheet本地文件格式以外,还需要支持采用逗号分隔的数据文件和Lotus 1-2-3 文件,就应当使用如下的文件过滤器:
tr("Spreadsheet files (* . sp)\n"
"Comma-separated values files (* . csv)\n"
"Lotus 1-2-3 files (* .wk1 * . wks)")
loadFile()私有函数是在open()中得到调用的,它用来载入文件。我们让它成为一个独立的函数,是因为会在载入最近打开的文件中使用同样的功能。
bool MainWindow::loadFile(const QString &fileName)
{
if (!spreadsheet->readFile(fileName)) {
statusBar()->showMessage(tr("Loading canceled"), 2000);
return false;
}
setCurrentFile(fileName);
statusBar()->showMessage(tr("File loaded"), 2000);
return true;
}
我们使用Spreadsheet::readFile()函数从磁盘中读取文件。
如果载入成功,会调用setCurentFile()函数来更新这个窗口的标题;否则,Spreadsheet::readFile()将会通过一个消息框把遇到的问题通知给用户。
在通常情况下,让底层组件来报告错误消息是一个不错的习惯,这是因为它们可以提供准确的错误细节信息。
在上述两种情况下,都会在状态栏中显示一个消息2秒(2000毫秒),这样可以通知用户应用程序正在做什么。
bool MainWindow::save()
{
if (curFile.isEmpty()) {
return saveAs();
} else {
return saveFile(curFile);
}
}
save()槽对File->Save做出响应。如果因为这个文件是之前打开的文件或者它是一个已经保存过的文件,这样已经有了一个名字,那么save()函数就会用这个名字调用saveFile()函数;否则,它只是简单地调用saveAs()函数。
bool MainWindow::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this,
tr("Save Spreadsheet"), ".",
tr("Spreadsheet files (*.sp)"));
if (fileName.isEmpty())
return false;
return saveFile(fileName);
}
saveAs()槽对File->Save As做出响应。调用QFileDialog::getSaveFileName()函数来从用户那里得到一个文件名。
如果用户单击了Cancel,则返回false,这将会使这个结果向上传递给它的调用者[save()或者okToContinue()]。
如果给定的文件已经存在,geSaveFileName( )函数将会要求用户确认是否需要覆盖该文件。但通过给getSveFileNarne()函数传递一个QFileDialog::DontConfirmOverwite附加参数,则可以改变这一行为。
void MainWindow::closeEvent(QCloseEvent *event)
{
if (okToContinue()) {
writeSettings();
event->accept();
} else {
event->ignore();
}
}
当用户单击File->Exit或者单击窗口标题栏中的关闭按钮时,将会调用QWidget::close()槽。该槽会给这个窗口部件发射一个close事件。通过重新实现QWidget::closeEvent()函数就可以中途截取对这个主窗口的关闭操作,并且可以确定到底是不是真的要关闭这个窗口。
如果存在未保存的更改并且用户选择了Cancel, 就会“忽略”这个关闭事件并且让这个窗口不受该操作的影响。
一般情况下,我们会接受这个事件,这会让Qt隐藏该窗口。也可以调用私有函数writeSettings()来保存这个应用程序的当前设置。
当最后一个窗口关闭后,这个应用程序就结束了。如果需要,通过把QApplication的quitOnLastWindowClosed属性设置为false,可以禁用这种行为。
在这种情况下,该应用程序将会持续保持运行,直到调用QApplication:: quit()函数,程序才会结束。
void MainWindow::setCurrentFile(const QString &fileName) { curFile = fileName; setWindowModified(false); QString shownName = tr("Untitled"); if (!curFile.isEmpty()) { shownName = strippedName(curFile); recentFiles.removeAll(curFile); recentFiles.prepend(curFile); updateRecentFileActions(); } setWindowTitle(tr("%1[*] - %2").arg(shownName) .arg(tr("Spreadsheet"))); }
在setCurrentFile()中,对保存正在编辑的文件名的curFile私有变量进行了设置。
每个QWidget都有一个windowModifed属性,如果该窗口的文档存在没有保存的变化,则应当把它设置为true,否则应当将其设置为falsle。
在Mac OS X下,未保存的文档是通过窗口标题栏上关闭按钮中的一个点来表示的;在其他平台下,则是通过文件名字后跟一个星号来表示的。
Qt会自动处理这一行为,只要始终让windowModified属性保持为当前最新状态,并且当需要显示星号的时候,把“[ * ]"标记放在窗口的标题栏上即可。
传递给setWindowTitle()函数的文本是:
tr("%1[*] - %2").arg(shownName).arg(tr("Spreadsheet"))
QString::arg()函数将会使用自己的参数替换最小数字的"%n"参数,并且会用它的参数返回结果“%n”字符和最终的结果字符串。
在本例中,arg()被用于两个"%n"参数中。第一个arg()调用会替换参数"%1",第二个arg()调用则会替换参数"%2"。
如果文件名是budget.sp并且没有载入翻译文件,那么结果字符串将是"budget.sp[ * ]- Spreadsheet"。
这本应更简单地写作如下代码:
setWindowTitle(shownName + tr("[*] - Spreadsheet"));
但使用arg()函数可以为翻译人员提供更多的灵活性。
如果存在文件名,就需要更新应用程序的最近打开文件列表recentFiles。在把这个文件名显示在标题栏中之前,需要使用strippedName()函数移除文件名中的路径字符,这样可以使文件名看起来更友好一些。可以调用removeAll()从列表中移除任何已经出现过的文件名,从而避免该文件名的重复。
然后,可以调用prepend()把这个文件名作为文件列表的第一项添加进去。在更新了文件列表之后,可以调用私有函数updateRecentFileActions()更新File菜单中的那些条目。
void MainWindow::updateRecentFileActions() { QMutableStringListIterator i(recentFiles); while (i.hasNext()) { if (!QFile::exists(i.next())) i.remove(); } for (int j = 0; j < MaxRecentFiles; ++j) { if (j < recentFiles.count()) { QString text = tr("&%1 %2") .arg(j + 1) .arg(strippedName(recentFiles[j])); recentFileActions[j]->setText(text); recentFileActions[j]->setData(recentFiles[j]); recentFileActions[j]->setVisible(true); } else { recentFileActions[j]->setVisible(false); } } separatorAction->setVisible(!recentFiles.isEmpty()); }
使用一个Java风格的迭代器,可以移除任何不再存在的文件。一些文件或许已经在前面的会话中使用过,但在此之前还没被删除掉。
recentFiles变量的类型是QStringList(QString型列表)。像QStringList一样的容器类,其中将会说明它们与C++标准模板库(StandardTemplate Library ,STL)之间的关系,也会说明Qt的Java风格迭代器类的用法。
然后,再遍历一次文件列表,这一次使用数组风格的索引形式。对于每一个文件,创建一个由一个与操作符、一位数字(j+ 1)、一个空格和该文件名(不带路径)组成的字符串。我们要为使用这种文本设置相应的动作。
例如,如果第一个文件是C:\My Documnents\tab04.sp,那么第一个动作的文本将会是“&1 tab04.sp”。
如下图给出了recentFileActions数组和菜单的最终结果之间的对应关系。
每一个动作都可以带一个与之相关的QVariant型data项。QVariant类型可以保存许多C++和Qt型变量。这里,将文件的全名保存在动作的data项中,以便随后可以方便地找到它。还要将这个动作设置为可见。
如果有比最新文件更多的文件动作;那么只需隐藏那些多余的动作即可。
最后,如果至少还存在一个最近打开的文件,那么就应该把间隔器设置为可见。
void MainWindow::openRecentFile()
{
if (okToContinue()) {
QAction *action = qobject_cast<QAction *>(sender());
if (action)
loadFile(action->data().toString());
}
}
当用户选择了一个最近打开的文件,就会调用openRecentFile()槽。只要有任何未保存的变化,就会调用okToContinue()函数,并且假定用户没有取消,还可以使用QObject::sender()查出是哪个特有动作调用了这个槽。
qobject_cast <T>()函数可在Qt的moc(meta-object comipiler,元对象编译器)所生成的元信息基础上执行动态类型强制转换(dynamic cast)。 它返回一个指向所需QObject子类的指针,或者是在该对象不能被转换成所需的那种类型时返回0。
与标准C++的dynamic_ cast <T> ()不同,Qt的qobject_ cast <T> ()可正确地跨越动态库边界。
在例子中,使用qobject _cast<T > ()把一个QObject指针转换成QAction指针。如果这个转换是成功的(应当是这样的),就可以利用从动作的data项中所提取的文件全名来调用loadFile()函数。
顺便值得一提的是,由于知道这个发射器是一个QAction,如果使用static_cast<T > ()或者传统的C风格的数据类型强制转换代替原有的数据转换方式,这个程序应当仍然是可以运行的。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。