Qt 项目实战 | 多界面编辑器

news2024/7/30 10:05:02

Qt 项目实战 | 多界面编辑器

  • Qt 项目实战 | 多界面编辑器
    • 界面设计
    • 创建子窗口类

官方博客:https://www.yafeilinux.com/

Qt开源社区:https://www.qter.org/

参考书:《Qt 及 Qt Quick 开发实战精解》

Qt 项目实战 | 多界面编辑器

开发环境:Qt Creator 4.6.2 Based on Qt 5.9.6

在这里插入图片描述

界面设计

这里主要是对主窗口和工具栏的设计。

新建 Qt Gui 应用,项目名称 myMdi,类名默认 MainWindow,基类默认为 QMainWindow都不做改动。

添加资源文件 myImage.qrc:

在这里插入图片描述

双击 mainwindow.ui 进入设计模式,添加菜单:

在这里插入图片描述

菜单栏和工具栏:

在这里插入图片描述

文件子菜单,注意有2个分隔符:

在这里插入图片描述

编辑子菜单,注意有1个分隔符:

在这里插入图片描述

窗口子菜单,注意有2个分隔符:

在这里插入图片描述

帮助子菜单:

在这里插入图片描述

设计完菜单栏和工具栏后,向主窗口中心区域拖入一个 MDI Area 部件,并单击主窗口界面,按下 Ctrl + G,使其处于栅格布局。

在这里插入图片描述

确保 MDI Area 部件的 objectName 是 mdiArea,而文件菜单、编辑菜单、窗口菜单、帮助菜单的 objectName 分别是 menuF、menuE、menuW、menuH。

在这里插入图片描述

创建子窗口类

为了实现多文档操作,需要向 QMdiArea 中添加子窗口,我们需要子类化子窗口的中心部件。

新建C++类文件,类名为 MdiChild,基类为 QTextEdit,类型信息选择“继承自 QWidget”:

在这里插入图片描述

在 mdichild.h 添加代码:

#ifndef MDICHILD_H
#define MDICHILD_H

#include <QTextEdit>
#include <QWidget>

class MdiChild : public QTextEdit
{
    Q_OBJECT
private:
    QString curFile;  //当前文件路径
    bool isUntitled;  //作为当前文件是否被保存到硬盘的标志

    bool maybeSave();                              //是否需要保存
    void setCurrentFile(const Qstring& fileName);  //设置当前文件
protected:
    void closeEvent(QCloseEvent* event);  //关闭事件
public:
    explicit MdiChild(QWidget* parent = 0);
    void newFile();                            //新建文件
    bool loadFile(const Qstring& fileName);    //加载文件
    bool save();                               //保存操作
    bool saveAs();                             //另存为操作
    bool saveFile(const QString& fileName);    //保存文件
    QString userFriendlyCurrentFile();         //提取文件名
    QString currentFile() { return curFile; }  //返回当前文件路径
private slots:
    void documentWasModified();  //文档被更改时,窗口显示更改状态标志
};

#endif  // MDICHILD_H

在 mdichild.cpp 添加代码:

#include "mdichild.h"

#include <QApplication>
#include <QCloseEvent>
#include <QFile>
#include <QFileDialog>
#include <QFileInfo>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>

// 是否需要保存
bool MdiChild::maybeSave()
{
    // 如果文档被更改过
    if (document()->isModified())
    {
        QMessageBox box;
        box.setWindowTitle(tr("多文档编辑器"));
        box.setText(tr("是否保存对“%1”的更改?").arg(userFriendlyCurrentFile()));
        box.setIcon(QMessageBox::Warning);
        // 添加按钮,QMessageBox::YesRole可以表明这个按钮的行为
        QPushButton* yesBtn = box.addButton(tr("是(&Y)"), QMessageBox::YesRole);
        box.addButton(tr("否(&N)"), QMessageBox::NoRole);
        QPushButton* cancelBtn = box.addButton(tr("取消"), QMessageBox::RejectRole);
        // 弹出对话框,让用户选择是否保存修改,或者取消关闭操作
        box.exec();
        if (box.clickedButton() == yesBtn)
        {
            // 如果用户选择是,则返回保存操作的结果
            return save();
        }
        else if (box.clickedButton() == cancelBtn)
        {
            // 如果选择取消,则返回false
            return false;
        }
    }
    return true;  // 如果文档没有更改过,则直接返回true
}

