【Qt_C++】Word类文档编辑软件

  用Qt5实现的多文档文字编辑软件,支持新建/打开/保存/另存为/打印/打印预览、撤销/重做、剪切/复制/粘贴、文字加粗/斜体/下划线、段落对齐、段落编号/标号、字体和字号选择、颜色设置等常见功能。主要分为以下几部分,
main.cpp:应用程序入口
myword.h/.cpp:主窗口类(菜单栏、工具栏、状态栏、多文档管理)
mychild.h/.cpp:子窗口类(具体的文档内容编辑逻辑,继承QTextEdit)

1. main.cpp

//main.cpp
#include "myword.h"

#include <QApplication>

int main(int argc, char *argv[]) {
  QApplication a(argc, argv);
  MyWord w;
  w.show();

  return a.exec();
}

  程序入口,初始化QApplicatio来整个Qt应用的生命周期,创建主窗口MyWord,并启动事件循环a.exec(),等待用户操作。


2. mychild.h /.cpp

  MyChild类继承自QTextEdit,是实际的文档编辑窗口。一个MyChild就是一个子文档,支持富文本、文件加载保存、文字和段落格式等操作。

//mychild.h 
#ifndef MYCHILD_H
#define MYCHILD_H

#include <QPrinter>
#include <QTextEdit>
#include <QtWidgets>

class MyChild : public QTextEdit {
  Q_OBJECT
public:
  MyChild();

  void newFile();                           // 新建word文件
  bool loadFile(const QString &fileName);   // 导入文件
  bool save();                              // 保存
  bool saveAs();                            // 另存为
  bool saveFile(QString fileName);          // 保存文件
  QString userFriendlyCurrentFile();        // 用户友好型文件(名)
  QString currentFile() { return curFile; } // 当前文件
  void mergeFormatOnWordOrSelection(const QTextCharFormat &format); // 格式字体
  void setAlign(int align);                                         // 段落对齐
  void setStyle(int style); // 段落标号、编号
protected:
  void closeEvent(QCloseEvent *event); // 关闭事件
private slots:
  void documentWasModified(); // 修改状态
private:
  bool maybeSave();                                  // 是否保存
  void setCurrentFile(const QString &fileName);      // 设置当前文件
  QString strippedName(const QString &fullFileName); // 提取文件名(不包含路径)

  QString curFile; // 当前文件
  bool isUntitled; // 文件命名状态
};

#endif // MYCHILD_H

文件操作相关:newFile()、loadFile()、maybeSave()等
文件状态相关:isUntitled()、documentWasModified()、closeEvent()
格式相关:mergeFormatOnWordOrSelection()、setAlign()等

//mychild.cpp
#include "mychild.h"

MyChild::MyChild() {
  setAttribute(Qt::WA_DeleteOnClose); // 关闭窗口时销毁,
  isUntitled = true;
}

void MyChild::newFile() // 新建文件
{
  static int sequenceNumber = 1;

  isUntitled = true;
  curFile = tr("Word文档-%1").arg(sequenceNumber++);
  setWindowTitle(curFile);
}

bool MyChild::loadFile(const QString &fileName) // 导入文件
{
  if (!fileName.isEmpty()) {
    if (!QFile::exists(fileName))
      return false;
    QFile file(fileName);
    if (!file.open(QFile::ReadOnly))
      return false;

    // QByteArray可完整存储原始字节数据(包括“\ 0” )
    // 不像传统的  char *  字符串把  \0  当成“字符串结束符”,一旦遇到就停止
    QByteArray data = file.readAll();

    QTextCodec *codec = Qt::codecForHtml(data);
    QString str = codec->toUnicode(data);

    if (Qt::mightBeRichText(str)) { // //富文本设置为HTml
      this->setHtml(str);
    } else { // 非富文本设置为简单字符串格式
      str = QString::fromLocal8Bit(data);
      this->setPlainText(str);
    }
    setCurrentFile(fileName);
    connect(document(), SIGNAL(contentsChanged()), this,
            SLOT(documentWasModified()));
    return true;
  }
}

void MyChild::documentWasModified() {
  // 若文件有修改,设置相关记号
  setWindowModified(document()->isModified());
}

QString MyChild::strippedName(
    const QString &fullFileName) // 提取文件名称(用户友好型当前文件名)
{
  return QFileInfo(fullFileName).fileName();
}

QString MyChild::userFriendlyCurrentFile() // 用户友好型当前文件
{
  return strippedName(curFile);
}

void MyChild::setCurrentFile(const QString &fileName) // 设置当前文件的状态
{
  curFile = QFileInfo(fileName).canonicalFilePath();
  isUntitled = false;
  document()->setModified(false);
  setWindowModified(false);
  setWindowTitle(userFriendlyCurrentFile() + "[*]");
}

