基于Qt的app开发第十天

写在前面

笔者昨天刚刚收到课设的截止时间要求,距离写这篇博客的时间还有一个月,我从申请自命题课设到今天已经27天了,先用两周时间学Qt,然后就开始做这个项目,现在已经快把基础功能全部实现了。

目前的打算是完成基础功能让学长能下手进行拓展就可以了,至于一些进阶的内容笔者想沉淀完设计模式重构代码之后再加,要不然现在加的内容越多,重构的时候越困难。反正离课设验收还有一个月,时间上绰绰有余。

好了,言归正传,本篇博客记录Tick-Task的笔记板块实现,这一板块在普通的增删改查基础上又多了很多东西:一个文本编辑器肯定不只是能打字,什么文本字体字号段落加粗变颜色下划线都要有,还需要能插入图片,可以说就是一个缩减版的word,而这部分也是笔者没接触过的,所以对我来说还是有点难实现的

需求分析

要在这个界面上加东西,作为一个文本编辑器应该有的功能

笔者计划在这里添加的功能有:

正常支持文本输入;

撤销操作,取消撤销操作;

设置字体的类型、大小、颜色、加粗、倾斜、下划线;

插入图片;

实现思路

(1)辅助控件

首先要在上边那个界面里加控件,这里来介绍几个控件:

QTextEdit:是一个很常见的控件,它已经在图中界面出现了,可以实现文本输入删除光标选中之类的

QFontCombox:这个控件是一个字体选择器,可以选择字体

QSpinBox:这个控件可以调整字号

QColorDialog:这不是一个可视化的控件,但是可以通过和pushButton连接来实现选择字体颜色

(2)核心原理

功能根据选择模式可以分为两种:一种是改掉光标以后的内容,即光标之后的变成用户想要的格式,一种是改变用户选中的内容,即用户用鼠标拖出的一片区域

其实这些功能都可以调用API接口直接实现,需要通过QTextCursor捕获选中范围,再通过QTextCursor::mergeCharFormat()这个函数改变格式就行

(3)梳理顺序

首先,在note.ui里添加需要的控件,包括撤销重做、字体选择器、调整字号、颜色选择器、段落调节

然后,实现新建和修改功能,这一部分和前三个板块思路类似

接着,要把控件的槽函数写出来,核心内容就是把选中文本捕获,然后修改对应文本的格式

具体实现

(1)新增控件

暂时先根据这个界面来实现功能

注意要修改新建出来的控件的命名

(2)实现新建修改

这一部分因为文本内容太多,而且是富文本,用表格视图有些不美观,所以表格里只展示笔记的标题,笔记的具体内容应该和存储联系起来之后再做

新建可以做,就是点击新建按钮然后跳到第二个界面,然后开始输入内容,存储不能实现,这个要连接数据库,然后把标题传回去,文本内容清空,等待下一次点击

修改也可以粗略地做一下,点击对应行,然后连接数据库把标题对应的内容传到第二个界面里,然后保存之后再把文本框的内容传到数据库

cpp 复制代码
//这个函数的作用是获取并初始化TableView控件
void Note::GetNoteDecideTableView()
{
    //获取界面中的表格对象
    QTableView *noteDecide = ui->noteDecideTableView;
    
    //设置表格的点击模式为行单选
    ui->noteDecideTableView->setSelectionBehavior(QAbstractItemView::SelectRows);   // 选择整行
    ui->noteDecideTableView->setSelectionMode(QAbstractItemView::SingleSelection);  // 单选模式
    
    noteDecide->setModel(model);
}

先把表格初始化一下

cpp 复制代码
//这个函数的作用是保存已经新建或修改的笔记-------------------未完成
void Note::on_note_applyButton_clicked()
{
    ui->stackedWidget->setCurrentIndex(0);

    if(addOrRevise==1)
    {
        //这行代码的作用是获取第二个界面输入框里的文本
        QString title=ui->notenext_nameInput->text();

        //这几行代码的作用是向tableView里添加一行
        int row = model->rowCount();
        model->insertRow(row);
        QStandardItem *item = new QStandardItem(title);
        model->setItem(row, item);
        ui->noteDecideTableView->setColumnWidth(row, 381);

        //textEdit中的内容存储过程-----------------------------------------------------------------------------未完成

        //这句代码的作用是清空这个数组以及第二个界面的内容,方便下次使用
        ui->notenext_nameInput->setText("");
        ui->noteTextEdit->setText("");

        addOrRevise=0;
    }
}