// 设置当前文件
void MdiChild::setCurrentFile(const QString& fileName)
{
    // canonicalFilePath()可以除去路径中的符号链接,“.”和“..”等符号
    curFile = QFileInfo(fileName).canonicalFilePath();
    // 文件已经被保存过了
    isUntitled = false;
    // 文档没有被更改过
    document()->setModified(false);
    // 窗口不显示被更改标志
    setWindowModified(false);
    // 设置窗口标题,userFriendlyCurrentFile() 函数返回文件名
    setWindowTitle(userFriendlyCurrentFile() + "[*]");
}

// 关闭操作,在关闭事件中执行
void MdiChild::closeEvent(QCloseEvent* event)
{
    if (maybeSave())
    {
        // 如果 maybeSave() 函数返回 true,则关闭窗口
        event->accept();
    }
    else
    {
        // 否则忽略该事件
        event->ignore();
    }
}

MdiChild::MdiChild(QWidget* parent) : QTextEdit(parent)
{
    // 设置在子窗口关闭时销毁这个类的对象
    setAttribute(Qt::WA_DeleteOnClose);
    // 初始 isUntitled 为 true
    isUntitled = true;
}

// 新建文件操作
void MdiChild::newFile()
{
    // 设置窗口编号,因为编号一直被保存,所以需要使用静态变量
    static int sequenceNumber = 1;
    // 新建的文档没有被保存过
    isUntitled = true;
    // 将当前文件命名为未命名文档加编号,编号先使用再加 1
    curFile = tr("未命名文档%1.txt").arg(sequenceNumber++);
    // 设置窗口标题,使用[*]可以在文档被更改后在文件名称后显示“*”号
    setWindowTitle(curFile + "[*]" + tr(" - 多文档编辑器"));
    // 当文档被更改时发射 contentsChanged() 信号,执行 documentWasModified() 槽函数
    connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
}

// 加载文件
bool MdiChild::loadFile(const QString& fileName)
{
    // 新建 QFile 对象
    QFile file(fileName);

    // 只读方式打开文件,出错则提示,并返回 false
    if (!file.open(QFile::ReadOnly | QFile::Text))
    {
        QMessageBox::warning(this, tr("多文档编辑器"),
                             tr("无法读取文件 %1:\n%2.").arg(fileName).arg(file.errorString()));
        return false;
    }
    // 新建文本流对象
    QTextStream in(&file);
    // 设置鼠标状态为等待状态
    QApplication::setOverrideCursor(Qt::WaitCursor);
    // 读取文件的全部文本内容,并添加到编辑器中
    setPlainText(in.readAll());
    // 恢复鼠标状态
    QApplication::restoreOverrideCursor();
    // 设置当前文件
    setCurrentFile(fileName);
    connect(document(), SIGNAL(contentsChanged()), this, SLOT(documentWasModified()));
    return true;
}

// 保存操作
bool MdiChild::save()
{
    if (isUntitled)
    {
        // 如果文件未被保存过,则执行另存为操作
        return saveAs();
    }
    else
    {
        // 否则直接保存文件
        return saveFile(curFile);
    }
}

// 另存为操作
bool MdiChild::saveAs()
{
    // 使用文件对话框获取文件路径
    QString fileName = QFileDialog::getSaveFileName(this, tr("另存为"), curFile);
    if (fileName.isEmpty())
    {
        // 如果文件路径为空,则返回 false
        return false;
    }
    // 否则保存文件
    return saveFile(fileName);
}

// 保存文件
bool MdiChild::saveFile(const QString& fileName)
{
    QFile file(fileName);
    if (!file.open(QFile::WriteOnly | QFile::Text))
    {
        QMessageBox::warning(this, tr("多文档编辑器"),
                             tr("无法写入文件 %1:\n%2.").arg(fileName).arg(file.errorString()));
        return false;
    }
    QTextStream out(&file);
    // 设置应用程序强制光标为等待旋转光标(设置鼠标状态为等待状态)
    QApplication::setOverrideCursor(Qt::WaitCursor);
    // 以纯文本文件写入
    out << toPlainText();
    // 恢复光标(恢复鼠标状态)
    QApplication::restoreOverrideCursor();
    setCurrentFile(fileName);
    return true;
}

// 提取文件名
QString MdiChild::userFriendlyCurrentFile()
{
    // 从文件路径中提取文件名
    return QFileInfo(curFile).fileName();
}

// 文档被更改时,窗口显示更改状态标志
void MdiChild::documentWasModified()
{
    // 根据文档的isModified()函数的返回值,判断编辑器内容是否被更改了
    // 如果被更改了,就要在设置了[*]号的地方显示“*”号,这里会在窗口标题中显示
    setWindowModified(document->isModified());
}

下面对这个类进行简单的测试。

