当前位置:   article > 正文

Qt实战:万能的无边框窗口(FramelessWindow)_qml 无边框窗口,边框支持拉大拉小,标题栏支持移动窗口,有置顶,最小化,最大化,关闭

qml 无边框窗口,边框支持拉大拉小,标题栏支持移动窗口,有置顶,最小化,最大化,关闭

文章转自博客园(Qt小罗):Qt实战6.万能的无边框窗口(FramelessWindow) - Qt小罗 - 博客园

 

1 需求描述

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

2 设计思路

最初实现无边框的目标只有一个,即简单好用。所有实现基于Qt本身,现将窗口分为三层,如图:

本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓ 


外层和内容层均使用垂直布局,使窗口拉伸时能够自动适应大小。下面对每一层做个简单说明。

2.1 XWidget

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

2.2 ContentWidget

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

2.3 CentralWidget

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

3 代码实现

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

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

  1. void XWidget::mousePressEvent(QMouseEvent *event)
  2. {
  3. if (event->button() == Qt::LeftButton) {
  4. m_bIsPressed = true;
  5. m_pressPoint = event->globalPos();
  6. }
  7. return QWidget::mousePressEvent(event);
  8. }
  1. void XWidget::mouseMoveEvent(QMouseEvent *event)
  2. {
  3. if (m_bIsPressed) {
  4. if (m_bIsResizing) {
  5. m_movePoint = event->globalPos() - m_pressPoint;
  6. m_pressPoint += m_movePoint;
  7. } else {
  8. if (!m_bIsDoublePressed && windowState() == Qt::WindowMaximized) {
  9. restoreWidget();
  10. QPointF point(width() * ((double)(event->globalPos().x())/QApplication::desktop()->width()),
  11. height() * ((double)(event->globalPos().y())/QApplication::desktop()->height()));
  12. move(event->globalPos() - point.toPoint());
  13. m_pressPoint = event->globalPos();
  14. }
  15. QPoint point = event->globalPos() - m_pressPoint;
  16. move(pos() + point);
  17. m_pressPoint = event->globalPos();
  18. }
  19. }
  20. if (windowState() != Qt::WindowMaximized) {
  21. updateRegion(event);
  22. }
  23. QWidget::mouseMoveEvent(event);
  24. }
  1. void XWidget::updateRegion(QMouseEvent *event)
  2. {
  3. QRect mainRect = geometry();
  4. int marginTop = event->globalY() - mainRect.y();
  5. int marginBottom = mainRect.y() + mainRect.height() - event->globalY();
  6. int marginLeft = event->globalX() - mainRect.x();
  7. int marginRight = mainRect.x() + mainRect.width() - event->globalX();
  8. if (!m_bIsResizing) {
  9. if ( (marginRight >= MARGIN_MIN_SIZE && marginRight <= MARGIN_MAX_SIZE)
  10. && ((marginBottom <= MARGIN_MAX_SIZE) && marginBottom >= MARGIN_MIN_SIZE) ) {
  11. m_direction = BOTTOMRIGHT;
  12. setCursor(Qt::SizeFDiagCursor);
  13. } else if ( (marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE)
  14. && (marginRight >= MARGIN_MIN_SIZE && marginRight <= MARGIN_MAX_SIZE)) {
  15. m_direction = TOPRIGHT;
  16. setCursor(Qt::SizeBDiagCursor);
  17. } else if ( (marginLeft >= MARGIN_MIN_SIZE && marginLeft <= MARGIN_MAX_SIZE)
  18. && (marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE) ) {
  19. m_direction = TOPLEFT;
  20. setCursor(Qt::SizeFDiagCursor);
  21. } else if ( (marginLeft >= MARGIN_MIN_SIZE && marginLeft <= MARGIN_MAX_SIZE)
  22. && (marginBottom >= MARGIN_MIN_SIZE && marginBottom <= MARGIN_MAX_SIZE)) {
  23. m_direction = BOTTOMLEFT;
  24. setCursor(Qt::SizeBDiagCursor);
  25. } else if (marginBottom <= MARGIN_MAX_SIZE && marginBottom >= MARGIN_MIN_SIZE) {
  26. m_direction = DOWN;
  27. setCursor(Qt::SizeVerCursor);
  28. } else if (marginLeft <= MARGIN_MAX_SIZE - 1 && marginLeft >= MARGIN_MIN_SIZE - 1) {
  29. m_direction = LEFT;
  30. setCursor(Qt::SizeHorCursor);
  31. } else if (marginRight <= MARGIN_MAX_SIZE && marginRight >= MARGIN_MIN_SIZE) {
  32. m_direction = RIGHT;
  33. setCursor(Qt::SizeHorCursor);
  34. } else if (marginTop <= MARGIN_MAX_SIZE && marginTop >= MARGIN_MIN_SIZE) {
  35. m_direction = UP;
  36. setCursor(Qt::SizeVerCursor);
  37. } else {
  38. if (!m_bIsPressed) {
  39. setCursor(Qt::ArrowCursor);
  40. }
  41. }
  42. }
  43. if (NONE != m_direction) {
  44. m_bIsResizing = true;
  45. resizeRegion(marginTop, marginBottom, marginLeft, marginRight);
  46. }
  47. }

