Qt实战6.万能的无边框窗口(FramelessWindow) 您所在的位置:网站首页 悬浮框模式 Qt实战6.万能的无边框窗口(FramelessWindow)

Qt实战6.万能的无边框窗口(FramelessWindow)

2024-01-06 00:05| 来源: 网络整理| 查看: 265

1 需求描述 实现一个Qt无边框窗口,自定义最大化、最小化、关闭按钮; 窗口支持任意拉伸、移动,支持边框阴影; 窗口能够集成任意其它窗口到内部形成一个整体。 2 设计思路

最初实现无边框的目标只有一个,即简单好用。所有实现基于Qt本身,现将窗口分为三层,如图: 外层和内容层均使用垂直布局,使窗口拉伸时能够自动适应大小。下面对每一层做个简单说明。

2.1 XWidget

作为窗口的最外层,设置为透明,为内层ContentWidget边框设置、阴影显示提供支持。同时根据位置设置光标形状(CursorShape),实现窗口的任意拉伸。

2.2 ContentWidget

作为内容包含层,可设置边框颜色、宽度、圆角、阴影等效果,同时增加最大化、最小化、关闭按钮,以及logo、软件名称显示部件。

2.3 CentralWidget

作为外部嵌入层,XWidget提供一个接口void setCentralWidget(QWidget *widget),将其它窗口集成到ContentWidget内部形成一个整体,这个与QMainWindow类似。

3 代码实现 首先,隐藏标题栏、启用样式表,XWidget背景透明,代码如下: setWindowFlags(Qt::FramelessWindowHint); //隐藏标题栏(无边框) setAttribute(Qt::WA_StyledBackground); //启用样式背景绘制 setAttribute(Qt::WA_TranslucentBackground); //背景透明 为了实现鼠标的位置信息获取不受子控件的影响,启动鼠标悬浮追踪,代码如下: setAttribute(Qt::WA_Hover); 随后便可以在event事件处理函数中获取到悬浮事件,将其转换为鼠标移动事件进行统一处理,代码如下: bool XWidget::event(QEvent *event) { if (event->type() == QEvent::HoverMove) { QHoverEvent *hoverEvent = static_cast(event); QMouseEvent mouseEvent(QEvent::MouseMove, hoverEvent->pos(), Qt::NoButton, Qt::NoButton, Qt::NoModifier); mouseMoveEvent(&mouseEvent); } return QWidget::event(event); } 进入鼠标移动事件,根据坐标设置鼠标对应的形状,如果鼠标为按下状态且到达XWidget边界则拉伸窗口,否则只移动窗口,代码如下:

m_bIsPressed 是否按下鼠标 m_bIsResizing 是否正在调整窗口,调整窗口大小时不移动窗口

void XWidget::mousePressEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_bIsPressed = true; m_pressPoint = event->globalPos(); } return QWidget::mousePressEvent(event); } void XWidget::mouseMoveEvent(QMouseEvent *event) { if (m_bIsPressed) { if (m_bIsResizing) { m_movePoint = event->globalPos() - m_pressPoint; m_pressPoint += m_movePoint; } else { if (!m_bIsDoublePressed && windowState() == Qt::WindowMaximized) { restoreWidget(); QPointF point(width() * ((double)(event->globalPos().x())/QApplication::desktop()->width()), height() * ((double)(event->globalPos().y())/QApplication::desktop()->height())); move(event->globalPos() - point.toPoint()); m_pressPoint = event->globalPos(); } QPoint point = event->globalPos() - m_pressPoint; move(pos() + point); m_pressPoint = event->globalPos(); } } if (windowState() != Qt::WindowMaximized) { updateRegion(event); } QWidget::mouseMoveEvent(event); } void XWidget::updateRegion(QMouseEvent *event) { QRect mainRect = geometry(); int marginTop = event->globalY() - mainRect.y(); int marginBottom = mainRect.y() + mainRect.height() - event->globalY(); int marginLeft = event->globalX() - mainRect.x(); int marginRight = mainRect.x() + mainRect.width() - event->globalX(); if (!m_bIsResizing) { if ( (marginRight >= MARGIN_MIN_SIZE && marginRight = MARGIN_MIN_SIZE && marginTop = MARGIN_MIN_SIZE && marginRight = MARGIN_MIN_SIZE && marginLeft = MARGIN_MIN_SIZE && marginTop = MARGIN_MIN_SIZE && marginLeft = MARGIN_MIN_SIZE && marginBottom minimumWidth() && marginBottom > minimumHeight()) { QRect rect = geometry(); rect.setTopRight(rect.topRight() + m_movePoint); setGeometry(rect); } } break; case TOPLEFT: { if (marginRight > minimumWidth() && marginBottom > minimumHeight()) { QRect rect = geometry(); rect.setTopLeft(rect.topLeft() + m_movePoint); setGeometry(rect); } } break; case BOTTOMLEFT: { if (marginRight > minimumWidth() && marginTop> minimumHeight()) { QRect rect = geometry(); rect.setBottomLeft(rect.bottomLeft() + m_movePoint); setGeometry(rect); } } break; case RIGHT: { QRect rect = geometry(); rect.setWidth(rect.width() + m_movePoint.x()); setGeometry(rect); } break; case DOWN: { QRect rect = geometry(); rect.setHeight(rect.height() + m_movePoint.y()); setGeometry(rect); } break; case LEFT: { if (marginRight > minimumWidth()) { QRect rect = geometry(); rect.setLeft(rect.x() + m_movePoint.x()); setGeometry(rect); } } break; case UP: { if (marginBottom > minimumHeight()) { QRect rect = geometry(); rect.setTop(rect.y() + m_movePoint.y()); setGeometry(rect); } } break; default: { } break; } } else { m_bIsResizing = false; m_direction = NONE; } }

