基于Qt的app开发第十天
写在前面
笔者昨天刚刚收到课设的截止时间要求,距离写这篇博客的时间还有一个月,我从申请自命题课设到今天已经27天了,先用两周时间学Qt,然后就开始做这个项目,现在已经快把基础功能全部实现了。
目前的打算是完成基础功能让学长能下手进行拓展就可以了,至于一些进阶的内容笔者想沉淀完设计模式重构代码之后再加,要不然现在加的内容越多,重构的时候越困难。反正离课设验收还有一个月,时间上绰绰有余。
好了,言归正传,本篇博客记录Tick-Task的笔记板块实现,这一板块在普通的增删改查基础上又多了很多东西:一个文本编辑器肯定不只是能打字,什么文本字体字号段落加粗变颜色下划线都要有,还需要能插入图片,可以说就是一个缩减版的word,而这部分也是笔者没接触过的,所以对我来说还是有点难实现的
需求分析
要在这个界面上加东西,作为一个文本编辑器应该有的功能
笔者计划在这里添加的功能有:
正常支持文本输入;
撤销操作,取消撤销操作;
设置字体的类型、大小、颜色、加粗、倾斜、下划线;
插入图片;
实现思路
(1)辅助控件
首先要在上边那个界面里加控件,这里来介绍几个控件:
QTextEdit:是一个很常见的控件,它已经在图中界面出现了,可以实现文本输入删除光标选中之类的
QFontCombox:这个控件是一个字体选择器,可以选择字体
QSpinBox:这个控件可以调整字号
QColorDialog:这不是一个可视化的控件,但是可以通过和pushButton连接来实现选择字体颜色
(2)核心原理
功能根据选择模式可以分为两种:一种是改掉光标以后的内容,即光标之后的变成用户想要的格式,一种是改变用户选中的内容,即用户用鼠标拖出的一片区域
其实这些功能都可以调用API接口直接实现,需要通过QTextCursor捕获选中范围,再通过QTextCursor::mergeCharFormat()这个函数改变格式就行
(3)梳理顺序
首先,在note.ui里添加需要的控件,包括撤销重做、字体选择器、调整字号、颜色选择器、段落调节
然后,实现新建和修改功能,这一部分和前三个板块思路类似
接着,要把控件的槽函数写出来,核心内容就是把选中文本捕获,然后修改对应文本的格式
具体实现
(1)新增控件
暂时先根据这个界面来实现功能
注意要修改新建出来的控件的命名
(2)实现新建修改
这一部分因为文本内容太多,而且是富文本,用表格视图有些不美观,所以表格里只展示笔记的标题,笔记的具体内容应该和存储联系起来之后再做
新建可以做,就是点击新建按钮然后跳到第二个界面,然后开始输入内容,存储不能实现,这个要连接数据库,然后把标题传回去,文本内容清空,等待下一次点击
修改也可以粗略地做一下,点击对应行,然后连接数据库把标题对应的内容传到第二个界面里,然后保存之后再把文本框的内容传到数据库
//这个函数的作用是获取并初始化TableView控件
void Note::GetNoteDecideTableView()
{//获取界面中的表格对象QTableView *noteDecide = ui->noteDecideTableView;//设置表格的点击模式为行单选ui->noteDecideTableView->setSelectionBehavior(QAbstractItemView::SelectRows); // 选择整行ui->noteDecideTableView->setSelectionMode(QAbstractItemView::SingleSelection); // 单选模式noteDecide->setModel(model);
}
先把表格初始化一下
//这个函数的作用是保存已经新建或修改的笔记-------------------未完成
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;}
}
这个是新建模式的保存代码 ,核心内容就是往表格插入一行标题,而富文本输入框的存储没有实现
//这个函数的作用是修改已存在的笔记-------------------------未完成
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;
}
点击修改之后把文本内容传过去
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的库函数就行
//撤销按钮的槽函数
void Note::on_undoButton_clicked()
{ui->noteTextEdit->undo();
}//重做按钮的槽函数
void Note::on_redoButton_clicked()
{ui->noteTextEdit->redo();
}
经测试是可以正常使用的,而且自带快捷键(Ctrl+Z)
再实现字体的加粗,注意这里需要把是否选中文本的情况都要考虑到
给加粗这个pushButton控件勾上checkable属性,然后识别它有没有被点就可以选择是细体还是粗体
注意:这里应该是双向传导信息的,选中文本之后再看按钮是什么状态,如果有粗体按钮就变成选中状态,如果没有粗体按钮就是默认状态
实现逻辑:写一个QTextEdit的槽函数,这个槽函数检测文本选中情况的改变,在这个槽函数里写判断是否包含粗体进而修改加粗按钮的选中形式,那这样的话自自然每次加粗之后加粗按钮都要变回默认状态了
//这个函数的作用是加粗字体和取消加粗字体
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);}
}
这是加粗按钮的槽函数,修改逻辑是根据按钮被选中状态来实现的
//这个函数的作用是判断选中文本的状态来决定按钮显示类型--------------------------------------------------------------未实现
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 //这个判断的作用是:如果选中文本有加粗或倾斜,那么按钮就显示选中状态,如果一个加粗或倾斜的字都没有,就显示未选中状态{//两个标记变量,初始化都是falsebool 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信号的槽函数,捕获选中区域然后遍历,如果有加粗和倾斜就改变按钮状态
现在实现字体倾斜操作,和加粗差不多方法
// 这个函数的作用是字体倾斜和取消倾斜
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槽函数添加的代码,就附一个下划线按钮的槽函数吧
//这个函数的作用是字体加下划线和取消下划线
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);}
}
其实,加粗、倾斜、下划线这三个代码重复性的有很多,我觉得这样写一点都不好,不过我一时半会儿想不到什么更好的办法,只能先以出功能为主,做完再考虑重构
接下来再实现字号的改变,这个和前三个有一些区别,前三个是非黑即白类型,但是字号可以有很多,所以实现起来也要比前三个难一点
//这个函数的作用是根据字号选择框中的内容来改变文本的字号
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里改实时变化
这里不附遍历代码了,逻辑和前几个功能一样没什么意思
// 打开颜色对话框,使用检测到的颜色作为默认值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就可以
//这个函数的作用是插入图片
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工程师,但是这样其实不好,搬砖是没啥技术含量的,一定要多做一些有技术含量的操作才行