Qt基础项目篇——Qt版Word字处理软件

news2025/1/23 3:26:56

一、核心功能

本软件为多文档型程序,界面是标准的 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);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2280673.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

USART_串口通讯轮询案例(HAL库实现)

引言 前面讲述的串口通讯案例是使用寄存器方式实现的&#xff0c;有利于深入理解串口通讯底层原理&#xff0c;但其开发效率较低&#xff1b;对此&#xff0c;我们这里再讲基于HAL库实现的串口通讯轮询案例&#xff0c;实现高效开发。当然&#xff0c;本次案例需求仍然和前面寄…

leetcode刷题记录(七十二)——146. LRU 缓存

&#xff08;一&#xff09;问题描述 146. LRU 缓存 - 力扣&#xff08;LeetCode&#xff09;146. LRU 缓存 - 请你设计并实现一个满足 LRU (最近最少使用) 缓存 [https://baike.baidu.com/item/LRU] 约束的数据结构。实现 LRUCache 类&#xff1a; * LRUCache(int capacity)…

C++ 类- 构造和析构

空类 class A {};空类大小&#xff1a; sizeof(A) 1编译器会默认生成 6 个成员函数&#xff1a; class A { public:A();//构造函数 - 完成对象初始化工作~A();//析构函数 - 完成对象的资源清理A(const A& a);//拷贝构造函数 - 使用同一类中之前创建的对象来初始化新创建…

ubuntu20.04有亮度调节条但是调节时亮度不变

尝试了修改grub文件&#xff0c;没有作用&#xff0c;下载了brightness-controllor&#xff0c;问题解决了。 sudo add-apt-repository ppa:apandada1/brightness-controller sudo apt update sudo apt install brightness-controller 之后在应用软件中找到brightness-contro…

macOS如何进入 Application Support 目录(cd: string not in pwd: Application)

错误信息 cd: string not in pwd: Application 表示在当前目录下找不到名为 Application Support 的目录。可能的原因如下&#xff1a; 拼写错误或路径错误&#xff1a;确保你输入的目录名称正确。目录名称是区分大小写的&#xff0c;因此请确保使用正确的大小写。正确的目录名…

包文件分析器 Webpack Bundle Analyzer

webpack-bundle-analyzer 是一个非常有用的工具&#xff0c;用于可视化和分析 Webpack 打包生成的文件。这使得开发者能够更好地理解应用的依赖关系、包的大小&#xff0c;以及优化打包的机会。以下是关于 webpack-bundle-analyzer 的详细介绍&#xff0c;包括它的安装、使用以…

【深度解析Java 20天速成】04_IDEA的安装与使用

【Why IDEA ?】 【注】JetBrains官方说明&#xff1a; 尽管我们采取了多种措施确保受访者的代表性&#xff0c;但结果可能会略微偏向 JetBrains 产品的用户&#xff0c;因为这些用户更有可能参加调查。 此外&#xff0c;2022年&#xff0c;某美国软件开发商在对近千名专业的Ja…

算法竞赛之差分进阶——等差数列差分 python

目录 前置知识进入正题实战演练 前置知识 给定区间 [ l, r ]&#xff0c;让我们把数组中的[ l, r ] 区间中的每一个数加上c,即 a[ l ] c , a[ l 1 ] c , a[ l 2] c , a[ r ] c; 怎么做&#xff1f;很简单&#xff0c;差分一下即可 还不会的小伙伴点此进入学习 进入正题 …

【HarmonyOS NEXT】华为分享-碰一碰开发分享

关键词&#xff1a;鸿蒙、碰一碰、systemShare、harmonyShare、Share Kit 华为分享新推出碰一碰分享&#xff0c;支持用户通过手机碰一碰发起跨端分享&#xff0c;可实现传输图片、共享wifi等。我们只需调用系统 api 传入所需参数拉起对应分享卡片模板即可&#xff0c;无需对 U…

小程序 -- uni-app开发微信小程序环境搭建(HBuilder X+微信开发者工具)