void MyChild::closeEvent(QCloseEvent *event) {
  if (maybeSave()) {
    event->accept();
  } else {
    event->ignore();
  }
}

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;    // 用户选择"丢弃/不保存"
}

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;
}

// 字符格式(字体、颜色、字符间距等等)设置
void MyChild::mergeFormatOnWordOrSelection(const QTextCharFormat &format) {
  QTextCursor cursor = this->textCursor();
  if (!cursor.hasSelection())
    cursor.select(QTextCursor::WordUnderCursor);
  cursor.mergeCharFormat(format);
  this->mergeCurrentCharFormat(format);
}

// 段落对齐
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); // 两端对齐。
}

// 段落、列表样式
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; // 小写阿拉伯数字,最大4999
      break;
    case 5:
      stylename = QTextListFormat::ListLowerAlpha; // 小写拉丁字符,按字母顺序
      break;
    case 6:
      stylename = QTextListFormat::ListUpperAlpha; // 大写拉丁字符,按字母顺序
      break;
    case 7:
      stylename = QTextListFormat::ListLowerRoman; // 小写罗马数字,最大4999
      break;
    case 8:
      stylename = QTextListFormat::ListUpperRoman; // 大写罗马数字,最大4999
      break;
    }

    // 段落、列表样式的修改相互依赖,需要进行原子操作
    cursor.beginEditBlock();
    // 设置段落样式
    QTextBlockFormat blockFmt = cursor.blockFormat();

    QTextListFormat listFmt;

    if (cursor.currentList()) {
      listFmt = cursor.currentList()->format();
    } else {
      blockFmt.setIndent(0);
      cursor.setBlockFormat(blockFmt);
      listFmt.setIndent(blockFmt.indent() + 1);
    }

    listFmt.setStyle(stylename);
    cursor.createList(listFmt);

    cursor.endEditBlock();
  } else {
    QTextBlockFormat bfmt;
    bfmt.setObjectIndex(-1);
    cursor.mergeBlockFormat(bfmt);
  }
}

对.h文件中声明的函数进行实现。

bool MyChild::loadFile(const QString &fileName) // 导入文件
{
    ...
    QByteArray data = file.readAll();

    QTextCodec *codec = Qt::codecForHtml(data);
    QString str = codec->toUnicode(data);

    if (Qt::mightBeRichText(str)) { // //富文本设置为HTml
      this->setHtml(str);
    } else { // 非富文本设置为简单字符串格式
      str = QString::fromLocal8Bit(data);
      this->setPlainText(str);
    }
    setCurrentFile(fileName);
    connect(document(), SIGNAL(contentsChanged()), this,
            SLOT(documentWasModified()));
    return true;
  }
}

  文件数据读入data(QByteArray能完整保存\0在内的原始字节,不会像传统的 char* 在遇到 \0就提前终止),随后默认把内容当作 HTML(富文本)来处理。使用 codecForHtml() 试图识别字节流data的字符编码方案(例如 UTF-8等),再用获得的编码方案通过toUnicode() 解码,得到(QString)str。
  接着判断 str 是否为富文本:如果是,调用 setHtml() 按 HTML 渲染显示。如果不是,则说明内容是普通文本。由于 codecForHtml() 对非 HTML 可能导致乱码。因此必须重新按本地编码(fromLocal8Bit)解码一次原始数据,再调用 setPlainText() 以纯文本形式显示。最后用connect()关联相应槽函数,文档内容发生变化时,自动调用 documentWasModified() 槽函数。

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;
}

  检查文件名。若没有 .htm/.html 后缀,补上 .html,默认保存为 HTML文件。随后通过 QTextDocumentWriter对象将当前文本编辑器的 document() 写入指定文件。写入成功后调用 setCurrentFile(fileName) 更新当前文件名。

bool MyChild::save() // 保存
{
  if (isUntitled) {
    return saveAs();
  } else {
    return saveFile(curFile);
  }
}

  负责决策逻辑。判断文件是否已保存过,决定调用哪个具体的保存方法。具体实现由saveAs()、saveFile()负责。

bool MyChild::maybeSave() //  是否保存
{
  ...
  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)
 ...
}

  若文档已修改,用QMessageBox::StandardButton变量ret保存用户在warning()中的选择,最后根据ret分支执行。

