Qt自定義按鈕彈窗控制元件
阿新 • • 發佈:2018-12-16
一直以為做一個按鈕彈窗控制元件很簡單,可做起來發現並不是那麼順利,折騰了挺長時間的,先看下效果:
前言
嘗試過兩種方案,方案一:使用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字串。這些資訊在我們寫事件過濾器的時候會很有用。