🌟 嗨,我是热爱嵌入式的涛涛同学!
每日一言
每一次坚持,都是在为未来的自己积累底气。
好的既然上面已经学会了怎么创建一个新的控件了,那么我们要将我们的记事本实现字体的放大缩小功能了。那我们知道我们的记事本的话是默认没有ctrl+滚轮这样组合的快捷键的,还有是因为一个是QShortcut这个类用于键盘上面的键的绑定,而我们的滚轮是鼠标上面的,所以我们不能用那个QShortcut这个类进行操作呢,所以哦我们这边可以使用这个事件的重写进行添加功能,那么首先我们要先创建一个新的类,叫做MyTextEdit,那么我们让他去继承QTextEdit这个类,那么是什么意思呢,意思就是我们不是全部都是自己重新进行极致的开发的,而是我们需要用到QT官方已经做好的TextEdit的功能,我们必须保留,所以我们继承QTextEdit,这样的话我们就可以做到,在原有textEdit控件上添加功能的效果了
好的那我们创建时需要手动的继承
出来的效果是这样的
我们创建完后是这样的
但是实际上我们需要变成这样
那么这个是什么意思呢?为什么需要进行传一个QWidget* parent
就是意思是说我们的每一个控件都需要有一个父控件,这个是必须的,出来顶级窗口,这个父控件很重要
因为:
是这样的,你这个 parent 的话是这 QT 官方设计的一个关键字,就是说基本上每一个控件会有一个父类,然后你这个 parent 传给我们的这个父类。比如说我们传给这个 QMainWindow,那我传给他的话我就等于 QT知道了我这个 main window 是 Qman window 的子类,然后涉及到一些什么布局排版那些的,涉及到的父子关系,什么内存关系就会生效。然后。除了这个之外,我们这个方法其实是。等于是显示的调用了子类构造函数调用基类构造函数,那么就可以将父类很多的一些方法继承下来。还有属性等等。这样的话,就很符合我们对控件进行二次开发是相互应的。
现在我补充一些关于parent参数的知识:
在 Qt 中,parent 参数是构建控件层级结构和内存管理的核心机制。让我通过具体例子解释它的作用:
一、parent 参数的本质
在 Qt 里,所有继承自 QWidget 的控件(如 QTextEdit、QPushButton 等)都有一个带 parent 参数的构造函数:
QWidget(QWidget *parent = nullptr); // 基类 QWidget 的构造函数
- parent 的类型:QWidget*,即指向父控件的指针。
- 作用:
- 建立父子关系:将当前控件作为子控件添加到 parent 指向的控件中。
- 内存管理:父控件负责销毁子控件(当父控件被删除时,所有子控件会自动被删除)。
- 显示层级:子控件会被限制在父控件的区域内显示。
二、为什么自定义控件需要传递 parent?
看你的代码:
MyTextEdit::MyTextEdit(QWidget *parent)
: QTextEdit(parent) // 传递 parent 给基类 QTextEdit{
// ...}
这里的 parent 参数是用户在创建 MyTextEdit 对象时传入的父控件。例如:
// 创建一个主窗口
QMainWindow *mainWindow = new QMainWindow();
// 创建 MyTextEdit 控件,并将 mainWindow 作为父控件
MyTextEdit *textEdit = new MyTextEdit(mainWindow);
当你这样做时:
- textEdit 会被显示在 mainWindow 的区域内。
- 当 mainWindow 被关闭或销毁时,textEdit 会被自动删除(无需手动 delete)。
- textEdit 的坐标系相对于 mainWindow(例如,textEdit->move(10, 10) 会将它放在 mainWindow 内部的 (10, 10) 位置)。
三、如果不传递 parent 会怎样?
如果创建控件时传入 nullptr 或省略 parent:
MyTextEdit *textEdit = new MyTextEdit(nullptr); // 或 new MyTextEdit();
- 内存管理:textEdit 的生命周期需要手动管理(必须显式 delete,否则会内存泄漏)。
- 显示效果:textEdit 会作为独立窗口显示(有自己的标题栏和边框),而不是嵌入到其他控件中。
四、Qt 为什么要这样设计?
这种父子关系设计带来两大好处:
1. 自动内存管理
Qt 使用 ** 对象树(Object Tree)** 管理内存:
- 当一个 QObject(包括 QWidget)被删除时,它会自动删除所有子对象。
例如:
QWidget *window = new QWidget();
QPushButton *button = new QPushButton("Click", window); // button 的父控件是 window// ...delete window; // 销毁 window 时,button 会被自动删除
2. 控件层级与布局
父子关系决定了控件的显示层级:
- 子控件会被绘制在父控件之上。
- 父控件的位置变化会带动子控件一起移动。
- 当父控件被隐藏时,子控件也会被隐藏。
五、常见应用场景
在对话框中添加控件:
QDialog *dialog = new QDialog();
MyTextEdit *textEdit = new MyTextEdit(dialog); // dialog 作为父控件
在布局中使用控件:
QVBoxLayout *layout = new QVBoxLayout(this);
MyTextEdit *textEdit = new MyTextEdit(this); // this 是当前窗口
layout->addWidget(textEdit);
顶级窗口:
MyTextEdit *window = new MyTextEdit(nullptr); // 作为独立窗口
window->show();
六、总结
parent 参数的核心作用是:
- 建立控件层级:决定控件在界面中的显示位置和嵌套关系。
- 自动内存管理:避免内存泄漏,简化代码。
在你的代码中,QTextEdit(parent) 将 parent 传递给基类,让基类 QTextEdit 完成父子关系的设置。这是 Qt 控件开发的标准做法,几乎所有自定义控件都需要这样处理。
好了接下来继续实现我们的功能
我们用鼠标的滚轮事件和按键按下和松开,这3个事件,我进行重写那么就可以实现我想要功能了
我们实现的原理也很简单
1、我们知道wheelEvent里面有一个angleDelta这个是返回我们的滚轮如果往上滚就会显示QPont(0,120),往下滑就显示QPont(0,-120),这个就是一个坐标的模式,还有一个函数是y(),也就是把QPoint(x,y)将y取出来,那么我们就可以根据这个y的值进行判定,返回值为int类型,那么我们可以比大小了,如果>0那么就放大,< 0 就缩小 ,那么其实这个时候我们就可以实现了,我们直接滚轮滑就可以放大和缩小了,但是,我们说好了要和按键ctrl配合使用的,那么有个keyPressEvent这个事件
我们可以通过这个事件找到这个
那么我们就可以根据这个按键
就是我们会发现,我们基本上所有的参数细节就是都会可以在右边括号里面的类里面可以找得到那是因为:
Qt 中各类事件的所有细节信息都被封装在对应的事件类(如 QKeyEvent、QMouseEvent 等)中,这些类提供了丰富的成员函数来获取具体信息。
核心逻辑:事件类是 “事件细节的容器”
每种事件类都针对其对应的事件类型,设计了专门的方法来暴露关键信息。例如:
1. QKeyEvent(键盘事件)的核心信息
- key():返回按下 / 释放的按键(如 Qt::Key_A、Qt::Key_Enter)。
- modifiers():返回同时按下的修饰键(如 Qt::ControlModifier、Qt::ShiftModifier)。
- text():返回按键对应的文本(如按下 A 键返回 "A",按下 Shift+A 返回 "A" 大写)。
- isAutoRepeat():判断是否是长按按键导致的重复触发。
所以啊,当我们按键按键的时候就会触发KeyPressEvent事件那么我们里面进行判断,如果是ctrl被按下那么我们就将变量赋值为1;还有这个我们按键松开的时候也需要进行判断将变量赋值为0,
我讲一下这个变量是我为了将按键事件和滚轮事件相联系在一起做的一个桥梁,不然两个事件没有办法建立联系啊,这个变量需要再MyTextEdit的这个类中定义出来,
继续讲那我们这边这个e->accept是什么意思呢
你代码中的 accept() 是否必要?
在你的代码中,accept() 是必要的。原因:
- QTextEdit 的默认 wheelEvent 会处理滚轮滚动(即上下滚动文本内容)。
- 当 Ctrl 键按下时,你希望滚轮事件用于缩放而非滚动,因此需要 accept() 来阻止 QTextEdit 执行默认的滚动逻辑。
- 如果不调用 accept(),事件会继续传递给 QTextEdit::wheelEvent(),导致缩放的同时文本也会滚动,产生冲突。
总结
- accept() 的本质:告诉 Qt“这个事件我处理完了,别再传给别人”。
- 在你的代码中:accept() 确保 Ctrl + 滚轮时只触发缩放,不触发文本滚动。
- 建议:在重写事件处理函数时,根据是否需要父控件继续处理事件,显式调用 accept() 或 ignore(),让事件流程更清晰。
现在我就要讲一下这个为什么要
else{
QTextEdit::wheelEvent(e);
}
那是因为我们会发现,当我字体很多的时候出现可以向下滑的时候,滑不动啊,那是为什么呢?
其实很简单,就是我们的父类的滚轮事件,其实是很丰富的,但是我们决定自己重写的时候,执行完自己想要的功能后,我们需要去调用原来父类的滚轮事件的操作,这样就可以完整的实现在原有控件上的开发了,
也就是我们在不按ctrl键的时候滑动滚轮的时候,会去调用原来父类的对滚轮事件的操作,那么就是可以滚动文字
这是 Qt 二次开发的核心原则:在扩展功能时,始终考虑是否需要保留父类的默认行为。
还有我们下面的按键按下事件,这个也是一样的
其他按键(如数字键、字母键)的事件被完全拦截,没有传递给 QTextEdit 的默认处理函数。因此,MyTextEdit 失去了所有文本输入能力,主要还是因为我们是重写啊,重写之后,我们添加完之后我们想要的功能了之后,必须去重新要调用父类的操作
1. 大多数事件的默认处理:accept()
对于绝大多数从 QWidget 继承的控件(如 QTextEdit、QPushButton 等),它们的事件处理函数(如 mousePressEvent、wheelEvent、keyPressEvent 等)默认会调用 accept()。
- 例如,当你重写 wheelEvent 但不手动调用 accept() 或 ignore() 时,事件会被默认标记为 “已处理”(等价于 accept()),不会继续向父控件传播。
- 这是因为控件默认认为 “自己的事件应该由自己处理”,不需要父控件干预。
2. 特殊事件的默认处理:ignore()
少数特殊事件的默认行为是 ignore(),最典型的是 QCloseEvent(窗口关闭事件):
- 当你点击窗口关闭按钮时,QCloseEvent 的默认处理是 ignore()(即 “拒绝关闭”)。
- 因此,如果你想让窗口正常关闭,必须在 closeEvent 中显式调用 e->accept(),否则窗口会忽略关闭请求。
3. 重写事件处理函数时的默认行为
当你自定义事件处理函数(如你代码中的 MyTextEdit::wheelEvent)时:
- 如果你不调用 accept() 或 ignore(),且没有调用父类的事件处理函数(如 QTextEdit::wheelEvent(e)),则事件的默认状态由 Qt 内部规则决定(大多数情况等价于 accept())。
- 如果你调用了父类的事件处理函数(如 QTextEdit::wheelEvent(e)),则事件的最终状态由父类的处理结果决定(父类默认 accept(),则事件被标记为 “已处理”)。
总结
- 大多数事件(如鼠标、滚轮、键盘事件)的默认处理是 accept(),事件会被当前控件 “消费”,不向父控件传播。
- 少数特殊事件(如 QCloseEvent)默认是 ignore(),需要显式 accept() 才会生效。
- 重写事件处理时,若调用父类方法(如 QTextEdit::wheelEvent(e)),则默认行为由父类决定(通常是 accept())。
因此,是否需要手动调用 accept(),取决于你是否希望改变事件的默认传播逻辑(例如阻止 / 允许父控件处理事件)。