第61讲 记事本实现当前行高亮功能
QList<QTextEdit::ExtraSelection> extraSelections;
void setExtraSelections(const QList<QTextEdit::ExtraSelection> &extraSelections)
ExtraSelection类内部有两个公有成员变量:
其中cursor可以存储获取的光标当前位置信息(比如行数),format用来存储文本样式属性。
综上所述,当前行高亮这个功能的实现还是需要在光标点击的响应槽函数onCursorPositionChanged内部。
第一步,声明了一个 QList
容器 extraSelections
,用于存储 QTextEdit::ExtraSelection
类型的元素,并且明了一个 QTextEdit::ExtraSelection(结构体)
类型的变量 ext;
QList <QTextEdit::ExtraSelection> extraSelections;
QTextEdit::ExtraSelection ext;
第二步,利用textEdit内部的textCursor方法获取当前行信息;
ext.cursor=ui->textEdit->textCursor();
第三步,开始配置颜色,创建了一个 QBrush
对象 qBrush
,并使用 Qt::lightGray
颜色初始化它,然后通过 ext.format.setBackground(qBrush)
这行代码,将创建好的画刷所代表的颜色(浅灰色)设置为 ext
这个 ExtraSelection
实例对应的文本格式(ext.format
)的背景颜色;
QBrush qBrush(Qt::lightGray);
ext.format.setBackground(qBrush);
第四步,利用setProperty方法配置段属性为整行显示
ext.format.setProperty(QTextFormat::FullWidthSelection,true);
这行代码调用了 ext.format
(QTextCharFormat
类型,用于设置文本的字符格式相关属性)的 setProperty
方法,将 QTextFormat::FullWidthSelection
属性设置为 true
。QTextFormat::FullWidthSelection
这个属性表示文本选择区域按照整行的宽度来显示,确保不仅仅是当前光标所在处的文本有背景颜色等样式设置,而是整行文本都会应用之前配置的背景颜色(也就是浅灰色),实现整行高亮的效果。
第五步,进行整体配置
extraSelections.append(ext);
ui->textEdit->setExtraSelections(extraSelections);
第一行 extraSelections.append(ext)
将配置好的 ext
(代表当前行的额外选择区域相关属性)添加到 extraSelections
列表中。最后一行 ui->textEdit->setExtraSelections(extraSelections)
则调用了 textEdit
部件的 setExtraSelections
方法,将包含了当前行额外选择区域配置信息的 extraSelections
列表传递进去,使得 textEdit
部件按照这个列表里定义的规则(这里就是当前行高亮的相关属性设置)来更新其显示效果,最终实现在文本编辑部件中当前行被高亮显示的功能。
最后整个槽函数:
void Widget::onCursorPositionChanged(void)
{
QTextCursor cursor=ui->textEdit->textCursor();
//qDebug()<<cursor.blockNumber()+1 <<cursor.columnNumber()+1;
QString blockNum=QString::number(cursor.blockNumber()+1);
QString columnNum=QString::number(cursor.columnNumber()+1);
const QString labelMes="L:"+blockNum+" "+"C:"+columnNum;
ui->labelPositon->setText(labelMes);
/*设置当前行高亮*/
//0.声明了一个 QList 容器 extraSelections
QList <QTextEdit::ExtraSelection> extraSelections;
//同时声明了ext这个结构体变量
QTextEdit::ExtraSelection ext;
//1.获取当前行的数值
ext.cursor=ui->textEdit->textCursor();
//2.配置颜色
QBrush qBrush(Qt::lightGray);
ext.format.setBackground(qBrush);
//3.配置段属性,整行显示
ext.format.setProperty(QTextFormat::FullWidthSelection,true);
//4.整体配置
extraSelections.append(ext);
ui->textEdit->setExtraSelections(extraSelections);
}
运行效果:
第62讲 记事本优化保存文件的逻辑
代码目前存在这么一个问题,在当前页面编辑了一部分文本之后无法通过按键关闭清除。
解决方法是按下关闭按键的时候,不进行文件是否打开的判断,直接使用clear函数清屏。
void Widget::on_btnClose_clicked()
{
ui->textEdit->clear();
if(file.isOpen())
{
file.close();
this->setWindowTitle("NotesBook");
}
}
现在无论文件是否存在于文件中都需要弹出对话框进行询问,和我们的操作习惯不搭,我们更希望在原文件已经存在的时候,直接按保存就行。所以我们需要对btnSave槽函数做出修改:
void Widget::on_btnSave_clicked()
{
//如果当前没有文件打开,就弹窗让用户选择新文件,创建新文件;
//而不是原来那样,都弹出新的文件保存窗口
if(!file.isOpen()){
QString fileName = QFileDialog::getSaveFileName(this, tr("SaveFile"),
"E:\\6_Qt Projects\\31_NotepadCodec\\files\\untitled file.txt",
tr("Text (*.txt*.doc)"));
file.setFileName(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error";
}
this->setWindowTitle(fileName + "- MyNoteBook");
}else
{
// 如果文件已打开,先将文件指针移动到文件开头,准备覆盖原有内容
file.seek(0);
// 截断文件内容,清除原有内容
file.resize(0);
}
//当保存被按下,不管是已有打开的文件还是上面if满足后用户选择新文件,
//都要读取TextEdit内容并写入文件中
QTextStream out(&file);
out.setCodec(ui->comboBox->currentText().toStdString().c_str());
QString context = ui->textEdit->toPlainText();
out << context;
}
第63讲 记事本关闭按钮功能优化
对于QT框架下的陌生组件,推荐大家在帮助界面查看Detailed Description。
可以看到这里有着各种类型的API:
我们选用一个静态的warning函数和一个switch循环分支结构:
int ret = QMessageBox::warning(this, tr("My Application"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel,
QMessageBox::Save);
switch (ret) {
case QMessageBox::Save:
// Save was clicked
break;
case QMessageBox::Discard:
// Don't Save was clicked
break;
case QMessageBox::Cancel:
// Cancel was clicked
break;
default:
// should never be reached
break;
}
为了警告对话框的出现更加符合我们的习惯,我在widget类添加了新的成员变量:
然后对widget.cpp文件做出如下修改:
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QDebug>
#include <QList>
#include <QColor>
#include <QMessageBox>
Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget)
{
ui->setupUi(this);
this->setLayout(ui->verticalLayout);
ui->widgetBottom->setLayout(ui->horizontalLayout);
connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),
this,SLOT(onCurrentIndexChanged(void)));
connect(ui->textEdit,SIGNAL(cursorPositionChanged()),
this,SLOT(onCursorPositionChanged()));
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_btnOpen_clicked()
{
QString fileName=QFileDialog::getOpenFileName(this,tr("Open File"),
"E:\\6_Qt Projects\\31_NotepadCodec\\files",tr("Text(*.txt *.doc)"));
//QFileDialog限制程序可打开的文件形式为txt文件或者doc文本
ui->textEdit->clear();
//每次打开文件时清除控件区域“textEdit”
file.setFileName(fileName);
if(!file.open(QIODevice::ReadWrite|QIODevice::Text))
{
qDebug()<<"file open error";
}
this->setWindowTitle(fileName+"-NotesBook");
QTextStream in(&file);
QString str=ui->comboBox->currentText();
const char* c_str=str.toStdString().c_str();
in.setCodec(c_str);
while(!in.atEnd())
{
QString context=in.readLine();
//qDebug()<<qPrintable(context);
ui->textEdit->append(context);
//将读取到的每行内容通过 append 方法添加到界面的文本编辑框(ui->textEdit)中
}
// 将此时ui->textEdit中的文本内容保存到initialText变量中,作为初始文本
initialText = ui->textEdit->toPlainText();
}
void Widget::on_btnSave_clicked()
{
//如果当前没有文件打开,就弹窗让用户选择新文件,创建新文件;
//而不是原来那样,都弹出新的文件保存窗口
if(!file.isOpen()){
QString fileName = QFileDialog::getSaveFileName(this, tr("SaveFile"),
"E:\\6_Qt Projects\\31_NotepadCodec\\files\\untitled file.txt",tr("Text (*.txt*.doc)"));
file.setFileName(fileName);
if(!file.open(QIODevice::WriteOnly | QIODevice::Text)){
qDebug() << "file open error";
}
this->setWindowTitle(fileName + "- MyNoteBook");
}else
{
// 如果文件已打开,先将文件指针移动到文件开头,准备覆盖原有内容
file.seek(0);
// 截断文件内容,清除原有内容
file.resize(0);
}
//当保存被按下,不管是已有打开的文件还是上面if满足后用户选择新文件,
//都要读取TextEdit内容并写入文件中
QTextStream out(&file);
out.setCodec(ui->comboBox->currentText().toStdString().c_str());
QString context = ui->textEdit->toPlainText();
out << context;
//按下保存按钮会修改初始文本
initialText=ui->textEdit->toPlainText();
}
void Widget::on_btnClose_clicked()
{
QString currentText = ui->textEdit->toPlainText();
if(currentText!= initialText){
int ret = QMessageBox::warning(this, tr("NotesBook Notice"),
tr("The document has been modified.\n"
"Do you want to save your changes?"),
QMessageBox::Save | QMessageBox::Discard
| QMessageBox::Cancel, QMessageBox::Save);
switch (ret) {
case QMessageBox::Save:
// Save was clicked
on_btnSave_clicked();
ui->textEdit->clear();
break;
case QMessageBox::Discard:
// Don't Save was clicked
ui->textEdit->clear();
if (file.isOpen()) {
file.close();
this->setWindowTitle("NotesBook");
}
break;
case QMessageBox::Cancel:
// Cancel was clicked
break;
default:
// should never be reached
break;
}
} else {
ui->textEdit->clear();
if (file.isOpen()) {
file.close();
this->setWindowTitle("NotesBook");
}
}
}
void Widget::onCurrentIndexChanged(void)
{
ui->textEdit->clear();
if(file.isOpen())
{
QTextStream in(&file);
in.setCodec(ui->comboBox->currentText().toStdString().c_str());
//链式调用访问成员变量
file.seek(0);//将光标移动回起始点
while(!in.atEnd())
{
QString context=in.readLine();
ui->textEdit->append(context);
}
}
}
void Widget::onCursorPositionChanged(void)
{
QTextCursor cursor=ui->textEdit->textCursor();
//qDebug()<<cursor.blockNumber()+1 <<cursor.columnNumber()+1;
QString blockNum=QString::number(cursor.blockNumber()+1);
QString columnNum=QString::number(cursor.columnNumber()+1);
const QString labelMes="L:"+blockNum+" "+"C:"+columnNum;
ui->labelPositon->setText(labelMes);
/*设置当前行高亮*/
//0.声明了一个 QList 容器 extraSelections
QList <QTextEdit::ExtraSelection> extraSelections;
//同时声明了ext这个结构体变量
QTextEdit::ExtraSelection ext;
//1.获取当前行的数值
ext.cursor=ui->textEdit->textCursor();
//2.配置颜色
QBrush qBrush(Qt::lightGray);
ext.format.setBackground(qBrush);
//3.配置段属性,整行显示
ext.format.setProperty(QTextFormat::FullWidthSelection,true);
//4.整体配置
extraSelections.append(ext);
ui->textEdit->setExtraSelections(extraSelections);
}
第64讲 记事本添加快捷键功能
我们通常使用QShortcut类来实现快捷键的功能,就像下图中的手册内容节选:
最简单的应用方法如下:
/*The simplest way to create a shortcut for a particular widget is to
construct the shortcut with a key sequence. For example:*/
QShortcut *shortcut = new QShortcut(QKeySequence(tr("Ctrl+O", "File|Open")),parent);
注意new创建的对象返回类型均为指针。
此时我们已经梳理出了事件“按下快捷键Ctrl+O”,槽函数可以为“on_btnOpen_clicked()”,还需要找到事件触发的信号。
查阅手册得知,QShortcut类继承QObject类的两个静态信号。
最后Widget的构造函数被修改为:
Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget)
{
ui->setupUi(this);
QShortcut *shortcutOpen = new QShortcut(QKeySequence(tr("Ctrl+O", "File|Open")),
this);
QShortcut *shortcutSave = new QShortcut(QKeySequence(tr("Ctrl+S", "File|Save")),
this);
connect(shortcutOpen,&QShortcut::activated,[=](){on_btnOpen_clicked();});
connect(shortcutSave,&QShortcut::activated,[=](){on_btnSave_clicked();});
this->setLayout(ui->verticalLayout);
ui->widgetBottom->setLayout(ui->horizontalLayout);
connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),
this,SLOT(onCurrentIndexChanged(void)));
connect(ui->textEdit,SIGNAL(cursorPositionChanged()),
this,SLOT(onCursorPositionChanged()));
}
第64讲 记事本快捷键放大缩小字体
QWidget类内部的setFont方法可以动态改变UI显示的字体大小,我们跳转查看一下函数定义:
void setFont(const QFont &);
注意该方法需要传入一个QFont类型的引用,同时QFont内部有pointSize方法获取当前字体大小。
综上所述,我们可以和上一章先使用QShortcut类创建按键事件,再使用connect函数将信号与槽关联起来。其中除了槽函数在Widget构造函数外部实现之外,其余均在内部修改。
Widget构造函数:
Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget)
{
ui->setupUi(this);
QShortcut *shortcutOpen = new QShortcut(QKeySequence(tr("Ctrl+O", "File|Open")),
this);
QShortcut *shortcutSave = new QShortcut(QKeySequence(tr("Ctrl+S", "File|Save")),
this);
QShortcut *shortcutEnlarge = new QShortcut(QKeySequence(tr("Ctrl+Shift+=", "File|Enlarge")),
this);
QShortcut *shortcutReduce = new QShortcut(QKeySequence(tr("Ctrl+Shift+-", "File|Reduce")),
this);
connect(shortcutOpen,&QShortcut::activated,[=](){on_btnOpen_clicked();});
connect(shortcutSave,&QShortcut::activated,[=](){on_btnSave_clicked();});
connect(shortcutEnlarge,&QShortcut::activated,[=](){textEditEnlarge();});
connect(shortcutReduce, &QShortcut::activated,[=](){textEditReduce();});
this->setLayout(ui->verticalLayout);
ui->widgetBottom->setLayout(ui->horizontalLayout);
connect(ui->comboBox,SIGNAL(currentIndexChanged(int)),
this,SLOT(onCurrentIndexChanged(void)));
connect(ui->textEdit,SIGNAL(cursorPositionChanged()),
this,SLOT(onCursorPositionChanged()));
}
使得字体增大\减小的槽函数:
void Widget::textEditEnlarge()
{
//获取当前TextEdit内的字体信息
QFont font=ui->textEdit->font();
//获取当前字体的大小
int fontSize=font.pointSize();
if(fontSize==-1) return;
//改变大小,并设置字体大小
int newFontSize=fontSize+1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}
void Widget::textEditReduce()
{
//获取当前TextEdit内的字体信息
QFont font=ui->textEdit->font();
//获取当前字体的大小
int fontSize=font.pointSize();
if(fontSize==-1) return;
//改变大小,并设置字体大小
int newFontSize=fontSize-1;
font.setPointSize(newFontSize);
ui->textEdit->setFont(font);
}
建议在Widget.h文件内部添加上两个槽函数的声明:
第65讲 QT事件的引入
人机交互最早的时候是用命令行,比如查看我们PC机上的网络信息(以Win11为例),可以按下Win+R->输入cmd->输入ipconfig:
本节课我们要实现的滚轮缩放文本字体本质上也是一种人机交互事件。众所周知Qt是一个基于C++的框架,主要用来开发带窗口的应用程序(不带窗口的也行,但不是主流)。 我们使用的基于窗口的应用程序都是基于事件,其目的主要是用来实现回调(因为只有这样程序的效率才是最高的)。所以在Qt框架内部为我们提供了一些列的事件处理机制,当窗口事件产生之后,事件会经过: “事件派发 -> 事件过滤->事件分发->事件处理” 几个阶段。Qt窗口中对于产生的一系列事件都有默认的处理动作,如果我们有特殊需求就需要在合适的阶段重写事件的处理动作,比如信号与槽就是一种事件(event)是由系统或者 Qt 本身在不同的场景下发出的。当用户按下/移动鼠标、敲下键盘,或者是窗口关闭/大小发生变化/隐藏或显示都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如鼠标/键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
每一个Qt应用程序都对应一个唯一的 QApplication 应用程序对象,然后调用这个对象的 exec() 函 数,这样Qt框架内部的事件检测就开始了( 程序将进入事件循环来监听应用程序的事件 )。
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow* w = new MainWindow;
w.show();
return a.exec();
}
[override virtual] bool QApplication::notify(QObject *receiver, QEvent *e);
// 需要先给窗口安装过滤器, 该事件才会触发
[virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
[override virtual protected] bool QWidget::event(QEvent *event);
// 鼠标按下
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);
// 鼠标释放
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event);
// 鼠标移动
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);
第66讲 重写窗口各类默认事件
创建了五个例子,分别是鼠标进入窗口的事件、鼠标离开窗口的事件、滚轮上下滚动的事件、店家关闭按钮的事件以及拖动窗口大小的事件。
代码示例如下:
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
#include <QMessageBox>
#include <QWheelEvent>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
}
Widget::~Widget()
{
delete ui;
}
void Widget::enterEvent(QEvent *event)
{
qDebug()<<"mouse enter";
}
void Widget::leaveEvent(QEvent *event)
{
qDebug()<<"mouse leave";
}
void Widget::wheelEvent(QWheelEvent *event)
{
qDebug()<<event->angleDelta();
}
void Widget::closeEvent(QCloseEvent *event)
{
int ret = QMessageBox::warning(this, tr("My Application"),
tr("close the window\n"
"Do you really want to leave?"),
QMessageBox::Ok | QMessageBox::No);
switch(ret)
{
case QMessageBox::Ok:
event->accept();
break;
case QMessageBox::No:
event->ignore();
break;
}
}
void Widget::resizeEvent(QResizeEvent *event)
{
qDebug()<<"oldSize"<<event->oldSize();
qDebug()<<"newSize"<<event->size();
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
/*personel EventGroup begin*/
void enterEvent(QEvent *event) override;
void leaveEvent(QEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
void closeEvent(QCloseEvent *event) override;
void resizeEvent(QResizeEvent *event)override;
/*personel EventGroup end*/
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
第67讲 用事件自定义出一个按钮
补充知识
①explicit关键字的作用?
抑制隐式转换;在 C++ 中,explicit
关键字主要用于修饰构造函数。当一个构造函数被声明为explicit
时,它不能用于隐式转换。例如:
class MyClass {
public:
int value;
explicit MyClass(int val) : value(val) {}
};
②在头文件内声明函数过后按下alt+enter可以快速在cpp文件中添加定义:
③在工程文件中新建一个res文件
(1)右键工程图标,新建
(2)选择Qt框架下的Qt Resource File
(3)清除前缀,添加文件
编程设计
首先在Widget.h头文件内部实例化一个QPixmap类的对象pic,以及三个鼠标事件(点击、离开、进入)和一个绘图事件。
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QWidget>
class MyButton : public QWidget
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
/*personel components begin*/
private:
QPixmap pic;
protected:
void mousePressEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
void enterEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
/*personel components end*/
signals:
};
#endif // MYBUTTON_H
其次在MyButton.cpp文件内对这些函数予以实现:
#include "mybutton.h"
#include <QPainter>
MyButton::MyButton(QWidget *parent) : QWidget(parent)
{
pic.load(":/icon/blackOpen.png");
setFixedSize(pic.size());
update();
}
void MyButton::mousePressEvent(QMouseEvent *event)
{
pic.load(":/icon/redOpen.png");
update();
}
void MyButton::leaveEvent(QEvent *event)
{
pic.load(":/icon/blackOpen.png");
update();
}
void MyButton::enterEvent(QEvent *event)
{
pic.load(":/icon/yellowOpen.png");
update();
}
void MyButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(rect(),pic);
}
总结,注意掌握QPixmap类与QPainter类的使用,讲解可以求助AI老师,如下:
①构造函数部分
②绘制事件处理部分
设计UI
①拖动一个Widget组件到UI画面
②然后提升类型为MyButton
第68讲 使用自定义按键的信号与槽
①在按键类内部新增信号clciked
#ifndef MYBUTTON_H
#define MYBUTTON_H
#include <QWidget>
class MyButton : public QWidget
{
Q_OBJECT
public:
explicit MyButton(QWidget *parent = nullptr);
/*personel components begin*/
private:
QPixmap pic;
protected:
void mousePressEvent(QMouseEvent *event) override;
void leaveEvent(QEvent *event) override;
void enterEvent(QEvent *event) override;
void paintEvent(QPaintEvent *event) override;
signals:
void clicked();
/*personel components end*/
};
#endif // MYBUTTON_H
②在鼠标点击事件中发射信号(mybutton.cpp内修改)
void MyButton::mousePressEvent(QMouseEvent *event)
{
pic.load(":/icon/redOpen.png");
update();
emit clicked();
}
③在widget类内部关联信号与槽(widget.cpp内修改)
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
connect(ui->mybtn,&MyButton::clicked,[=](){
qDebug()<<"myButton is clicked!";
});
}