Qt自定义标题窗体实现(包含移动,缩放,阴影)

前言

网上想找一下qt的自定义标题窗口发现很多都不怎么全,或者扩展性,或者效果,或者解说一般,或者要收费。(嗯,我承认是我太菜了)。于是想要整理一下,顺带深入讨论一下一些我在实现时思考的问题。目前我的实现主要参考自 (https://www.cnblogs.com/luoxiang/p/13528745.html)其他还有很多零零散散的就算了。不列了,如果有问题欢迎联系删除(总不会分享点代码还能侵权吧)。以及该代码是我写的毕设的部分代码,所以,喜欢的自取吧(愿意标下出处当然是最好的。)

实现代码

  • customizeTitleWidget.hpp
#pragma once

#include <QGroupBox>
#include <QHBoxLayout>
#include <QLabel>
#include <QMainWindow>
#include <QToolBar>
#include <QToolButton>

/**
 * @brief TitleBar(llhsdmd-TitleBar ^-^)
 * 自定义窗体的标题栏,实现了窗体的拖动事件(只有标题栏能够拖动窗体)
 * 这里使用QToolBar代替(不知道有没有专门的标题栏)
 * 这里定义的标题栏构成参考自vscode等软件
 * 包含以下部分
 * 图标(Label) | 菜单(Menubar) | 空白 | 标题(Label) | 空白 |
 * 功能按钮(QToolButton)
 */
class TitleBar final : public QWidget {
  Q_OBJECT
 public:
  /**
   * @brief Construct a new Title Bar object
   * 在这里使用layout构建了标题栏的大体结构。
   * @param parent
   */
  TitleBar(QWidget *parent = nullptr);
  /**
   * @brief Set the Title object
   *
   * @param title
   */
  void setTitle(const QString &title);
  /**
   * @brief Set the Icon object
   *
   * @param icon
   */
  void setIcon(const QPixmap &icon);
  /**
   * @brief 添加控件到标题栏中
   *
   * @param widget 控件
   * @param index !!!
   * 你得自己保证插入的地方不影响现有布局,index包括现有部分,没有单独划分插入区域(不晓得怎么划比较好就干脆不划了吧
   * - by llhsdmd)
   * @param stretch layout接受的参数,表示对可分配空间的优先分配权重(
   * 空白为1,如果设为1会和其他空白均分可分配空间,默认控件都是0,即需要多少给多少。)
   * @param alignment 对齐方式
   */
  void addWidget(QWidget *widget, int index, int stretch = 0,
                 Qt::Alignment alignment = {0});
  /**
   * @brief Set the Resizable
   * 设置窗体是否可以resize(这里会影响最大化按钮)
   * @param resizeable
   */
  void setResizable(bool resizeable = true);
  /**
   * @brief Set the Movable
   * 设置窗体是否接受移动事件。
   * @param moveable
   */
  void setMovable(bool moveable = true);

  // 已下函数均继承自父类,用于定义标题栏需要的行为。
  void mousePressEvent(QMouseEvent *event) override;
  void mouseReleaseEvent(QMouseEvent *event) override;
  void mouseMoveEvent(QMouseEvent *event) override;
  void mouseDoubleClickEvent(QMouseEvent *event) override;
  void leaveEvent(QEvent *event) override;
	void paintEvent(QPaintEvent *event) override;

  // void showEvent(QShowEvent *event) override;

 public:
  /**
   * @brief show full left screen
   *使窗口占满左半边屏幕
   */
  void showFullLeftScreen();
  /**
   * @brief show full right screen
   * 使窗口占满右半边屏幕
   */
  void showFullRightScreen();
  /**
   * @brief show normal
   * 恢复窗口状态(主要恢复左半边和右半边满屏状态,Qt好像没有提供这两个)
   */
  void showNormal();
	void showMaximized();

 public slots:
  void MinimizedEvent(bool checked);
  void MaximizedEvent(bool checked);
  void CloseEvent(bool checked);

 private:
  QToolButton *toolButton_mini = nullptr;   // 最小化
  QToolButton *toolButton_max = nullptr;    // 最大化
  QToolButton *toolButton_close = nullptr;  // 关闭
  QHBoxLayout *hBoxLayout = nullptr;
  QLabel *titleLabel = nullptr;
  QLabel *titleIcon = nullptr;
  bool flag_resizable = true;
  bool flag_movable = true;
  bool flag_moving = false;
  bool flag_doubleClick = false;
  bool flag_welted = false;
  QPoint diff_position;
  QSize normal_size;
};

class CustomizeTitleWidget : public QWidget {
  Q_OBJECT
 public:
  CustomizeTitleWidget(QWidget *parent = nullptr);
  void addWidget(QWidget *widget, int index, int stretch = 0,
                 Qt::Alignment alignment = {0});
  void updateRegion(QMouseEvent *event);
  void resizeRegion(int marginTop, int marginBottom, int marginLeft,
                    int marginRight);
  void createShadow();

 public:
  bool event(QEvent *event) override;
  void mousePressEvent(QMouseEvent *event) override;
  void mouseMoveEvent(QMouseEvent *event) override;
  void mouseReleaseEvent(QMouseEvent *event) override;
  void leaveEvent(QEvent *event) override;
  void paintEvent(QPaintEvent *event) override;

 public:
  void setWindowTitle(const QString &title);
  void setWindowIcon(const QPixmap &icon);
  void addToolBar(QToolBar *toolBar);
  void addMenuBar(QMenuBar *menu);
  void setCentralWidget(QWidget *centralWidget);
  void setStatusBar(QStatusBar *statusBar);
  void showMinimized();
  void showMaximized();
  void showFullScreen();
  void showNormal();
  void setWindowResizable(bool resizable);

 protected:
  TitleBar *title_bar = nullptr;
  QGroupBox *container_widget = nullptr;
  QWidget *center_widget = nullptr;
  QVBoxLayout *container_layout = nullptr;
  QVBoxLayout *main_layout = nullptr;
  QVBoxLayout *top_layout = nullptr;
  QVBoxLayout *center_layout = nullptr;
  QVBoxLayout *bottom_layout = nullptr;

 private:
  const int MARGIN_MIN_SIZE = 0;
  const int MARGIN_MAX_SIZE = 10;
  enum MovingDirection {
    NONE,
    BOTTOMRIGHT,
    TOPRIGHT,
    TOPLEFT,
    BOTTOMLEFT,
    DOWN,
    LEFT,
    RIGHT,
    UP
  };

 private:
  MovingDirection m_direction = NONE;
  QPoint press_pos;
  QPoint move_pos;
  bool flag_resizing = false;
  bool flag_pressed = false;
  bool flag_resizable = true;
};
  • customizeTitleWidget.cpp
#include "customizeTitleWidget.hpp"

#include <QGraphicsDropShadowEffect>
#include <QMenuBar>
#include <QPainter>
#include <QPainterPath>
#include <QScreen>
#include <QShowEvent>
#include <QStatusBar>
#include <QStyle>

TitleBar::TitleBar(QWidget *parent) : QWidget(parent) {
  setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
  // QWidget::setMovable(false);

  hBoxLayout = new QHBoxLayout();

  titleLabel = new QLabel();
  titleLabel->setAlignment(Qt::AlignCenter);
  setWindowTitle(windowTitle());

  titleIcon = new QLabel();
  titleIcon->setAlignment(Qt::AlignCenter);
  setWindowIcon(windowIcon());

  toolButton_mini = new QToolButton();
  toolButton_max = new QToolButton();
  toolButton_close = new QToolButton();

  hBoxLayout->addWidget(titleIcon);
  hBoxLayout->addStretch(1);
  hBoxLayout->addWidget(titleLabel, 0, Qt::AlignCenter);
  hBoxLayout->addStretch(1);
  hBoxLayout->addWidget(toolButton_mini);
  hBoxLayout->addWidget(toolButton_max);
  hBoxLayout->addWidget(toolButton_close);

  setLayout(hBoxLayout);
  hBoxLayout->setContentsMargins(2, 4, 2, 5);

  toolButton_mini->setIcon(
      style()->standardPixmap(QStyle::SP_TitleBarMinButton));
  QWidget::connect(toolButton_mini, &QToolButton::clicked, this,
                   &TitleBar::MinimizedEvent);
  toolButton_max->setIcon(
      style()->standardPixmap(QStyle::SP_TitleBarMaxButton));
  QWidget::connect(toolButton_max, &QToolButton::clicked, this,
                   &TitleBar::MaximizedEvent);
  toolButton_close->setIcon(
      style()->standardPixmap(QStyle::SP_TitleBarCloseButton));
  QWidget::connect(toolButton_close, &QToolButton::clicked, this,
                   &TitleBar::CloseEvent);
}
void TitleBar::MinimizedEvent(bool checked) {
  CustomizeTitleWidget *main_window =
      dynamic_cast<CustomizeTitleWidget *>(window());
  if (main_window != nullptr) {
    main_window->showMinimized();
  } else {
    window()->showMinimized();
  }
}
void TitleBar::MaximizedEvent(bool checked) {
  if (!flag_resizable) {
    return;
  }
  if (!window()->isMaximized()) {
    showMaximized();
  } else {
    showNormal();
  }
}
void TitleBar::showMaximized() {
  CustomizeTitleWidget *main_window =
      dynamic_cast<CustomizeTitleWidget *>(window());
  if (main_window != nullptr) {
    main_window->showMaximized();
  } else {
    window()->showMaximized();
  }
  toolButton_max->setIcon(
      style()->standardPixmap(QStyle::SP_TitleBarNormalButton));
}

void TitleBar::showFullLeftScreen() {
  if (!flag_welted && flag_resizable) {
    flag_welted = true;
    normal_size = window()->size();
    auto screen_size = window()->screen()->availableSize();
    window()->resize(screen_size.width() / 2, screen_size.height());
    window()->move(0, 0);
  }
}
void TitleBar::showFullRightScreen() {
  if (!flag_welted && flag_resizable) {
    flag_welted = true;
    normal_size = window()->size();
    auto screen_size = window()->screen()->availableSize();
    window()->resize(screen_size.width() / 2, screen_size.height());
    window()->move(screen_size.width() / 2, 0);
  }
}
void TitleBar::showNormal() {
  if (flag_welted) {
    flag_welted = false;
    window()->resize(normal_size);
  } else {
    CustomizeTitleWidget *main_window =
        dynamic_cast<CustomizeTitleWidget *>(window());
    if (main_window != nullptr) {
      main_window->showNormal();
    } else {
      window()->showNormal();
    }
    toolButton_max->setIcon(
        style()->standardPixmap(QStyle::SP_TitleBarMaxButton));
  }
}

void TitleBar::CloseEvent(bool checked) { window()->close(); }

void TitleBar::setResizable(bool resizeable) {
  flag_resizable = resizeable;
  toolButton_max->setVisible(resizeable);
}

void TitleBar::setMovable(bool moveable) { flag_movable = moveable; }

void TitleBar::mousePressEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton && flag_movable && !flag_doubleClick) {
    flag_moving = true;
    diff_position = window()->pos() - event->globalPos();
  }
  QWidget::mousePressEvent(event);
}