这个是新建模式的保存代码 ,核心内容就是往表格插入一行标题,而富文本输入框的存储没有实现

cpp 复制代码
//这个函数的作用是修改已存在的笔记-------------------------未完成
void Note::on_note_reviseButton_clicked()
{
    ui->stackedWidget->setCurrentIndex(1);
    
    //这段代码的作用是将表格中被选中的行的标题传到第二个界面里
    if (currentRow != -1)
    {
        QModelIndex index = model->index(currentRow,0);
        QVariant data = model->data(index);
        QString title=(data.toString());
        
        ui->notenext_nameInput->setText(title);
    }
    
    //把数据库中对应的文本传到第二个界面的textEdit中-------------------------------------------------------未完成
    
    ui->stackedWidget->setCurrentIndex(1);
    
    addOrRevise=2;
}

点击修改之后把文本内容传过去

cpp 复制代码
else if(addOrRevise==2)
    {
        //这行代码的作用是获取第二个界面输入框里的文本
        QString title=ui->notenext_nameInput->text();

        //这几行代码的作用是向tableView选中的一行修改
        QStandardItem *item = new QStandardItem(title);
        model->setItem(currentRow, item);
        ui->noteDecideTableView->setColumnWidth(currentRow, 381);

        //textEdit中的内容存储过程-----------------------------------------------------------------------------未完成

        //这句代码的作用是清空这个数组以及第二个界面的内容,方便下次使用
        ui->notenext_nameInput->setText("");
        ui->noteTextEdit->setText("");

        addOrRevise=0;
    }

点击保存再改对应行的内容

在不连接数据库的情况下,这个新建修改是四个板块中最好实现的,但是事实上连接数据库反而更麻烦 ,那是之后的事情

(3)实现文本编辑

先实现撤销和重做功能,这两个直接调用QTextEdit的库函数就行

cpp 复制代码
//撤销按钮的槽函数
void Note::on_undoButton_clicked()
{
    ui->noteTextEdit->undo();
}

//重做按钮的槽函数
void Note::on_redoButton_clicked()
{
    ui->noteTextEdit->redo();
}

经测试是可以正常使用的,而且自带快捷键(Ctrl+Z)

再实现字体的加粗,注意这里需要把是否选中文本的情况都要考虑到

给加粗这个pushButton控件勾上checkable属性,然后识别它有没有被点就可以选择是细体还是粗体

注意:这里应该是双向传导信息的,选中文本之后再看按钮是什么状态,如果有粗体按钮就变成选中状态,如果没有粗体按钮就是默认状态

实现逻辑:写一个QTextEdit的槽函数,这个槽函数检测文本选中情况的改变,在这个槽函数里写判断是否包含粗体进而修改加粗按钮的选中形式,那这样的话自自然每次加粗之后加粗按钮都要变回默认状态了

cpp 复制代码
//这个函数的作用是加粗字体和取消加粗字体
void Note::on_boldButton_clicked()
{
    //这句的作用是捕获光标选中的位置
    QTextCursor currentCursor = ui->noteTextEdit->textCursor();

    //这两句的作用是确定加粗是被选中状态还是未选中状态
    QTextCharFormat format;
    format.setFontWeight(ui->boldButton->isChecked() ? QFont::Bold : QFont::Normal);

    //判断是是否有选中的文本,如果有就改变选中的文本,没有就改变光标后的内容
    if (currentCursor.hasSelection())
    {
        //对选中的文本应用格式
        currentCursor.mergeCharFormat(format);
        ui->noteTextEdit->setTextCursor(currentCursor);
    }
    else
    {
        //对后续输入的文本应用格式
        ui->noteTextEdit->mergeCurrentCharFormat(format);
    }
}

