Qt如何寫一個三秒自動消失提示窗,適用於窗體和tableView中

Inkred發表於2020-09-23

#include <QDateTime>
#include "QTimer"
#include "QAbstractItemModel"
class QVBoxLayout;
class GlodonTableView;
class QModelIndex;
class QLabel;
class QPropertyAnimation;

class  ToolTipParam
{
public:
    ToolTipParam() :
        m_sizeMinTip(80, 60),
        m_sizeMaxTip(0, 0),
        m_sizeArrow(19, 9),
        m_nRowHeight(20),
        m_nTimeFadeOut(1000),
        m_nTimeRemain(3000),
        m_fontText(QFont("Microsoft YaHei", 12, QFont::Normal)),
        m_colorText(Qt::black),
        m_sizeIcon(16, 16),
        m_pParentWidget(NULL),
        m_strIconPath("xxx.png"),
        m_strText(QString(""))
    {

    }

    ToolTipParam & operator = (const ToolTipParam & param)
    {
        m_strIconPath = param.m_strIconPath;
        m_nRowHeight = param.m_nRowHeight;
        m_strText = param.m_strText;
        m_fontText = param.m_fontText;
        m_colorText = param.m_colorText;
        m_nTimeRemain = param.m_nTimeRemain;
        m_nTimeFadeOut = param.m_nTimeFadeOut;
        m_sizeArrow = param.m_sizeArrow;
        m_sizeMinTip = param.m_sizeMinTip;
        m_sizeMaxTip = param.m_sizeMaxTip;
        m_sizeIcon = param.m_sizeIcon;
        m_pParentWidget = param.m_pParentWidget;
        return *this;
    }

    QString   m_strIconPath;             // 圖示路徑
    QSize     m_sizeIcon;                // 圖示尺寸
    QString   m_strText;                 // 文字
    QFont     m_fontText;                // 字型
    QColor    m_colorText;               //文字顏色
    int       m_nRowHeight;              // 文字行高
    int       m_nTimeRemain;             // 停留時間(毫秒)
    int       m_nTimeFadeOut;            // 淡出時間(毫秒)
    QSize     m_sizeArrow;               // layout的外邊距
    QSize     m_sizeMinTip;              // tip控制元件的大小
    QSize     m_sizeMaxTip;              // tip的換行大小
    QWidget   *m_pParentWidget;          // 被指向的widget(父窗體)
};


class  ToolTipWidget : public QWidget
{
    Q_OBJECT

public:
    static void showToolTip(QWidget * pParent, const QString &strShowText);  //使用時直接呼叫這個介面就可以
    static void tableViewShowToolTip(QWidget *pTableView, const QModelIndex& oIndex, const QString& strShowText, bool bIsTableView = false);  //tableView可以直接呼叫該靜態方法
    static void closeTip(QWidget * pParent = nullptr);     //關閉提示
    virtual void ImmediateCloseTip();        // 立即關閉,並銷燬    
    virtual QPoint calcPosOfpParentWidget();        //
    bool isStartFadeOut();                 // 判斷是否開始漸變退出
    void tipShowLocal();               //tip出現的位置,父窗體的上方
    void startTimer(void);   //
    void initUI();
    void initConnect();
    QPainterPath buildRoundRectTip();   //畫圓角提示框
    void stopTime();
    ~ToolTipWidget();
protected:
    virtual void showEvent(QShowEvent * pEvent);
    virtual void paintEvent(QPaintEvent * pEvent);
    virtual void mouseReleaseEvent(QMouseEvent *pEvent);
    virtual bool eventFilter(QObject *pObj, QEvent *pEvent);
    virtual void enterEvent(QEvent *pEvent);
    virtual void leaveEvent(QEvent *pEvent);

private:
    explicit ToolTipWidget(ToolTipParam & oTipParam, QWidget *pParent = nullptr, const QModelIndex oIndex = QModelIndex(), bool bIsShowTotalQty = false);

    protected slots:
    void slotCloseTimer();
    void slotFadeout();
    void slotMoveTimer();

private:
    static ToolTipParam m_TipParam;
    QLabel             *m_pLabelIco;                      // 圖示
    QLabel             *m_pLabelText;                     // 內容文字區
    QVBoxLayout        *m_pLayoutLeft;                        
    QVBoxLayout        *m_pLayoutRight;
    QTimer             *m_pTimerClose;                   // 關閉定時器
    QTimer             *m_pTimerMovePos;                 // 移動定時器
    QPropertyAnimation *m_pAnimFadeOut;                  // 窗體逐漸消失效果
    QPoint             m_ptThisCenter;
    bool               m_bStartFadeOut;
    static QDateTime   m_CloseDataTime;
    static QPointer<ToolTipWidget> m_QPointerTip;
    QModelIndex m_oIndex;
    bool m_bTableViewShow;  
};