在 mainwindow.cpp 中添加以下代码:

#include "mdichild.h"

因为程序中使用了中文,要在 main.cpp 文件中添加头文件和代码:

#include <QTextCodec>
// 解决 Qt 中文乱码问题
QTextCodec::setCodecForLocale(QTextCodec::codecForLocale());
// QTextCodec::setCodecForLocale(QTextCodec::codecForName("utf-8"));

转到设计模式,在 Action Editor 中“新建文件”动作上右击,转到它的触发信号 triggered() 的槽,并更改如下:

void MainWindow::on_actionNew_triggered()
{
    //创建 MdiChild
    MdiChild* child = new MdiChild;
    //多文档区域添加子窗口
    ui->mdiArea->addSubWindow(child);
    //新建文件
    child->newFile();
    //显示子窗口
    child->show();
}

测试结果:

在这里插入图片描述

打开多个界面也没有问题。

在这里插入图片描述

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

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

相关文章

运行segment anything模型的web demo 教程

这个web应用放在在源码的demo文件夹里&#xff1a; 这个前端仅基于React的web演示了如何加载固定图像和相应的SAM image embedding的.npy文件。 运行需要配置npm环境。 首先导出onnx的模型&#xff1a; import torch import numpy as np import cv2 import matplotlib.pyplo…

解决 Vue3 + Element Plus 树形表格全选多选以及子节点勾选的问题

原文链接&#xff1a; 解决 Vue3 Element Plus 树形表格全选多选以及子节点勾选的问题 前言 最近用到了 Element Plus 组件库的中的树形表格&#xff0c;但官网例子只能做到一层勾选&#xff0c;不能做到多层勾选&#xff0c;无法满足业务需求&#xff0c;所以研究了下&#…

PHP 预定义超全局变量 笔记/练习

预定义超全局数组变量 $_FILES 练习在最后 其他练习跟在每条笔记后 概述 预定义&#xff1a;预定义变量是 PHP 已定义&#xff0c;可以直接使用超全局&#xff1a;作用域是全局&#xff0c;可以在脚本的任何地方&#xff08;包括函数内部、外部&#xff09;都可以进行访问 常…

CSS必学:你需要知道的盒子模型的秘密

作者:WangMin 格言:努力做好自己喜欢的每一件事 CSDN原创文章 博客地址 &#x1f449; WangMin 作为前端开发来说&#xff0c;要掌握的CSS基础一定很多&#xff0c;那么CSS中盒子模型肯定是必考必问必掌握的前端知识点&#xff0c;因为它是CSS基础中非常重要的内容&#xff0c;…

分步实现编写LED驱动,实现特备文件和设备的绑定

通过字符设备驱动的分步实现编写LED驱动&#xff0c;另外实现特备文件和设备的绑定&#xff0c;发布到CSDN head.h #ifndef __HEAD_H__ #define __HEAD_H__ typedef struct {unsigned int MODER;unsigned int OTYPER;unsigned int OSPEEDR;unsigned int PUPDR;unsigned int ID…

photoshop2024免费插件Portraiture3

随着手机摄影的普及&#xff0c;修图可以说是现代人的必备生活技能之一了&#xff0c;现在谁发个朋友圈不把自己的照片修的美美的呢&#xff1f;那么如何拥有一张氛围感满满的照片呢&#xff1f;这不得不提图片处理软件中的王牌——photoshop。作为专业的图片处理软件&#xff…

Node.js与npm版本比对

Node.js与npm版本比对 Node.js与npm版本比对版本对比表Node版本对比 Node.js与npm版本比对 我们在项目开发过程中&#xff0c;经常会遇到公司一些老的前端工程项目&#xff0c;而我们当前的node及npm版本都是相对比较新的了。 在运行以前工程时&#xff0c;会遇到相关环境不匹…

2023年中国汽车差速器需求量、竞争现状及行业市场规模分析[图]

差速器是汽车驱动系统的主要部件&#xff0c;它的作用就是在向两边半轴传递动力的同时&#xff0c;允许两边半轴以不同的转速旋转&#xff0c;满足两边车轮尽可能以纯滚动的形式作不等距行驶&#xff0c;减少轮胎与地面的摩擦。汽车差速器是驱动车轮差速转弯或复杂路面强力通过…

KNN(K近邻)水仙花的分类(含答案)

题目 以下采用K-NN算法来解决水仙花的分类问题&#xff0c;每个样本有两个特征&#xff0c;第一个为水仙花的花萼长度&#xff0c;第二个为水仙花 的花萼宽度&#xff0c;具体数据见表&#xff0c; 1&#xff09;设置k3&#xff0c; 采用欧式距离&#xff0c;分析分类精度为多少…