不要看着代码多就感觉复杂,上面其实就干了一件事,判断鼠标是否达到边框限定位置,达到了就把方向记录下来。

  1. void XWidget::resizeRegion(int marginTop, int marginBottom,
  2. int marginLeft, int marginRight)
  3. {
  4. if (m_bIsPressed) {
  5. switch (m_direction) {
  6. case BOTTOMRIGHT:
  7. {
  8. QRect rect = geometry();
  9. rect.setBottomRight(rect.bottomRight() + m_movePoint);
  10. setGeometry(rect);
  11. }
  12. break;
  13. case TOPRIGHT:
  14. {
  15. if (marginLeft > minimumWidth() && marginBottom > minimumHeight()) {
  16. QRect rect = geometry();
  17. rect.setTopRight(rect.topRight() + m_movePoint);
  18. setGeometry(rect);
  19. }
  20. }
  21. break;
  22. case TOPLEFT:
  23. {
  24. if (marginRight > minimumWidth() && marginBottom > minimumHeight()) {
  25. QRect rect = geometry();
  26. rect.setTopLeft(rect.topLeft() + m_movePoint);
  27. setGeometry(rect);
  28. }
  29. }
  30. break;
  31. case BOTTOMLEFT:
  32. {
  33. if (marginRight > minimumWidth() && marginTop> minimumHeight()) {
  34. QRect rect = geometry();
  35. rect.setBottomLeft(rect.bottomLeft() + m_movePoint);
  36. setGeometry(rect);
  37. }
  38. }
  39. break;
  40. case RIGHT:
  41. {
  42. QRect rect = geometry();
  43. rect.setWidth(rect.width() + m_movePoint.x());
  44. setGeometry(rect);
  45. }
  46. break;
  47. case DOWN:
  48. {
  49. QRect rect = geometry();
  50. rect.setHeight(rect.height() + m_movePoint.y());
  51. setGeometry(rect);
  52. }
  53. break;
  54. case LEFT:
  55. {
  56. if (marginRight > minimumWidth()) {
  57. QRect rect = geometry();
  58. rect.setLeft(rect.x() + m_movePoint.x());
  59. setGeometry(rect);
  60. }
  61. }
  62. break;
  63. case UP:
  64. {
  65. if (marginBottom > minimumHeight()) {
  66. QRect rect = geometry();
  67. rect.setTop(rect.y() + m_movePoint.y());
  68. setGeometry(rect);
  69. }
  70. }
  71. break;
  72. default:
  73. {
  74. }
  75. break;
  76. }
  77. } else {
  78. m_bIsResizing = false;
  79. m_direction = NONE;
  80. }
  81. }

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

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

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

  1. 由于ContentWidget和XWidget之间有间距,最大化时可能不能占满全屏,手动处理下,最大化时边距设为0,还原时恢复即可,代码如下:
  1. void XWidget::maximizeWidget()
  2. {
  3. ui->pushButtonRestore->show();
  4. ui->pushButtonMax->hide();
  5. ui->verticalLayoutShadow->setContentsMargins(0, 0, 0, 0);
  6. showMaximized();
  7. }
  1. void XWidget::restoreWidget()
  2. {
  3. ui->pushButtonRestore->hide();
  4. ui->pushButtonMax->show();
  5. ui->verticalLayoutShadow->setContentsMargins(9, 9, 9, 9);
  6. showNormal();
  7. }

