项目地址:GitHub - Outlier9/CatEditor: Cat文本编辑器--Qt
有帮助的话各位点点 star 啦,感谢!
如果有需要学习该项目的人,觉得看文档较为困难,可以加我联系方式,给github点个star后可免费提供学习视频!!!
2.功能模块
(1)新建文件以及多文档界面
MDI(Multiple Document Interface,多文档界面)是一种允许用户同时打开多个文档窗口的界面模式。在这种模式下,每个文档窗口都可以独立操作,并且可以缩放、移动和关闭。
在.ui
添加MDI控件,然后创建ChileWnd
类,父类选择QTextEdit
在.ui
界面对newAction转到槽,选triggered
信号,我们将新建操作进行封装,命名为docNew
,并在主窗口头文件声明
接着在新建好的子窗口头文件中进行功能函数和全局变量
的声明,实现子窗口需要的功能
#ifndef CHILEWND_H #define CHILEWND_H #include <QWidget> #include<QTextEdit> class ChileWnd : public QTextEdit { Q_OBJECT public: ChileWnd(); QString m_CurDocPath; //当前文档路径 void newDoc(); //新建文档 QString getCurDocName(); //文档路径中提取文档名 private slots: void docBeModified(); //文档修改时,窗口的标题栏加‘*’ private: bool m_bSaved; //文档是否保存 }; #endif // CHILEWND_H
接着实现各个函数的功能
#include "chilewnd.h" #include<QFileInfo> ChileWnd::ChileWnd() { //子窗口关闭时销毁该类的对象 setAttribute(Qt::WA_DeleteOnClose); // m_bSaved = false; } //新建文档 void ChileWnd::newDoc() { static int wndSeqNum = 1; //新建文档序号 m_CurDocPath = QString("文档 %1").arg(wndSeqNum++); // 实现新建文档序号自加 //设置窗体标题,文档改动后名称后加‘*’ setWindowTitle(m_CurDocPath + "[*]" + "- MyWPS"); connect(document(),SIGNAL(contentsChanged()),this,SLOT(docBeModified()));//连接槽函数 } //获取文件名字 QString ChileWnd::getCurDocName() { return QFileInfo(m_CurDocPath).fileName(); } //文档修改时,窗口的标题栏加‘*’ void ChileWnd::docBeModified() { setWindowModified(document()->isModified()); }
void MainWindow::docNew() { ChileWnd *childwnd = new ChileWnd; ui->mdiArea->addSubWindow(childwnd); // connect(childwnd, &ChileWnd::copyAvailble, ui->cutAction, &QAction::setEnabled); // connect(childwnd, &ChileWnd::copyAvailble, ui->copyAction, &QAction::setEnabled); void MainWindow::docNew() { ChileWnd *childwnd = new ChileWnd; ui->mdiArea->addSubWindow(childwnd); // connect(childwnd, &ChileWnd::copyAvailble, ui->cutAction, &QAction::setEnabled); // connect(childwnd, &ChileWnd::copyAvailble, ui->copyAction, &QAction::setEnabled); //将各种功能设置为可用状态 connect(childwnd,SIGNAL(copyAvailable(bool)),ui->cutAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->copyAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->leftAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->rightAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->centerAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->justifyAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->blodAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->inclineAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->underlineAction,SLOT(setEnabled(bool))); connect(childwnd,SIGNAL(copyAvailable(bool)),ui->colorAction,SLOT(setEnabled(bool))); childwnd->newDoc(); childwnd->show(); }childwnd->newDoc(); childwnd->show(); }
(2)窗口标题图标设置
标题:给窗口设置标题,在.ui
文件内的属性栏找到windowTitle
然后直接修改即可
图标:需要.ico
文件,这里不能直接改后缀,要用软件进行格式转换(尺寸16和32等均可),可使用网页工具图片转ICO-在线图片格式转换-批量图片格式转换工具,将文件添加到qrc中即可
bug报错:(修改完重新构建)
- [Makefile:84:WPS resource res.o]Error 1 (检查icon文件格式)
- No rule to make target '../WPS/images/icon.png',needed by'arc_res.cpp'.Stop. (名称不能有中文)
(3)窗体列表
①在工具栏的窗体列添加目前创建的文档的列表项功能
首先要刷新菜单,也就是每次点开软件后都要刷新菜单项的可用状态,根据当前应用程序的状态(如是否有活动窗口、是否有文本被选中等)来启用或禁用菜单项,当用户在MDI应用程序中切换到另一个子窗口时,QMdiArea
的 subWindowActivated
信号会被触发。这个信号传递了被激活的子窗口的指针。然后在新建文档的时候会自动触发,所以新建的时候也需要进行更新该列表,因此在初始化的时候就添加这两种信号与槽
//刷新菜单 refreshMenus(); connect(ui->mdiArea,&QMdiArea::subWindowActivated,this,&MainWindow::refreshMenus); //更新菜单 addSubWndListMenu(); //点击W(窗体)就更新并添加位于该部分的子窗口列表 connect(ui->menu_W,&QMenu::aboutToShow,this,&MainWindow::addSubWndListMenu);
接着是刷新与更新的具体实现:
//刷新菜单 void MainWindow::refreshMenus() { bool hasChild; hasChild = (activateChildWnd() != 0); ui->saveAction->setEnabled(hasChild); ui->saveOther->setEnabled(hasChild); ui->printAction->setEnabled(hasChild); ui->printPreviewAction->setEnabled(hasChild); ui->pasteAction->setEnabled(hasChild); ui->closeAction->setEnabled(hasChild); ui->closeAllAction->setEnabled(hasChild); ui->tileAction->setEnabled(hasChild); ui->cascadeAction->setEnabled(hasChild); ui->nextAction->setEnabled(hasChild); ui->previousAction->setEnabled(hasChild); //有没有选中文本 bool hasSelect = (activateChildWnd() && activateChildWnd()->textCursor().hasSelection()); //获取光标信息,以及是否有内容选中 ui->cutAction->setEnabled(hasSelect); ui->copyAction->setEnabled(hasSelect); ui->blodAction->setEnabled(hasSelect); ui->inclineAction->setEnabled(hasSelect); ui->underlineAction->setEnabled(hasSelect); ui->leftAction->setEnabled(hasSelect); ui->centerAction->setEnabled(hasSelect); ui->rightAction->setEnabled(hasSelect); ui->justifyAction->setEnabled(hasSelect); ui->colorAction->setEnabled(hasSelect); }
void MainWindow::addSubWndListMenu() { //每次点击都要进行清空并更新,否则会重复将整个列表再次添加到该列 ui->menu_W->clear(); ui->menu_W->addAction(ui->closeAction); ui->menu_W->addAction(ui->closeAllAction); ui->menu_W->addSeparator(); ui->menu_W->addAction(ui->tileAction); ui->menu_W->addAction(ui->cascadeAction); ui->menu_W->addSeparator(); ui->menu_W->addAction(ui->nextAction); ui->menu_W->addAction(ui->previousAction); //获取所有的子窗口 QList<QMdiSubWindow*> wnds = ui->mdiArea->subWindowList(); //判断有没有子窗口 if(!wnds.isEmpty()) ui->menu_W->addSeparator(); //加分割线 //将所有子窗口添加到列表 for (int i = 0;i < wnds.size();++i) { ChileWnd* childWnd = qobject_cast<ChileWnd*>(wnds.at(i)->widget()); //获取文档名称 QString menuitem_text; menuitem_text = QString("%1 %2") .arg(i+1) //arg方法将字符串中的占位符替换为指定的值 .arg(childWnd->getCurDocName()); QAction *menuitem_act = ui->menu_W->addAction(menuitem_text); //添加勾选 menuitem_act->setCheckable(true); menuitem_act->setChecked(childWnd == activateChildWnd()); } formatEnable(); }
②点击列表项也能实现文档的焦点切换功能
这里需要使用信号映射器,并在构造函数中(也就是初始化)实现该机制,
整体的过程为:触发连接菜单项的triggered信号
--> 触发信号映射器的map方法
--> 连接信号映射器的mapped信号
--> 执行setActiveSubWindow槽函数
//创建信号映射器 m_WndMapper = new QSignalMapper(this); //点击窗口菜单项的时候,执行map方法,使setActiveSubWindow响应 connect(m_WndMapper,SIGNAL(mapped(QWidget*)),this,SLOT(setActiveSubWindow(QWidget*)));
//将所有子窗口添加到列表 for (int i = 0;i < wnds.size();++i) { ChileWnd* childWnd = qobject_cast<ChileWnd*>(wnds.at(i)->widget()); //获取文档名称 QString menuitem_text; menuitem_text = QString("%1 %2") .arg(i+1) //arg方法将字符串中的占位符替换为指定的值 .arg(childWnd->getCurDocName()); QAction *menuitem_act = ui->menu_W->addAction(menuitem_text); //添加勾选 menuitem_act->setCheckable(true); menuitem_act->setChecked(childWnd == activateChildWnd()); //使用信号映射器,触发map方法,与初始化时候设置好的槽函数关联 connect(menuitem_act,SIGNAL(triggered(bool)),m_WndMapper,SLOT(map())); //设置参数 m_WndMapper->setMapping(menuitem_act,wnds.at(i)); }
- 当
menuitem_act
(一个QAction
或QMenuItem
对象)被触发(例如用户点击了菜单项)时,它会发出triggered
信号。这个信号被连接到m_WndMapper
(一个QSignalMapper
对象)的map
槽函数。m_WndMapper
的map
方法被调用。 - 当
m_WndMapper
的map
方法被调用时,它会发出mapped
信号,并传递一个QWidget*
参数(参数需要单独设置,使用setMapping
),这个信号被连接到MainWindow
类的setActiveSubWindow
槽函数。 MainWindow
类的setActiveSubWindow
方法被调用,并传递参数后,这个方法负责将设置参数中指定的窗口为当前活动的子窗口。
拓展:信号映射器的使用场景
- 动态信号连接:当你需要在运行时动态地将信号连接到槽函数时,信号映射器非常有用。例如,你可以在创建对象时决定将哪个信号连接到哪个槽。
- 参数传递:有些信号不提供足够的参数或不提供你需要的参数类型。使用信号映射器可以将信号与一个参数化的槽函数连接,从而传递额外的信息。
- 简化代码:当多个信号需要触发相同的槽函数,但每个信号需要传递不同的参数时,信号映射器可以简化代码。你不需要为每个信号编写单独的连接代码。
- 解耦信号和槽:信号映射器提供了一种将信号源和槽函数解耦的方法。这使得代码更加模块化,易于维护和扩展。
- 处理复杂的用户界面事件:在复杂的用户界面中,可能有许多按钮或其他控件需要触发不同的操作。使用信号映射器可以根据控件的不同状态或类型来调用不同的槽函数。
- 实现自定义的信号分发:如果你需要根据某些条件或逻辑来决定如何响应信号,信号映射器可以作为一个中间件来实现自定义的信号分发逻辑。
- 多信号到单槽的映射:当多个不同的信号需要调用同一个槽函数,但每个信号需要传递不同的参数时,信号映射器可以统一这些信号到一个槽函数。