这是加粗按钮的槽函数,修改逻辑是根据按钮被选中状态来实现的

cpp 复制代码
//这个函数的作用是判断选中文本的状态来决定按钮显示类型--------------------------------------------------------------未实现
void Note::on_noteTextEdit_selectionChanged()
{
    //捕获当前选中内容
    QTextCursor cursor = ui->noteTextEdit->textCursor();

    //判断是否有选中内容
    if (!cursor.hasSelection())
    {

        // 如果没有选中文本,使用当前光标位置的格式
        QTextCharFormat format = ui->noteTextEdit->currentCharFormat();

        // 安全检查:确保格式对象有效
        if (!format.isEmpty())
        {
            ui->boldButton->setChecked(format.fontWeight() == QFont::Bold);
            ui->italicButton->setChecked(format.fontItalic());
        }
        else
        {
            //格式对象无效,重置按钮状态
            ui->boldButton->setChecked(false);
            ui->italicButton->setChecked(false);
        }

    }
    else //这个判断的作用是:如果选中文本有加粗或倾斜,那么按钮就显示选中状态,如果一个加粗或倾斜的字都没有,就显示未选中状态
    {
        //两个标记变量,初始化都是false
        bool hasBold = false;
        bool hasItalic = false;

        //定一个临时光标,用于遍历操作
        QTextCursor tempCursor = cursor;
        //把这个临时光标移到选中区域的第一个字符处,准备开始遍历
        tempCursor.setPosition(cursor.selectionStart());


        //循环:作用是寻找加粗、倾斜字符
        while (!tempCursor.atEnd() && tempCursor.position() < cursor.selectionEnd())
        {
            //临时光标移动操作
            tempCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 1);
            //设一个变量存当前光标指向的字符
            QTextCharFormat tempFormat = tempCursor.charFormat();

            //只要发现一个加粗字符,就标记为存在加粗
            if (tempFormat.fontWeight() == QFont::Bold)
                hasBold = true;

            //只要发现一个倾斜字符,就标记为存在倾斜
            if (tempFormat.fontItalic())
                hasItalic = true;

            //如果两者都已找到,可以提前结束循环
            if (hasBold && hasItalic)
                break;

            tempCursor.setPosition(tempCursor.selectionEnd());
        }

        //根据检查结果设置按钮状态,如果有加粗或倾斜按钮就变成加粗或倾斜
        ui->boldButton->setChecked(hasBold);
        ui->italicButton->setChecked(hasItalic);
    }
}

这段代码是实现加粗和倾斜的核心,主要功能是选中文本,然后根据文本状态改变按钮的状态,按钮的槽函数再根据自己的状态修改文本状态

代码逻辑是利用QTextEdit的selectionChanged信号的槽函数,捕获选中区域然后遍历,如果有加粗和倾斜就改变按钮状态

现在实现字体倾斜操作,和加粗差不多方法

cpp 复制代码
// 这个函数的作用是字体倾斜和取消倾斜
void Note::on_italicButton_clicked()
{
    // 捕获光标选中的位置
    QTextCursor currentCursor = ui->noteTextEdit->textCursor();
    
    // 确定倾斜按钮是被选中状态还是未选中状态
    QTextCharFormat format;
    format.setFontItalic(ui->italicButton->isChecked()); // 设置字体倾斜状态
    
    // 判断是否有选中的文本,如果有就改变选中的文本,没有就改变光标后的内容
    if (currentCursor.hasSelection())
    {
        // 对选中的文本应用格式
        currentCursor.mergeCharFormat(format);
        ui->noteTextEdit->setTextCursor(currentCursor);
    }
    else
    {
        // 对后续输入的文本应用格式
        ui->noteTextEdit->mergeCurrentCharFormat(format);
    }    
}

这个函数只有一处地方和加粗函数不同,就是判断是否被选中时的设置函数不同

