一、核心功能
本软件为多文档型程序,界面是标准的 Windows 主从窗口
拥有:主菜单、工具栏、文档显示区 和 状态栏。
所要实现的东西,均在下图了。
开发该软件,主要分为下面三个阶段
1)界面设计开发
- 多窗口 MDI 程序框架的建立
- 菜单设计
- 工具栏设计
- 工具按钮
- 状态栏的帮助提示文本
- 多个文档子窗口的管理和控制
2)文本编辑功能实现
- 建立、打开和保存
- 剪切、复制和粘贴
- 撤销和恢复
3)排版美化功能实现
- 字体选择
- 字形
- 字号
- 文字颜色
- 文档段落标号和标号的添加
- 段落对齐方式
二、界面设计与开发
新建项目,MyselfWord。
1. 建立 MDI 程序框架
1.1 多文档区域的创建
在头文件 “myword.h” 中添加 QMdiArea 类的声明和定义变量:
#ifndef MYWORD_H
#define MYWORD_H
#include <QMainWindow>
//申明
class QMdiArea;
class MyWord : public QMainWindow
{
Q_OBJECT
public:
MyWord(QWidget *parent = nullptr);
~MyWord();
private:
//定义变量
QMdiArea *mdiArea;
};
#endif // MYWORD_H
在 “myword.cpp” 的构造函数编写如下:
#include "myword.h"
#include <QtWidgets>
MyWord::MyWord(QWidget *parent)
: QMainWindow(parent)
{
mdiArea = new QMdiArea; // 创建一个新的QMdiArea实例
mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 设置水平滚动条策略为根据需要显示
mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // 设置垂直滚动条策略为根据需要显示
setCentralWidget(mdiArea); // 将QMdiArea设置为窗口的中心部件
move(200,150);
resize(800,500);
setWindowTitle(tr("Myself Word"));
}
MyWord::~MyWord()
{
}
1.2 子窗口类的创建
为了实现多文档操作和管理,需要向 QMdiArea 中添加子窗口。为了可以更好地操作子窗口,则必须实例化子窗口的中心部件。而子窗口的中心部件使用了 QTextEdit 类,所以要实现自己的类,它必须继承自 QTextEdit 类。
添加新的类,具体如下:
在 “mychild.h” 中添加:
#ifndef MYCHILD_H
#define MYCHILD_H
#include <QWidget>
#include <QTextEdit>
class MyChild : public QTextEdit
{
Q_OBJECT
public:
MyChild();
void newFile(); //新建操作
QString userFriendlyCurrentFile(); //提取文件名
QString currentFile() { return curFile; } //返回当前文件路径
protected:
void closeEvent(QCloseEvent *event); //关闭事件
private slots:
void documentWasModified(); //文档被更改时,窗口显示更改状态标识
private:
QString strippedName(const QString &fullFileName); //获取较短的绝对路径
QString curFile; //保存当前文件路径
bool isUntitled; //作为当前文件是否被保存到硬盘上的标识
};
#endif // MYCHILD_H
声明了许多函数和定义了几个变量。其实可以一边实现功能,一边添加需要的函数。现在先实现新建功能,首先声明函数 newFile()。
1.3 新建文件操作
(1)newFile() 设计思路
- 设置窗口编号
- 设置文件未被保存过 “isUntitiled = true;”
- 保存文件路径,为 curFile 赋初值,用 strippedName 函数修改为绝对路径
- 设置子窗口标题
- 关联文档内容改变信号 contentsChanged() 到显示文档更改状态标志槽documentWasModified()。
(2)newFile() 实现
子窗口的初始化
在“mychild.cpp”文件中添加:
#include "mychild.h"
#include <QtWidgets>
MyChild::MyChild()
{
setAttribute(Qt::WA_DeleteOnClose); //设置在子窗口关闭时销毁这个类的对象
isUntitled = true; //初始 isUntitled 为 true
}
newFile() 的实现
void MyChild::newFile()
{
//设置窗口编号,因为编号会一直保存,所以需要使用静态变量
static int sequenceNumber = 1;
//新建的文档默认为命名
isUntitled = true;
//将当前文件命名为"文档+编号"的形式,编号先使用再1
curFile = tr("文档 %1").arg(sequenceNumber++);
//设置窗口标题,使用[*]可以在文档被更改后在文件名称后显示"*"号
setWindowTitle(curFile + "[*]" + tr(" - Myself Word"));
//文档更改时发送 contentsChanged()信号,执行 documentWasModified() 曹函数
connect(document(),SIGNAL(contentsChanged()),this,SLOT(documentWasModified()));
}
这里在设置窗口标题时添加了“[*]”字符,它可以保证编辑器内容被更改后,在文档标题中显示“*”号。
(3)文件更改标记
下面是 documentWasModified() 槽函数的定义:
void MyChild::documentWasModified()
{
//根据文档的 isModified() 函数的返回值,判断编辑器内容是否被更改
setWindowModified(document()->isModified());
}
编辑器内容是否被更改,可以使用 QTextDocument 类的 isModified() 函数得知。
设置文档子窗口标题
QString MyChild::userFriendlyCurrentFile()
{
return strippedName(curFile);
}
strippedName 函数用于修改文件名为较短的绝对路径。接收一个完整的文件名(包括路径)作为参数(类型为 QString
),然后返回这个文件名中的基本文件名部分(即去除路径后的文件名)。
QString MyChild::strippedName(const QString &fullFileName)
{
return QFileInfo(fullFileName).fileName();
}
先不考虑关闭文档时的保存逻辑,在 closeEvent() 中无条件地接收关闭事件。
void MyChild::closeEvent(QCloseEvent *event)
{
event->accept();
}
2. 菜单系统设计
MyselfWord 的菜单系统包括主菜单、菜单栏和子菜单三级。
在 “myword.h” 中声明以及系统动作和菜单的实现:
#ifndef MYWORD_H
#define MYWORD_H
#include <QMainWindow>
class QMdiArea;
class QAction;//
class QMenu;//
class MyWord : public QMainWindow
{
Q_OBJECT
public:
MyWord(QWidget *parent = nullptr);
~MyWord();
private:
QMdiArea *mdiArea;
void createActions();//
void createMenus();//
};
#endif // MYWORD_H
2.1 文件 主菜单
需要包括:新建N,打开O,保存S,另存为A,打印P,打印预览,退出X。
在“myword.h”文件中,定义“文件”菜单指针,定义“文件”主菜单下各个功能项的 QAction
private:
void createActions();
void createMenus();
QMdiArea *mdiArea;
//菜单
QMenu *fileMenu;
//动作(Action)
QAction *newAct; //【文件】主菜单
QAction *openAct;
QAction *saveAct;
QAction *saveAsAct;
QAction *printAct;
QAction *printPreviewAct;
QAction *exitAct;
在“myword.cpp”文件中编写函数 createActions() 的代码
记得在 images目录下搞点菜单图标。
const QString rsrcPath = ":/images";
void MyWord::createActions()
{
/*【文件】菜单动作集*/
//&N 表示这个菜单项可以使用快捷键 Alt+N 来访问
newAct = new QAction(QIcon(rsrcPath + "/filenew.png"), tr("新建(&N)"), this);
newAct->setShortcuts(QKeySequence::New); //设置了快捷键
newAct->setToolTip("新建"); //设置工具栏按钮的提示文本
newAct->setStatusTip(tr("创建一个新文档")); //设置状态栏提示文本
//connect(newAct, SIGNAL(triggered()), this, SLOT(fileNew()));
openAct = new QAction(QIcon(rsrcPath + "/fileopen.png"), tr("打开(&O)..."), this);
openAct->setShortcuts(QKeySequence::Open);
openAct->setToolTip("打开");
openAct->setStatusTip(tr("打开已存在的文档"));
//connect(openAct, SIGNAL(triggered()), this, SLOT(fileOpen()));
saveAct = new QAction(QIcon(rsrcPath + "/filesave.png"), tr("保存(&S)"), this);
saveAct->setShortcuts(QKeySequence::Save);
saveAct->setToolTip("保存");
saveAct->setStatusTip(tr("将当前文档存盘"));
//connect(saveAct, SIGNAL(triggered()), this, SLOT(fileSave()));
saveAsAct = new QAction(tr("另存为(&A)..."), this);
saveAsAct->setShortcuts(QKeySequence::SaveAs);
saveAsAct->setStatusTip(tr("以一个新名字保存文档"));
//connect(saveAsAct, SIGNAL(triggered()), this, SLOT(fileSaveAs()));
printAct = new QAction(QIcon(rsrcPath + "/fileprint.png"), tr("打印(&P)..."), this);
printAct->setShortcuts(QKeySequence::Print);
printAct->setToolTip("打印");
printAct->setStatusTip(tr("打印文档"));
//connect(printAct, SIGNAL(triggered()), this, SLOT(filePrint()));
printPreviewAct = new QAction(tr("打印预览..."), this);
printPreviewAct->setStatusTip(tr("预览打印效果"));
//connect(printPreviewAct, SIGNAL(triggered()), this, SLOT(filePrintPreview()));
exitAct = new QAction(tr("退出(&X)"), this);
exitAct->setShortcuts(QKeySequence::Quit);
exitAct->setStatusTip(tr("退出应用程序"));
//connect(exitAct, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));
}
上面所写的槽函数,后面会一一实现。
然后再编写函数 createMenus() 的代码
void MyWord::createMenus()
{
//【文件】主菜单
fileMenu = menuBar()->addMenu(tr("文件(&F)"));
fileMenu->addAction(newAct);
fileMenu->addAction(openAct);
fileMenu->addSeparator(); //分隔线
fileMenu->addAction(saveAct);
fileMenu->addAction(saveAsAct);
fileMenu->addSeparator(); //分隔线
fileMenu->addAction(printAct);
fileMenu->addAction(printPreviewAct);
fileMenu->addSeparator(); //分隔线
fileMenu->addAction(exitAct);
}
记得别忘记在“myword.cpp”的构造函数中添加这里两个函数
createActions();
createMe