3-4 实现文件菜单(Implementing the File Menu)

3-4 实现文件菜单(Implementing the File Menu

在这一节,我们实现与文件菜单有关的槽函数和相关的私有函数,以使文件菜单可以工作,同时管理最近打开文件列表。

void  MainWindow::newFile()

{

    
if  (okToContinue()) {

        spreadsheet
-> clear();

        setCurrentFile(
"" );

    }

}

 

newFile()槽函数在用户点击了File|New菜单或者工具条上的New按钮后调用。

<script type="text/javascript"> </script>okToContinue()是一个私有函数,在这里如果需要存盘,程序会询问用户“Do you want to save your changes ?(是否存盘提示)”,如果用户选择了Yes或者No,函数返回true,如果用户选择了Cancel,返回falseSpreadsheet::clear()函数清楚所有spreadsheet控件的格子和公式。setCurrentFile()也是一个私有函数,它更新窗口标题,重新设置curFile变量,更新最近打开的文件列表,为用户开始编辑没有名字的新文档做好准备。

bool  MainWindow::okToContinue()

{

    
if  (isWindowModified()) { 

        
int  r  =  QMessageBox::warning( this , tr( " Spreadsheet " ),

                        tr(
" The document has been modified. "

                           
" Do you want to save your changes? " ),

                        QMessageBox::Yes 
|  QMessageBox::Default,

                        QMessageBox::No,

                        QMessageBox::Cancel 
|  QMessageBox::Escape);

        
if  (r  ==  QMessageBox::Yes) {

            
return  save();

        } 
else   if  (r  ==  QMessageBox::Cancel) {

            
return   false ;

        }

    }

    
return   true ;

}

okToContinue()函数中,检查windowModified属性的状态,如果为true,那么就会显示如下的消息框。这个消息框有YesNo,和Cancel按钮。QMessageBox::Default说明Yes为默认的按钮,QMessageBox::Escape说明按键EscCancel按钮等效

message box

咋一看,QMessageBox::warning() <script type="text/javascript"> </script>看起来有些复杂,实际是很简单明了的。

QMessageBox::warning(parent, title, message, button0, button1, ...);

QMessageBox还提供其他函数如:information()question()critical(),每一个函数都有他们自己特殊的显示图标:

 

槽函数open()相应菜单File|Open,它首先也是调用okToContinue()处理为保存的信息。然后使用QFileDialog::getOpenFileName(),这个函数弹出一个对话框,让用户选择一个文件的名字,如果用户选择了一个文件,那么函数返回文件的名字,如果用户点击了Cancel按钮,则返回一个空字符串。

void  MainWindow::open()
{

    
if  (okToContinue()) {

        QString fileName 
=  QFileDialog::getOpenFileName( this ,

                                   tr(
" Open Spreadsheet " ),  " . " ,

                                   tr(
" Spreadsheet files (*.sp) " ));

        
if  ( ! fileName.isEmpty())

            loadFile(fileName);

    }

}

QFileDialog::getOpenFileName()的第一个参数是它的父控件。父子关系对于对话框来说和其他控件有些不同,一个对话框总是显示为一个窗口,如果它有父控件,那么它一般显示在父控件的中上位置,A child dialog also shares its parent's taskbar entry.(怎么准确翻译那,好像是共享父控件的一些东西,taskbar

第二个参数是对话框使用的标题。第三个参数是显示的初始目录,.表示的是程序的当前目录。

第四个参数用来说明文件过滤器,即确定文件类型。文件过滤器由一个描述性的文本和通配符格式组成。如果我们在spreadsheet程序中除了支持自定义的文件格式外,还支持了Comma-separated values 文件和Lotus 1-2-3 文件,那么过滤器就要这样:

tr("Spreadsheet files (*.sp)/n"

<script type="text/javascript"> </script>   "Comma-separated values files (*.csv)/n"

   "Lotus 1-2-3 files (*.wk1 *.wks)")

loadFile()是一个私有函数,用来加载文件。把这段代码独立出来是因为在打开最近文件时我们还要使用它:

Spreadsheet::readFile()来读取硬盘的文件。如果读取成功,调用setCurrentFile() <script type="text/javascript"> </script>更新窗口标题。否则,该函数给出一个错误的提示框。通常,在低级别的控件中给出相信的错误信息是个好的习惯,这样可以清楚知道出错的原因。

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 ;

}

不管成功与否,程序的状态条上都显示2秒(2000毫秒)的状态信息,告诉用户程序的正在做的事情。

菜单File|Savesave()函数相应的。如果文件已经有了名字,或者是在磁盘上打开的,或者已经保存过,函数直接调用saveFile(),文件名字不变。否则调用saveAs()


bool  MainWindow::save()
{
    
if  (curFile.isEmpty()) {
        
return  saveAs();
    } 
else  {
        
return  saveFile(curFile);
    }
}

bool  MainWindow::saveFile( const  QString  & fileName)
{
    
if  ( ! spreadsheet -> writeFile(fileName)) {
        statusBar()
-> showMessage(tr( " Saving canceled " ),  2000 ); 
        
return   false ;
    }

    setCurrentFile(fileName);
    statusBar()
-> showMessage(tr( " File saved " ),  2000 );
    
return   true ;
}