目录 前言 一 软件部分 1. 微信开发者工具 2. HBuilder X 开发工具 二 配置部分 1. 关于 HBuilder X 配置 2. 关于 微信开发工具 配置 三 运行项目 1. 新建项目 2. 代码编写 3. 内置浏览器 编译 4. 配置小程序 AppID获取 注意 四 实现效果 前言 uni-app开发小程…

Element修改表格结构样式集合(后续实时更新)

场景 修改前端Element组件el-table样式 实现 线表格 <div class"tablepro"><el-table:data"tableData":header-cell-style"{ textAlign:center}"class"tablepro-table"borderstyle"width: 100%;height:100%"&g…

【C++】如何从源代码编译红色警戒2地图编辑器

【C】如何从源代码编译红色警戒2地图编辑器 操作视频视频中的代码不需要下载三方库&#xff0c;已经包含三方库。 一、运行效果&#xff1a;二、源代码来源及编程语言&#xff1a;三、环境搭建&#xff1a;安装红警2安装VS2022下载代码&#xff0c;源代码其实不太多&#xff0c…

[unity 高阶]使用ASE制作一个cubed的skybox的shader,跟做版本

第一步,导入ASE 此步骤不在此讲解,有时间再补充 第二步,创建shader 需要选择shader的类型,此处选择legacy/Unlit第三步,创建变量 根据默认shader中的变量 _Tint (“Tint Color”, Color) = (.5, .5, .5, .5)[Gamma] _Exposure (“Exposure”, Range(0, 8)) = 1.0_Rotat…

雷电9最新版安装Magisk+LSPosd(新手速通)

大家好啊&#xff01;我是NiJiMingCheng 我的博客&#xff1a;NiJiMingCheng 在安卓系统的定制与拓展过程中&#xff0c;获取 ROOT 权限以及安装各类框架是进阶玩家常用的操作&#xff0c;这可以帮助我们实现更多系统层面的个性化功能。今天&#xff0c;我将为大家详细介绍如何…

Spring Boot Starter介绍

前言 大概10来年以前&#xff0c;当时springboot刚刚出现并没有流行&#xff0c;当时的Java开发者们开发Web应用主要是使用spring整合springmvc或者struts、iBatis、hibernate等开发框架来进行开发。项目里一般有许多xml文件配置&#xff0c;其中配置了很多项目中需要用到的Be…

PyTorch使用教程(4)-如何使用torch.nn构建模型?

torch.nn 是 PyTorch 深度学习框架中的一个核心模块&#xff0c;专门用于构建和训练神经网络。它提供了一系列用于构建神经网络所需的组件&#xff0c;包括层&#xff08;Layers&#xff09;、激活函数&#xff08;Activation Functions&#xff09;、损失函数&#xff08;Loss…

Qt之QDjango-db的简单使用

QDjango是一款由C编写、依托于Qt库的Web开发框架&#xff0c;其设计理念受到了广受欢迎的Python框架Django的影响。这个项目旨在提供一个高效、灵活且易于使用的工具集&#xff0c;帮助开发者构建高质量的Web应用。其项目地址: https://gitcode.com/gh_mirrors/qd/qdjango&…

音频入门(二):音频数据增强

本文介绍了一些常见的音频数据增强方法&#xff0c;并给出了代码实现。 目录 一、简介 二、代码 1. 安装必要的库 2. 代码 3. 各函数的介绍 4. 使用方法 参考&#xff1a; 一、简介 音频数据增强是机器学习和深度学习领域中用于改善模型性能和泛化能力的技术。 使用数据…

【C++】引用(上)

1、引用的基本使用 作用&#xff1a;给变量起别名 语法&#xff1a;数据类型&#xff08;该数据类型要与原名的数据类型一致&#xff09; &别名原名&#xff1b; 示例&#xff1a; #include<iostream> using namespace std; int main() {int a 10;int& …

DBeaver下载安装及数据库连接(MySQL)

1. DBeaver下载 官网下载地址:Download | DBeaver Community 2. 安装 1. 双击下载的安装包&#xff0c;选择简体中文。 2. 点击下一步。 3. 点击我接受。 4. 如下勾选为所有用户安装&#xff0c;点击下一步。 5. 需重复做1~3 的步骤。 6. 选择组件&#xff0c;默认即可&…