void TitleBar::mouseReleaseEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton) {
    flag_moving = false;
  }
  flag_doubleClick = false;
  QWidget::mouseReleaseEvent(event);
}

void TitleBar::leaveEvent(QEvent *event) {
  flag_moving = false;
  QWidget::leaveEvent(event);
}

void TitleBar::mouseMoveEvent(QMouseEvent *event) {
  if (flag_moving && flag_movable) {
    auto screen_size = window()->screen()->availableSize();
    if ((window()->isMaximized() && event->globalY() >= 10) ||
        (flag_welted && (event->globalX() >= 5 &&
                         event->globalX() <= screen_size.width() - 5))) {
      showNormal();
      diff_position.setX(1);
    } else {
      if (diff_position.x() == 1) {
        diff_position.setX(-window()->size().width() / 2);
      }
      if (event->globalY() < 10) {
        if (!window()->isMaximized()) {
          MaximizedEvent(true);
        }
      } else if (event->globalX() < 5) {
        showFullLeftScreen();
      } else if (screen_size.width() - event->globalX() < 5) {
        showFullRightScreen();
      } else {
        window()->move(event->globalPos() + diff_position);
      }
    }
  }
  QWidget::mouseMoveEvent(event);
}
void TitleBar::mouseDoubleClickEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton) {
    flag_doubleClick = true;
    MaximizedEvent(true);
  }
  QWidget::mouseDoubleClickEvent(event);
}

