当前位置: 首页 > news >正文

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

一、核心功能

本软件为多文档型程序,界面是标准的 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_OBJECTpublic: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;//将当前文件命名为"文档+编号"的形式,编号先使用再1curFile = 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_OBJECTpublic: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);
}

相关文章:

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

一、核心功能 本软件为多文档型程序&#xff0c;界面是标准的 Windows 主从窗口 拥有&#xff1a;主菜单、工具栏、文档显示区 和 状态栏。 所要实现的东西&#xff0c;均在下图了。 开发该软件&#xff0c;主要分为下面三个阶段 1&#xff09;界面设计开发 多窗口 MDI 程序…...

【Postgres_Python】使用python脚本批量创建和导入多个PG数据库

之前批量创建和导入数据库分为2个python脚本进行&#xff0c;现整合优化代码合并为一个python脚本&#xff0c;可同步实现数据库的创建和数据导入。之前的文章链接&#xff1a; 【Postgres_Python】使用python脚本批量创建PG数据库 【Postgres_Python】使用python脚本将多个.S…...

消息队列篇--原理篇--RabbitMQ和Kafka对比分析

RabbitMQ和Kafka是两种非常流行的消息队列系统&#xff0c;但它们的设计哲学、架构特点和适用场景存在显著差异。对比如下。 1、架构设计 RabbitMQ&#xff1a; 基AMQP协议&#xff1a;RabbitMQ是基于AMQP&#xff08;高级消息队列协议&#xff09;构建的&#xff0c;支持多…...

俄语画外音的特点

随着全球媒体消费的增加&#xff0c;语音服务呈指数级增长。作为视听翻译和本地化的一个关键方面&#xff0c;画外音在确保来自不同语言和文化背景的观众能够以一种真实和可访问的方式参与内容方面发挥着重要作用。说到俄语&#xff0c;画外音有其独特的特点、挑战和复杂性&…...

【机器学习实战中阶】音乐流派分类-自动化分类不同音乐风格

音乐流派分类 – 自动化分类不同音乐风格 在本教程中,我们将开发一个深度学习项目,用于自动化地从音频文件中分类不同的音乐流派。我们将使用音频文件的频率域和时间域低级特征来分类这些音频文件。 对于这个项目,我们需要一个具有相似大小和相似频率范围的音频曲目数据集…...

Keil5 IDE使用笔记

1 Keil生成bin文件 $K\ARM\ARMCLANG\bin\fromelf.exe --bin --outputL/L.bin !L $K: 表示 Keil 5的安装路径 L: 表示 工程名 !L: 表示 工程名.arf 后缀的文件 可根据实际需要修改 --output 的值调整生成的bin文件的存放路径。 2 下载程序报错 No ST-LINK detected Error: Fla…...

自动化办公|使用Python重命名并移动文件到对应文件夹

在日常的文件管理和处理过程中&#xff0c;我们可能会遇到需要将文件整理到不同文件夹中的需求。例如&#xff0c;我们有一个包含多个文件的目录&#xff0c;文件名的首字符表示文件应该存放在哪个文件夹中。我们可以使用Python脚本来自动完成这个任务&#xff0c;实现文件的分…...

【全栈】SprintBoot+vue3迷你商城(5)

【全栈】SprintBootvue3迷你商城&#xff08;5&#xff09; 上一期我们基本完成了与用户相关的接口&#xff0c;而这些接口都是用户才能干的事情&#xff0c;如果你没登录&#xff0c;那么这些接口功能你都不能实现。 那么如何做到这一步呢&#xff1f; 1.Token 作用 身份…...

Java 并发编程:Java 中的乐观锁与 CAS

大家好,我是栗筝i,这篇文章是我的 “栗筝i 的 Java 技术栈” 专栏的第 025 篇文章,在 “栗筝i 的 Java 技术栈” 这个专栏中我会持续为大家更新 Java 技术相关全套技术栈内容。专栏的主要目标是已经有一定 Java 开发经验,并希望进一步完善自己对整个 Java 技术体系来充实自…...

模拟飞行入坑(五) P3D 多通道视角配置 viewgroup