#include "QWidget"
#include "QRect"
#include "QBoxLayout"
#include "QLabel"
#include "QPropertyAnimation"
#include "QPainter"
#include "QLineEdit"
#include "QPointer"
#include "QApplication"

#include "QAbstractItemModel"

static const QString s_strLabelText = QString("<p style=\"line-height:%1px\">%2</p>");
QPointer<ToolTipWidget> ToolTipWidget::m_QPointerTip;
QDateTime ToolTipWidget::m_CloseDataTime = QDateTime::fromTime_t(0);
ToolTipParam ToolTipWidget::m_TipParam;
ToolTipWidget::ToolTipWidget(ToolTipParam & oTipParam, QWidget *pParent /*= nullptr*/,
    const QModelIndex oIndex /*= QModelIndex()*/
    , bool bIsShowTotalQty /*= false*/) :
    m_pLabelIco(NULL),
    m_pLabelText(NULL),
    m_pTimerClose(NULL),
    m_pAnimFadeOut(NULL),
    m_bStartFadeOut(false),
    m_oIndex(oIndex),
    m_bTableViewShow(bIsShowTotalQty),
    QWidget(pParent)
{
    m_TipParam = oTipParam;
    initUI();
    installEventFilter(pParent);
    initConnect();

}

ToolTipWidget::~ToolTipWidget()
{
}
void ToolTipWidget::mouseReleaseEvent(QMouseEvent *pEvent)
{
    //點tip就直接讓它關了
    if (pEvent->button() == Qt::LeftButton)
    {
        closeTip();
    }
}

void ToolTipWidget::paintEvent(QPaintEvent * pEvent)
{
    QWidget::paintEvent(pEvent);
    if (!m_TipParam.m_pParentWidget)
        return;
    m_ptThisCenter = m_TipParam.m_pParentWidget->mapToGlobal(m_TipParam.m_pParentWidget->rect().center());
    QPainterPath path;
    // 畫圓角tip
    path = this->buildRoundRectTip();
    QPainter * painter = new QPainter(this);
    painter->begin(this);
    painter->setRenderHint(QPainter::Antialiasing);  // 圓角處反鋸齒,讓其圓滑;
    if (!m_bTableViewShow)
    {
        QColor  c_CurColor = QColor(QString("255 255 255 255"));
        painter->setPen(QPen(c_CurColor, 1, Qt::SolidLine));
        painter->fillPath(path, QBrush(c_CurColor));
    }
    else
    {
        this->move(calcPosOfpParentWidget());
        painter->setPen(QPen(QColor(199, 199, 199), 1, Qt::SolidLine));
        painter->fillPath(path, QBrush(QColor(231, 231, 231)));
    }
    painter->drawPath(path);
    painter->end();
}
bool ToolTipWidget::eventFilter(QObject *pObj, QEvent *pEvent)
{

    auto eType = pEvent->type();

    if (eType == QEvent::Resize
        || eType == QEvent::WindowStateChange
        || eType == QEvent::Move
        || eType == QEvent::ParentChange
        || eType == QEvent::Paint
        || eType == QEvent::DragMove)
    {

        this->show();
        this->update();
    }
    
    if (eType == QEvent::Hide)
    {
        this->hide();
    }
    if (eType == QEvent::Resize)
    {
        tipShowLocal();
    }
        
        
    if (eType == QEvent::Move)
    {
        tipShowLocal();
    }
    // 錯誤提示所在的控制元件隱藏時,錯誤提示直接銷燬
    if (pObj == m_TipParam.m_pParentWidget)
    {
        if (pEvent->type() == QEvent::Hide)
            ImmediateCloseTip();
    }

    return QWidget::eventFilter(pObj, pEvent);
}

void ToolTipWidget::enterEvent(QEvent *pEvent)
{
    if (m_pTimerClose)  //滑鼠懸停停止計時,窗體不消失
    {
        m_pTimerClose->stop();
    }
    QWidget::enterEvent(pEvent);

}

void ToolTipWidget::leaveEvent(QEvent *pEvent)
{
    if (m_pTimerClose)   //滑鼠離開重新計時
    {
        m_pTimerClose->start(m_TipParam.m_nTimeRemain);
    }
    QWidget::leaveEvent(pEvent);
}