void TitleBar::paintEvent(QPaintEvent *event) {
  QPainter painter(this);
  auto self_rect = rect();
  painter.setPen(QColor(250, 250, 250));
  painter.drawLine(0, self_rect.height() - 1, self_rect.width(),
                   self_rect.height() - 1);
  QWidget::paintEvent(event);
}

void TitleBar::setIcon(const QPixmap &icon) { titleIcon->setPixmap(icon); }

void TitleBar::addWidget(QWidget *widget, int index, int stretch,
                         Qt::Alignment alignment) {
  hBoxLayout->insertWidget(index, widget, stretch, alignment);
}

void TitleBar::setTitle(const QString &title) {
  titleLabel->setText(title);
}

CustomizeTitleWidget::CustomizeTitleWidget(QWidget *parent) : QWidget(parent) {
  setWindowFlags(Qt::FramelessWindowHint);  // 隐藏窗体原始边框
  setAttribute(Qt::WA_StyledBackground);       // 启用样式背景绘制
  setAttribute(Qt::WA_TranslucentBackground);  // 设置背景透明
  setAttribute(Qt::WA_Hover);                  // 启动鼠标悬浮追踪

  title_bar = new TitleBar();

  container_widget = new QGroupBox();
  container_layout = new QVBoxLayout();
  container_widget->setSizePolicy(QSizePolicy::Expanding,
                                  QSizePolicy::Expanding);
  container_layout->addWidget(container_widget);
  container_layout->setMargin(5);
  container_layout->setSpacing(0);
  setLayout(container_layout);

  main_layout = new QVBoxLayout();
  container_widget->setLayout(main_layout);
  main_layout->setMargin(0);
  main_layout->setSpacing(0);

  top_layout = new QVBoxLayout();
  center_layout = new QVBoxLayout();
  bottom_layout = new QVBoxLayout();

  main_layout->addWidget(title_bar);
  main_layout->addLayout(top_layout);
  main_layout->addLayout(center_layout, 1);
  main_layout->addLayout(bottom_layout);

  setStyleSheet(
      "QGroupBox{background-color: lightblue}"
      "QGroupBox{border: 1px solid rgb(200,200,200)}"
      "QGroupBox{border-radius: 5px}");

  createShadow();
}

