實現過程
因爲 QToolTip 自定義樣式不大方便,而且半透明也沒法設置,所以需要自定義。而且,Qt 中的頂層 widget 好像默認是不支持透明樣式的,可以設置:
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground, true); //無邊框纔有效
這樣頂層窗口是透明瞭,但是樣式表又沒效果了。雖然可以用 QStyleOption 獲取到樣式表設置的顏色等信息,然後在 paintEvent 中繪製,但是圖片我不知道怎麼獲取 。索性就嵌套了兩個 widget ,給裏層的 widget 設置樣式。
顯示和隱藏我是過濾的 QEvent::Enter 和 QEvent::Leave 來進行操作:
switch (event->type()) {
case QEvent::Enter:
//showTip(QCursor::pos());
showTip(targetWidget);
break;
case QEvent::Leave:
hideTip();
break;
default:
break;
}
目前的實現是相對 widget 固定位置 show 的,沒有處理鼠標移動事件。
彈出的時候因爲我是先計算的位置再 show ,可能大小還沒計算出來,所以在 resizeEvent 中重新調用了計算位置的函數。
(目前沒有條件測試多屏幕時彈出的位置,先不寫了)
對於設置樣式表,目前只能通過 qApp 或者直接給實例對象設置。
參考 Qt 源碼:E:\Qt\qt-everywhere-src-5.15.0\qtbase\src\widgets\kernel\qtooltip.h
實現代碼
實現效果
代碼鏈接
github 鏈接(RToolTip 類):https://github.com/gongjianbo/RectangleComponent.git
主要代碼
#ifndef RTOOLTIP_H
#define RTOOLTIP_H
#include <QLabel>
#include <QBasicTimer>
/**
* @brief 最簡易的ToolTip
* @note 這是頂層窗口不要設置parent
* @details 頂層設置透明後,樣式表失效了,所以我在裏面套了一層label
* 本來想外層也用QLabel,show時內層label把屬性設置爲外層的,感覺沒必要
*/
class RToolTip : public QWidget
{
Q_OBJECT
//READ WRITE可以替換成MEMBER
//默認顯示爲point的左上角,通過屬性設置偏移,以右下角爲起點,左減右加,上減下加
//qss右移1px:qproperty-rightOffset:1;
Q_PROPERTY(int rightOffset READ getRightOffset WRITE setRightOffset)
//qss上移1px:qproperty-bottomOffset:"-1";
Q_PROPERTY(int bottomOffset READ getBottomOffset WRITE setBottomOffset)
Q_PROPERTY(QString text READ getText WRITE setText)
Q_PROPERTY(Qt::Alignment alignment READ getAlignment WRITE setAlignment)
public:
//獨立的窗口不設置parent,樣式表可用qApp設置
explicit RToolTip(const QString &objectName=QString(),const QString text=QString());
~RToolTip();
//右側偏移
int getRightOffset();
void setRightOffset(int offset);
//底部偏移
int getBottomOffset();
void setBottomOffset(int offset);
//文本
QString getText() const;
void setText(const QString &text);
//對齊方式
Qt::Alignment getAlignment() const;
void setAlignment(Qt::Alignment alignment);
//設置錨定窗口,鼠標放上去時顯示tooltip
void anchorTarget(QWidget *target);
//獲取內層label對象
const QLabel *label() const;
//顯示tip在widget的左上角
void showTip(const QWidget *obj);
//顯示tip在點的左上角
void showTip(const QPoint &rightBottom);
//隱藏tip
void hideTip();
protected:
//過濾錨定窗口的enter和leave事件
bool eventFilter(QObject *target, QEvent *event) override;
void timerEvent(QTimerEvent *event) override;
void resizeEvent(QResizeEvent *event) override;
private:
//外層設置背景透明後樣式不生效,所以嵌套了一層
QLabel *contentLabel;
//默認顯示爲point的左上角,通過屬性設置偏移
//以右下角爲起點,左減右加,上減下加
int rightOffset = 0;
int bottomOffset = 0;
//錨定的窗口
QWidget *targetWidget=nullptr;
QPoint targetPoint;
//show和hide延遲
QBasicTimer showTimer;
QBasicTimer hideTimer;
};
#endif // RTOOLTIP_H
#include "RToolTip.h"
#include <QApplication>
#include <QDesktopWidget>
#include <QEvent>
#include <QTimerEvent>
#include <QResizeEvent>
//#include <QStyle>
#include <QHBoxLayout>
//#include <QDebug>
RToolTip::RToolTip(const QString &objectName, const QString text)
: QWidget(nullptr)
,contentLabel(new QLabel(this))
{
//把內層label添加到透明widget
QHBoxLayout *layout = new QHBoxLayout(this);
layout->setMargin(0);
layout->setSpacing(0);
layout->addWidget(contentLabel);
contentLabel->setAlignment(Qt::AlignCenter);
contentLabel->setText(text);
setObjectName(objectName);
//把widget設置爲透明,樣式表設置給label
setWindowFlags(Qt::FramelessWindowHint | Qt::ToolTip);
setAttribute(Qt::WA_TranslucentBackground, true);
//默認hide
setVisible(false);
//qDebug()<<"new tooltip"<<objectName;
}
RToolTip::~RToolTip()
{
//qDebug()<<"delete tooltip"<<objectName();
}
int RToolTip::getRightOffset()
{
return rightOffset;
}
void RToolTip::setRightOffset(int offset)
{
if(rightOffset!=offset){
rightOffset=offset;
//沒有動態樣式的處理,可自行添加
//style()->unpolish(this);
//style()->polish(this);
}
}
int RToolTip::getBottomOffset()
{
return bottomOffset;
}
void RToolTip::setBottomOffset(int offset)
{
if(bottomOffset!=offset){
bottomOffset=offset;
}
}
QString RToolTip::getText() const
{
return contentLabel->text();
}
void RToolTip::setText(const QString &text)
{
contentLabel->setText(text);
}
Qt::Alignment RToolTip::getAlignment() const
{
return contentLabel->alignment();
}
void RToolTip::setAlignment(Qt::Alignment alignment)
{
contentLabel->setAlignment(alignment);
}
void RToolTip::anchorTarget(QWidget *target)
{
if(target&&target!=targetWidget){
if(targetWidget){
targetWidget->removeEventFilter(this);
}
targetWidget=target;
targetWidget->installEventFilter(this);
targetWidget->setMouseTracking(true);
//如果是隨窗口關閉的,看不到析構的打印,難道此時事件循環已停止?
connect(targetWidget,&QObject::destroyed,this,&QObject::deleteLater);
}
}
const QLabel *RToolTip::label() const
{
return contentLabel;
}
void RToolTip::showTip(const QWidget *obj)
{
if(!obj)
return;
showTip(obj->mapToGlobal(QPoint(0, 0)));
}
void RToolTip::showTip(const QPoint &rightBottom)
{
targetPoint=rightBottom;
//move(rightBottom.x() - width() + rightOffset,
// rightBottom.y() - height() + bottomOffset);
//直接用size+point得到的位置可能顯示不全,這裏計算下
int rect_left=rightBottom.x()-width()+rightOffset;
int rect_top=rightBottom.y()-height()+bottomOffset;
if(rect_left<0)
rect_left=0;
if(rect_top<0)
rect_top=0;
//要考慮多個顯示器情況,【待測試】
//根據當前所在屏幕尺寸計算
QDesktopWidget * desktop=QApplication::desktop();
if(desktop){
QRect desk_rect=desktop->screenGeometry(targetWidget?targetWidget:this);
if(rect_left+width()>desk_rect.width())
rect_left=desk_rect.width()-width();
if(rect_top+height()>desk_rect.height())
rect_top=desk_rect.height()-height();
}
move(rect_left,rect_top);
if(!showTimer.isActive())
showTimer.start(200,this);
}
void RToolTip::hideTip()
{
if(!hideTimer.isActive())
hideTimer.start(300,this);
}
bool RToolTip::eventFilter(QObject *target, QEvent *event)
{
if(target==targetWidget){
switch (event->type()) {
case QEvent::Enter:
//showTip(QCursor::pos());
showTip(targetWidget);
break;
case QEvent::Leave:
hideTip();
break;
default:
break;
}
}
return QWidget::eventFilter(target,event);
}
void RToolTip::timerEvent(QTimerEvent *event)
{
if(event->timerId()==showTimer.timerId()) {
showTimer.stop();
//hideTimer.stop();
if(!hideTimer.isActive()&&isHidden()){
show();
}
}else if(event->timerId()==hideTimer.timerId()){
showTimer.stop();
hideTimer.stop();
if(!isHidden()){
hide();
}
}else{
QWidget::timerEvent(event);
}
}
void RToolTip::resizeEvent(QResizeEvent *event)
{
//初次show的時候可能size可能還沒計算好
showTip(targetPoint);
QWidget::resizeEvent(event);
}
使用
#include "RToolTip.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
initAppStyleSheet();
/*配合樣式表設置tab背景*/
ui->tabWidget->setAttribute(Qt::WA_StyledBackground);
RToolTip *tipA=new RToolTip("tipA","第1個tip");
tipA->anchorTarget(ui->btnToolTipA);
connect(ui->btnToolTipA,&QPushButton::clicked,[=]{
if(ui->btnToolTipB){
ui->btnToolTipB->deleteLater();
ui->btnToolTipB=nullptr;
}
});
RToolTip *tipB=new RToolTip("tipB","第2個tip");
tipB->anchorTarget(ui->btnToolTipB);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::initAppStyleSheet()
{
qApp->setStyleSheet(R"(
.RToolTip#tipA{
min-width:100px;
max-width:100px;
min-height:30px;
max-height:30px;
}
.RToolTip#tipB{
qproperty-rightOffset:"20";
qproperty-bottomOffset:"3";
}
.RToolTip QLabel{
padding:10px 50px;
color:white;
border: 1px solid white;
background-color:rgb(20,50,90);
}
.RToolTip#tipA QLabel{
padding:0;
border-radius:5px;
background-color:rgba(250,170,0,150);
}
)");
}