下面再实现下划线,这个需要在TextEdit的槽函数中添加类似加粗和倾斜的遍历,然后再实现类似的槽函数就行

这里不好附向TextEdit槽函数添加的代码,就附一个下划线按钮的槽函数吧

cpp 复制代码
//这个函数的作用是字体加下划线和取消下划线
void Note::on_underlineButton_clicked()
{
    //捕获光标选中的位置
    QTextCursor currentCursor = ui->noteTextEdit->textCursor();

    //确定倾斜按钮是被选中状态还是未选中状态
    QTextCharFormat format;
    format.setFontUnderline(ui->underlineButton->isChecked()); // 设置字体下划线状态

    //判断是否有选中的文本,如果有就改变选中的文本,没有就改变光标后的内容
    if (currentCursor.hasSelection())
    {
        //对选中的文本应用格式
        currentCursor.mergeCharFormat(format);
        ui->noteTextEdit->setTextCursor(currentCursor);
    }
    else
    {
        //对后续输入的文本应用格式
        ui->noteTextEdit->mergeCurrentCharFormat(format);
    }
}

其实,加粗、倾斜、下划线这三个代码重复性的有很多,我觉得这样写一点都不好,不过我一时半会儿想不到什么更好的办法,只能先以出功能为主,做完再考虑重构

接下来再实现字号的改变,这个和前三个有一些区别,前三个是非黑即白类型,但是字号可以有很多,所以实现起来也要比前三个难一点

cpp 复制代码
//这个函数的作用是根据字号选择框中的内容来改变文本的字号
void Note::on_spinChoice_valueChanged(int value)
{
    QTextCursor cursor = ui->noteTextEdit->textCursor();

    if (cursor.hasSelection())
    {
        //选中文本的情况:修改选中部分的字号
        QTextCharFormat format;
        format.setFontPointSize(value);
        cursor.mergeCharFormat(format);
        ui->noteTextEdit->setTextCursor(cursor);
    }
    else
    {
        //无选中内容:设置后续输入的字号
        QTextCharFormat format;
        format.setFontPointSize(value);
        ui->noteTextEdit->mergeCurrentCharFormat(format);
    }
}

这个函数是改变字号输入框的槽函数,核心思路就是传入当前输入框的数值,然后把数值设置为选中文本的字号

在textEdit的selectionChanged的槽函数中也需要加东西,遍历选中文本判断其字号并显示在字号输入框中,如果有字号不统一的情况就清空字号输入框

笔者感觉修改字体的方式和字号的方式差不多,所以接下来再做修改字体的操作

先在QTextEdit的槽函数中向字体的输入框中传递信息,再在输入框的槽函数中修改选中文本的字体,这些都有很强的重复性,就不附代码了

接下来实现文本颜色的改变,这个与前几个功能有些不一样,因为它要弹出一个对话框让用户选择颜色

梳理一下实现逻辑:给颜色按钮设置一个槽函数,这个槽函数的作用是打开选择颜色的对话框,然后用户去选择颜色,改变选中文本的颜色,注意这里也有一个判断,因为打开对话框之后要把当前颜色给设置好,因为这个是打开对话框才显示颜色,所以不需要像前几个功能那样在QTextEdit里改实时变化

这里不附遍历代码了,逻辑和前几个功能一样没什么意思

cpp 复制代码
// 打开颜色对话框,使用检测到的颜色作为默认值
    QColor selectedColor = QColorDialog::getColor(currentColor, this, "选择颜色");

    if (selectedColor.isValid())
    {
        QTextCharFormat format;
        format.setForeground(QBrush(selectedColor));

        if (cursor.hasSelection())
        {
            cursor.mergeCharFormat(format);
            ui->noteTextEdit->setTextCursor(cursor);
        }
        else
        {
            ui->noteTextEdit->mergeCurrentCharFormat(format);
        }
    }

(4)实现插图功能

直接在插图按钮的槽函数里调用一堆API就可以