4 QSS一下

  1. #widgetContent {
  2. background-color: white;
  3. border: 1px solid lightgray;
  4. border-radius: 3px;
  5. }
  6. #widgetContent QTreeWidget {
  7. border: 1px solid lightgray;
  8. }
  9. #titleBarWidget QPushButton {
  10. min-width: 25px;
  11. max-width: 25px;
  12. min-height: 25px;
  13. max-height: 25px;
  14. qproperty-flat: true;
  15. border: none;
  16. }
  17. #titleBarWidget QPushButton:hover {
  18. background-color: #D5E1F2;
  19. }
  20. #titleBarWidget QPushButton:pressed {
  21. background-color: #A3BDE3;
  22. }
  23. #titleBarWidget QPushButton#pushButtonClose {
  24. border-image: url(:/img/titleBar/close.png) 0 0 0 0 stretch stretch;
  25. }
  26. #titleBarWidget QPushButton#pushButtonRestore {
  27. border-image: url(:/img/titleBar/restore.png) 0 0 0 0 stretch stretch;
  28. }
  29. #titleBarWidget QPushButton#pushButtonMax {
  30. border-image: url(:/img/titleBar/max.png) 0 0 0 0 stretch stretch;
  31. }
  32. #titleBarWidget QPushButton#pushButtonMin {
  33. border-image: url(:/img/titleBar/min.png) 0 0 0 0 stretch stretch;
  34. }
  35. #titleBarWidget QPushButton#pushButtonMenu {
  36. border-image: url(:/img/titleBar/menu.png) 0 0 0 0 stretch stretch;
  37. }
  38. #menuBarTabWidget::tab-bar {
  39. left: 65px;
  40. }
  41. #menuBarTabWidget {
  42. border: 1px;
  43. }
  44. #menuBarTabWidget {
  45. background-color: #2B579A;
  46. }
  47. #menuBarTabWidget::pane {
  48. border: 1px solid lightgray;
  49. border-left: 0px;
  50. border-right: 0px;
  51. }
  52. #menuBarTabWidget QTabBar::tab{
  53. min-width: 55px;
  54. max-width: 55px;
  55. min-height: 23px;
  56. max-height: 23px;
  57. }
  58. #menuBarTabWidget QTabBar::tab {
  59. background: transparent;
  60. margin-left: 4px;
  61. margin-right: 4px;
  62. }
  63. #menuBarTabWidget QTabBar::tab:hover {
  64. color: #2B579A;
  65. }
  66. #menuBarTabWidget QTabBar::tab:selected {
  67. border: 1px solid #BAC9DB;
  68. background: white;
  69. border-bottom-color: #FFFFFF;
  70. }
  71. #menuBarTabWidget QTabBar::tab:!selected {
  72. margin-top: 1px;
  73. }
  74. QMenu {
  75. background-color: #FCFCFC;
  76. border: 1px solid #8492A6;
  77. }
  78. QMenu::item {
  79. background-color: transparent;
  80. }
  81. QMenu::item:selected {
  82. color: black;
  83. background-color: #D5E1F2;
  84. }
  85. #pushBtnFileMenu {
  86. min-width: 58px;
  87. max-width: 58px;
  88. min-height: 23px;
  89. max-height: 23px;
  90. color: white;
  91. border: 1px solid #2B579A;
  92. background-color: #2B579A;
  93. }
  94. #pushBtnFileMenu::menu-indicator {
  95. image: none;
  96. }

5 总结

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

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

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

本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号