背景&#xff1a; P3D进行多个屏幕显示的时候&#xff0c;如果使用英伟达自带的屏幕融合成一个屏&#xff0c;或者使用P3D单独拉伸窗口&#xff0c;会使得P3D的画面被整体拉伸&#xff0c;又或者,当使用Multichannel进行多个设备联动时&#xff0c;视角同步组合需要配置&#…...

react中hooks之 React 19 新 Hooks useActionState useFormStatus用法总结

React 19 新 Hooks 使用指南: useActionState & useFormStatus 目录 useActionStateuseFormStatus最佳实践 useActionState 概述 useActionState 是 React 19 引入的新 Hook&#xff0c;用于处理表单 action 的状态更新。它允许你基于表单 action 的结果来更新组件状态…...

为AI聊天工具添加一个知识系统 之48 蒙板程序设计(第二版):Respect九宫格【社会形态:治理】

本文要点 1、词汇表Vocabulary &#xff08;普通名词&#xff09; 1) 三组词&#xff08;数据库支持的三个数字散列&#xff09;&#xff1a; 工作&#xff0c;工件&#xff0c;工具。论题&#xff0c;主题词&#xff0c;关键字。口号&#xff0c;符号&#xff0c;编号。 2…...

靠右行驶数学建模分析(2014MCM美赛A题)

笔记 题目 要求分析&#xff1a; 比较规则的性能&#xff0c;分为light和heavy两种情况&#xff0c;性能指的是 a.流量与安全 b. 速度限制等分析左侧驾驶分析智能系统 论文 参考论文 两类规则分析 靠右行驶&#xff08;第一条&#xff09;2. 无限制&#xff08;去掉了第一条…...

6.5、密集波分复用系统(DWDM)/OTN

图中从左到右分为多个部分&#xff0c;分别代表了信号的输入、传输和输出过程。 左侧是客户侧&#xff0c;有普通接口和彩色接口&#xff0c;分别连接到光转发单元&#xff08;OTU&#xff09;。 中间部分是传输线路&#xff0c;包含多个光放大器&#xff08;OBA、OLA、OPA&…...

Unity3D基于Unity整合BEPUphysicsint物理引擎实战详解

引言 Unity3D是一款流行的游戏引擎&#xff0c;提供了丰富的功能和工具&#xff0c;使开发者能够轻松创建各种类型的游戏。其中&#xff0c;帧同步技术是游戏开发中至关重要的一环&#xff0c;它能确保多个玩家在同一时间内看到的游戏状态是一致的。BEPUphysicsint是一个基于U…...

《探秘鸿蒙Next:如何保障AI模型轻量化后多设备协同功能一致》

在鸿蒙Next的多设备协同场景中&#xff0c;确保人工智能模型轻量化后功能的一致性是一项极具挑战性但又至关重要的任务。以下是一些关键的方法和策略。 统一的模型架构与标准 采用标准化框架&#xff1a;选择如TensorFlow Lite、PyTorch Mobile等在鸿蒙Next上适配良好的轻量化…...

微服务知识——4大主流微服务架构方案

文章目录 1、微服务聚合模式2、微服务共享模式3、微服务代理模式4、微服务异步消息模式 微服务是大型架构的必经之路&#xff0c;也是大厂重点考察对象&#xff0c;下面我就重点详解4大主流微服务架构方案。 1、微服务聚合模式 微服务聚合设计模式&#xff0c;解决了如何从多个…...

Java 方法重写

目录 一、什么是方法重写&#xff0c;为什么需要它 二、方法重写的规则 三、方法重写的实际应用场景 四、方法重写与重载的区别 五、总结 在 Java 编程的精彩世界里&#xff0c;方法重写是一项极为重要且实用的特性&#xff0c;它犹如一把神奇的钥匙&#xff0c;为我们开启…...

华为E9000刀箱服务器监控指标解读

美信监控易内置了数千种常见设备监测器&#xff0c;能够监测超过20万项指标。这些指标涵盖了从硬件设备到软件系统&#xff0c;从网络性能到安全状态等各个方面。如下基于美信监控易——IT基础监控模块&#xff0c;对华为E9000刀箱服务器部分监控指标进行解读。 一、华为E9000…...

正则表达式基础与应用