【强化学习】06 —— 基于无模型的强化学习(Control)

文章目录 简介On-policy Monte-Carlo ControlGLIEMC vs. TD Control On-policy Temporal Difference ControlSarsaExample1 Windy GridworldForward View Sarsa(λ)Backward View Sarsa(λ)Sarsa 代码 Off-Policy Learning重要性采样Importance Sampling使用重要性采样的离线策…

【计算机网络】应用层协议--HTTP协议及HTTP报文格式

目录 1、HTTP是什么 2、HTTP请求与响应 3、HTTP请求的两种方法(get和post)及区别 (面试题) 4、几种常见的错误的说法 5、HTTP协议的特点 6、应用场景 7、HTTP报文格式 8、面试题&#xff1a;HTTP常见的状态码都有哪些&#xff1f; 1、HTTP是什么 HTTP协议是在Web上进行…

Day08File类IO流字符集

File:代表文本和文件夹 File类只能对文件本身进行操作&#xff0c;不能读写文件里面存储的数据。 创建File类的对象 路径写法 绝对路径:从盘符开始 File file new File(“D:\\ceshi\\a.txt”); 相对路径:不带盘符&#xff0c;默认直接到当前工程下的目录寻找文件 F…

Ubuntu 安装 npm 和 node

前言 最近学习VUE&#xff0c;在ubuntu 2204 上配置开发环境&#xff0c;涉及到npm node nodejs vue-Cli脚手架等内容&#xff0c;做以记录。 一、node nodejs npm nvm 区别 &#xff1f; node 是框架&#xff0c;类似python的解释器。nodejs 是编程语言&#xff0c;是js语言的…

[AUTOSAR][诊断管理][ECU][$19] 读取ECU的DTC故障信息

一、简介 在车载诊断中常用的诊断协议有ISO 14229等&#xff0c;在协议中主要定义了诊断请求、诊断响应的报文格式及ECU该如何处理诊断请求的应用。其中ISO 14229系列标准协议定义了用于行业内诊断通信的需求规范&#xff0c;也就是UDS。UDS主要应用于OSI七层模型的第七层——…

LInux系统编程(3)

取得拓展属性 #include <sys/types.h> #include <attr/xattr.h>ssize_t getxattr(const char* path, const char* key, void* value, size_t size); ssize_t lgetxattr(const char* path, const char* key, void* value, size_t size); ssize_t fgetxattr(int fd,…

FreeRTOS 任务的创建与删除

目录 1. 什么是任务&#xff1f; 2. 任务创建与删除相关函数 任务创建与删除相关函数有如下三个&#xff1a; 任务动态创建与静态创建的区别&#xff1a; xTaskCreate 函数原型​编辑 vTaskDelete 函数原型 3. 创建两个任务进行点灯实操 使用CubeMX快速移植 1.增加两个…

【谢希尔 计算机网络】第4章 网络层

目录 网络层 网络层的几个重要概念 网络层提供的两种服务 网络层的两个层面 网际协议 IP 虚拟互连网络 IP 地址 IP 地址与 MAC 地址 地址解析协议 ARP IP 数据报的格式 IP 层转发分组的过程 基于终点的转发 最长前缀匹配 使用二叉线索查找转发 网际控制报文协议…

2023CSPS 种树 —— 二分+前缀和

This way 题意&#xff1a; 一开始以为是水题&#xff0c;敲了一个二分贪心检查的代码&#xff0c;20分。发现从根往某个节点x走的时候&#xff0c;一路走来的子树上的节点到已栽树的节点的距离会变短&#xff0c;那么并不能按照初始情况贪心。 于是就想着检查时候用线段树…

2023-1024‍节日(内含表白代码)

文章目录 一、前言二、代码实现三、动态展示四、总结 一、前言 1024可以是计算机操作系统的进制单位&#xff0c;也可以是&#x1f9d1;‍&#x1f4bb;程序员们的特殊纪念日。 每年10月24日被行业认定为“程序员节”。 今天&#xff0c;正是一年一度的“1024程序员节”在此纪…

LeetCode讲解篇之面试题 01.08. 零矩阵

文章目录 题目描述题解思路题解代码 题目描述 题解思路 遍历矩阵&#xff0c;若当前元素为零&#xff0c;则将该行和该列的第一个元素置零 遍历第一行&#xff0c;若当前元素为零&#xff0c;则将当前列置零 遍历第一列&#xff0c;若当前元素为零&#xff0c;则将当前行置零 …