// 字符格式(字体、颜色、字符间距等等)设置
void MyChild::mergeFormatOnWordOrSelection(
    const QTextCharFormat &format) 
{
  QTextCursor cursor = this->textCursor();
  if (!cursor.hasSelection())
    cursor.select(QTextCursor::WordUnderCursor);
  cursor.mergeCharFormat(format);
  this->mergeCurrentCharFormat(format);
}

  用textCursor()获取当前的QTextCursor光标。若光标没有选中内容,默认选择光标所在位置。调用mergeCharFormat(format)在光标选择的区域应用新的字符格式(字体、颜色等)。mergeCurrentCharFormat()更新当前字符格式,设置当前光标位置的字符格式、为后续输入的文本设置默认格式。

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;
    ...
    }

    // 段落、列表样式的修改相互依赖,需要进行原子操作
    cursor.beginEditBlock();
    // 设置段落样式
    QTextBlockFormat blockFmt = cursor.blockFormat();
    QTextListFormat listFmt;

    if (cursor.currentList()) {
      listFmt = cursor.currentList()->format();
    } else {
      blockFmt.setIndent(0);
      cursor.setBlockFormat(blockFmt);
      listFmt.setIndent(blockFmt.indent() + 1);
    }

    listFmt.setStyle(stylename);
    cursor.createList(listFmt);

    cursor.endEditBlock();
  } else {
    QTextBlockFormat bfmt;
    bfmt.setObjectIndex(-1);
    cursor.mergeBlockFormat(bfmt);
  }
}

  根据传入的style选择相应的QTextListFormat::Style(圆点、方块等)。若传入无效值,默认选择"样式为圆圈"。style=0时,清除列表样式,恢复为普通段落。
因为段落缩进和列表缩进紧密关联,使用cursor.beginEditBlock()与cursor.endEditBlock()包裹整个修改过程,保证段落、列表样式的修改是一个原子操作。若当前已有列表,继承其格式。若没有,重置段落缩进,建立新的列表格式。最后调用listFmt.setStyle(stylename)设置选中的样式,再执行cursor.createList(listFmt)将其应用到文本。

void MyChild::setCurrentFile(const QString &fileName) // 设置当前文件的状态
{
  curFile = QFileInfo(fileName).canonicalFilePath();
  isUntitled = false;
  document()->setModified(false);
  setWindowModified(false);
  setWindowTitle(userFriendlyCurrentFile() + "[*]");
}

  设置当前文件状态,在saveAs()、saveFile()、loadFIle()这些完成文件操作的函数中被调用。
setWindowTitle(userFriendlyCurrentFile()+"[*]")是Qt的特殊机制。[*]是占位符,只有当文件被修改时才会显示。

3. myword.h / .cpp

  MyWord是整个应用的主窗口类,继承自QMainWindow。负责菜单栏、工具栏、状态栏的创建,以及多文档(MDI)管理。

//myword.h
#ifndef MYWORD_H
#define MYWORD_H

#include <QMainWindow>

// 第二步:添加相关头文件
#include "mychild.h"
#include <QPrintDialog>
#include <QPrintPreviewDialog>
#include <QPrinter>

class MyWord : public QMainWindow {
  Q_OBJECT

public:
  MyWord(QWidget *parent = nullptr);
  ~MyWord();

protected:
  void closeEvent(QCloseEvent *event);

private slots:
  void fileNew();                // 新建
  void fileOpen();               // 打开
  void fileSave();               // 保存
  void fileSaveAs();             // 另存为
  void filePrint();              // 打印
  void filePrintPreview();       // 文件打印预览
  void printPreview(QPrinter *); // 打印预览

  void undo();  // 撤消
  void redo();  // 重做
  void cut();   // 剪切
  void copy();  // 复制
  void paste(); // 粘贴

  void about(); // 关于

  void textBold();                   // 加粗
  void textItalic();                 // 斜体
  void textUnderline();              // 下划线
  void textAlign(QAction *a);        // 文本对齐判断函数
  void textStyle(int styleIndex);    // 文本样式
  void textFamily(const QString &f); // 文本族
  void textSize(const QString &p);   // 文本大小
  void textColor();                  // 文本颜色

  void updateMenus();                       // 更新菜单
  void updateWindowMenu();                  // 更新窗口菜单
  MyChild *createMyChild();                 // 创建子窗口对象
  void setActiveSubWindow(QWidget *window); // 设置active激活窗口

private:
  void createActions();                   // 创建菜单动作
  void createMenus();                     // 创建菜单
  void createToolBars();                  // 创建工具条
  void createStatusBar();                 // 创建状态条
  void enabledText();                     // 使得【格式】下的各个子菜单项可用
  void fontChanged(const QFont &f);       // 字体更改
  void colorChanged(const QColor &c);     // 颜色更改
  void alignmentChanged(Qt::Alignment a); // 对齐判断

  MyChild *activeMyChild();                            // 激活子窗口
  QMdiSubWindow *findMyChild(const QString &fileName); // 查找子窗口