void CustomizeTitleWidget::addToolBar(QToolBar *toolBar) {
  top_layout->addWidget(toolBar);
}
void CustomizeTitleWidget::addMenuBar(QMenuBar *menu) {
  title_bar->addWidget(menu, 1);
}
void CustomizeTitleWidget::setCentralWidget(QWidget *centralWidget) {
  if (!center_layout->isEmpty()) {
    QLayoutItem *child;
    while ((child = center_layout->takeAt(0)) != 0) {
      if (child->widget()) {
        child->widget()->setParent(NULL);
      }
      delete child;
    }
  }
  center_layout->addWidget(centralWidget);
}
void CustomizeTitleWidget::setStatusBar(QStatusBar *statusBar) {
  bottom_layout->addWidget(statusBar);
}

void CustomizeTitleWidget::setWindowTitle(const QString &title) {
  title_bar->setTitle(title);
}

void CustomizeTitleWidget::setWindowIcon(const QPixmap &icon) {
  title_bar->setIcon(icon);
}

void CustomizeTitleWidget::addWidget(QWidget *widget, int index, int stretch,
                                     Qt::Alignment alignment) {
  title_bar->addWidget(widget, index, stretch, alignment);
}

void CustomizeTitleWidget::paintEvent(QPaintEvent *event) {
  QWidget::paintEvent(event);
}

bool CustomizeTitleWidget::event(QEvent *event) {
  if (event->type() == QEvent::HoverMove) {
    QHoverEvent *hoverEvent = static_cast<QHoverEvent *>(event);
    QMouseEvent mouseEvent(QEvent::MouseMove, hoverEvent->pos(), Qt::NoButton,
                           Qt::NoButton, Qt::NoModifier);
    mouseMoveEvent(&mouseEvent);
  }

  return QWidget::event(event);
}

void CustomizeTitleWidget::mousePressEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton) {
    flag_pressed = true;
    press_pos = event->globalPos();
  }
  QWidget::mousePressEvent(event);
}