什么是正则表达式&#xff1f; 正则表达式&#xff08;Regular Expression&#xff0c;简称regex&#xff09;是一种用于描述字符串结构的语法规则。它定义了一个搜索模式&#xff0c;可以用来匹配、替换或提取文本中的子串。正则表达式广泛应用于文本处理、数据验证、查找和替…...

微信小程序使用上拉加载onReachBottom。页面拖不动。一直无法触发上拉的事件。

1&#xff0c;可能是原因是你使用了scroll-view的标签&#xff0c;用onReachBottom触发加载事件。这两个是有冲突的。没办法一起使用。如果页面的样式是滚动的是无法去触发页面的onReachBottom的函数的。因此&#xff0c;你使用overflow:auto.来使用页面的某些元素滚动&#xf…...

9. 神经网络(一.神经元模型)

首先&#xff0c;先看一个简化的生物神经元结构&#xff1a; 生物神经元有多种类型&#xff0c;内部也有复杂的结构&#xff0c;但是可以把单个神经元简化为3部分组成&#xff1a; 树突&#xff1a;一个神经元往往有多个树突&#xff0c;用于接收传入的信息。轴突&#xff1a;…...

Mysql安装,mysql-installer-community-8.0.41.0

“windowR"键弹出运行框&#xff0c;输入”cmd"进入window命令提示符&#xff0c;输入“mysql -uroot -p"按下回车&#xff0c;再输入密码&#xff0c;按下回车&#xff0c;出现下面界面则是配置成功。 默认会在 C:\Program Files\MySQL\MySQL Server 8.0\bin …...

吴恩达深度学习——神经网络介绍

文章内容来自BV11H4y1F7uH&#xff0c;仅为个人学习所用。 文章目录 什么是神经网络引入神经网络神经元激活函数ReLU隐藏单元 用神经网络进行监督学习监督学习与无监督学习举例 什么是神经网络 引入 已经有六个房子的数据集&#xff0c;横轴为房子大小&#xff0c;纵轴为房子…...

【SpringBoot】SpringBoot中分页插件(PageHelper)的使用

目录 1.分页概念 2.原生写法 3.PageHelper插件分页查询 3.1 介绍 3.2?使用 3.3 Page对象和PageInf对象 1.分页概念 用户查询的数据不可能一次性全部展示给用户&#xff08;如果用户有一万条数据呢&#xff09;&#xff0c;而是分页展示给用户&#xff0c;这就是分页查询…...

JavaScript DOM 操作与事件处理

Hi&#xff0c;我是布兰妮甜 &#xff01;在现代Web开发中&#xff0c;JavaScript不仅是用来增强用户体验的工具&#xff0c;它更是创建动态、交互式网页的关键。通过操作文档对象模型&#xff08;DOM&#xff09;和处理用户事件&#xff0c;开发者能够构建出响应迅速且功能丰富…...

rstrip 方法是 Python 字符串的一个内置方法,用于 删除字符串右边(末尾)的指定字符

rstrip 方法是 Python 字符串的一个内置方法&#xff0c;用于 删除字符串右边&#xff08;末尾&#xff09;的指定字符。 语法&#xff1a; string.rstrip([chars])string&#xff1a;原始字符串。chars&#xff1a;可选参数&#xff0c;指定要删除的字符。默认为 None&#…...

【Elasticsearch】腾讯云安装Elasticsearch

Elasticsearch 认识Elasticsearch安装Elasticsearch安装Kibana安装IK分词器分词器的作用是什么&#xff1f;IK分词器有几种模式&#xff1f;IK分词器如何拓展词条&#xff1f;如何停用词条&#xff1f; 认识Elasticsearch Elasticsearch的官方网站如下 Elasticsearch官网 Ela…...

rsync结合inotify实现文件实时同步

rsync 1.复制工具 本地复制 远程复制 cp dd 跨主机传递文件 rz sz ftp scp rsync nfs samba drdb 2.rsync作用 实现文件的备份&#xff0c;可以是当前主机&#xff0c;也可以是远程主机&#xff1b;可以完全备份&#xff0c;也可以是增量备份 2.1功能 类似于cp的复制功能…...

浅谈 PID 控制算法