void ToolTipWidget::showEvent(QShowEvent * pEvent)
{
    if (!m_TipParam.m_pParentWidget)
        return;

    if (!m_TipParam.m_sizeMinTip.isEmpty())
        this->setMinimumSize(m_TipParam.m_sizeMinTip);
    if (!m_TipParam.m_sizeMaxTip.isEmpty())
        this->setMaximumSize(m_TipParam.m_sizeMaxTip);
    QString strTemp = m_TipParam.m_strText;
    QString sBody = s_strLabelText.arg(m_TipParam.m_nRowHeight).arg(m_TipParam.m_strText);
    m_pLabelText->setText(sBody);

    int nTitleWidth = m_pLabelText->fontMetrics().width(strTemp);
    if (nTitleWidth < (this->maximumWidth() - 100))
    {
        m_pLayoutRight->setContentsMargins(0, 4, 15, 12);
        m_pLabelText->setWordWrap(false);
        m_pLabelText->adjustSize();
        if (!m_bTableViewShow)
        {
            this->setFixedSize(m_pLabelText->width(), m_pLabelText->height() );
        }
        else
        {
            this->setFixedSize(m_pLabelText->width() , m_pLabelText->height());
        }

    }
    else
    {
        m_pLayoutLeft->setContentsMargins(0, 2, 0, 0);
        m_pLayoutRight->setContentsMargins(0, 4, 15, 12);
        m_pLabelText->setMinimumWidth(this->maximumWidth());
        this->setMinimumWidth(this->maximumWidth() );
        m_pLabelText->adjustSize();
        if (m_pLabelText->size().height() > 60)
        {
            adjustSize();
        }
        else
        {
            if (!m_bTableViewShow)
            {
                this->setFixedSize(m_pLabelText->width(), m_pLabelText->height());
            }
            else
            {
                this->setFixedSize(m_pLabelText->width(), m_pLabelText->height());
            }
        }
    }
    if (!m_bTableViewShow)
    {
        QPixmap pixIco;
        pixIco.load(m_TipParam.m_strIconPath);
        if (!m_TipParam.m_sizeIcon.isEmpty())
        {
            pixIco = pixIco.scaled(m_TipParam.m_sizeIcon.width(), m_TipParam.m_sizeIcon.height(), Qt::KeepAspectRatio);
        }
        m_pLabelIco->setPixmap(pixIco);
    }

    QWidget::showEvent(pEvent);
}
QPainterPath ToolTipWidget::buildRoundRectTip()
{
    QPainterPath oPath;
    if (!m_TipParam.m_sizeArrow.isValid() || !m_TipParam.m_pParentWidget)
    {
        return oPath;
    }
    int nHeight = m_TipParam.m_sizeArrow.height();
    QRect oOuterRect = this->rect();
    QRect oInnerRect;
    if (!m_bTableViewShow)
    {
        oInnerRect = oOuterRect - QMargins(nHeight, nHeight, nHeight, nHeight);
        oPath.addRoundedRect(oInnerRect, 5, 5);  // 新增圓角矩形
    }
    else
    {
        oInnerRect = oOuterRect - QMargins(nHeight, nHeight, nHeight, nHeight + 10);
        oPath.addRoundedRect(oInnerRect, 0, 0);
    }
    return oPath;
}

void ToolTipWidget::stopTime()
{
    if (m_pTimerClose)
        m_pTimerClose->stop();

    if (m_pTimerMovePos)
        m_pTimerMovePos->stop();

    m_CloseDataTime = QDateTime::currentDateTime();
    this->close();
    this->deleteLater();
}
void ToolTipWidget::slotCloseTimer()
{
    m_pTimerClose->stop();
    m_bStartFadeOut = true;
    m_pAnimFadeOut->start();
}

void ToolTipWidget::slotMoveTimer()
{
    QPoint ptThisCenter = m_TipParam.m_pParentWidget->mapToGlobal(m_TipParam.m_pParentWidget->rect().center());
    if (ptThisCenter != m_ptThisCenter)
    {
        stopTime();
    }
}

void ToolTipWidget::slotFadeout()
{
    if (m_pTimerMovePos)
        m_pTimerMovePos->stop();
    this->close();
    this->deleteLater();
}

void ToolTipWidget::ImmediateCloseTip()
{
    this->stopTime();
}