void CustomizeTitleWidget::mouseMoveEvent(QMouseEvent *event) {
  if (flag_pressed) {
    move_pos = event->globalPos() - press_pos;
    press_pos += move_pos;
  }

  if (windowState() != Qt::WindowMaximized) {
    updateRegion(event);
  }

  if (!flag_resizing) {
    flag_pressed = false;
  }
  QWidget::mouseMoveEvent(event);
}

void CustomizeTitleWidget::mouseReleaseEvent(QMouseEvent *event) {
  if (event->button() == Qt::LeftButton) {
    flag_pressed = false;
    flag_resizing = false;
    title_bar->setMovable(true);
    setCursor(Qt::ArrowCursor);
  }

  QWidget::mouseReleaseEvent(event);
}

void CustomizeTitleWidget::leaveEvent(QEvent *event) {
  flag_pressed = false;
  flag_resizing = false;
  title_bar->setMovable(true);
  setCursor(Qt::ArrowCursor);

  QWidget::leaveEvent(event);
}

void CustomizeTitleWidget::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 (!flag_resizing && flag_resizable) {
    if ((marginRight >= MARGIN_MIN_SIZE && marginRight <= MARGIN_MAX_SIZE) &&
        ((marginBottom <= MARGIN_MAX_SIZE) &&
         marginBottom >= MARGIN_MIN_SIZE)) {
      m_direction = BOTTOMRIGHT;
      setCursor(Qt::SizeFDiagCursor);
    } else if ((marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE) &&
               (marginRight >= MARGIN_MIN_SIZE &&
                marginRight <= MARGIN_MAX_SIZE)) {
      m_direction = TOPRIGHT;
      setCursor(Qt::SizeBDiagCursor);
    } else if ((marginLeft >= MARGIN_MIN_SIZE &&
                marginLeft <= MARGIN_MAX_SIZE) &&
               (marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE)) {
      m_direction = TOPLEFT;
      setCursor(Qt::SizeFDiagCursor);
    } else if ((marginLeft >= MARGIN_MIN_SIZE &&
                marginLeft <= MARGIN_MAX_SIZE) &&
               (marginBottom >= MARGIN_MIN_SIZE &&
                marginBottom <= MARGIN_MAX_SIZE)) {
      m_direction = BOTTOMLEFT;
      setCursor(Qt::SizeBDiagCursor);
    } else if (marginBottom <= MARGIN_MAX_SIZE &&
               marginBottom >= MARGIN_MIN_SIZE) {
      m_direction = DOWN;
      setCursor(Qt::SizeVerCursor);
    } else if (marginLeft <= MARGIN_MAX_SIZE - 1 &&
               marginLeft >= MARGIN_MIN_SIZE - 1) {
      m_direction = LEFT;
      setCursor(Qt::SizeHorCursor);
    } else if (marginRight <= MARGIN_MAX_SIZE &&
               marginRight >= MARGIN_MIN_SIZE) {
      m_direction = RIGHT;
      setCursor(Qt::SizeHorCursor);
    } else if (marginTop <= MARGIN_MAX_SIZE && marginTop >= MARGIN_MIN_SIZE) {
      m_direction = UP;
      setCursor(Qt::SizeVerCursor);
    } else {
      if (!flag_pressed) {
        setCursor(Qt::ArrowCursor);
      }
    }
  }

  if (NONE != m_direction) {
    flag_resizing = true;
    title_bar->setMovable(false);
    resizeRegion(marginTop, marginBottom, marginLeft, marginRight);
  }
}
void CustomizeTitleWidget::resizeRegion(int marginTop, int marginBottom,
                                        int marginLeft, int marginRight) {
  if (flag_pressed) {
    switch (m_direction) {
      case BOTTOMRIGHT: {
        QRect rect = geometry();
        rect.setBottomRight(rect.bottomRight() + move_pos);
        setGeometry(rect);
      } break;
      case TOPRIGHT: {
        if (marginLeft > minimumWidth() && marginBottom > minimumHeight()) {
          QRect rect = geometry();
          rect.setTopRight(rect.topRight() + move_pos);
          setGeometry(rect);
        }
      } break;
      case TOPLEFT: {
        if (marginRight > minimumWidth() && marginBottom > minimumHeight()) {
          QRect rect = geometry();
          rect.setTopLeft(rect.topLeft() + move_pos);
          setGeometry(rect);
        }
      } break;
      case BOTTOMLEFT: {
        if (marginRight > minimumWidth() && marginTop > minimumHeight()) {
          QRect rect = geometry();
          rect.setBottomLeft(rect.bottomLeft() + move_pos);
          setGeometry(rect);
        }
      } break;
      case RIGHT: {
        QRect rect = geometry();
        rect.setWidth(rect.width() + move_pos.x());
        setGeometry(rect);
      } break;
      case DOWN: {
        QRect rect = geometry();
        rect.setHeight(rect.height() + move_pos.y());
        setGeometry(rect);
      } break;
      case LEFT: {
        if (marginRight > minimumWidth()) {
          QRect rect = geometry();
          rect.setLeft(rect.x() + move_pos.x());
          setGeometry(rect);
        }
      } break;
      case UP: {
        if (marginBottom > minimumHeight()) {
          QRect rect = geometry();
          rect.setTop(rect.y() + move_pos.y());
          setGeometry(rect);
        }
      } break;
      default: {
      } break;
    }
  } else {
    flag_resizing = false;
    title_bar->setMovable(true);
    m_direction = NONE;
  }
}
void CustomizeTitleWidget::showMinimized() { QWidget::showMinimized(); }
void CustomizeTitleWidget::showMaximized() {
  container_layout->setMargin(0);
  QWidget::showMaximized();
}
void CustomizeTitleWidget::showFullScreen() { QWidget::showFullScreen(); }
void CustomizeTitleWidget::showNormal() {
  container_layout->setMargin(5);
  QWidget::showNormal();
}