  QMdiArea *mdiArea; // 多文档界面区域,管理子窗口

  // 窗口菜单信号映射器,处理子窗口切换
  QSignalMapper *windowMapper;

  // 菜单
  QMenu *fileMenu;   // 文件菜单
  QMenu *editMenu;   // 编辑菜单
  QMenu *formatMenu; // 格式菜单
  QMenu *fontMenu;   // 字体子菜单:加粗 倾斜 下划线
  QMenu *alignMenu;  // 对齐子菜单:左 右 居中 两端
  QMenu *windowMenu; // 窗口菜单
  QMenu *helpMenu;   // 帮助菜单

  // 工具栏
  QToolBar *fileToolBar;    // 文件工具条
  QToolBar *editToolBar;    // 编辑工具条
  QToolBar *formatToolBar;  // 格式工具条
  QToolBar *comboToolBar;   // 组合工具条
  QComboBox *comboStyle;    // 子控件 标准组合框
  QFontComboBox *comboFont; // 子控件 字体组合框
  QComboBox *comboSize;     // 子控件 字体大小组合框

  // 动作(Action)
  QAction *newAct;          // 【文件】主菜单 新建
  QAction *openAct;         // 打开
  QAction *saveAct;         // 保存
  QAction *saveAsAct;       // 另存为
  QAction *printAct;        // 打印
  QAction *printPreviewAct; // 打印预览
  QAction *exitAct;         // 退出

  QAction *undoAct;  // 【编辑】主菜单 撤消
  QAction *redoAct;  // 重做
  QAction *cutAct;   // 剪切
  QAction *copyAct;  // 复制
  QAction *pasteAct; // 粘贴

  QAction *boldAct;       // 【格式】主菜单 加粗
  QAction *italicAct;     // 斜体
  QAction *underlineAct;  // 下划画
  QAction *leftAlignAct;  // 左对齐
  QAction *centerAct;     // 居中对象
  QAction *rightAlignAct; // 右对象
  QAction *justifyAct;    // 两端
  QAction *colorAct;      // 颜色

  QAction *closeAct;     // 【窗口】主菜单 关闭
  QAction *closeAllAct;  // 关闭所有
  QAction *tileAct;      // 平铺
  QAction *cascadeAct;   // 层叠
  QAction *nextAct;      // 下一个
  QAction *previousAct;  // 上一个
  QAction *separatorAct; // 分离动作

  QAction *aboutAct;   // 【帮助】主菜单 关于
  QAction *aboutQtAct; // 关于Qt
};
#endif // MYWORD_H

  声明了文件操作、编辑功能、文本格式、窗口管理等等所需的函数和对象。

//myword.cpp
#include "myword.h"

#include "mychild.h"
#include <QtWidgets>

const QString srcpaths = ":/new/prefix1/images";

MyWord::MyWord(QWidget *parent) : QMainWindow(parent) {
  setWindowIcon(QIcon(srcpaths + "/wordlogo2.png"));

  mdiArea = new QMdiArea;
  mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);

  setCentralWidget(mdiArea);

  connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow *)), this,
          SLOT(updateMenus()));
  windowMapper = new QSignalMapper(this);
  connect(windowMapper, SIGNAL(mapped(QWidget *)), this,
          SLOT(setActiveSubWindow(QWidget *)));

  createActions(); // 创建菜单操作(动作)
  createMenus();
  createToolBars();
  createStatusBar();
  updateMenus();

  resize(900, 600);

  setWindowTitle(tr("Offce办公自动化文字编辑软件 V2.1"));
  setUnifiedTitleAndToolBarOnMac(true);
}

MyWord::~MyWord() {}

void MyWord::closeEvent(QCloseEvent *event) {
  mdiArea->closeAllSubWindows();
  if (mdiArea->currentSubWindow()) {
    event->ignore();
  } else {
    event->accept();
  }
}

void MyWord::updateMenus() // 更新菜单
{
  // 至少有一个子文档打开着的情况
  bool hasMyChild = activeMyChild();

  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);
}

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(); // 使得字体设置菜单可用
}

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;
}