bool  MainWindow::saveAs()
{
    QString fileName 
=  QFileDialog::getSaveFileName( this ,
                               tr(
" Save Spreadsheet " ),  " . " ,
                               tr(
" Spreadsheet files (*.sp) " ));
    
if  (fileName.isEmpty())
        
return   false ;
    
return  saveFile(fileName);

菜单File|SaveAs相应函数为saveAs()QFileDialog::getSaveFileName()提示用户输入文件名。如果用户点击了Cancel按钮,函数返回false,并将状态传递给调用者。如果文件已经存在,getSaveFileName()询问用户是否要覆盖。在getSaveFileName()的一个默认参数就是是否要覆盖,默认参数为QFileDialog::DontConfirmOverwrite

当用户点击了File|Close菜单或者窗口标题栏上的关闭按钮,QWidget::close()就会被调用。并发送close()信号。重新实现QWidget::closeEvent()能够拦截这个消息,以便确定是否真的要关闭窗口,防止误操作。

void  MainWindow::closeEvent(QCloseEvent  * event )
{
    
if  (okToContinue()) {
        writeSettings();
        
event -> accept();
    } 
else  {
        
event -> ignore();
    }
}

如果需要存盘或者用户选择了Cancel,那么就忽视这个事件,不关闭窗口。通常如果接受了这个事件,Qt就会隐藏这个窗口。私有函数writeSettings()保存应用程序当前的设置。当最后一个窗口也关闭后,应用程序中止。如果不需要这个功能,可以设置QApplicationquitOnLastWindowClosed属性为false。这样,程序会一直运行,直到我们调用函数QApplication::quit()

<script type="text/javascript"> </script>setCurrentFile()函数中,我们让curFile这个私有变量保存当前正在编辑的文件的名字。这个变量保存的是全路径名,我们用函数strippedName()删除掉文件的路径,再在窗口的标题栏显示这个文件的名字。

void  MainWindow::setCurrentFile( const  QString  & fileName)
{
    curFile 
=  fileName;
    setWindowModified(
false ); 
    QString shownName 
=   " Untitled " ;
    
if  ( ! curFile.isEmpty()) {
        shownName 
=  strippedName(curFile);
        recentFiles.removeAll(curFile);
        recentFiles.prepend(curFile);
        updateRecentFileActions();
    }

    setWindowTitle(tr(
" %1[*] - %2 " ).arg(shownName)
                                   .arg(tr(
" Spreadsheet " )));
}

QString MainWindow::strippedName(
const  QString  & fullFileName)
{
    
return  QFileInfo(fullFileName).fileName();
}

每一个QWidget都有一个windowModified属性,如果有文件没有保存,那么就设置为true。否则设置为false。在Mac OS X平台,如果有没有保存的文件,在窗口的标题栏的关闭按钮旁有一个小点。在其他平台,在文件名后面加一个“*”表示。只要我们保持更新windowModified属性,把“[*]”放在合适的地方,Qt就能够自动处理。

传递给setWindowTitle()的文本是:

tr("%1[*] - %2").arg(shownName).arg(tr("Spreadsheet"))

QString::arg()函数用自己的参数代替文本中的数字%n,并返回结果字符创。上面语句有两个.arg(),分别用来代替%1%2 <script type="text/javascript"> </script>。如果文件名为“budget.sp”,且没有加载翻译文件,那么显示的字符串就是“budget.sp[*] - Spreadsheet”。也可以简写如下:

setWindowTitle(shownName + tr("[*] - Spreadsheet"));

但是使用arg()更加灵活且容易实现国际化。

打开文件后,我们要更新rencentFiles(最近打开文件列表)。使用removeAll()函数删除列表里的这个文件名,然后把它加在列表的前面。最后调用updateRecentFileActions()更新File菜单项。

首先我们用一个java样式的迭代器删除不存在的文件,因为有些文件可能在列表中但是已经被删除掉了。recentFiles的变量类型是QStringList。第11章详细介绍容器,迭代器及它们与c++标准模板库(STL)的关系。

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]
-> setVisibl e( true );
        } 
else  {
            recentFileActions[j]
-> setVisible( false );
        }
    }
    separatorAction
-> setVisible( ! recentFiles.isEmpty());
}

再看文件列表,后一部分我们使用了数组索引方式。每一个文件用一个&号,数字序号,一个空格,和文件名组成,行为名字就是这个字符串。例如,如果第一个文件是C:/My Documents/tab04.sp,那么第一个行为显示的文本就是“&1 tab04.sp”。

每一个行为都有一个大data项,存储QVariant <script type="text/javascript"> </script>类型的数据。QVariant能够存贮很多c++数据类型和Qt数据类型,将在第11章进行介绍。这里我们存储文件的全名,这样在将来我们打开文件时就可以很方便的找到它。

如果用户选择了一个最近打开的文件,openRecentFile()就被调用。okToContinue()用来检查是否需要存盘。这个函数特别的地方就是用QObject::sender()得到信号的发送者。

void  MainWindow::openRecentFile()
{
    
if  (okToContinue()) {
        QAction 
* action  =  qobject_cast < QAction  *> (sender());
        
if  (action)
            loadFile(action
-> data().toString());
    }
}

 qobject_case<T>()实现基于moc生成的元信息的动态类型转换。它返回一个QObject类的子类对象的指针,如果这个对象不能转换成类型T,返回一个空指针。和标准c++dynamic_case<T>不同,qobject_cast<T>()只在动态库内使用。在这个例子中,我们把一个QObject指针变为一个QAction指针。如果转换成功,调用loadFile(),打开保存在QActiondata属性中保存的文件。

需要说明的是,因为我们知道发送者是一个QAction对象,如果使用static_cast<T>或者一个传统的C样式的类型转换都能正确。

 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值