void CustomizeTitleWidget::createShadow() {
  QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
  shadowEffect->setColor(QColor(100, 100, 100));
  shadowEffect->setOffset(0, 0);
  shadowEffect->setBlurRadius(13);
  container_widget->setGraphicsEffect(shadowEffect);
}

void CustomizeTitleWidget::setWindowResizable(bool resizable) {
  flag_resizable = resizable;
  title_bar->setResizable(resizable);
}

实现细节讨论

好的,接下来就是讨论如何实现的部分了,如果只是需要一个能跑的自定义标题窗口的话,稍微看看代码里面的接口直接用应该就可以了,没必须要继续了。
在qt中,最外层的window也就是物理窗口是会自带标题栏的。通过设置窗口属性FramelessWindowHint

setWindowFlags(Qt::FramelessWindowHint);  // 隐藏窗体原始边框

可以生成不带标题栏等带有边框的窗口,设置这个会失去以下的默认行为

  • 标题栏
  • 窗口拖动
  • 使用鼠标进行resize
  • 贴边时的靠边行为
  • 窗口边缘的阴影,圆角
  • 等我也不知道还有没有了。

因此现在进行的讨论就是如何重新实现以上的行为或控件。

标题栏

标题栏,也就是这个问题的起始,首先我们已经生成了不带边框的空白窗口了。那么接下来所有的组件都可以使用可以自定义的QWidget类了。
那么,接下来就是标题栏内容的设计了,我的标题栏采用如下内容结构
标题栏
这个标题栏包含了 图标 菜单 标题 功能按钮。
嗯,实现思路就是按建立一个layout依次添加就可以了,这里给一个简单的实现思路,(完整代码在上面,这里只是部分)

  hBoxLayout->setContentsMargins(0, 0, 0, 0); // 设置控件与边框之间的间距为0
  hBoxLayout->addWidget(titleIcon); // Icon
  hBoxLayout->addStretch(1); // 空白填充
  hBoxLayout->addWidget(titleLabel, 0, Qt::AlignCenter); // 标题
  hBoxLayout->addStretch(1); // 空白填充
  hBoxLayout->addWidget(toolButton_mini); // 最小化按钮
  hBoxLayout->addWidget(toolButton_max); // 最大化按钮
  hBoxLayout->addWidget(toolButton_close); // 关闭按钮

