Qt自定义Widget实现互斥效果详解
前沿
什么叫做自定义Widget实现互斥效果呢?
在使用Qt做一个界面美观性比较强的功能时,可能会遇到这种问题:多个控件互斥,类似于QRadiButton控件,但又不是单纯的QRadioButton控件,互斥的可能是一个窗口,也可能是几个按钮,等等多种情况。
这里我只是列举了一个简单的互斥例子,虽然简单,但是包含了各种坑,有需要的掘友们可以小笔记们记一下,尤其是对Qt新手来说,还是很有必要的。
由效果图可以看出创建了3个自定义widget,点击其中一个时,另外两个背景色以及文本颜色变化,处于选中状态。
接下来,针对效果图展示的功能进行逐一讲解,包含了知识点以及踩坑记录。
功能实现
实现自定义互斥widget过程中遇到了如下知识点以及问题,看看有没有你曾经遇到的或者是刚好需要的功能吧!
知识点
1:Widget模拟按钮的四态功能,包括了:常态、按下、聚焦、禁用
2:Widget自定义类的背景色设置以及文本内容风格设置
3:如何让多个widget实现互斥效果
问题
1:自定义Widget背景色设置之后为什么不生效?
针对上述知识点以及问题来讲述这个简单的功能吧!
讲解知识点1
使用Widget模拟按钮的四态功能,需要用到Widget自身的消息:鼠标按下,鼠标进入、鼠标离开。
virtual void mousePressEvent(QMouseEvent *event); //鼠标按下响应消息 virtual void enterEvent(QEvent *event); //鼠标进入响应消息 virtual void leaveEvent(QEvent *event); //鼠标离开响应消息
有没有人会问道,为什么没有mouseMoveEvent消息?
解答:在Qt中直接使用mouseMoveEvent消息鼠标是无法触发的,必须要设置setMouseTracking(true)让鼠标跟踪事件在当前窗口处于有效状态。
根据使用的具体情况是否需要设置这个功能。当前的小demo中,只是做图片的转换,没有必要在mouseMove中一直消耗资源。
(题外话:在MFC框架下的鼠标mosemove事件是直接可用的不需要进行特殊设置)
鼠标进入到widget之后,就可以标记为鼠标一直在该widget中活动,除非触发了leaveEvent消息。
鼠标按下响应消息
void QCustomWidget::mousePressEvent(QMouseEvent *event) { this->SetWidgetStyle(Style_Down); QWidget::mousePressEvent(event); }
当前采用的枚举类型:鼠标按下响应。
鼠标进入widget响应消息
void QCustomWidget::enterEvent(QEvent *event) { this->SetWidgetStyle(Style_Focus); QWidget::enterEvent(event); }
当前采用的枚举类型:鼠标聚焦状态,使用进入消息代替了mousemove消息。
如果大家打日志会发现,该触发函数只会在鼠标进入的时候走一次,当鼠标持续在widget内部移动时是不触发的,极大的减少了消息处理。
鼠标离开widget响应消息
void QCustomWidget::leaveEvent(QEvent *event) { this->SetWidgetStyle(Style_Normal); QWidget::leaveEvent(event); }
当前采用的枚举类型:鼠标离开状态。
我只是展示了最简单的离开设置,有一点需要考虑,当前widget如果处于按下状态,此刻鼠标离开了,该如何展示呢?
难道还要显示常态风格吗?
答案肯定是NO!
虽然鼠标已经移开,但是选中状态已经由常态变成了按下状态。在程序中我们需要用一个bool值变量来记录当前widget是否已经被选中过,如果选中过,当鼠标离开时就需要更改为选中状态
修改如下所示:
if (m_bClickedState == true) { this->SetWidgetStyle(Style_Down); } else this->SetWidgetStyle(Style_Normal);
讲解知识点2
在程序中对于模拟的状态采用了枚举的类型进行表示。
类型 | 说明 |
---|---|
Style_Normal | 鼠标未做任何操作的初始状态 |
Style_Down | 鼠标在wiget中进行了按下操作 |
Style_Focus | 鼠标在widget中进行移动时操作状态 |
Style_Disable | 当前widget处于进行状态 |
每一个widget中都展示了相同的内容:编号,文本
因为只是做了展示功能,所以全部使用了QLabel控件
QLabel *m_labNumber; //编号类指针
QLabel *m_LabContent; //内容类指针
对应的实际处理
void QCustomWidget::SetWidgetStyle(ENUM_WidgetStyle enumStyle) { //TODO:设置widget风格 QString qsStyle = "", gStyleNumberNormal = "", gStyleContentNormal = ""; switch (enumStyle) { case Style_Normal: //常态显示 { //设置:背景 qsStyle = "QWidget{background-color:#FFD700}"; //设置:编号风格 gStyleNumberNormal = "QLabel{color:#666666; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; //设置:内容风格 gStyleContentNormal = "QLabel{color:#666666; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; } break; case Style_Down: //按下 { //设置:背景 qsStyle = "QWidget{background-color:#FFB6C1}"; //设置:编号风格 gStyleNumberNormal = "QLabel{color:#0000FF; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; //设置:内容风格 gStyleContentNormal = "QLabel{color:#00FFFF; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; } break; case Style_Focus: //聚焦 { //设置:背景 qsStyle = "QWidget{background-color:#FFF0F5}"; //设置:编号风格 gStyleNumberNormal = "QLabel{color:#98FB98; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; //设置:内容风格 gStyleContentNormal = "QLabel{color:#98FB98; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; } break; case Style_Disable: //禁用 { //设置:背景 qsStyle = "QWidget{background-color:#DCDCDC}"; //设置:编号风格 gStyleNumberNormal = "QLabel{color:#696969; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; //设置:内容风格 gStyleContentNormal = "QLabel{color:#696969; font-family:Microsoft YaHei UI; font-size:14px;} QLabel{background-color: transparent}"; } break; default: break; } this->setStyleSheet(qsStyle); m_labNumber->setStyleSheet(gStyleNumberNormal); m_labContent->setStyleSheet(gStyleContentNormal); }
根据不同的类型对应的背景风格也不同。大家可以将代码带入,运行查看下效果,是不是跟我展示的效果一致呢?
哈哈!如果你尝试了,就会发现是这个样子的效果:
为什么只能显示文字,我的背景呢?去了哪里?我不是已经设置了吗?
很多Qt新手在这里都会遇到这样的问题,于是开启了各种搜索模式,尝试各种方法,有的时候改着改着就对了,也就忽略了这个问题。
当我们创建一个自定义widget时,通用的方法使用new实例的方式,在new的过程中,为了层级关系好打理已经父子关系明确,都会传入this作为新创建窗口的父指针。
一旦我们传入了this指针之后,并未在自定义Widget中做任何处理时,此时就会出现这样的情况。
子类继承了父窗口的风格样式。
一般遇到这种情况时,会有两种处理方式:重写当前窗口的paintEvent函数,设置不沿用父窗口风格
为了方便起见,当窗口绘制的背景图不复杂的情况下都会采用第二种方式设置:
this->setAttribute(Qt::WA_StyledBackground);
在当前自定义widget类构造函数中设置上述代码后,之前出现的设置了背景风格却看不见的问题就迎刃而解了。
讲解知识点3
如何实现多个widget之间的互斥呢?
使用过QRadioButton控件的掘友们都知道,该控件想要设置互斥只需要简单的设置函数就可以了。
对于我们自定义的widget来说,是不存在这种函数的,互斥效果只能是手动用代码设置并根据选中与非选中状态来更换对应的展示效果。
假设,当前选中了“内容1”的自定义Widget,此时需要在Widget中鼠标按下响应中触发一个消息,通知外界,当前自定义Widget做了按下操作,需要做特殊的处理
void QCustomWidget::mousePressEvent(QMouseEvent *event) { this->SetWidgetStyle(Style_Down); emit Msg_SendClicked(); QWidget::mousePressEvent(event); }
在调用自定义Widget的父类中响应对应的槽函数做特殊处理。
总结
到这里实现自定义Widget互斥效果就简单实现了。
对于互斥操作的实现很简单,最最需要掌握的就是如何设置widget的背景。
很多情况下子窗口与父窗口嵌套层级过多时,这种问题最容易出现了,因为我们在每次创建一个新widget对象时,最好的方式每次都不沿用父窗口的样式。