QPoint ToolTipWidget::calcPosOfpParentWidget()
{
    QPoint pt(0, 0);
    if (!m_TipParam.m_pParentWidget)
        return pt;
    int nCounWidth = m_TipParam.m_pParentWidget->width()*0.5;//當前單元格的列寬
    int nTooltipHeight = -this->height()* 0.5 ;  //提示框的高度
    QPoint ptGlobalOwnerCenter = m_TipParam.m_pParentWidget->mapToGlobal(m_TipParam.m_pParentWidget->rect().center());
    pt = QPoint(ptGlobalOwnerCenter.x(), ptGlobalOwnerCenter.y());
    pt += QPoint(nCounWidth, nTooltipHeight);
    return pt;
}

void ToolTipWidget::showToolTip(QWidget * pParentWidget, const QString &strText)
{
    ToolTipWidget * pTip = NULL;
    assert(pParentWidget);
    if (!pParentWidget)
        return;

    if (!m_QPointerTip.isNull() && m_QPointerTip.data() && pParentWidget == m_QPointerTip.data()->parent()
        && strText.compare(m_TipParam.m_strText) == 0
        && !m_QPointerTip->isStartFadeOut())
    {
        //窗體還沒有退出直接使用原來的
        return;
    }
    __int64 llDuration = m_CloseDataTime.msecsTo(QDateTime::currentDateTime());   //第一次與第二次彈出窗體的間隔時間
    if (llDuration < 500)
    {
        pTip = ToolTipWidget::m_QPointerTip;
        return;
    }
    if (!ToolTipWidget::m_QPointerTip.isNull())
    {
        ToolTipWidget::m_QPointerTip->ImmediateCloseTip();
    }
    ToolTipParam param = m_TipParam;
    param.m_pParentWidget = pParentWidget;
    param.m_strText = strText;
    pTip = new ToolTipWidget(param, pParentWidget);
    ToolTipWidget::m_QPointerTip = pTip;
    pTip->tipShowLocal();
    pTip->show();
    pTip->startTimer(); //show之後啟動定時器
}

void ToolTipWidget::startTimer(void)
{
    if (!m_TipParam.m_pParentWidget)
    {
        return;
    }

    m_pAnimFadeOut->setDuration(m_TipParam.m_nTimeFadeOut);
    if (m_pTimerClose)
    {
        m_pTimerClose->start(m_TipParam.m_nTimeRemain);
    }

    if (m_pTimerMovePos)
    {
        m_pTimerMovePos->start();
    }
}

//對應的單元格
void ToolTipWidget::tableViewShowToolTip(QWidget *editor, const QModelIndex& oIndex, const QString& strTotalQtyExpr, bool bIsShowTotalQtyExpr/*=false*/)
{
    if (editor == nullptr || !oIndex.isValid())
        return;

    ToolTipWidget * pTip = NULL;
    if (!m_QPointerTip.isNull() && m_QPointerTip.data() && editor == m_QPointerTip.data()->parent()
        && strTotalQtyExpr.compare(m_TipParam.m_strText) == 0
        && !m_QPointerTip->isStartFadeOut())
    {
        //文字相同、父窗體相同、窗體還沒有退出直接使用原來的。
        return;
    }
    __int64 llDuration = m_CloseDataTime.msecsTo(QDateTime::currentDateTime());   //第一次與第二次彈出窗體的間隔時間
    if (llDuration < 100)
    {
        pTip = ToolTipWidget::m_QPointerTip;
        return;
    }
    if (!ToolTipWidget::m_QPointerTip.isNull())
    {
        ToolTipWidget::m_QPointerTip->ImmediateCloseTip();
    }
    ToolTipParam param = m_TipParam;
    param.m_colorText = QColor(100, 100, 100, 0);
    param.m_pParentWidget = editor;
    param.m_strText = strTotalQtyExpr;
    pTip = new ToolTipWidget(param, editor, oIndex, bIsShowTotalQtyExpr);
    ToolTipWidget::m_QPointerTip = pTip;
    pTip->show();
    pTip->startTimer(); //show之後啟動定時器
}