然后设置对应的控件干对应的事就可以了。至此,那么讨论的部分是什么呢?
显然,这里其实可以不用控件,而是直接把这些组件加到一个QHBoxLayout中再作为第一个加到main_layout(QVBoxLayout)的第一个就可以了。那么为什么我作为一个控件单独写呢,这样对标题栏上发生的事件可以更细致的进行处理,也不会导致一个函数过长,但我的处理显然是不够好的。
不知道有没有人有更好的解决方案。

  • 标题栏具备定义的样式
  • 方便的定义标题栏的行为如双击单击拖拽(窗体和标题栏是否需要区分呢)

窗口拖动

如上所述,我将窗体的移动写到了标题栏的事件中。这里也没有什么好讨论的,应该没有除了自己响应以下事件来实现以外的好办法了。

 void mousePressEvent(QMouseEvent *event) override;
 void mouseReleaseEvent(QMouseEvent *event) override;
 void mouseMoveEvent(QMouseEvent *event) override;

当然,如果有还请赐教。

使用鼠标进行resize

依旧是自定义上述行为,但是想要实现这个需要额外的设置让窗口能够透过自己的子窗口获取到鼠标的事件

  setAttribute(Qt::WA_Hover);                  // 启动鼠标悬浮追踪

之后便是根据鼠标的位置和状态设置窗体的状态并做出对应的行为来响应鼠标事件了。
其核心包括在鼠标move事件中更新鼠标的图标和窗口保存的状态量

void CustomizeTitleWidget::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 (!flag_resizing) {
    if ((marginRight >= MARGIN_MIN_SIZE && marginRight <= MARGIN_MAX_SIZE) &&
        ((marginBottom <= MARGIN_MAX_SIZE) &&
         marginBottom >= MARGIN_MIN_SIZE)) {
      m_direction = BOTTOMRIGHT;
      setCursor(Qt::SizeFDiagCursor);
    } else if ((marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE) &&
               (marginRight >= MARGIN_MIN_SIZE &&
                marginRight <= MARGIN_MAX_SIZE)) {
      m_direction = TOPRIGHT;
      setCursor(Qt::SizeBDiagCursor);
    } else if ((marginLeft >= MARGIN_MIN_SIZE &&
                marginLeft <= MARGIN_MAX_SIZE) &&
               (marginTop >= MARGIN_MIN_SIZE && marginTop <= MARGIN_MAX_SIZE)) {
      m_direction = TOPLEFT;
      setCursor(Qt::SizeFDiagCursor);
    } else if ((marginLeft >= MARGIN_MIN_SIZE &&
                marginLeft <= MARGIN_MAX_SIZE) &&
               (marginBottom >= MARGIN_MIN_SIZE &&
                marginBottom <= MARGIN_MAX_SIZE)) {
      m_direction = BOTTOMLEFT;
      setCursor(Qt::SizeBDiagCursor);
    } else if (marginBottom <= MARGIN_MAX_SIZE &&
               marginBottom >= MARGIN_MIN_SIZE) {
      m_direction = DOWN;
      setCursor(Qt::SizeVerCursor);
    } else if (marginLeft <= MARGIN_MAX_SIZE - 1 &&
               marginLeft >= MARGIN_MIN_SIZE - 1) {
      m_direction = LEFT;
      setCursor(Qt::SizeHorCursor);
    } else if (marginRight <= MARGIN_MAX_SIZE &&
               marginRight >= MARGIN_MIN_SIZE) {
      m_direction = RIGHT;
      setCursor(Qt::SizeHorCursor);
    } else if (marginTop <= MARGIN_MAX_SIZE && marginTop >= MARGIN_MIN_SIZE) {
      m_direction = UP;
      setCursor(Qt::SizeVerCursor);
    } else {
      if (!flag_pressed) {
        setCursor(Qt::ArrowCursor);
      }
    }
  }

  if (NONE != m_direction) {
    flag_resizing = true;
    title_bar->setMovable(false);
    resizeRegion(marginTop, marginBottom, marginLeft, marginRight);
  }
}

我的这部分实现很屎呢,因为resize和move行为是互斥的,但两个行为被我分给了两个不同的控件,导致需要相互传递(设置)对方的状态。这里实在有待考虑。

贴边时的靠边行为

