1. 程式人生 > >Qt自定義按鈕彈窗控制元件

Qt自定義按鈕彈窗控制元件

  一直以為做一個按鈕彈窗控制元件很簡單,可做起來發現並不是那麼順利,折騰了挺長時間的,先看下效果: 在這裡插入圖片描述

前言

  嘗試過兩種方案,方案一:使用QToolButton控制元件,我們可以自定義一個widget,然後setMenu設定為該widget;方案二:點選一個QPushButton然後show一個自定義widget。使用方案一的話各種滑鼠事件不用我們管了,但是如果我們想要做的漂亮一些就會有很多侷限性,不容易實現。上面的效果圖採用的方案二實現的。

需要注意的幾點

1.點選按鈕彈出一個視窗。再次點選按鈕視窗消失。 2.同時只能顯示一個視窗。 3.視窗彈出時,點選主視窗其它位置,視窗消失(因為陰影效果的原因會有些小瑕疵,下面會介紹到)。 4.彈出視窗圓角並具有陰影效果。

原始碼分析

  下面分析下原始碼中比較重要的幾個點。 1.彈出的視窗需要設定下面這些屬性:

setWindowFlags((Qt::Dialog | Qt::FramelessWindowHint | this->windowFlags()));
setAttribute(Qt::WA_TranslucentBackground);
setMouseTracking(true);

2.陰影效果:

m_pShadow = new QGraphicsDropShadowEffect(this);
m_pShadow->setOffset(0, 0);
m_pShadow->setColor(QColor("#cccccc"));
m_pShadow->setBlurRadius(10);
m_pMainWidget = new QWidget(this);
m_pCentralWidget = new QWidget(m_pMainWidget);
m_pCentralWidget->setStyleSheet("QWidget { border: none; }");
m_pCentralLayout = new QHBoxLayout(m_pMainWidget);
m_pCentralLayout->addWidget(m_pCentralWidget);
m_pMainWidget->setStyleSheet(" border: 1px solid lightgray; background-color: #fafafa; border-radius: 10px;");
m_pMainWidget->setGraphicsEffect(m_pShadow);

  小瑕疵:這裡實現陰影效果採用了一個QWidget嵌入到另一個QWidget(該widget是透明的),所以如果點選彈窗陰影附近(範圍和被嵌入的widget的佈局的margin大小有關)的時候彈窗是不會消失的。 3.按鈕點選時需要將彈出widget的座標對映到螢幕的座標:

connect(this, &PopupWidgetButton::ButtonClicked, [=]() {
	if (m_pMainWidget->isHidden()) {
	     QPoint pos;
	     if (m_orien == PWB::Horizontal) {
	         pos.setX(m_pButton->mapToGlobal(QPoint(0, 0)).x() + m_pButton->width());
	         pos.setY(m_pButton->mapToGlobal(QPoint(0, 0)).y() - 40);
	     } else if (m_orien == PWB::Vertical) {
	         pos.setX(m_pButton->mapToGlobal(QPoint(0, 0)).x() + m_pButton->width()/2 - m_pMainWidget->width()/2);
	         pos.setY(m_pButton->mapToGlobal(QPoint(0, 0)).y() + m_pButton->height());
	     }
	     // 同時只能顯示一個 popupwidget
	     foreach (auto widget, m_pWidgets) {
	         if (widget != m_pMainWidget) {
	             widget->hide();
	         }
	     }
	     m_pMainWidget->move(pos);
	     m_pMainWidget->show();
	 } else {
	     m_pMainWidget->hide();
	 }
	});
	connect(this, &PopupWidgetButton::OthersClicked, m_pMainWidget, &PopupWidget::hide);

4.事件處理:

bool PopupWidgetButton::eventFilter(QObject *watched, QEvent *event)
{
    if (event->type() == QEvent::MouseButtonRelease) {
        QMouseEvent *e = static_cast<QMouseEvent *>(event);
        QPoint buttonPoint = m_pButton->mapToGlobal(QPoint(0, 0));
        // 需要獲取主視窗的座標
        QPoint ePoint = e->pos() + MainWidget::getMainWidgetPos();

        /**
          * \warning
          *  Qt5中的事件傳遞的第一層watched並不是我們自己的主窗體Widget類或者ButtonPopupWidget類,
          *  而是它們的一個私有實現類QWidgetWindow,該類的objectName會自動加上“Window”
          */
        if (watched->objectName() == "MainWidgetWindow") {
            m_mainWidgetClicked = false;
            // 點選按鈕
            if (ePoint.x() >= buttonPoint.x() && ePoint.x() <= buttonPoint.x() + m_pButton->width()
                    && ePoint.y() >= buttonPoint.y() && ePoint.y() <= buttonPoint.y() + m_pButton->height()) {
                emit ButtonClicked();
                return true;
            }
        }
        // 點選彈出的widget
        else if (watched->objectName() == "PopupMainWidgetWindow") {
            m_mainWidgetClicked =  true;
            return QWidget::eventFilter(watched, event);
        }

        if (!m_pMainWidget->isHidden() && !m_mainWidgetClicked) {
            emit OthersClicked();
        }

    }
    // 點選標題欄
    else if (event->type() == QEvent::NonClientAreaMouseButtonRelease ||
               event->type() == QEvent::NonClientAreaMouseButtonPress ||
               event->type() == QEvent::NonClientAreaMouseButtonDblClick) {
        emit OthersClicked();
    }
    return QObject::eventFilter(watched, event);
}

  關於事件的處理多說兩句。Qt的事件傳遞是從父->子的方向傳遞的。也就是說如果一個QPushButton指定了parent為QWidget,如何滑鼠點選按鈕,那麼是QWidget一個私有實現類QWidgetWindow類先捕獲到該事件的然後再傳遞給QWidget再傳遞給QPushButton。QWidgetWindow會有一個objectname,就是在QWidget的objectname的基礎上加上window字串。這些資訊在我們寫事件過濾器的時候會很有用。

程式碼下載