void MyWord::createActions() // 创建菜单操作(动作)
{
  // 【文件】菜单动作
  // 新建
  newAct = new QAction(QIcon(srcpaths + "/filenew.png"), tr("新建(&N)"), this);
  newAct->setShortcuts(QKeySequence::New);
  newAct->setToolTip("新建"); // 设置工具栏按钮的提示文本信息
  newAct->setStatusTip(tr("创建一个新的word文档.")); // 设置状态栏提示文本信息
  connect(newAct, SIGNAL(triggered()), this, SLOT(fileNew()));

  // 打开
  openAct =
      new QAction(QIcon(srcpaths + "/fileopen.png"), tr("打开(&O)"), this);
  openAct->setShortcuts(QKeySequence::Open);
  openAct->setToolTip("打开"); // 设置工具栏按钮的提示文本信息
  openAct->setStatusTip(
      tr("打开已经存在的word文档.")); // 设置状态栏提示文本信息
  connect(openAct, SIGNAL(triggered()), this, SLOT(fileOpen()));

  // 保存
  saveAct =
      new QAction(QIcon(srcpaths + "/filesave.png"), tr("保存(&S)"), this);
  saveAct->setShortcuts(QKeySequence::Save);
  saveAct->setToolTip("保存");
  saveAct->setStatusTip(tr("将当前word文档存盘."));
  connect(saveAct, SIGNAL(triggered()), this, SLOT(fileSave()));

  // 另存为
  saveAsAct = new QAction(tr("另存为(&A)..."), this);
  saveAsAct->setShortcuts(QKeySequence::SaveAs);
  // saveAsAct->setToolTip("另存为"); // 此工具栏按钮大家根据自已需要决定是保留
  saveAsAct->setStatusTip(tr("用另一个新的文件名称,保存当前word文档."));
  connect(saveAsAct, SIGNAL(triggered()), this, SLOT(fileSaveAs()));

  // 打印
  printAct =
      new QAction(QIcon(srcpaths + "/fileprint.png"), tr("打印(&P)"), this);
  printAct->setShortcuts(QKeySequence::Print);
  printAct->setToolTip("打印");
  printAct->setStatusTip(tr("打印当前word文档."));
  connect(printAct, SIGNAL(triggered()), this, SLOT(filePrint()));

  // 打印预览
  printPreviewAct = new QAction(tr("打印预览..."), this);
  printPreviewAct->setStatusTip(tr("打印预览当前word文档效果."));
  connect(printPreviewAct, SIGNAL(triggered()), this, SLOT(filePrintPreview()));

  // 退出操作
  exitAct = new QAction(tr("退出(X)"), this);
  exitAct->setShortcuts(QKeySequence::Quit);
  exitAct->setStatusTip("退出Word文件应用程序");
  connect(exitAct, SIGNAL(triggered()), qApp, SLOT(closeAllWindows()));

  // 【编辑】菜单动作
  // 撤销
  undoAct =
      new QAction(QIcon(srcpaths + "/editundo.png"), tr("撤销(&U)"), this);
  undoAct->setShortcuts(QKeySequence::Undo);
  undoAct->setToolTip("撤销");
  undoAct->setStatusTip(tr("撤销当前Word文档操作."));
  connect(undoAct, SIGNAL(triggered()), this, SLOT(undo()));

  // 重做
  redoAct =
      new QAction(QIcon(srcpaths + "/editredo.png"), tr("重做(&R)"), this);
  redoAct->setShortcuts(QKeySequence::Redo);
  redoAct->setToolTip("重做");
  redoAct->setStatusTip(tr("恢复之前的Word文档操作."));
  connect(redoAct, SIGNAL(triggered()), this, SLOT(redo()));

  // 剪切
  cutAct = new QAction(QIcon(srcpaths + "/editcut.png"), tr("剪切(&T)"), this);
  cutAct->setShortcuts(QKeySequence::Cut);
  cutAct->setToolTip("剪切");
  cutAct->setStatusTip(tr("从Word文档中裁剪所选择的倥,将它存放到剪贴板."));
  connect(cutAct, SIGNAL(triggered()), this, SLOT(cut()));

  // 复制
  copyAct =
      new QAction(QIcon(srcpaths + "/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(srcpaths + "/editpaste.png"), tr("粘贴(&P)"), this);
  pasteAct->setShortcuts(QKeySequence::Paste);
  pasteAct->setToolTip("粘贴");
  pasteAct->setStatusTip(tr("将剪贴板里面的内容粘贴到当前word文档."));
  connect(pasteAct, SIGNAL(triggered()), this, SLOT(paste()));

  // 【格式】-->(字体)菜单动作
  // 加粗
  boldAct =
      new QAction(QIcon(srcpaths + "/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(srcpaths + "/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(srcpaths + "/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(srcpaths + "/textleft.png"), tr("左对齐(&L)"), grp);
    centerAct = new QAction(QIcon(srcpaths + "/textcenter.png"),
                            tr("居中对齐(&E)"), grp);
    rightAlignAct =
        new QAction(QIcon(srcpaths + "/textright.png"), tr("右对齐(&R)"), grp);
  } else {
    rightAlignAct =
        new QAction(QIcon(srcpaths + "/textright.png"), tr("右对齐(&R)"), grp);
    centerAct = new QAction(QIcon(srcpaths + "/textcenter.png"),
                            tr("居中对齐(&E)"), grp);
    leftAlignAct =
        new QAction(QIcon(srcpaths + "/textleft.png"), tr("左对齐(&L)"), grp);
  }
  justifyAct = new QAction(QIcon(srcpaths + "/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()));

  // 【窗口】菜单
  closeAct = new QAction(tr("关闭(&O)"), this);
  closeAct->setStatusTip(tr("关闭活动word文档子窗口."));
  connect(closeAct, SIGNAL(triggered()), mdiArea, SLOT(closeActiveSubWindow()));

  closeAllAct = new QAction(tr("关闭所有(&A)"), this);
  closeAllAct->setStatusTip(tr("关闭活动word文档所有子窗口."));
  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("下一个(&T)"), 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("关于Office Word相关信息.");
  connect(aboutAct, SIGNAL(triggered()), this, SLOT(about()));

  aboutQtAct = new QAction(tr("关于 Qt(&Qt)"), this);
  aboutQtAct->setStatusTip("关于 Qt库相关信息.");
  connect(aboutQtAct, SIGNAL(triggered()), this, SLOT(aboutQt()));
}

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);

  // 【编辑】主菜单
  editMenu = menuBar()->addMenu(tr("编辑(&E)"));
  editMenu->addAction(undoAct);
  editMenu->addAction(redoAct);
  editMenu->addSeparator(); // 分隔线
  editMenu->addAction(cutAct);
  editMenu->addAction(copyAct);
  editMenu->addAction(pasteAct);

  // 【格式】主菜单
  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);

  formatMenu->addAction(colorAct);

  // 【窗口】主菜单
  windowMenu = menuBar()->addMenu(tr("窗口(&W)"));
  updateWindowMenu();
  connect(windowMenu, SIGNAL(aboutToShow()), this, SLOT(updateWindowMenu()));
  menuBar()->addSeparator();

  // 【帮助】主菜单
  helpMenu = menuBar()->addMenu(tr("帮助(&H)"));
  helpMenu->addAction(aboutAct);
  helpMenu->addSeparator(); // 分隔线
  helpMenu->addAction(aboutQtAct);
}

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())));
}