这个就是实现里面最蛋疼的部分了,如果要对接系统行为的话是需要调用系统API的,因此为了保证代码可以在不同的平台上运行,我这里偷懒的直接自己resize了。但ubuntu下行为有问题(任务栏不在底部导致的问题)。需要的自己解决一下吧。
这里我也没有研究了,但是可以给到实现的思路,重载以下事件

bool nativeEvent(const QByteArray &eventType, void *message, long *result)

然后根据系统给的message执行相应调用。

窗口边缘的阴影,圆角

到此,窗口的功能上其实已经实现的差不多了,在效率上来讲不做这里的美化也是更好的,因为这里采用的是比较低效的方式实现。我在网上找到的实现将主窗口进行了如下分层
窗口结构
背景层:用于放置绘制的背景(边框阴影是超过边界的),于是设置一个比实际窗口更大的透明窗口用于绘制超过可视边界的阴影。设置窗体背景透明的方法如下

  setAttribute(Qt::WA_TranslucentBackground);  // 设置背景透明

这里要注意QWidget,QToolBar,QLabel等都是默认背景透明的控件。
而QPushButton等不是透明背景的控件
容器层:容器层是最终控件所在的层,在这里你可以组织你的title和menu,toolbar,自定的控件,statusbar等控件了,我的组织方式是将他们全部放到QVBoxLayout中,并且分成不同的区域,各各区域又直接用一个layout来区分,区分的layout可以根据你的设计选用,我的分区如下,拉伸系数分别是0,0,1,0。除了自定义控件区域其他控件均分配到最合适的大小
容器层结构
而容器层另一个重要的职责是实际绘制背景。同时边缘阴影和圆角也是提供该层绘制的。
边缘阴影的实现可以使用qt的shadowEffect来实现

QGraphicsDropShadowEffect *shadowEffect = new QGraphicsDropShadowEffect(this);
  shadowEffect->setColor(QColor(100, 100, 100));
  shadowEffect->setOffset(0, 0);
  shadowEffect->setBlurRadius(13);
  container_widget->setGraphicsEffect(shadowEffect);

当然,画布已经空出来了,这里你想要如何实现都可以自己在painterEvent里面自由发挥了。

圆角的绘制可以通过setStyleSheet来实现。但这里会有一个很大的问题,那就是StyleSheet是会向下广播的,而且容器层是所有你后面增加的控件的根,这里的设置不可避免的会影响到后面的控件,这里我还没有找到好的办法,当然,如果使用painterEvent自由发挥的话就不会有这个问题了,因此这里我推荐自行绘制。虽然我偷懒了,我使用QGroupBox,以牺牲它的默认样式为代价使用了stylesheet。关于qt这个属性是否可以细致到单个控件不广播的方法我没有找到,如果有知道的还请指点一下。
以下是我设置的背景

 setStyleSheet(
      "QGroupBox{background-color: lightblue}"
      "QGroupBox{border: 1px solid rgb(200,200,200)}"
      "QGroupBox{border-radius: 5px}");

这里有一个细节,那就是如果背景上方的窗口如果不是透明窗口的话是会遮挡背景的,也就是如果背景上方是个直角边不透明背景控件的话,该控件贴的边也会变成直角(这里可以调整自定义控件与容器控件的边界解决,但有些情况下这个边界看着视觉效果会不好,所以自行斟酌吧。)
当前实现思路中全屏或贴边的问题
因为我们实际的窗口是一个比可视部分更大的透明的窗口,因此直接全屏的话窗体的边缘会有一条明显的缝隙。需要注意在全屏时将可视窗口设置成和实际窗口一样大小。在退出全屏时要恢复状态,我这里直接使用layout装容器窗口,因此设置layout的边缘间距设置即可

void CustomizeTitleWidget::showMaximized() {
  container_layout->setMargin(0);
  QWidget::showMaximized();
}
void CustomizeTitleWidget::showNormal() {
  container_layout->setMargin(5);
  QWidget::showNormal();
}

如果是在painterEvent中自己绘制的话
那么最外层的背景层就不需要了呢,直接设置容器层的主layout让内部的控件与边缘的边距即可。

最终的效果展示

最终界面展示
嗯,确实不怎么好看呢,这里提供了一个自定义标题框架的实现思路,也算是我个人的一个学习记录吧。欢迎讨论学习。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值