同样的,上面这段代码也只干了一件事,如果鼠标达到了边框限定位置,并且按下了鼠标按键,就跟着改变窗口大小。

对标记成员进行重置,代码如下: void XWidget::mouseReleaseEvent(QMouseEvent *event) { if (event->button() == Qt::LeftButton) { m_bIsPressed = false; m_bIsResizing = false; m_bIsDoublePressed = false; } QWidget::mouseReleaseEvent(event); } void XWidget::leaveEvent(QEvent *event) { m_bIsPressed = false; m_bIsDoublePressed = false; m_bIsResizing = false; QWidget::leaveEvent(event); } 最后实现ContentWidget边框阴影效果,代码如下: void XWidget::createShadow() { QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this); shadowEffect->setColor(Qt::black); shadowEffect->setOffset(0, 0); shadowEffect->setBlurRadius(13); ui->widgetContent->setGraphicsEffect(shadowEffect); }

此方法虽有效,会损耗性能,复杂界面不建议使用。

由于ContentWidget和XWidget之间有间距,最大化时可能不能占满全屏,手动处理下,最大化时边距设为0,还原时恢复即可,代码如下: void XWidget::maximizeWidget() { ui->pushButtonRestore->show(); ui->pushButtonMax->hide(); ui->verticalLayoutShadow->setContentsMargins(0, 0, 0, 0); showMaximized(); } void XWidget::restoreWidget() { ui->pushButtonRestore->hide(); ui->pushButtonMax->show(); ui->verticalLayoutShadow->setContentsMargins(9, 9, 9, 9); showNormal(); } 4 QSS一下 #widgetContent { background-color: white; border: 1px solid lightgray; border-radius: 3px; } #widgetContent QTreeWidget { border: 1px solid lightgray; } #titleBarWidget QPushButton { min-width: 25px; max-width: 25px; min-height: 25px; max-height: 25px; qproperty-flat: true; border: none; } #titleBarWidget QPushButton:hover { background-color: #D5E1F2; } #titleBarWidget QPushButton:pressed { background-color: #A3BDE3; } #titleBarWidget QPushButton#pushButtonClose { border-image: url(:/img/titleBar/close.png) 0 0 0 0 stretch stretch; } #titleBarWidget QPushButton#pushButtonRestore { border-image: url(:/img/titleBar/restore.png) 0 0 0 0 stretch stretch; } #titleBarWidget QPushButton#pushButtonMax { border-image: url(:/img/titleBar/max.png) 0 0 0 0 stretch stretch; } #titleBarWidget QPushButton#pushButtonMin { border-image: url(:/img/titleBar/min.png) 0 0 0 0 stretch stretch; } #titleBarWidget QPushButton#pushButtonMenu { border-image: url(:/img/titleBar/menu.png) 0 0 0 0 stretch stretch; } #menuBarTabWidget::tab-bar { left: 65px; } #menuBarTabWidget { border: 1px; } #menuBarTabWidget { background-color: #2B579A; } #menuBarTabWidget::pane { border: 1px solid lightgray; border-left: 0px; border-right: 0px; } #menuBarTabWidget QTabBar::tab{ min-width: 55px; max-width: 55px; min-height: 23px; max-height: 23px; } #menuBarTabWidget QTabBar::tab { background: transparent; margin-left: 4px; margin-right: 4px; } #menuBarTabWidget QTabBar::tab:hover { color: #2B579A; } #menuBarTabWidget QTabBar::tab:selected { border: 1px solid #BAC9DB; background: white; border-bottom-color: #FFFFFF; } #menuBarTabWidget QTabBar::tab:!selected { margin-top: 1px; } QMenu { background-color: #FCFCFC; border: 1px solid #8492A6; } QMenu::item { background-color: transparent; } QMenu::item:selected { color: black; background-color: #D5E1F2; } #pushBtnFileMenu { min-width: 58px; max-width: 58px; min-height: 23px; max-height: 23px; color: white; border: 1px solid #2B579A; background-color: #2B579A; } #pushBtnFileMenu::menu-indicator { image: none; } 5 总结

之前也看了不少Qt实现FramelessWindow的例子,不是很复杂就是不通用。通过上面的实现,现在已完成了一个通用的版本,只要将自己的窗口设置到ContentWidget即可。本次实践关键地方有以下三点:

界面的分层,感兴趣的朋友可以尝试下,如果没有XWidget这一层会有什么效果,ContentWidget边框效果会失效,这样当然就达不到预期结果了; 启用了WA_Hover鼠标悬浮追踪,如果不启用,鼠标的移动事件可能会被子控件覆盖,这样就不会知道鼠标是否到达边框位置,从而无法正确设置鼠标的形状; 窗口拉伸时有个偏移量m_movePoint,鼠标其实到达ContentWidget边界就改变形状了,拉伸是对XWidget进行的,所以这里有一定的偏移。

可能算不上最佳实践,但是已经能够满足绝大多数使用场景了,往里面套就行,使用起来非常之简单,还是很nice的。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有