void MyWord::createStatusBar() // 创建状态条
{
  statusBar()->showMessage(tr("就绪状态"));
}

MyChild *MyWord::activeMyChild() // 获取激活的子窗口
{
  if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow())
    return qobject_cast<MyChild *>(activeSubWindow->widget());
  return 0;
}

// 打开文件用
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;
}
void MyWord::setActiveSubWindow(QWidget *window) // 设置active激活窗口
{
  if (!window)
    return;
  mdiArea->setActiveSubWindow(qobject_cast<QMdiSubWindow *>(window));
}

void MyWord::fileNew() // 新建
{
  MyChild *child = createMyChild();
  child->newFile();
  child->show();
  enabledText(); // 使得字体设置菜单可用的
}

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();
    }
  }
}

void MyWord::fileSave() // 保存
{
  if (activeMyChild() && activeMyChild()->save())
    statusBar()->showMessage(tr("Word文档保存成功."), 2000);
}

void MyWord::fileSaveAs() // 另存为
{
  if (activeMyChild() && activeMyChild()->saveAs())
    statusBar()->showMessage(tr("Word文档另存为成功."));
}

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();
}

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);
}

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);
}

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);
  }
}

void MyWord::textStyle(int styleIndex) // 文本样式
{
  if (activeMyChild())
    activeMyChild()->setStyle(styleIndex);
}
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);
  }
}

void MyWord::textColor() // 文本颜色
{
  if (activeMyChild()) {
    QColor color = QColorDialog::getColor(activeMyChild()->textColor(), this);

    if (!color.isValid())
      return;
    QTextCharFormat fmt;
    fmt.setForeground(color);
    activeMyChild()->mergeFormatOnWordOrSelection(fmt);
    colorChanged(color);
  }
}

void MyWord::fontChanged(const QFont &f) // 字体更改的UI同步
{
  comboFont->setCurrentIndex(comboFont->findText(QFontInfo(f).family()));
  comboSize->setCurrentIndex(
      comboSize->findText(QString::number(f.pointSize())));

  boldAct->setChecked(f.bold());
  italicAct->setChecked(f.italic());
  underlineAct->setChecked(f.underline());
}

void MyWord::colorChanged(const QColor &c) // 颜色更改的UI同步
{
  QPixmap pix(16, 16);
  pix.fill(c);
  colorAct->setIcon(pix);
}

