前沿
什么叫做自定义 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
在程序中对于模拟的状态采用了枚举的类型进行表示。
每一个 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 对象时,最好的方式每次都不沿用父窗口的样式。
我是中国好公民,专注 C++开发程序媛~
评论