学习C++、QT---30(QT库中如何自定义控件(自定义按钮)讲解)
每日一言
你比想象中更有韧性,那些看似艰难的日子,终将成为勋章。
自定义按钮
我们要知道自定义控件就需要我们创建一个新的类加上继承父类,但是我们还要注意一个点,就是如果我们是自己重头开始造控件的话,那么我们就直接可以继承QWidget就可以了,因为这个属于是极致的定制开发,如果是只需要在QT已有的控件上添加QT官方在这个控件上没有的功能的话就需要我们自己创建新的类之后还需要将原有的控件进行提升为这个我们创建的新类,提升成自己的类,那么我们写的新控件就等于是在原有控件上的改变,变的拥有了更多功能,但是可以说又保存了90%的原有控件的功能,这个就是两种不同的自定义控件的方法
好的那我们现在来自定义一个按钮吧(实际上是自定义一个控件,看起来像按钮一样的功能)
就是我们这个说实话不叫自定义按钮,应该是叫做自定义控件,只是看起来像一个按钮,现在我来讲讲怎么搞,首先我们先创建一个C++的类叫做MyButton随后继承这个QWidget,那么就是意味着是全部是自己极致开发,全部自己干,所以我们需要重写的事件有鼠标点击事件、鼠标的进入事件
Widget::enterEvent
MyButton::enterEvent
这个两个是不一样的效果的,一个是在窗口的时候,鼠标进入到窗口就会触发事件,一个是要在Mybutton这个控件上的时候才会触发事件,
每一个控件都有自己的(鼠标、键盘)事件
好的现在我们想要我们的自定义按钮是一个图片的样子我们怎么操作呢?
那么我们这边就需要进行接收新的知识点了
QPixmap 这个类是专门用于画图的
这里有一个load这个函数,就是可以载入我们的资源文件的,值得注意的是我们去看这个函数的参数
涨知识啦
核心是理解 const QString & 作为函数参数的特性—— 它允许直接接收临时对象(比如字符串字面量转换的临时 QString),而不需要显式定义一个变量。我们一步步拆解:
一、const QString & 为什么能直接接收字符串字面量?
当你写 pic.load(":/QTicon/open2.png") 时,发生了这几步:
- 字符串字面量 ":/QTicon/open2.png" 是 const char* 类型,Qt 会自动调用 QString 的构造函数,创建一个临时的 QString 对象(这个对象在内存中短暂存在,没有名字)。
- 函数参数 const QString &fileName 是常量引用,它可以直接绑定到这个临时 QString 对象上(这是 C++ 标准允许的,const 引用的特殊权限)。
二、为什么不需要先用变量接收?
非 const 引用(如 QString &)必须绑定到一个 “有名字的、已存在的变量”,不能绑定临时对象。例如:
void func(QString &s) {} // 非const引用参数func("hello"); // 报错!不能绑定临时对象
但 const 引用 被设计为可以绑定到:
- 已存在的变量(如 QString path = "..."; load(path);)
- 临时对象(如字符串字面量转换的临时 QString)
这是 C++ 的语法规则,目的是方便使用(不需要显式定义变量),同时保证安全性(const 确保不会修改这个临时对象)。
三、const QString & 中 “引用” 的作用是什么?
引用的核心作用是 “避免不必要的拷贝,提高效率”:
如果函数参数是 QString fileName(按值传递):
调用时会把传递的字符串(无论是变量还是临时对象)完整拷贝一份到函数内部的 fileName 中。对于长字符串(比如路径很长),拷贝会浪费内存和时间。
如果参数是 const QString &fileName(按 const 引用传递):
不会拷贝,fileName 只是一个 “别名”,直接指向传递进来的对象(可能是变量,也可能是临时对象)。既节省了拷贝的开销,又通过 const 保证不会意外修改这个对象。
四、总结你的代码为什么可行?
pic.load(":/QTicon/open2.png") 的过程:
- 字符串字面量被转换为临时 QString 对象(存储路径)。
- const QString &fileName 绑定到这个临时对象(不需要显式变量)。
- 函数内部通过引用直接访问这个临时对象,避免拷贝,高效且安全。
简单说:const 引用的特性就是 “既能直接用字面量 / 临时对象,又不浪费内存”,所以不需要先定义一个 QString 变量~
那么我们的代码怎么写呢?
我们需要在类这边写QPixmap pic这个对象,还记得这个是什么操作吗,这个就是我们的类的组合的操作,在类中创建其他类的对象,可以实现获得其他类的属性和方法,然后我们去这个cpp中调用他的load函数
我来一个一个讲,这个MyButton的这个里面的 pic.load(":/QTicon/open.png");这个是在构造函数里面写的嘛,说明这个就是我们设置的这个程序第一眼看到的样子,我们毕竟是自定义按钮嘛,这些初始化的设置肯定是在构造函数里面写的
就是这个样子,
其他的几个事件也都是一样的意思,这个图片文件的话是我通过添加Qt Resources文件添加的就像我们给记事本的按钮添加上图片作为图标一样做的前期工作,需要将我们想要的图片先放在资源文件里面
setFixedSize(pic.size());
这个是什么意思呢,这个就是设置我们的控件的尺寸,尺寸为pic.size()调用这个函数,就是表示我们设置这个按钮的尺寸为这个图片的尺寸
后面会发现没有任何的结果啊,不显示啊,这也是因为我们是继承的QWidget这个是为了极致的开发的,什么都自己干,所以连很基础的绘图都没有,因此我们也有一个绘图事件,对于这个事件我们可以去QWidget这个类里面去寻找
我们会找到这个绘制事件,然后我们可以通过这个事件找到QPaint这个类
我们通过这个类创建一个对象,调用这个drawPixmap()这个函数,这个函数的话就是,可以将我们的图片绘制出来,
void MyButton::paintEvent(QPaintEvent *event)
{
QPainter painter(this);
painter.drawPixmap(rect(),pic);
}
会发现我们的painter(this)要加上这个才会正常运行,this的意思就是这个Mybutton这个控件,
我们也可以看这个案例,会发现这个用法painter(this)要这样写
目的是:作用:作为 QPainter 的构造参数时,它告诉 QPainter 在哪个控件上进行绘制。
对于这个drawPixmap(),他需要一个矩形区域,还有一个就是QPixmap 的对象的引用,说实话有点像我们的之前的那个font对象的那个操作,那个ui->textEdit->setFont(font)
这样的操作都是传的是对象的引用,
好的说一下这个矩形
rect() 作为目标矩形(控件的完整区域),让图片自适应控件大小:
painter.drawPixmap(rect(), pic); // 图片填满整个控件(可能拉伸)
最后一步就是我们需要在加载完这个图片的时候需要手动更新一下update一下,这个是为什么呢?而且我之前那个设置字体的缩放的时候好像也不需要这样啊?
因为:
- 内置属性(如字体):Qt 已处理自动更新,无需手动 update()。
- 自定义绘图:必须手动调用 update() 来触发重绘,否则界面不会更新。
这种设计体现了 Qt 的灵活性:内置控件负责基础属性的自动管理,而自定义绘图则交给开发者控制,避免了不必要的性能开销。
其实到最后如果我前面没有讲提升的问题的话,那么我们就需要进行提升
就是因为我们是继承这个QWidget的,所以我们需要拉一个widget就好了
我提出一个问题??是不是就是我由于是继承的QWidget那么就是意味着我需要的是极致的自己开发控件,所以也就是只能去拉一个widget出来,然后我去提升他
是的,你的理解非常准确!我用更简洁的方式总结一下:
为什么必须拉一个 QWidget 并提升?
因为你继承 QWidget 时,相当于从头开始开发一个全新控件,而 Qt Designer 默认没有你的控件。具体逻辑链:
继承 QWidget 的本质:
QWidget 是最基础的控件类,几乎什么都没实现(没有按钮外观、没有点击事件)。你继承它,就像 “从零开始造一辆车”,所有功能都要自己写。
“提升控件” 如何解决问题?
“提升” 操作的本质是:告诉 Qt Designer,把这个普通的 QWidget 当成我的 MyButton 来用。
具体步骤的意义:
- 拖一个普通 QWidget 到界面上:这是因为 Qt Designer 没有 MyButton 控件可选,只能用 widget 占位。就是因为我们是极致开发啊,记住我们这个是自己开发的按钮,不是在QT已有的控件上的开发,另外的是我选择继承的是QWidget,就说明全部自己干,说明就是拖一个Widget就好了
- 右键 → 提升为 MyButton:
- Qt Designer 会记录这个操作,并在生成的代码中使用 MyButton 而非 Widget。
- 生成的代码会自动包含 mybutton.h 头文件。
最后完成以上所有的操作就可以实现啦
效果为:我鼠标移到上方的时候就是紫色,点击的时候就是黑色,鼠标离开这个控件的时候就是蓝色,一打开程序的时候也是蓝色