一、核心功能
本软件为多文档型程序,界面是标准的 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();
createMenus();
“文件”主菜单的运行显示效果如下:
2.2 编辑 主菜单
“编辑” 主菜单功能项应包含:撤销U、重做R、剪切T、复制C、粘贴P
在 “myword.h”文件中,定义“编辑”主菜单指针:
QMenu *editMenu;
QAction *undoAct; //【编辑】主菜单
QAction *redoAct;
QAction *cutAct;
QAction *copyAct;
QAction *pasteAct;
在“myword.cpp”文件,函数 createActions() 的代码中添加:
/*【编辑】菜单动作集*/
undoAct = new QAction(QIcon(rsrcPath + "/editundo.png"),tr("撤销(&U)"), this);
undoAct->setShortcut(QKeySequence::Undo);
undoAct->setToolTip("撤销");
undoAct->setStatusTip(tr("撤销当前操作"));
connect(undoAct, SIGNAL(triggered()), this, SLOT(undo())); //不用子窗口类去实现
redoAct = new QAction(QIcon(rsrcPath + "/editredo.png"),tr("重做(&R)"), this);
redoAct->setShortcut(QKeySequence::Redo);
redoAct->setToolTip("重做");
redoAct->setStatusTip(tr("恢复之前操作"));
connect(redoAct, SIGNAL(triggered()), this, SLOT(redo())); //不用子窗口类去实现
cutAct = new QAction(QIcon(rsrcPath + "/editcut.png"),tr("剪切(&T)"), this);
cutAct->setShortcuts(QKeySequence::Cut);
cutAct->setToolTip("剪切");
cutAct->setStatusTip(tr("从文档中裁剪所选内容,并将其放入剪贴板"));
connect(cutAct, SIGNAL(triggered()), this, SLOT(cut())); //不用子窗口类去实现
copyAct = new QAction(QIcon(rsrcPath + "/editcopy.png"),tr("复制(&C)"), this);
copyAct->setShortcuts(QKeySequence::Copy);
copyAct->setToolTip("复制");
copyAct->setStatusTip(tr("拷贝所选内容,并将其放入剪贴板"));
connect(copyAct, SIGNAL(triggered()), this, SLOT(copy())); //不用子窗口类去实现
pasteAct = new QAction(QIcon(rsrcPath + "/editpaste.png"),tr("粘贴(&P)"), this);
pasteAct->setShortcuts(QKeySequence::Paste);
pasteAct->setToolTip("粘贴");
pasteAct->setStatusTip(tr("将剪贴板的内容粘贴到文档"));
connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste())); //不用子窗口类去实现
在“myword.cpp”文件,函数 createMenus() 的代码中添加:
//【编辑】主菜单
editMenu = menuBar()->addMenu(tr("编辑(&E)"));
editMenu->addAction(undoAct);
editMenu->addAction(redoAct);
editMenu->addSeparator(); //分隔线
editMenu->addAction(cutAct);
editMenu->addAction(copyAct);
editMenu->addAction(pasteAct);
“编辑” 主菜单的运行效果:
2.3 格式 主菜单
“格式”主菜单功能项应包含:
- 字体D
- 加粗B、倾斜I、下画线U
- 段落
- 左对齐L、居中E、右对齐R、两端对齐J
- 颜色C
这是多级菜单了,在“myword.h”中,定义“格式”主菜单及其自菜单的指针:
QMenu *formatMenu;
QMenu *fontMenu; //子菜单
QMenu *alignMenu; //子菜单
QAction *boldAct; //【格式】主菜单
QAction *italicAct;
QAction *underlineAct;
QAction *leftAlignAct;
QAction *centerAct;
QAction *rightAlignAct;
QAction *justifyAct;
QAction *colorAct;
在“myword.cpp”文件,函数 createActions() 的代码中添加:
/*【格式】菜单动作集*/
boldAct = new QAction(QIcon(rsrcPath + "/textbold.png"),tr("加粗(&B)"), this);
boldAct->setCheckable(true);
boldAct->setShortcut(Qt::CTRL + Qt::Key_B);
boldAct->setToolTip("加粗");
boldAct->setStatusTip(tr("将所选文字加粗"));
QFont bold;
bold.setBold(true);
boldAct->setFont(bold);
connect(boldAct, SIGNAL(triggered()), this, SLOT(textBold()));
italicAct = new QAction(QIcon(rsrcPath + "/textitalic.png"),tr("倾斜(&I)"), this);
italicAct->setCheckable(true);
italicAct->setShortcut(Qt::CTRL + Qt::Key_I);
italicAct->setToolTip("倾斜");
italicAct->setStatusTip(tr("将所选文字用斜体显示"));
QFont italic;
italic.setItalic(true);
italicAct->setFont(italic);
connect(italicAct, SIGNAL(triggered()), this, SLOT(textItalic()));
underlineAct = new QAction(QIcon(rsrcPath + "/textunder.png"),tr("下划线(&U)"), this);
underlineAct->setCheckable(true);
underlineAct->setShortcut(Qt::CTRL + Qt::Key_U);
underlineAct->setToolTip("下划线");
underlineAct->setStatusTip(tr("给所选文字加下划线"));
QFont underline;
underline.setUnderline(true);
underlineAct->setFont(underline);
connect(underlineAct, SIGNAL(triggered()), this, SLOT(textUnderline()));
//【格式】→【段落】子菜单下的各项为同一个菜单项组,只能选中其中一项
QActionGroup *grp = new QActionGroup(this);
connect(grp, SIGNAL(triggered(QAction*)), this, SLOT(textAlign(QAction*)));
if (QApplication::isLeftToRight()) {
leftAlignAct = new QAction(QIcon(rsrcPath + "/textleft.png"),tr("左对齐(&L)"), grp);
centerAct = new QAction(QIcon(rsrcPath + "/textcenter.png"),tr("居中(&E)"), grp);
rightAlignAct = new QAction(QIcon(rsrcPath + "/textright.png"),tr("右对齐(&R)"), grp);
} else {
rightAlignAct = new QAction(QIcon(rsrcPath + "/textright.png"),tr("右对齐(&R)"), grp);
centerAct = new QAction(QIcon(rsrcPath + "/textcenter.png"),tr("居中(&E)"), grp);
leftAlignAct = new QAction(QIcon(rsrcPath + "/textleft.png"),tr("左对齐(&L)"), grp);
}
justifyAct = new QAction(QIcon(rsrcPath + "/textjustify.png"),tr("两端对齐(&J)"), grp);
leftAlignAct->setShortcut(Qt::CTRL + Qt::Key_L);
leftAlignAct->setCheckable(true);
leftAlignAct->setToolTip("左对齐");
leftAlignAct->setStatusTip(tr("将文字左对齐"));
centerAct->setShortcut(Qt::CTRL + Qt::Key_E);
centerAct->setCheckable(true);
centerAct->setToolTip("居中");
centerAct->setStatusTip(tr("将文字居中对齐"));
rightAlignAct->setShortcut(Qt::CTRL + Qt::Key_R);
rightAlignAct->setCheckable(true);
rightAlignAct->setToolTip("右对齐");
rightAlignAct->setStatusTip(tr("将文字右对齐"));
justifyAct->setShortcut(Qt::CTRL + Qt::Key_J);
justifyAct->setCheckable(true);
justifyAct->setToolTip("两端对齐");
justifyAct->setStatusTip(tr("将文字左右两端同时对齐,并根据需要增加字间距"));
QPixmap pix(16, 16);
pix.fill(Qt::red);
colorAct = new QAction(pix, tr("颜色(&C)..."), this);
colorAct->setToolTip("颜色");
colorAct->setStatusTip(tr("设置文字颜色"));
connect(colorAct, SIGNAL(triggered()), this, SLOT(textColor()));
这里用到了 QActionGroup 类,它将菜单动作分组。
在上面的代码创建了一个 Action 组 grp。由于 Action 组默认是互斥的,所以同一时刻只有一个会被选中。
在“myword.h”文件中,添加 textAlign() 声明,以及对应cpp文件,添加定义:
//myword.h
private slots:
void textAlign(QAction *a);
//myword.cpp
void MyWord::textAlign(QAction *a)
{
}
暂时不写其中的代码,只是定义函数体。
在“myword.cpp”文件,函数 createMenus() 的代码中添加:
//【格式】主菜单
formatMenu = menuBar()->addMenu(tr("格式(&O)"));
fontMenu = formatMenu->addMenu(tr("字体(&D)")); //【字体】子菜单
fontMenu->addAction(boldAct);
fontMenu->addAction(italicAct);
fontMenu->addAction(underlineAct);
alignMenu = formatMenu->addMenu(tr("段落")); //【段落】子菜单
alignMenu->addAction(leftAlignAct);
alignMenu->addAction(centerAct);
alignMenu->addAction(rightAlignAct);
alignMenu->addAction(justifyAct);
运行效果如下:
2.4 窗口 和 帮助 主菜单
“窗口”主菜单功能项应包含:关闭O,关闭所有A,平铺T,层叠C,下一个X,前一个V。
在“myword.h”头文件中,定义“窗口”主菜单指针,以及各个功能项的动作:
QMenu *windowMenu;
QAction *closeAct; //【窗口】主菜单
QAction *closeAllAct;
QAction *tileAct;
QAction *cascadeAct;
QAction *nextAct;
QAction *previousAct;
QAction *separatorAct;
QAction *aboutAct; //【帮助】主菜单
QAction *aboutQtAct;
在“myword.cpp”文件,函数 createActions() 的代码中添加:
/*【窗口】菜单动作集*/
closeAct = new QAction(tr("关闭(&O)"), this);
closeAct->setStatusTip(tr("关闭活动文档子窗口"));
//connect(closeAct, SIGNAL(triggered()),mdiArea, SLOT(closeActiveSubWindow())); //不用自己实现
closeAllAct = new QAction(tr("关闭所有(&A)"), this);
closeAllAct->setStatusTip(tr("关闭所有子窗口"));
//connect(closeAllAct, SIGNAL(triggered()),mdiArea, SLOT(closeAllSubWindows())); //不用自己实现
tileAct = new QAction(tr("平铺(&T)"), this);
tileAct->setStatusTip(tr("平铺子窗口"));
//connect(tileAct, SIGNAL(triggered()), mdiArea, SLOT(tileSubWindows())); //不用自己实现
cascadeAct = new QAction(tr("层叠(&C)"), this);
cascadeAct->setStatusTip(tr("层叠子窗口"));
//connect(cascadeAct, SIGNAL(triggered()), mdiArea, SLOT(cascadeSubWindows())); //不用自己实现
nextAct = new QAction(tr("下一个(&X)"), this);
nextAct->setShortcuts(QKeySequence::NextChild);
nextAct->setStatusTip(tr("移动焦点到下一个子窗口"));
//connect(nextAct, SIGNAL(triggered()),mdiArea, SLOT(activateNextSubWindow())); //不用自己实现
previousAct = new QAction(tr("前一个(&V)"), this);
previousAct->setShortcuts(QKeySequence::PreviousChild);
previousAct->setStatusTip(tr("移动焦点到前一个子窗口"));
//connect(previousAct, SIGNAL(triggered()),mdiArea, SLOT(activatePreviousSubWindow())); //不用自己实现
separatorAct = new QAction(this);
separatorAct->setSeparator(true);
/*【帮助】菜单动作集*/
aboutAct = new QAction(tr("关于(&A)"), this);
aboutAct->setStatusTip(tr("关于 Myself Word"));
//connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));
aboutQtAct = new QAction(tr("关于 Qt(&Q)"), this);
aboutQtAct->setStatusTip(tr("关于 Qt 库"));
//connect(aboutQtAct, SIGNAL(triggered()), qApp, SLOT(aboutQt()));
在“myword.cpp”文件,函数 createMenus() 的代码中添加:
//【窗口】主菜单
windowMenu = menuBar()->addMenu(tr("窗口(&W)"));
//updateWindowMenu();
//connect(windowMenu, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu()));
menuBar()->addSeparator();
2.5 图标问题
把图标的问题解决下,主要是路径的问题。
这段改成绝对路径即可。当然,这里可以先不改,后面会创建 qrc 文件来管理资源。
const QString rsrcPath = "D:/qt_project/MyselfWord/images";
3. 工具栏设计
工具栏共有四个工具条,其中三个分别对应 “文件”、“编辑” 和:“格式” 菜单的功能。最后一个为组合选择栏,它提供一组选择框控件。
3.1 工具条开发
在“myword.h”头文件中声明:
class QComboBox;
class QFontComboBox;
private:
void createToolBars();
//工具栏
QToolBar *fileToolBar; //文件 工具条
QToolBar *editToolBar; //编辑 工具条
QToolBar *formatToolBar; //格式 工具条
QToolBar *comboToolBar; //组合选择框
QComboBox *comboStyle; //子控件 标号与编号类型选择框
QFontComboBox *comboFont; //子控件 字体选择框
QComboBox *comboSize; //子控件 字号选择框
在“myword.cpp”实现 createToolBars() 函数,别忘记构造函数添加成员函数:
void MyWord::createToolBars()
{
//"文件"工具栏
fileToolBar = addToolBar(tr("文件"));
fileToolBar->addAction(newAct);
fileToolBar->addAction(openAct);
fileToolBar->addAction(saveAct);
fileToolBar->addSeparator(); //分隔条
fileToolBar->addAction(printAct);
//"编辑"工具栏
editToolBar = addToolBar(tr("编辑"));
editToolBar->addAction(undoAct);
editToolBar->addAction(redoAct);
editToolBar->addSeparator(); //分隔条
editToolBar->addAction(cutAct);
editToolBar->addAction(copyAct);
editToolBar->addAction(pasteAct);
//"格式"工具栏
formatToolBar = addToolBar(tr("格式"));
formatToolBar->addAction(boldAct);
formatToolBar->addAction(italicAct);
formatToolBar->addAction(underlineAct);
formatToolBar->addSeparator(); //分隔条
formatToolBar->addAction(leftAlignAct);
formatToolBar->addAction(centerAct);
formatToolBar->addAction(rightAlignAct);
formatToolBar->addAction(justifyAct);
formatToolBar->addSeparator(); //分隔条
formatToolBar->addAction(colorAct);
//组合工具栏
addToolBarBreak(Qt::TopToolBarArea); //使这个工具条在界面上另起一行显示
comboToolBar = addToolBar(tr("组合选择"));
comboStyle = new QComboBox();
comboToolBar->addWidget(comboStyle);
comboStyle->addItem("标准");
comboStyle->addItem("项目符号 (●)");
comboStyle->addItem("项目符号 (○)");
comboStyle->addItem("项目符号 (■)");
comboStyle->addItem("编号 (⒈⒉⒊)");
comboStyle->addItem("编号 ( a.b.c.)");
comboStyle->addItem("编号 ( A.B.C.)");
comboStyle->addItem("编号 (ⅰ.ⅱ.ⅲ.)");
comboStyle->addItem("编号 (Ⅰ.Ⅱ.Ⅲ.)");
comboStyle->setStatusTip("段落加标号或编号");
connect(comboStyle, SIGNAL(activated(int)), this, SLOT(textStyle(int)));
comboFont = new QFontComboBox();
comboToolBar->addWidget(comboFont);
comboFont->setStatusTip("更改字体");
connect(comboFont, SIGNAL(activated(QString)), this, SLOT(textFamily(QString)));
comboSize = new QComboBox();
comboToolBar->addWidget(comboSize);
comboSize->setEditable(true);
comboSize->setStatusTip("更改字号");
QFontDatabase db;
foreach(int size, db.standardSizes())
comboSize->addItem(QString::number(size));
connect(comboSize, SIGNAL(activated(QString)), this, SLOT(textSize(QString)));
comboSize->setCurrentIndex(comboSize->findText(QString::number(QApplication::font().pointSize())));
}
3.2 导入图标资源
图片资源存放在 D:\qt_project\MyselfWord 下。
给工程项目添加 Qt Resource File,也就是 qrc 后缀的文件。
添加以下内容:
<RCC>
<qresource prefix="/">
<file>images/editcopy.png</file>
<file>images/editcut.png</file>
<file>images/editpaste.png</file>
<file>images/editredo.png</file>
<file>images/editundo.png</file>
<file>images/filenew.png</file>
<file>images/fileopen.png</file>
<file>images/fileprint.png</file>
<file>images/filesave.png</file>
<file>images/textbold.png</file>
<file>images/textcenter.png</file>
<file>images/textitalic.png</file>
<file>images/textjustify.png</file>
<file>images/textleft.png</file>
<file>images/textright.png</file>
<file>images/textunder.png</file>
</qresource>
</RCC>
在 “myword.cpp”文件开头添加资源路径:
const QString rsrcPath = ":/images";
然后,运行效果如下:
4. 子窗口管理
4.1 新建子窗口
前面已经建立了子窗口的中心部件 MyChild 类,它继承自 QTextEDit 类。
下面便可以使用这个类来创建文档子窗口。
“myword.h”
class MyChild;
private slots:
void fileNew();
MyChild *createMyChild();
“myword.cpp”
#include "mychild.h"
MyChild *MyWord::createMyChild()
{
MyChild *child = new MyChild;
mdiArea->addSubWindow(child);
connect(child, SIGNAL(copyAvailable(bool)),cutAct, SLOT(setEnabled(bool)));
connect(child, SIGNAL(copyAvailable(bool)),copyAct, SLOT(setEnabled(bool)));
return child;
}
函数 createActions() 中,去掉注释。
connect(newAct, SIGNAL(triggered()), this, SLOT(fileNew()));
void MyWord::fileNew()
{
MyChild *child = createMyChild();
child->newFile();
child->show();
enabledText(); //使得字体设置菜单可用
}
“myword.h”
private:
void enabledText(); //使得【格式】下的各个子菜单项可用
“myword.cpp”
void MyWord::enabledText()
{
boldAct->setEnabled(true);
italicAct->setEnabled(true);
underlineAct->setEnabled(true);
leftAlignAct->setEnabled(true);
centerAct->setEnabled(true);
rightAlignAct->setEnabled(true);
justifyAct->setEnabled(true);
colorAct->setEnabled(true);
}
文件 → 新建,出现 “文档1” 子窗口。
4.2 更新菜单状态
在“myword.h”
class QMdiSubWindow;
private slots:
void updateMenus(); //更新菜单
private:
MyChild *activeMyChild(); //活动窗口
在“myword.cpp”
connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow*)),this, SLOT(updateMenus()));
updateMenus();
updateMenus() 函数
void MyWord::updateMenus()
{
//至少有一个子文档打开着的情况
bool hasMyChild = (activeMyChild()!=0);
saveAct->setEnabled(hasMyChild);
saveAsAct->setEnabled(hasMyChild);
printAct->setEnabled(hasMyChild);
printPreviewAct->setEnabled(hasMyChild);
pasteAct->setEnabled(hasMyChild);
closeAct->setEnabled(hasMyChild);
closeAllAct->setEnabled(hasMyChild);
tileAct->setEnabled(hasMyChild);
cascadeAct->setEnabled(hasMyChild);
nextAct->setEnabled(hasMyChild);
previousAct->setEnabled(hasMyChild);
separatorAct->setVisible(hasMyChild);
//文档打开着并且其中有内容被选中的情况
bool hasSelection = (activeMyChild() && activeMyChild()->textCursor().hasSelection());
cutAct->setEnabled(hasSelection);
copyAct->setEnabled(hasSelection);
boldAct->setEnabled(hasSelection);
italicAct->setEnabled(hasSelection);
underlineAct->setEnabled(hasSelection);
leftAlignAct->setEnabled(hasSelection);
centerAct->setEnabled(hasSelection);
rightAlignAct->setEnabled(hasSelection);
justifyAct->setEnabled(hasSelection);
colorAct->setEnabled(hasSelection);
}
activeMyChild() 函数
MyChild *MyWord::activeMyChild()
{
if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow())
return qobject_cast<MyChild *>(activeSubWindow->widget());
return 0;
}
4.3 添加子窗口列表
“myword.h”
class QSignalMapper;
private:
QSignalMapper *windowMapper;
private slots:
void updateWindowMenu();
“myword.cpp”
windowMapper = new QSignalMapper(this);
connect(windowMapper, SIGNAL(mapped(QWidget*)),this, SLOT(setActiveSubWindow(QWidget*)));
updateWindowMenu();
connect(windowMenu, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu()));
void MyWord::updateWindowMenu()
{
windowMenu->clear();
windowMenu->addAction(closeAct);
windowMenu->addAction(closeAllAct);
windowMenu->addSeparator();
windowMenu->addAction(tileAct);
windowMenu->addAction(cascadeAct);
windowMenu->addSeparator();
windowMenu->addAction(nextAct);
windowMenu->addAction(previousAct);
windowMenu->addAction(separatorAct);
QList<QMdiSubWindow *> windows = mdiArea->subWindowList();
separatorAct->setVisible(!windows.isEmpty());
//显示当前打开着的文档子窗口项
for (int i = 0; i < windows.size(); ++i) {
MyChild *child = qobject_cast<MyChild *>(windows.at(i)->widget());
QString text;
if (i < 9) {
text = tr("&%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
} else {
text = tr("%1 %2").arg(i + 1).arg(child->userFriendlyCurrentFile());
}
QAction *action = windowMenu->addAction(text);
action->setCheckable(true);
action ->setChecked(child == activeMyChild());
connect(action, SIGNAL(triggered()), windowMapper, SLOT(map()));
windowMapper->setMapping(action, windows.at(i));
}
enabledText(); //使得字体设置菜单可用
}
4.4 窗口关闭
protected:
void closeEvent(QCloseEvent *event);
void MyWord::closeEvent(QCloseEvent *event)
{
mdiArea->closeAllSubWindows();
if (mdiArea->currentSubWindow()) {
event->ignore();
} else {
event->accept();
}
}
新建四个文档,然后窗口菜单的显示效果。
然后,点击“关闭”就会把当前活动的文档关闭,点击 “关闭所有”,就会把全部的4个文档关闭。
5. 界面生成试运行
private:
void createStatusBar();
void MyWord::createStatusBar()
{
statusBar()->showMessage(tr("就绪"));
}
三、基本编辑功能实现
开发好软件界面后,就可以向系统中添加各种各样的功能。
首先实现的基本编辑功能包括:打开、保存、另存为、剪切、复制、粘贴、撤销和回复。
1. 打开文件
实现打开文件功能需要在子窗口类 MyChild 中定义加载文件操作。
1.1 加载文件操作步骤
1)打开指定的文件,并读取文件内容到编辑器。
2)设置当前文件的 setCurrentFile() ,该函数可以获取文件路径,完成文件和窗口状态的设置。
3)关联文档内容改变信号到显示文档更改状态槽 documentWasModified() 。
加载文件操作采用 loadFile() 函数实现。
1.2 加载文件操作实现
//mychild.h
public:
bool loadFile(const QString &fileName);
private:
void setCurrentFile(const QString &fileName);
//mychild.cpp
bool MyChild::loadFile(const QString &fileName)
{
if (fileName.isEmpty())
return false;
QFile file(fileName);
if (!file.exists()) {
qWarning() << "File does not exist:" << fileName;
return false;
}
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
qWarning() << "Failed to open file for reading:" << fileName;
return false;
}
// 假设文件是以 UTF-8 编码的,这是 HTML 文件的常见情况
QTextStream in(&file);
QString content = in.readAll();
// 不再需要 QTextCodec,因为我们已经读取了 UTF-8 编码的字符串
// 检查内容是否为富文本(这里简单假设 HTML 就是富文本)
if (content.contains("<html>") || content.contains("<HTML>")) { // 简单的检查,可能不够准确
this->setHtml(content);
} else {
// 如果不是明显的 HTML,我们可以尝试将其视为纯文本
// 注意:这里我们不再转换编码,因为 content 已经是 QString 了
this->setPlainText(content);
}
setCurrentFile(fileName);
// 连接信号和槽以监控文档更改(这部分看起来没问题)
connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
return true;
}
void MyChild::setCurrentFile(const QString &fileName)
{
curFile = QFileInfo(fileName).canonicalFilePath();
isUntitled = false;
document()->setModified(false);
setWindowModified(false);
setWindowTitle(userFriendlyCurrentFile() + "[*]");
}
1.3 加载文件操作的调用
//myword.h
private slots:
void fileOpen();
privete:
QMdiSubWindow *findMyChild(const QString &fileName);
//myword.cpp
void MyWord::createActions()
{
connect(newAct, SIGNAL(triggered()), this, SLOT(fileNew()));
}
void MyWord::fileOpen()
{
QString fileName = QFileDialog::getOpenFileName(this, tr("打开"),QString(), tr("HTML 文档 (*.htm *.html);;所有文件 (*.*)"));
if (!fileName.isEmpty()) {
QMdiSubWindow *existing = findMyChild(fileName);
if (existing) {
mdiArea->setActiveSubWindow(existing);
return;
}
MyChild *child = createMyChild();
if (child->loadFile(fileName)) {
statusBar()->showMessage(tr("文件已载入"), 2000);
child->show();
enabledText(); //使得字体设置菜单可用
} else {
child->close();
}
}
}
//打开文件用
QMdiSubWindow *MyWord::findMyChild(const QString &fileName)
{
QString canonicalFilePath = QFileInfo(fileName).canonicalFilePath();
foreach (QMdiSubWindow *window, mdiArea->subWindowList()) {
MyChild *myChild = qobject_cast<MyChild *>(window->widget());
if (myChild->currentFile() == canonicalFilePath)
return window;
}
return 0;
}
提取准备一个1.html在bulid的Debug目录下。
2. 保存文件操作实现
保存文件功能分为“保存”和“另存为”两种操作,这两种操作都需要在子窗口 MyChild 中定义。
2.1 保存文件操作步骤
保存 save() 的逻辑
1)如果文件没有被保存过(用 isUntitled 判断),则执行 “另存为” 操作 saveAs()。
2)否则直接 “保存” 文件 saveFile(),改函数首先打开指定文件,然后将编辑器的内容写入该文件,最后设置当前文件 setCurrentFile()。
另存为 saveAs() 的逻辑
1)从“文件”对话框获取文件路径。
2)如果路径不为空,则保存文件 saveFile()。
2.2 保存文件操作实现
//mychild.h
public:
bool save();
bool saveAs();
bool saveFile(QString fileName);
//mychild.cpp
bool MyChild::save()
{
if (isUntitled) {
return saveAs();
} else {
return saveFile(curFile);
}
}
bool MyChild::saveAs()
{
QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"),curFile,tr("HTML 文档 (*.htm *.html);;所有文件 (*.*)"));
if (fileName.isEmpty())
return false;
return saveFile(fileName);
}
bool MyChild::saveFile(QString fileName)
{
if (!(fileName.endsWith(".htm", Qt::CaseInsensitive) || fileName.endsWith(".html", Qt::CaseInsensitive))) {
fileName += ".html"; // 默认保存为 HTML 文档
}
QTextDocumentWriter writer(fileName);
bool success = writer.write(this->document());
if (success)
setCurrentFile(fileName);
return success;
}
2.3 保存文件操作调用
void MyWord::createActions()
{
connect(saveAct, SIGNAL(triggered()), this, SLOT(fileSave()));
connect(saveAsAct, SIGNAL(triggered()), this, SLOT(fileSaveAs()));
}
//myword.h
private slots:
void fileSave();
void fileSaveAs();
//myword.cpp
void MyWord::fileSave()
{
if (activeMyChild() && activeMyChild()->save())
statusBar()->showMessage(tr("保存成功"), 2000);
}
void MyWord::fileSaveAs()
{
if (activeMyChild() && activeMyChild()->saveAs())
statusBar()->showMessage(tr("保存成功"), 2000);
}
2.4 提醒保存文件
//mychild.h
private:
bool maybeSave();
protected:
void closeEvent(QCloseEvent *event);
//mychild.cpp
bool MyChild::maybeSave()
{
if (!document()->isModified())
return true;
QMessageBox::StandardButton ret;
ret = QMessageBox::warning(this, tr("Myself Qt Word"),tr("文档'%1'已被修改,保存吗?").arg(userFriendlyCurrentFile()),QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
if (ret == QMessageBox::Save)
return save();
else if (ret == QMessageBox::Cancel)
return false;
return true;
}
void MyChild::closeEvent(QCloseEvent *event)
{
if (maybeSave()) {
event->accept();
} else {
event->ignore();
}
}
3. 文本操作
最基本的文本操作包括:撤销、重做、剪切、复制和粘贴。又QTextEdit 类提供。
3.1 撤销与重做
编辑菜单动作集
connect(undoAct, SIGNAL(triggered()), this, SLOT(undo())); //不用子窗口类去实现
connect(redoAct, SIGNAL(triggered()), this, SLOT(redo())); //不用子窗口类去实现
3.2 剪切、复制和粘贴
connect(cutAct, SIGNAL(triggered()), this, SLOT(cut()));
connect(copyAct, SIGNAL(triggered()), this, SLOT(copy()));
connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste())); //不用子窗口类去实现
//myword.h
private slots:
void undo();
void redo();
void cut();
void copy();
void paste();
//myword.cpp
void MyWord::undo()
{
if(activeMyChild())
activeMyChild()->undo();
}
void MyWord::redo()
{
if(activeMyChild())
activeMyChild()->redo();
}
void MyWord::cut()
{
if (activeMyChild())
activeMyChild()->cut();
}
void MyWord::copy()
{
if (activeMyChild())
activeMyChild()->copy();
}
void MyWord::paste()
{
if (activeMyChild())
activeMyChild()->paste();
}
到此,功能完成的差不多了。
四、文档排版美化功能实现
1. 字体格式设置
基本设置包括:加粗、倾斜和加下划线。
1.1 子窗口的操作
//mychild.h
pubilc:
void mergeFormatOnWordOrSelection(const QTextCharFormat &format); //格式字体设置
//mychild.cpp
//格式设置
void MyChild::mergeFormatOnWordOrSelection(const QTextCharFormat &format)
{
QTextCursor cursor = this->textCursor();
if (!cursor.hasSelection())
cursor.select(QTextCursor::WordUnderCursor);
cursor.mergeCharFormat(format);
this->mergeCurrentCharFormat(format);
}
1.2 主窗口调用格式函数
connect(boldAct, SIGNAL(triggered()), this, SLOT(textBold()));
connect(italicAct, SIGNAL(triggered()), this, SLOT(textItalic()));
connect(underlineAct, SIGNAL(triggered()), this, SLOT(textUnderline()));
//myword.h
private slots:
void textBold();
void textItalic();
void textUnderline();
//myword.cpp
void MyWord::textBold()
{
QTextCharFormat fmt;
fmt.setFontWeight(boldAct->isChecked() ? QFont::Bold : QFont::Normal);
if(activeMyChild())
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}
void MyWord::textItalic()
{
QTextCharFormat fmt;
fmt.setFontItalic(italicAct->isChecked());
if(activeMyChild())
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}
void MyWord::textUnderline()
{
QTextCharFormat fmt;
fmt.setFontUnderline(underlineAct->isChecked());
if(activeMyChild())
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}
1.3 字体、字号选择框
void MyWord::createToolBars(){
connect(comboFont, SIGNAL(activated(QString)), this, SLOT(textFamily(QString)));
connect(comboSize, SIGNAL(activated(QString)), this, SLOT(textSize(QString)));
comboSize->setCurrentIndex(comboSize->findText(QString::number(QApplication::font().pointSize())));
}
//myword.h
private slots:
void textFamily(const QString &f);
void textSize(const QString &p);
//myword.cpp
void MyWord::textFamily(const QString &f)
{
QTextCharFormat fmt;
fmt.setFontFamily(f);
if(activeMyChild())
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}
void MyWord::textSize(const QString &p)
{
qreal pointSize = p.toFloat();
if (p.toFloat() > 0) {
QTextCharFormat fmt;
fmt.setFontPointSize(pointSize);
if(activeMyChild())
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}
}
这边5代和6代Qt,有一定的不同。
2. 段落对齐设置
//mychild.h
public:
void setAlign(int align);
//mychild.cpp
//段落对齐设置
void MyChild::setAlign(int align)
{
if (align == 1)
this->setAlignment(Qt::AlignLeft | Qt::AlignAbsolute);
else if (align == 2)
this->setAlignment(Qt::AlignHCenter);
else if (align == 3)
this->setAlignment(Qt::AlignRight | Qt::AlignAbsolute);
else if (align == 4)
this->setAlignment(Qt::AlignJustify);
}
//myword.cpp
void MyWord::textAlign(QAction *a)
{
if(activeMyChild())
{
if (a == leftAlignAct)
activeMyChild()->setAlign(1);
else if (a == centerAct)
activeMyChild()->setAlign(2);
else if (a == rightAlignAct)
activeMyChild()->setAlign(3);
else if (a == justifyAct)
activeMyChild()->setAlign(4);
}
}
3. 颜色设置
//Action
connect(colorAct, SIGNAL(triggered()), this, SLOT(textColor()));
//myword.h
private:
void colorChanged(const QColor &c);
private slots:
void textColor();
//myword.cpp
void MyWord::textColor()
{
if(activeMyChild())
{
QColor col = QColorDialog::getColor(activeMyChild()->textColor(), this);
if (!col.isValid())
return;
QTextCharFormat fmt;
fmt.setForeground(col);
activeMyChild()->mergeFormatOnWordOrSelection(fmt);
colorChanged(col);
}
}
void MyWord::colorChanged(const QColor &c)
{
QPixmap pix(16, 16);
pix.fill(c);
colorAct->setIcon(pix);
}
4. 段落标号、编号
4.1 子窗口设置段落标号、编号操作
//mychild.h
public:
void setStyle(int style);
//mychild.cpp
//段落标号、编号
void MyChild::setStyle(int style)
{
QTextCursor cursor = this->textCursor();
if (style != 0) {
QTextListFormat::Style stylename = QTextListFormat::ListDisc;
switch (style) {
default:
case 1:
stylename = QTextListFormat::ListDisc;
break;
case 2:
stylename = QTextListFormat::ListCircle;
break;
case 3:
stylename = QTextListFormat::ListSquare;
break;
case 4:
stylename = QTextListFormat::ListDecimal;
break;
case 5:
stylename = QTextListFormat::ListLowerAlpha;
break;
case 6:
stylename = QTextListFormat::ListUpperAlpha;
break;
case 7:
stylename = QTextListFormat::ListLowerRoman;
break;
case 8:
stylename = QTextListFormat::ListUpperRoman;
break;
}
cursor.beginEditBlock();
QTextBlockFormat blockFmt = cursor.blockFormat();
QTextListFormat listFmt;
if (cursor.currentList()) {
listFmt = cursor.currentList()->format();
} else {
listFmt.setIndent(blockFmt.indent() + 1);
blockFmt.setIndent(0);
cursor.setBlockFormat(blockFmt);
}
listFmt.setStyle(stylename);
cursor.createList(listFmt);
cursor.endEditBlock();
} else {
QTextBlockFormat bfmt;
bfmt.setObjectIndex(-1);
cursor.mergeBlockFormat(bfmt);
}
}
4.2 实现段落标号、编号选择框
//createToolBars()
//组合工具栏
connect(comboStyle, SIGNAL(activated(int)), this, SLOT(textStyle(int)));
4.3 主窗口调用
//myword.h
private slots:
void textStyle(int styleIndex);
//myword.cpp
void MyWord::textStyle(int styleIndex)
{
if(activeMyChild())
{
activeMyChild()->setStyle(styleIndex);
}
}
5. 文档打印与预览
5.1 添加打印模块支持
在 MyselfWord.pro 中添加支持:
//6代QT
QT += printsupport
//5代QT
qtHaveModule(printsupport): QT += printsupport
5.2 实现打印及预览功能
//Action
connect(printAct, SIGNAL(triggered()), this, SLOT(filePrint()));
connect(printPreviewAct, SIGNAL(triggered()), this, SLOT(filePrintPreview()));
//myword.h
private slots:
void filePrint();
void filePrintPreview();
void printPreview(QPrinter *);
//myword.cpp
void MyWord::filePrint()
{
QPrinter printer(QPrinter::HighResolution);
QPrintDialog *dlg = new QPrintDialog(&printer, this);
if (activeMyChild()->textCursor().hasSelection())
//6代QT
//dlg->setOption(QAbstractPrintDialog::PrintSelection);
dlg->addEnabledOption(QAbstractPrintDialog::PrintSelection);
dlg->setWindowTitle(tr("打印文档"));
if (dlg->exec() == QDialog::Accepted)
activeMyChild()->print(&printer);
delete dlg;
}
void MyWord::filePrintPreview()
{
QPrinter printer(QPrinter::HighResolution);
QPrintPreviewDialog preview(&printer, this);
connect(&preview, SIGNAL(paintRequested(QPrinter*)), SLOT(printPreview(QPrinter*)));
preview.exec();
}
void MyWord::printPreview(QPrinter *printer)
{
activeMyChild()->print(printer);
}