void MyWord::alignmentChanged(Qt::Alignment a) // 对齐判断的UI同步
{
  if (a & Qt::AlignLeft)
    leftAlignAct->setChecked(true);
  else if (a & Qt::AlignCenter)
    centerAct->setChecked(true);
  else if (a & Qt::AlignRight)
    rightAlignAct->setChecked(true);
  else if (a & Qt::AlignJustify)
    justifyAct->setChecked(true);
}

void MyWord::filePrint() // 打印文档
{
  QPrinter printer(QPrinter::HighResolution);
  QPrintDialog *pdlg = new QPrintDialog(&printer, this);
  if (activeMyChild()->textCursor().hasSelection())
    pdlg->addEnabledOption(QAbstractPrintDialog::PrintSelection);
  pdlg->setWhatsThis(tr("打印文档"));
  if (pdlg->exec() == QDialog::Accepted)
    activeMyChild()->print(&printer);

  delete pdlg;
}

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);
}

void MyWord::about() // 关于
{
  QMessageBox::about(this, tr("关于"),
                     tr("基于Qt5实现的Word类文字处理软件!!!"));
}

  对.h文件中声明的函数进行实现

MyWord::MyWord(QWidget *parent) : QMainWindow(parent) {
  setWindowIcon(QIcon(srcpaths + "/wordlogo2.png"));

  mdiArea = new QMdiArea;
  mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
  mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);

  setCentralWidget(mdiArea);

  connect(mdiArea, SIGNAL(subWindowActivated(QMdiSubWindow *)), this,
          SLOT(updateMenus()));
  windowMapper = new QSignalMapper(this);
  connect(windowMapper, SIGNAL(mapped(QWidget *)), this,
          SLOT(setActiveSubWindow(QWidget *)));

  createActions(); // 创建菜单操作(动作)
  createMenus();
  createToolBars();
  createStatusBar();
  updateMenus();

  resize(900, 600);

  setWindowTitle(tr("Offce办公自动化文字编辑软件 V2.1"));
  setUnifiedTitleAndToolBarOnMac(true);
}

  初始化主窗口并做信号槽连接。
  窗口外观:setWindowIcon(QIcon(...))设置软件图标;setWindowTitle()指定标题;resize(900,600)设置初始大小。
  多文档区域:创建QMdiArea多文档区域(含垂直、水平滚动条Qt::ScrollBarAsNeeded),并用setCentralWidget(mdiArea)作为主窗口中心控件。
  信号槽函数connect():subWindowActivated()、updateMenus(),子窗口切换时自动更新菜单。
windowMapper(QSignalMapper)映射子窗口,调用setActiveSubWindow()。
  界面部分:调用createActions()、createMenus()、createToolBars()、createStatusBar(),将动作、菜单、工具栏和状态栏搭建完整。

void MyWord::createActions() // 创建菜单操作(动作)
{
  // 【文件】菜单动作
  // 新建
  newAct = new QAction(QIcon(srcpaths + "/filenew.png"), tr("新建(&N)"), this);
  newAct->setShortcuts(QKeySequence::New);
  newAct->setToolTip("新建"); // 设置工具栏按钮的提示文本信息
  newAct->setStatusTip(tr("创建一个新的word文档.")); // 设置状态栏提示文本信息
  connect(newAct, SIGNAL(triggered()), this, SLOT(fileNew()));
  ...
  // 【格式】-->(字体)菜单动作
  // 加粗
  boldAct =
      new QAction(QIcon(srcpaths + "/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()));
  ...
}

  createActions()把菜单功能与用户操作(图标、快捷键、提示、槽函数)紧密结合,使得“新建文档”“加粗”等核心操作直观。
【文件】新建动作:
newAct=newQAction(QIcon(srcpaths+"/filenew.png"),tr("新建(&N)"),this);
newAct->setShortcuts(QKeySequence::New);设置图标、快捷键Ctrl+N。
connect(newAct,SIGNAL(triggered()),this,SLOT(fileNew()));绑定槽函数fileNew()
用setToolTip()、setStatusTip()设置工具栏提示和状态栏提示。
【格式】加粗动作:
boldAct=newQAction(QIcon(srcpaths+"/textbold.png"),tr("加粗(&B)"),this);boldAct->setCheckable(true);boldAct->setShortcut(Qt::CTRL+Qt::Key_B);connect(boldAct,SIGNAL(triggered()),this,SLOT(textBold()));
除了与“新建动作”一样的图标、快捷键(Ctrl+B)、提示信息和绑定槽函数外,setCheckable(true)让该动作具备开关状态,并通过setFont()这个动作的文本在菜单栏和工具栏中以粗体显示。
  以上述两个动作为例,可得动作创建标准流程:QAction=图标+名称(+快捷键)+提示信息+槽函数(+其它)

void MyWord::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);

  formatMenu->addAction(colorAct);
  ...
}

  以【格式】菜单为例,通过formatMenu=menuBar()->addMenu(tr("格式(&O)"));创建主菜单。然后用formatMenu->addMenu进一步添加为字体、段落两个子菜单,用formatMenu->addAction添加一个独立动作"颜色"。接着在字体子菜单(fontMenu)、段落子菜单(alignMenu)分别添加相应动作。

