C++ QT无边框界面
效果预览
众所周知, QT开发默认的窗口不可以定制, 而且因为不可定制, 在外观方面就不可以根据自己项目的主体样式进行修改, 统一风格。


项目布局
项目布局使用的是上下分割式, header为标题栏, Main为主内容栏。

实现代码
void BorderlessWidget::init()
{
layoutMain = new QVBoxLayout(this);
layoutContent = new QVBoxLayout();
// 添加标题栏
titleBar = new TitleBar(this);
layoutMain->addWidget(titleBar);
auto *spacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Expanding);
layoutMain->addItem(spacer);
layoutMain->addLayout(layoutContent);
layoutMain->addItem(spacer);
setLayout(layoutMain);
}
标题栏页面设置

本项目所使用的页面 主要有 logo标签,title标签,以及四个按钮,分别是固定窗口于最上方,最小化窗口,最大化窗口,关闭应用。当然可以根据自己需要定制标题栏。
代码实现
Qlabel 实现
void TitleBar::initLabel()
{
// 创建文本水平布局
layoutLabel = new QHBoxLayout;
iconLabel = new QLabel(this);
// 设置图标大小
iconLabel->setFixedSize(LabelBtnSize);
// 设置图标
iconLabel->setPixmap(QPixmap(":/res/application.svg").scaled(iconSize, iconSize));
titleLabel = new QLabel(this);
// 设置标题字体
titleLabel->setFont(QFont("Microsoft Yahei", 10));
setTitle("Test Title");
// 创建弹簧
// title显示在左边,所以这里创建一个弹簧,使得title显示在左边
auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Fixed, QSizePolicy::Fixed);
// title显示在中间, 使用这个弹簧,可以使得title显示在中间
// auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Expanding, QSizePolicy::Fixed);
layoutLabel->addWidget(iconLabel);
layoutLabel->addItem(spacer);
layoutLabel->addWidget(titleLabel);
}
QToolbutton实现
void TitleBar::initButton()
{
// 创建按钮水平布局
layoutBtn = new QHBoxLayout;
fixedButton = new QToolButton;
// 设置按钮大小
fixedButton->setFixedSize(LabelBtnSize);
// 设置固定按钮的图标
fixedButton->setIcon(QIcon(":/res/guding.svg").pixmap(iconSize, iconSize));
minimizeButton = new QToolButton;
// 设置按钮大小
minimizeButton->setFixedSize(LabelBtnSize);
// 设置最小化按钮的图标
minimizeButton->setIcon(QIcon(":/res/zuixiaohua.svg").pixmap(iconSize, iconSize));
maximizeButton = new QToolButton;
// 设置按钮大小
maximizeButton->setFixedSize(LabelBtnSize);
// 设置最大化按钮的图标
maximizeButton->setIcon(QIcon(":/res/quanping.svg").pixmap(iconSize, iconSize));
closeButton = new QToolButton(this);
// 设置按钮大小
closeButton->setFixedSize(LabelBtnSize);
// 设置关闭按钮的图标
closeButton->setIcon(QIcon(":/res/guanbi.svg").pixmap(iconSize, iconSize));
// 设置按钮的样式表
closeButton->setStyleSheet("QToolButton:hover {"
" border-radius: 4px;"
" border: 1px solid rgba(252, 53, 3, 50%);"
" background-color: rgba(252, 53, 3, 10%);"
"}"
"QToolButton:pressed {"
" background-color: rgba(252, 53, 3, 100%);"
"}");
auto spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Fixed, QSizePolicy::Fixed);
layoutBtn->addWidget(fixedButton);
layoutBtn->addItem(spacer);
layoutBtn->addWidget(minimizeButton);
layoutBtn->addItem(spacer);
layoutBtn->addWidget(maximizeButton);
layoutBtn->addItem(spacer);
layoutBtn->addWidget(closeButton);
}
主框架实现
void TitleBar::init()
{
setMinimumSize(QSize(300, 50));
// 关闭窗口边界
setWindowFlags(Qt::FramelessWindowHint);
// 设置窗口颜色为灰色
QPalette pal = palette();
pal.setColor(QPalette::Window, QColor(236, 236, 236));
setAutoFillBackground(true);
setPalette(pal);
// 创建一个水平弹簧
auto *spacer = new QSpacerItem(btnLabelWidth, btnLabelWidth, QSizePolicy::Expanding, QSizePolicy::Fixed);
// 初始化布局
layoutBtn = new QHBoxLayout;
layoutLabel = new QHBoxLayout;
layout = new QHBoxLayout;
// 初始化标题栏
initLabel();
// 初始化按钮
initButton();
layout->addLayout(layoutLabel);
layout->addItem(spacer);
layout->addLayout(layoutBtn);
// 设置布局
this->setLayout(layout);
}
标题栏功能设置
功能概述
- 四个按钮均可以实现对应的功能
- 双击标题栏窗口会在
全屏状态和正常状态之间切换 - 标题栏 logo 图标,title 内容都可以改变
- 点击左键移动鼠标,窗口会一起
移动
按钮功能设置
在TitleBar页面
public slots:
void minimizeClicked(); // 最小化按钮点击
void maximizeClicked(); // 最大化和窗口化按钮点击
void closeWindowClicked(); // 关闭按钮点击
void fixedWindowClicked(); // 固定按钮点击
signals:
void minimizeWindow(); // 最小化窗口 信号
void restoreWindow(); // 还原窗口 信号
void maximizeWindow(); // 最大化窗口 信号
void closeWindow(); // 关闭窗口 信号
void fixedWindow(); // 固定窗口 信号
void unFixedWindow(); // 取消固定窗口 信号
void TitleBar::minimizeClicked()
{
emit minimizeWindow();
}
void TitleBar::maximizeClicked()
{
isMaximized = !isMaximized;
if (isMaximized)
{
maximizeButton->setIcon(QIcon(":/res/chuangkouhua.svg").pixmap(iconSize, iconSize));
emit maximizeWindow();
}
else
{
maximizeButton->setIcon(QIcon(":/res/quanping.svg").pixmap(iconSize, iconSize));
emit restoreWindow();
}
}
void TitleBar::closeWindowClicked()
{
emit closeWindow();
}
void TitleBar::fixedWindowClicked()
{
isFixed = !isFixed;
if (isFixed)
{
fixedButton->setIcon(QIcon(":/res/relieve-full.svg").pixmap(iconSize, iconSize));
emit fixedWindow();
}
else
{
fixedButton->setIcon(QIcon(":/res/guding.svg").pixmap(iconSize, iconSize));
emit unFixedWindow();
}
}
在父页面 borderlessWidget 页面连接connect
// 设置connect
connect(titleBar, &TitleBar::closeWindow, this, &QWidget::close);
connect(titleBar, &TitleBar::minimizeWindow, this, &QWidget::showMinimized);
connect(titleBar, &TitleBar::maximizeWindow, this, &QWidget::showMaximized);
connect(titleBar, &TitleBar::restoreWindow, this, &QWidget::showNormal);
// 窗口始终在最上方显示
connect(titleBar, &TitleBar::fixedWindow, this, &BorderlessWidget::setWindowOnTop);
connect(titleBar, &TitleBar::unFixedWindow, this, &BorderlessWidget::unsetWindowOnTop);
按钮功能的设置中, 固定窗口于最上方有个小问题 点击按钮之后,窗口会闪烁,也尝试了其他方法,但是没有解决,可能要重写某一部分,有些麻烦。放在这以后有时间再优化(欢迎指点)。
双击标题栏窗口变化
这里直接使用了最大最小按钮的函数的实现
void TitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
maximizeClicked();
}
}
标题栏logo与title更换 // 这个比较简单
void TitleBar::setTitle(const QString &title)
{
if (!title.isEmpty())
{
windowTitle = title;
}
titleLabel->setText(windowTitle);
}
void TitleBar::setIcon(const QIcon &icon)
{
if (icon.isNull())
{
return;
}
iconLabel->setPixmap(icon.pixmap(16, 16));
}
移动窗口
移动窗口复杂一点, 我在写的时候为了避免与BorderlessWidget中窗口大小变化的函数搞混,故而在TitleBar中实现了该功能。
实现代码
在title.h中定义一个父窗口并且初始化. 切记要初始化parentWidget。如果窗口为全屏模式,则在鼠标变动时,需要将窗口正常化,并且尽可能保证相对位置不变。
// 父窗口
QWidget *parentWidget;
void TitleBar::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
isPressed = true;
m_startPoint = event->globalPos();
if (parentWidget)
{
m_windowPoint = parentWidget->pos();
}
}
}
void TitleBar::mouseMoveEvent(QMouseEvent *event)
{
if (isPressed && parentWidget)
{
if (!isMaximized)
{
parentWidget->move(m_windowPoint + event->globalPos() - m_startPoint);
}
else {
float width_ratio = static_cast<float>(event->globalPos().x()) / static_cast<float>(parentWidget->width());
maximizeClicked();
//
auto new_x = static_cast<float>(last_width * width_ratio);
int new_width = static_cast<int>(new_x);
int new_height = this->height() / 4;
m_windowPoint = event->globalPos() - QPoint(new_width, new_height);
}
}
}
void TitleBar::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event);
isPressed = false;
}
父窗口
父窗口需要实现的是拖动窗口边缘可以缩放窗口。
实现逻辑是在窗口的四周定义一个PADDING 当鼠标在这个边框内, 并且鼠标处在左键按压状态, 随着光标的移动, 窗口会按照相应的逻辑变化。直接看代码吧。
void BorderlessWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
isPressed = false;
if (dir != NONE) {
this->releaseMouse();
this->setCursor(QCursor(Qt::ArrowCursor));
dir = NONE;
}
}
}
void BorderlessWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton)
{
isPressed = true;
if (dir != NONE)
{
BorderlessWidget::mouseGrabber();
}
else
{
dragPosition = event->globalPos() - this->frameGeometry().topLeft();
}
}
}
void BorderlessWidget::mouseMoveEvent(QMouseEvent *event)
{
// 获取鼠标当前位置
QPoint gloPoint = event->globalPos();
// 获取鼠标在窗口的位置
QRect rect = this->rect();
QPoint tl = this->mapToGlobal(rect.topLeft());
QPoint br = this->mapToGlobal(rect.bottomRight());
if (!isPressed)
{
this->region(gloPoint);
}
else
{
if (dir != NONE) {
QRect rMove(tl, br);
switch (dir) {
case LEFT:
if (br.x() - gloPoint.x() <= this->minimumWidth()) {
rMove.setX(tl.x());
}
else {
rMove.setX(gloPoint.x());
}
break;
case RIGHT:
rMove.setWidth(gloPoint.x() - tl.x());
break;
case UP:
if (br.y() - gloPoint.y() <= this->minimumHeight()) {
rMove.setY(tl.y());
}
else {
rMove.setY(gloPoint.y());
}
break;
case DOWN:
rMove.setHeight(gloPoint.y() - tl.y());
break;
case LEFTTOP:
if (br.x() - gloPoint.x() <= this->minimumWidth()) {
rMove.setX(tl.x());
}
else {
rMove.setX(gloPoint.x());
}
if (br.y() - gloPoint.y() <= this->minimumHeight()) {
rMove.setY(tl.y());
}
else {
rMove.setY(gloPoint.y());
}
break;
case RIGHTTOP:
rMove.setWidth(gloPoint.x() - tl.x());
rMove.setY(gloPoint.y());
break;
case LEFTBOTTOM:
rMove.setX(gloPoint.x());
rMove.setHeight(gloPoint.y() - tl.y());
break;
case RIGHTBOTTOM:
rMove.setWidth(gloPoint.x() - tl.x());
rMove.setHeight(gloPoint.y() - tl.y());
break;
default:
break;
}
this->setGeometry(rMove);
}
}
QWidget::mouseMoveEvent(event);
}
void BorderlessWidget::changeEvent(QEvent *event)
{
if (event->type() == QEvent::WindowStateChange)
{
if (this->windowState() == Qt::WindowMaximized)
{
setWindowStyle(true);
event->ignore(); // 忽略事件
}
else
{
setWindowStyle(false);
event->ignore(); // 忽略事件
}
}
event->accept(); // 接受事件
}
bool BorderlessWidget::eventFilter(QObject *watched, QEvent *event)
{
if(isMaximized())
{
return QWidget::eventFilter(watched, event);
}
// 鼠标事件
switch (event->type())
{
case QEvent::MouseMove:
{
auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
if (mouseEvent)
{
mouseMoveEvent(mouseEvent);
}
break;
}
case QEvent::MouseButtonPress:
{
if(watched == this) {
auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
if (mouseEvent) {
mousePressEvent(mouseEvent);
}
}
break;
}
case QEvent::MouseButtonRelease:
{
if(isPressed) {
auto *mouseEvent = dynamic_cast<QMouseEvent *>(event);
if (mouseEvent) {
mouseReleaseEvent(mouseEvent);
}
}
break;
}
default:
break;
}
return QWidget::eventFilter(watched, event);
}
void BorderlessWidget::region(const QPoint &cursorGlobalPoint)
{
QRect rect = this->rect();
QPoint tl = this->mapToGlobal(rect.topLeft());
QPoint br = this->mapToGlobal(rect.bottomRight());
int x = cursorGlobalPoint.x();
int y = cursorGlobalPoint.y();
if(tl.x() + PADDING >= x && tl.x() <= x && tl.y() + PADDING >= y && tl.y() <= y)
{
this->dir = LEFTTOP;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
}
else if(br.x() - PADDING <= x && br.x() >= x && br.y() - PADDING <= y && br.y() >= y)
{
this->dir = RIGHTBOTTOM;
this->setCursor(QCursor(Qt::SizeFDiagCursor));
}
else if(tl.x() + PADDING >= x && tl.x() <= x && br.y() - PADDING <= y && br.y() >= y)
{
this->dir = LEFTBOTTOM;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
}
else if(br.x() - PADDING <= x && br.x() >= x && tl.y() + PADDING >= y && tl.y() <= y)
{
this->dir = RIGHTTOP;
this->setCursor(QCursor(Qt::SizeBDiagCursor));
}
else if(tl.x() + PADDING >= x && tl.x() <= x)
{
this->dir = LEFT;
this->setCursor(QCursor(Qt::SizeHorCursor));
}
else if(br.x() - PADDING <= x && br.x() >= x)
{
this->dir = RIGHT;
this->setCursor(QCursor(Qt::SizeHorCursor));
}
else if(tl.y() + PADDING >= y && tl.y() <= y)
{
this->dir = UP;
this->setCursor(QCursor(Qt::SizeVerCursor));
}
else if(br.y() - PADDING <= y && br.y() >= y)
{
this->dir = DOWN;
this->setCursor(QCursor(Qt::SizeVerCursor));
}
else
{
this->dir = NONE;
this->setCursor(QCursor(Qt::ArrowCursor));
}
}
void BorderlessWidget::setWindowStyle(bool isMaximized)
{
if(isMaximized)
{
this->layoutMain->setContentsMargins(0, 0, 0, 0);
this->layoutMain->setSpacing(0);
}
else
{
this->layoutMain->setContentsMargins(PADDING, PADDING, PADDING, PADDING);
this->layoutMain->setSpacing(0);
}
}

浙公网安备 33010602011771号