该文章原创于Qter开源社区(www.qter.org),作者yafeilinux,转载请注明出处!
导语
前面已经在主窗口中添加了菜单和工具栏,这一篇中我们将实现基本的文本编辑功能。在开始正式写程序之前,我们先要考虑一下整个流程。因为这里要写一个记事本一样的程序,所以最好先打开Windows中的记事本,进行一些简单的操作,然后考虑怎样去实现这些功能。再者,再强大的软件,它的功能也是一个一个加上去的,不要设想一下子写出所有的功能。我们这里先实现新建文件,保存文件,和文件另存为三个功能,是因为它们联系很紧,而且这三个功能总的代码量也不是很大。
环境是:
Windows 7 + Qt 4.8.1+ Qt Creator 2.4.1
目录
一、实现新建文件、文件保存和另存为功能
二、实现打开、关闭、退出、撤销、复制、剪切、粘贴等功能
正文
一、实现新建文件、文件保存和另存为功能
1.
首先来分析下整个流程,当新建文件时,要考虑是否保存正在编辑的文件,如果需要保存,还要根据该文件以前是否保存过来进行保存或者另存为操作。下面我们根据这里的分析来添加需要的函数和对象。
2.
打开上一篇完成的项目,然后先在main.cpp
文件中添加代码来保证代码中可以使用中文字符。
首先添加
#include
<QTextCodec>
头文件包含,然后在主函数中添加如下代码:
QTextCodec
::setCodecForTr(QTextCodec::codecForLocale());
3.
在mainwindow.h
文件中添加public
函数声明:
void
newFile(); //
新建操作
bool
maybeSave(); //
判断是否需要保存
bool
save(); //
保存操作
bool
saveAs(); //
另存为操作
bool
saveFile(const QString &fileName); //
保存文件
这里的几个函数就是用来完成功能逻辑的,下面我们会添加它们的定义来实现相应的功能。因为这几个功能联系紧密,所以这几个函数会相互调用。
4.
然后添加private
变量定义:
//
为真表示文件没有保存过,为假表示文件已经被保存过了
bool
isUntitled
;
//
保存当前文件的路径
QString
curFile
;
这里的isUntitled
是一个标志,用来判断文档是否被保存过。而curFile
用来保存当前打开的文件的路径。
5.
下面到mainwindow.cpp
文件,先添加头文件:
#include
<QMessageBox>
#include
<QPushButton>
#include
<QFileDialog>
#include
<QTextStream>
然后在构造函数中添加如下代码来进行一些初始化操作:
//
初始化文件为未保存状态
isUntitled = true;
//
初始化文件名为"未命名.txt"
curFile = tr("
未命名.txt"
);
//
初始化窗口标题为文件名
setWindowTitle(curFile);
这里设置了在启动程序时窗口标题显示文件的名字,效果如下图所示。
<ignore_js_op>
6.
下面添加那几个函数的定义。
首先是新建文件操作的函数:
void
MainWindow
::
newFile()
{
if
(
maybeSave()) {
isUntitled
=
true
;
curFile
=
tr("
未命名.txt"
);
setWindowTitle(curFile);
ui
->
textEdit
->
clear();
ui
->
textEdit
->setVisible(
true
);
}
}
这里先使用maybeSave()
来判断文档是否需要保存,如果已经保存完了,则新建文档,并进行初始化。下面是maybeSave()
函数的定义:
bool
MainWindow
::
maybeSave()
{
//
如果文档被更改了
if (ui->textEdit->document()->isModified()) {
// 自定义一个警告对话框
QMessageBox
box;
box.
setWindowTitle(tr("
警告"
));
box.
setIcon(QMessageBox::Warning);
box.
setText(curFile + tr("
尚未保存,是否保存?"
));
QPushButton
*yesBtn
=
box.
addButton(tr("
是(&Y)"
),
QMessageBox
::
YesRole
);
box.
addButton(tr("
否(&N)"
),
QMessageBox
::
NoRole
);
QPushButton
*cancelBut
=
box.
addButton(tr("
取消"
),
QMessageBox
::
RejectRole
);
box.
exec();
if
(box.
clickedButton() == yesBtn)
return
save();
else
if
(box.
clickedButton() == cancelBut)
return
false
;
}
//
如果文档没有被更改,则直接返回true
return
true
;
}
这里先使用了isModified()来判断文档是否被更改了,如果被更改了,则弹出对话框让用户选择是否进行保存,或者取消操作。如果取消操作,那么就返回false,什么都不执行。下面是save()函数的定义:
bool
MainWindow
::
save()
{
if
(
isUntitled
)
{
return
saveAs();
}
else
{
return
saveFile(curFile);
}
}
这里如果文档以前没有保存过,那么执行另存为操作saveAs(),如果已经保存过,那么调用saveFile()执行文件保存操作。下面是saveAs()函数的定义:
bool
MainWindow
::
saveAs()
{
QString
fileName
=
QFileDialog
::
getSaveFileName(this,
tr("
另存为"
),
curFile
);
if
(fileName.
isEmpty()) return false;
return
saveFile(fileName);
}
这里使用QFileDialog
来实现了一个另存为对话框,并且获取了文件的路径,然后使用文件路径来保存文件。下面是saveFile()
函数的定义:
bool
MainWindow
::
saveFile(const QString &fileName)
{
QFile
file(fileName);
if
(!file.open(
QFile
::
WriteOnly
|
QFile
::
Text
))
{
//
%1
和%2分别对应后面arg两个参数,/n起换行的作用
QMessageBox
::
warning(this, tr("
多文档编辑器"
),
tr("
无法写入文件
%1
:/n
%2"
)
.
arg(fileName).arg(file.errorString()));
return
false
;
}
QTextStream
out(&file);
//
鼠标指针变为等待状态
QApplication
::
setOverrideCursor(Qt::WaitCursor);
out
<<
ui
->
textEdit
->
toPlainText();
//
鼠标指针恢复原来的状态
QApplication
::
restoreOverrideCursor();
isUntitled
=
false
;
//
获得文件的标准路径
curFile
=
QFileInfo
(fileName).
canonicalFilePath();
setWindowTitle(curFile);
return
true
;
}
该函数执行真正的文件保存操作。先是使用一个QFile
类对象来指向要保存的文件,然后将其使用写入方式打开。打开后再使用QTextStream
文本流将编辑器中的内容写入到文件中。
这里使用了很多新的类,以后我们对自己不明白的类都可以去帮助里进行查找,这也许是我们以后要做的最多的一件事了。对于其中的英文解释,我们最好想办法弄明白它的大意,其实网上也有一些中文的翻译,但最好还是从一开始就尝试着看英文原版的帮助,这样以后才不会对中文翻译产生依赖。
7.
设置菜单功能。双击mainwindow.ui
文件,在图形界面窗口下面的Action
编辑器里,我们右击“
新建”
菜单一条,选择“转到槽”,然后选择triggered
(),进入其触发事件槽。如下图所示。
<ignore_js_op>
同理,进入其他两个菜单的槽,将相应的操作的函数写入槽中。最终代码如下:
void
MainWindow
::
on_action_New_triggered()
{
newFile();
}
void
MainWindow
::
on_action_Save_triggered()
{
save();
}
void
MainWindow
::
on_action_SaveAs_triggered()
{
saveAs();
}
现在运行程序,已经能够实现新建文件,保存文件,文件另存为的功能了。
二、实现打开、关闭、退出、撤销、复制、剪切、粘贴等功能
先到mainwindow.h
文件中添加public
函数声明:
bool
loadFile(const QString &fileName); //
加载文件
然后到mainwindow.cpp文件中添加该函数的定义:
bool
MainWindow
::
loadFile(const QString &fileName)
{
QFile
file(fileName);
//
新建QFile对象
if
(!file.open(
QFile
::
ReadOnly
|
QFile
::
Text
))
{
QMessageBox
::
warning(this, tr("
多文档编辑器"
),
tr("
无法读取文件
%1:\n%2."
)
.
arg(fileName).arg(file.errorString()));
return
false
;
//
只读方式打开文件,出错则提示,并返回false
}
QTextStream
in(&file);
//
新建文本流对象
QApplication::setOverrideCursor(Qt::WaitCursor);
// 读取文件的全部文本内容,并添加到编辑器中
ui->textEdit->setPlainText(in.readAll()); QApplication::restoreOverrideCursor();
//
设置当前文件
curFile
=
QFileInfo
(fileName).
canonicalFilePath();
setWindowTitle(curFile);
return
true
;
}
这里的操作和saveFile()函数是相似的。下面到设计模式,分别进入其他几个动作的触发信号的槽,更改如下:
//
打开动作
void
MainWindow
::
on_action_Open_triggered()
{
if
(
maybeSave()) {
QString
fileName
=
QFileDialog
::
getOpenFileName(this);
//
如果文件名不为空,则加载文件
if
(!fileName.
isEmpty()) {
loadFile(fileName);
ui
->
textEdit
->setVisible(
true
);
}
}
}
//
关闭动作
void
MainWindow
::
on_action_Close_triggered()
{
if
(
maybeSave()) {
ui
->
textEdit
->setVisible(
false
);
}
}
//
退出动作
void
MainWindow
::
on_action_Exit_triggered()
{
//
先执行关闭操作,再退出程序
//
qApp
是指向应用程序的全局指针
on_action_Close_triggered();
qApp->quit();
}
//
撤销动作
void
MainWindow
::
on_action_Undo_triggered()
{
ui
->
textEdit
->
undo();
}
//
剪切动作
void
MainWindow
::
on_action_Cut_triggered()
{
ui
->
textEdit
->
cut();
}
//
复制动作
void
MainWindow
::
on_action_Copy_triggered()
{
ui
->
textEdit
->
copy();
}
//
粘贴动作
void
MainWindow
::
on_action_Paste_triggered()
{
ui
->
textEdit
->
paste();
}
这里可以看到,复制、粘贴等常用功能是QTextEdit
已经实现的,我们只需要调用相应的函数。虽然实现了退出功能,但是,有时候会使用窗口标题栏的关闭按钮来关闭程序,这里我们需要使用关闭事件处理函数来实现相应的功能。
下面到mainwindow.h
文件中,先添加头文件包含
#include
<QCloseEvent>
,然后
添加函数声明:
protected
:
void
closeEvent
(
QCloseEvent
*
event); //
关闭事件
然后到mainwindow.cpp文件中添加该函数的定义:
void
MainWindow
::closeEvent(
QCloseEvent
*event)
{
//
如果maybeSave()函数返回true,则关闭程序
if
(
maybeSave()) {
event->
accept();
}
else
{
//
否则忽略该事件
event->
ignore();
}
}
关于事件的概念,会在后面的教程中讲解。
结语