MyChild *MyWord::activeMyChild() // 获取激活的子窗口
{
  if (QMdiSubWindow *activeSubWindow = mdiArea->activeSubWindow())
    return qobject_cast<MyChild *>(activeSubWindow->widget());
  return 0;
}

  在多文档界面(MDI)中获取当前激活的子窗口。
  通过mdiArea->activeSubWindow()判断是否有活动的子窗口;若存在,用qobject_cast<MyChild*>将子窗口中的QWidget类型转换成MyChild类型并返回;若没有活动窗口,则返回0(空指针)

void MyWord::updateMenus() // 更新菜单
{
  // 至少有一个子文档打开着的情况
  bool hasMyChild = activeMyChild();

  saveAct->setEnabled(hasMyChild);
  ...

  // 文档打开且有内容被选中的情况
  bool hasSelection =
      (activeMyChild() && activeMyChild()->textCursor().hasSelection());

  cutAct->setEnabled(hasSelection);
  ...
}

根据子文档的打开情况以及文档内容是否被选中,设置各个动作的启用情况。

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;
}

  创建并管理一个子文档窗口。
  newMyChild新建一个子窗口对象。通过mdiArea->addSubWindow(child)把它添加到多文档区域中。利用信号槽机制,将子窗口的copyAvailable(bool)信号连接到cutAct和copyAct的setEnabled(bool)槽上。当子窗口中有可复制的内容时,剪切和复制动作(按钮)会自动启用,否则保持灰色不可用状态。最后返回新建的子窗口对象。

void MyWord::textBold() // 加粗
{
  QTextCharFormat fmt;
  fmt.setFontWeight(boldAct->isChecked() ? QFont::Bold : QFont::Normal);
  if (activeMyChild())
    activeMyChild()->mergeFormatOnWordOrSelection(fmt);
}

  文本格式化函数textBold()、textItalic()、textFamily()等,逻辑类似,以textBold()举例。
  创建QTextCharFormatfmt来定义字符格式。通过boldAct->isChecked()判断当前“加粗”按钮是否被选中:若选中则设置为QFont::Bold,否则为QFont::Normal。最后调用activeMyChild()->mergeFormatOnWordOrSelection(fmt),将该格式应用到当前激活的子窗口上。

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;
}

  用于在多文档区域中查找指定文件对应的子窗口,避免同一个文件被重复打开。          QFileInfo(fileName).canonicalFilePath()获取文件的标准路径,再通过mdiArea->subWindowList()遍历所有子窗口,逐个取出其中的MyChild对象。比较myChild->currentFile()与标准路径是否一致,如果匹配,就返回该子窗口指针。如果没有找到,则返回0

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();
    }
  }
}

  通过QFileDialog::getOpenFileName()弹出对话框,让用户选择文件(默认支持HTML文间或所有文件)。若文件名不为空,调用findMyChild(filename)检查该文件是否已在子窗口中打开:如果已存在,直接mdiArea->setActiveSubWindow(existing)激活该窗口,避免重复打开;如果不存在,通过createMyChild()新建子窗口,并调用child->loadFile(filename)加载文件。若加载成功,状态栏输出statusBar()->showMessage("文件已经加载"),并展示子窗口child->show(),启用格式菜单enabledText();加载失败,则关闭子窗口child->close()。

void MyWord::fontChanged(const QFont &f) // 字体更改
{
  comboFont->setCurrentIndex(comboFont->findText(QFontInfo(f).family()));
  comboSize->setCurrentIndex(
      comboSize->findText(QString::number(f.pointSize())));

  boldAct->setChecked(f.bold());
  italicAct->setChecked(f.italic());
  underlineAct->setChecked(f.underline());
}

  状态同步函数fontChanged(),colorChanged()等,逻辑类似,以fontChanged()举例。
在UI界面中同步字体状态,保证菜单/工具栏的状态与实际文字格式保持一致。comboFont->setCurrentIndex(...)设置字体组合框为当前字体。comboSize->setCurrentIndex(...)设置字体大小组合框为当前大小。boldAct->setChecked(f.bold())等动作按钮的勾选状态也是同理。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值