PID 控制算法概念 在我们的生活中可能大家都没有听说过 PID 控制算法&#xff0c;但它可以说是无处不在&#xff0c;小到空调的温度控制、无人机的精准悬停、机器人运作系统&#xff0c;大到飞机和火箭的飞行姿态控制都有 PID 的身影。 PID 控制算法&#xff0c;即比例 - 积分…...

react中hooks之useId用法总结以及与useRef用法区别

React useId Hook 使用指南 概述 useId 是 React 18 引入的新 Hook&#xff0c;用于生成唯一的 ID&#xff0c;主要用于可访问性&#xff08;accessibility&#xff09;属性。它在服务端和客户端渲染时都能保持一致性。 useId vs useRef useId: 生成稳定的唯一标识符&#…...

Spring Boot AOP实现动态数据脱敏

依赖&配置 <!-- Spring Boot AOP起步依赖 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId> </dependency>/*** Author: 说淑人* Date: 2025/1/18 23:03* Desc…...

AutoGen入门——快速实现多角色、多用户、多智能体对话系统

1.前言 如https://github.com/microsoft/autogen所述&#xff0c;autogen是一多智能体的框架&#xff0c;属于微软旗下的产品。 依靠AutoGen我们可以快速构建出一个多智能体应用&#xff0c;以满足我们各种业务场景。 本文将以几个示例场景&#xff0c;使用AutoGen快速构建出…...

.NET Framework

.NET Framework 是微软推出的一个软件开发平台&#xff0c;主要用于构建和运行 Windows 应用程序。它是 .NET 生态系统的早期版本&#xff0c;专注于 Windows 平台&#xff0c;并提供了丰富的类库和运行时环境。 注意事项 跨平台限制&#xff1a;.NET Framework 主要适用于 W…...

算法中的移动窗帘——C++滑动窗口算法详解

1. 滑动窗口简介 滑动窗口是一种在算法中常用的技巧&#xff0c;主要用来处理具有连续性的子数组或子序列问题。通过滑动窗口&#xff0c;可以在一维数组或字符串上维护一个固定或可变长度的窗口&#xff0c;逐步移动窗口&#xff0c;避免重复计算&#xff0c;从而提升效率。常…...

DuckDB:Golang操作DuckDB实战案例

DuckDB是一个嵌入式SQL数据库引擎。它与众所周知的SQLite非常相似&#xff0c;但它是为olap风格的工作负载设计的。DuckDB支持各种数据类型和SQL特性。凭借其在以内存为中心的环境中处理高速分析的能力&#xff0c;它迅速受到数据科学家和分析师的欢迎。在这篇博文中&#xff0…...

C++通过输入3D相机像素点集{u、v、z}和机械手世界坐标点集{X、Y、Z}求得变换矩阵RT(眼在手外)

👑主页:吾名招财 👓简介:工科学硕,研究方向机器视觉,爱好较广泛… ​💫签名:面朝大海,春暖花开! C++通过输入3D相机像素点集{u、v、z}和机械手世界坐标点集{X、Y、Z}求得变换矩阵RT(眼在手外) 引言原理简介点集数据(含像素坐标、世界坐标及求解后的变换矩阵)配…...

手机怎么远程操控电脑?

远程看看是一款免费使用的远程控制软件&#xff0c;兼容 Windows、iOS 和 Android 系统&#xff0c;用户可以通过电脑或移动设备轻松远程控制电脑。不仅如此&#xff0c;远程看看还提供了文件传输、在线聊天和隐私屏等实用功能。如果您需要在远程操作时隐藏被控电脑的操作界面&…...

【Golang/gRPC/Nacos】在golang中将gRPC和Nacos结合使用

Nacos与gRPC 前言 关于这部分&#xff0c;前段时间我在看文档以及视频教程的时候&#xff0c;怎么都想不明白&#xff0c;到底为什么要用gRPC是什么&#xff0c;他在项目中应该充当什么样的角色&#xff1f;Nacos又是如何和他结合的&#xff1f; 于是我就决定去看看一些小项…...

数据库-多表关系

项目开发中&#xff0c;在进行数据库表结构设计时&#xff0c;会根据业务需求及业务模块之间的关系&#xff0c;分析并设计表结构。由于业务之间相互关联&#xff0c;所以各个表结构之间也存在着各种联系。 多表关系&#xff1a; 一对多(多对一) 一对一 多对多 多表关系 一对…...

回归算法、聚类算法、决策树、随机森林、神经网络

这也太全了&#xff01;回归算法、聚类算法、决策树、随机森林、神经网络、贝叶斯算法、支持向量机等十大机器学习算法一口气学完&#xff01;_哔哩哔哩_bilibili 【线性回归、代价函数、损失函数】动画讲解_哔哩哔哩_bilibili 14分钟详解所有机器学习算法&#xff1a;…...

RabbitMQ1-消息队列

目录 MQ的相关概念 什么是MQ 为什么要用MQ MQ的分类 MQ的选择 RabbitMQ RabbitMQ的概念 四大核心概念 RabbitMQ的核心部分 各个名词介绍 MQ的相关概念 什么是MQ MQ(message queue)&#xff0c;从字面意思上看&#xff0c;本质是个队列&#xff0c;FIFO 先入先出&am…...

第17章:Python TDD回顾与总结货币类开发

写在前面 这本书是我们老板推荐过的&#xff0c;我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后&#xff0c;我突然思考&#xff0c;对于测试开发工程师来说&#xff0c;什么才更有价值呢&#xff1f;如何让 AI 工具更好地辅助自己写代码&#xff0c;或许…...

7、数组知识点汇总

一、 数组基本概念 程序算法数据结构 算法&#xff1a;解决程序的流程步骤数据结构&#xff1a;将数据按照某种特定的结构来存储设计良好的数据结构会导致良好的算法。ArrayList、LinkedList 数组是最简单的数据结构。 1、数组&#xff1a; 数组&#xff1a;存放同一种类型…...

蓝桥杯c/c++需要掌握的基础语法总结

1、#include<bits/stdc.h> 万能头文件 2、using namespace std&#xff1b; 3、输出 cout<<""<<end1; (换行) printf(""); 4、int x3&#xff1b;整数 double d3.14&#xff1b;小数 char ch’A‘;字符 char s[]"Hell…...

学习第七十四行

qt调用信号与槽机制&#xff1a; MOC查找头文件中的signal与slots&#xff0c;标记出信号槽。将信号槽信息储存到类静态变量staticMetaObject中&#xff0c;并按照声明的顺序进行存放&#xff0c;建立索引。connect链接&#xff0c;将信号槽的索引信息放到一个双向链表中&…...

《罗宾逊-旅途VR》Build2108907官方学习版

《罗宾逊-旅途VR》官方版 https://pan.xunlei.com/s/VODiY5gn_fNxKREdVRdwVboCA1?pwdsh3f# 从第一人称的角度进行探索&#xff0c;玩家将遇到一系列恐龙和生物&#xff0c;这些恐龙和生物会对它们在泰森三世生态系统中的存在做出反应。强调与周围环境的互动&#xff0c;鼓励玩…...

详解共享WiFi小程序怎么弄!

在数字化时代&#xff0c;共享WiFi项目​正逐渐成为公共场所的新标配&#xff0c;它不仅为用户提供了便捷的上网方式&#xff0c;还为商家带来了额外的收入来源。那么共享wifi怎么弄&#xff0c;如何搭建并运营一个成功的共享WiFi项目呢&#xff1f; 共享WiFi项目通过在公共场所…...

Glide加载gif遇到的几个坑

Glide本身支持gif格式的动画加载&#xff0c;但是大多数情况下我们用Glide都是去加载一些静态图片&#xff0c;加载gif动态图的需求不是很多&#xff0c;因此这次使用Glide加载gif就遇到了一些令人匪夷所思的问题 问题一&#xff1a;加载gif图片会有明显的卡顿 通常情况下我们…...

mybatis(19/134)

大致了解了一下工具类&#xff0c;自己手敲了一边&#xff0c;java的封装还是真的省去了很多麻烦&#xff0c;封装成一个工具类就可以不用写很多重复的步骤&#xff0c;一个工厂对应一个数据库一个environment就好了。 mybatis中调用sql中的delete占位符里面需要有字符&#xf…...