void ToolTipWidget::closeTip(QWidget * pParent)
{
    if (!ToolTipWidget::m_QPointerTip.isNull())
    {
        //傳進來的父不為空,且不是當前氣泡的父時,不關閉當前氣泡
        if (pParent && pParent != ToolTipWidget::m_QPointerTip.data()->parent())
            return;
        ToolTipWidget::m_QPointerTip->ImmediateCloseTip();
    }
}
void ToolTipWidget::tipShowLocal()
{
    QWidget* pParentWidget = this->parentWidget();
    QSize oSzParentWidget = pParentWidget->size();
    int nParentWidgetX = oSzParentWidget.width();
    int nParentWidgetY = oSzParentWidget.height();
    QPoint oPtRightBottomParentWidget = pParentWidget->mapToGlobal(QPoint(nParentWidgetX, nParentWidgetY));
    //獲取父窗體的左下角座標
    QPoint oPtLeftBottom = oPtRightBottomParentWidget - QPoint(oSzParentWidget.width(), 0);
    int nLeftBottomX = oPtLeftBottom.x();
    int nLeftBottomY = oPtLeftBottom.y();
    //移動到父窗體的頂上中部的位置
    int nTipWidth = this->width();
    nLeftBottomX = nLeftBottomX + nParentWidgetX / 2 - nTipWidth;
    nLeftBottomY = nLeftBottomY - nParentWidgetY - 2;
    this->move(nLeftBottomX, nLeftBottomY);
}

bool ToolTipWidget::isStartFadeOut()
{
    return m_bStartFadeOut;
}
void ToolTipWidget::initUI()
{
    this->setMaximumHeight(25);
    this->setWindowFlags(Qt::WindowDoesNotAcceptFocus | Qt::FramelessWindowHint | Qt::ToolTip);   
    this->setAttribute(Qt::WA_TranslucentBackground);                                            
    QVBoxLayout * layoutMain = new QVBoxLayout();
    layoutMain->setSpacing(0);
    if (m_bTableViewShow)
    {
        layoutMain->setContentsMargins(m_TipParam.m_sizeArrow.height(),
            m_TipParam.m_sizeArrow.height(),
            m_TipParam.m_sizeArrow.height(),
            m_TipParam.m_sizeArrow.height());
        this->setLayout(layoutMain);
    }
    else
    {
        layoutMain->setContentsMargins(m_TipParam.m_sizeArrow.height() + 12,
            m_TipParam.m_sizeArrow.height() + 8,
            m_TipParam.m_sizeArrow.height() + 8,
            m_TipParam.m_sizeArrow.height());
    }
    this->setLayout(layoutMain);
    QHBoxLayout * layoutContent = new QHBoxLayout();
    layoutContent->setSpacing(6);
    layoutMain->addLayout(layoutContent);
    m_pLayoutLeft = new QVBoxLayout();
    m_pLabelIco = new QLabel(this);
    m_pLayoutLeft->setContentsMargins(0, 2, 0, 0);
    m_pLayoutLeft->addWidget(m_pLabelIco);
    m_pLayoutLeft->addStretch();
    m_pLayoutRight = new QVBoxLayout();
    m_pLayoutRight->setContentsMargins(0, 0, 15, 0);
    m_pLayoutRight->setSpacing(0);
    m_pLayoutRight->addSpacing(-6);
    m_pLabelText = new QLabel();
    m_pLabelText->setObjectName(QStringLiteral("LabelText"));
    m_pLabelText->adjustSize();
    QPalette pa;
    pa.setColor(QPalette::WindowText, m_TipParam.m_colorText);
    m_pLabelText->setPalette(pa);
    m_pLabelText->setWordWrap(true);
    if (!m_bTableViewShow)
        m_pLabelText->setAlignment(Qt::AlignTop);
    else
        m_pLabelText->setAlignment(Qt::AlignCenter);
    m_pLabelText->setAttribute(Qt::WA_TransparentForMouseEvents, true);
    m_pLayoutRight->addWidget(m_pLabelText);
    layoutContent->addLayout(m_pLayoutLeft, 0);
    layoutContent->addLayout(m_pLayoutRight, 1);
    // 關閉定時器設定
    m_pTimerClose = new QTimer(this);
    m_pTimerMovePos = new QTimer(this);
    m_pTimerMovePos->setInterval(200);
    // 漸隱效果動畫設定
    m_pAnimFadeOut = new QPropertyAnimation(this, "windowOpacity");
    m_pAnimFadeOut->setStartValue(1);
    m_pAnimFadeOut->setEndValue(0);
    m_pAnimFadeOut->setEasingCurve(QEasingCurve::Linear);
}

void ToolTipWidget::initConnect()
{
    connect(m_pTimerClose, SIGNAL(timeout()), this, SLOT(slotCloseTimer()));
    connect(m_pTimerMovePos, SIGNAL(timeout()), this, SLOT(slotMoveTimer()));
    connect(m_pAnimFadeOut, SIGNAL(finished()), this, SLOT(slotFadeout()));
}
 

相關文章