cpp 复制代码
//这个函数的作用是插入图片
void Note::on_insertPictureButton_clicked()
{
    //打开文件对话框选择图片,这是个固定传参类型:(父类窗口指针,窗口标题,文件路径,文件过滤器)
    QString filePath = QFileDialog::getOpenFileName(
        this,
        "选择图片",
        "",
        "图片文件 (*.png *.jpg *.jpeg *.bmp *.gif);;所有文件 (*)"
        );

    if (!filePath.isEmpty())
    {
        //获取当前光标位置
        QTextCursor cursor = ui->noteTextEdit->textCursor();

        //检查文件是否存在且可读
        QFile file(filePath);
        if (file.exists() && file.open(QIODevice::ReadOnly))
        {
            //生成唯一的图片名称(避免重复)操作是获取当前时间的毫秒数然后加上文件后缀
            QString imageName = "image_" + QString::number(QDateTime::currentMSecsSinceEpoch()) + "." +
                                QFileInfo(filePath).suffix();

            //将图片添加到文档资源中
            QImage image(filePath);
            if (!image.isNull())
            {
                //缩放图片(可选,避免过大图片)
                if (image.width() > 200)
                {  //设置最大宽度
                    image = image.scaledToWidth(200, Qt::SmoothTransformation);
                }

                //将图片添加到文档资源中
                ui->noteTextEdit->document()->addResource(QTextDocument::ImageResource,
                                                          QUrl("file:///" + imageName),
                                                          image);

                //插入图片到文本中
                QTextImageFormat imageFormat;
                imageFormat.setName("file:///" + imageName);
                imageFormat.setWidth(image.width());
                imageFormat.setHeight(image.height());

                cursor.insertImage(imageFormat);
                ui->noteTextEdit->setTextCursor(cursor);
            }

            file.close();
        }
    }
}

简单解释一下这个函数,就是打开选择图片的窗口,然后设置这个窗口的一些属性,接着就对打开的文件路径(注意这里只是路径)操作,这里的逻辑是把文件路径传进QTextEdit自己的文件系统里,然后给这张图设置东西,再把它放到QTextEdit里

篇末总结

至此,Tick-Task课设所有基础功能已经全部实现,剩下的是数据向数据库的存储和取出,以及打卡板块时间的更新。笔者在做这个项目的过程中也有了很多感悟:

  1. 做项目也是一个学习的过程,很多东西都是在做项目的过程中学会的,经验也会在一次次修改过程中累积

  2. 调用早就出现的API接口,使用AI是一个很好很有效率的方法,AI不是洪水猛兽,用AI写代码也不丢人

  3. 饭要一点一点吃,路要一步一步走,程序也要一个小功能一个小功能一步步实现

还有一些反思:

  1. QTextEdit控件的selection的槽函数包括的内容实在太多了,这不是一个好事情,事后应该重构

  2. 使用框架的时候操作最多的就是调用库函数,一直调用库函数,活脱脱一个API工程师,但是这样其实不好,搬砖是没啥技术含量的,一定要多做一些有技术含量的操作才行

相关推荐
木易小熙11 分钟前
chromedp -—— 基于 go 的自动化操作浏览器库
开发语言·golang·自动化
天天进步201524 分钟前
C# Prism框架详解:构建模块化WPF应用程序
开发语言·c#·wpf
_extraordinary_31 分钟前
Java 继承
java·开发语言·继承
小鹭同学_40 分钟前
Java基础 Day17
java·开发语言
xun_xin6661 小时前
C++ for QWidget:正则表达式和QRegExp
c++
飞人博尔特的摄影师1 小时前
C#开发利器:SharpBoxesCore全解析
开发语言·设计模式·系统架构·c#·.net·.net core
范纹杉想快点毕业2 小时前
深入解析C++静态成员变量与函数
java·开发语言·jvm
一匹电信狗2 小时前
【Linux我做主】探秘进程与fork
linux·运维·服务器·c++·ubuntu·小程序·unix
JosieBook2 小时前
【web应用】配置Java JDK与maven3的环境变量
java·开发语言
普通的冒险者3 小时前
用java实现内网通讯,可多开客户端链接同一个服务器
java·开发语言