当前位置: 首页 > news >正文

QT自定义无边框窗口(可移动控制和窗口大小调整)

         QT是一个功能强大的跨平台开发框架,它提供了丰富的界面设计工具和组件。在界面开发中,QT窗口自带的标题栏无法满足我们的需求。我们就需要自定义无边框窗口,包括自定义标题栏和窗口大小调整功能。本文将介绍如何在QT中实现这些功能。

一、简述

         本文介绍了如何使用Qt框架创建一个无边框窗口,并提供了详细的源码,包括窗口样式设置、移动区域控制和窗口大小调整功能。用于设置窗口为无边框窗口。可为窗口添加自定义标题栏、边框和系统菜单按钮。

二、 设计思路             

        首先,在QT中,我们可以通过设置窗口属性为Qt::FramelessWindowHint来实现无边框窗口BaseWindow(由于系统窗口被设置为Qt::FramelessWindowHint会导致窗口不能被拖动,需要通过捕获鼠标移动事件从而实现窗口移动。)。

        然后,我们可以通过一个自定义的QWidget来扮演标题栏的角色BaseTitleBar。在这个自定义的QWidget中,我们可以添加一些控件,比如窗口标题,关闭按钮等。我们通过自定义的标题栏和边框样式来实现无边框窗口的外观。使用titleBar类来定义标题栏的样式,其中包括标题文本和关闭按钮。通过重写QWidget的mousePressEventmouseMoveEventmouseReleaseEvent函数,我们可以实现拖动窗口的功能。

        最后,自定义FramelessHelper一个辅助类,我们可以自定义窗口的行为,如设置窗口的可移动和可缩放属性。

三、效果 

四、核心代码  
1、头文件

BaseWindow.h 

#ifndef BASEWINDOW_H
#define BASEWINDOW_H#include <QWidget>
#include <QMainWindow>
#include "BaseTitleBar.h"class BaseWindow : public QWidget
{Q_OBJECTpublic:BaseWindow(QWidget *parent = 0);~BaseWindow();private:void initTitleBar();void paintEvent(QPaintEvent *event);void loadStyleSheet(const QString &sheetName);private slots:void onButtonMinClicked();void onButtonRestoreClicked();void onButtonMaxClicked();void onButtonCloseClicked();protected:BaseTitleBar* m_titleBar;};#endif // BASEWINDOW_H

 BaseTitleBar.h

#ifndef BASETITLEBAR_H
#define BASETITLEBAR_H#include <QWidget>
#include <QLabel>
#include <QPushButton>
#include <QTimer>enum ButtonType
{MIN_BUTTON = 0,			// 最小化和关闭按钮;MIN_MAX_BUTTON ,		// 最小化、最大化和关闭按钮;ONLY_CLOSE_BUTTON		// 只有关闭按钮;
};class BaseTitleBar : public QWidget
{Q_OBJECTpublic:BaseTitleBar(QWidget *parent = NULL);~BaseTitleBar();// 设置标题栏背景色;void setBackgroundColor(int r, int g, int b);// 设置标题栏图标;void setTitleIcon(QString filePath);// 设置标题内容;void setTitleContent(QString titleContent);// 设置标题栏长度;void setTitleWidth(int width);// 设置标题栏上按钮类型;void setButtonType(ButtonType buttonType);// 设置标题栏中的标题是否会滚动;具体可以看效果;void setTitleRoll();// 保存/获取 最大化前窗口的位置及大小;void saveRestoreInfo(const QPoint point, const QSize size);void getRestoreInfo(QPoint& point, QSize& size);private:void paintEvent(QPaintEvent *event);void mouseDoubleClickEvent(QMouseEvent *event);void mousePressEvent(QMouseEvent *event);void mouseMoveEvent(QMouseEvent *event);void mouseReleaseEvent(QMouseEvent *event);// 初始化控件;void initControl();// 信号槽的绑定;void initConnections();// 加载样式文件;void loadStyleSheet(const QString &sheetName);signals:// 按钮触发的信号;void signalButtonMinClicked();void signalButtonRestoreClicked();void signalButtonMaxClicked();void signalButtonCloseClicked();private slots:// 按钮触发的槽;void onButtonMinClicked();void onButtonRestoreClicked();void onButtonMaxClicked();void onButtonCloseClicked();void onRollTitle();private:QLabel* m_pIcon;					// 标题栏图标;QLabel* m_pTitleContent;			// 标题栏内容;QPushButton* m_pButtonMin;			// 最小化按钮;QPushButton* m_pButtonRestore;		// 最大化还原按钮;QPushButton* m_pButtonMax;			// 最大化按钮;QPushButton* m_pButtonClose;		// 关闭按钮;// 标题栏背景色;int m_colorR;int m_colorG;int m_colorB;// 最大化,最小化变量;QPoint m_restorePos;QSize m_restoreSize;// 移动窗口的变量;bool m_isPressed;QPoint m_startMovePos;// 标题栏跑马灯效果时钟;QTimer m_titleRollTimer;// 标题栏内容;QString m_titleContent;// 按钮类型;ButtonType m_buttonType;
};#endif // BASETITLEBAR_H

 framelesshelper.h

#ifndef FRAMELESSHELPER_H
#define FRAMELESSHELPER_H#include <QtGui>
#include <QRubberBand>
#include <QStylePainter>
#include <QStyleOptionFocusRect>class WidgetData;
/****** FramelessHelperPrivate* 存储界面对应的数据集合,以及是否可移动、可缩放属性
*****/
class FramelessHelperPrivate
{
public:QHash<QWidget*, WidgetData*> m_widgetDataHash;bool m_bWidgetMovable        : true;bool m_bWidgetResizable      : true;bool m_bRubberBandOnResize   : true;bool m_bRubberBandOnMove     : true;
};class FramelessHelper : public QObject
{Q_OBJECTpublic:explicit FramelessHelper(QObject *parent = 0);~FramelessHelper();// 激活窗体void activateOn(QWidget *topLevelWidget);// 移除窗体void removeFrom(QWidget *topLevelWidget);// 设置窗体移动void setWidgetMovable(bool movable);// 设置窗体缩放void setWidgetResizable(bool resizable);// 设置橡皮筋移动void setRubberBandOnMove(bool movable);// 设置橡皮筋缩放void setRubberBandOnResize(bool resizable);// 设置边框的宽度void setBorderWidth(uint width);// 设置标题栏高度void setTitleHeight(uint height);bool widgetResizable();bool widgetMovable();bool rubberBandOnMove();bool rubberBandOnResisze();uint borderWidth();uint titleHeight();protected:// 事件过滤,进行移动、缩放等virtual bool eventFilter(QObject *obj, QEvent *event);private:FramelessHelperPrivate *d;
};class LinuxRubberBand : public QRubberBand
{
public:LinuxRubberBand(Shape s, QWidget * p = 0 ): QRubberBand( s, p ){QPalette palette;palette.setBrush( QPalette::WindowText, QBrush(Qt::lightGray) );setPalette(palette);repaint();}protected:virtual void paintEvent( QPaintEvent * ){QStylePainter painter(this);QStyleOptionFocusRect option;option.initFrom(this);QPen pen;pen.setStyle(Qt::DashLine);pen.setWidth(1);pen.setColor(QColor(Qt::red));painter.setPen(pen);painter.drawControl(QStyle::CE_FocusFrame, option);}};/****** CursorPosCalculator* 计算鼠标是否位于左、上、右、下、左上角、左下角、右上角、右下角
*****/
class CursorPosCalculator
{
public:explicit CursorPosCalculator();void reset();void recalculate(const QPoint &globalMousePos, const QRect &frameRect);public:bool m_bOnEdges              : true;bool m_bOnLeftEdge           : true;bool m_bOnRightEdge          : true;bool m_bOnTopEdge            : true;bool m_bOnBottomEdge         : true;bool m_bOnTopLeftEdge        : true;bool m_bOnBottomLeftEdge     : true;bool m_bOnTopRightEdge       : true;bool m_bOnBottomRightEdge    : true;static int m_nBorderWidth;static int m_nTitleHeight;
};/****** WidgetData* 更新鼠标样式、移动窗体、缩放窗体
*****/
class WidgetData
{
public:explicit WidgetData(FramelessHelperPrivate *d, QWidget *pTopLevelWidget);~WidgetData();QWidget* widget();// 处理鼠标事件-划过、厉害、按下、释放、移动void handleWidgetEvent(QEvent *event);// 更新橡皮筋状态void updateRubberBandStatus();private:// 更新鼠标样式void updateCursorShape(const QPoint &gMousePos);// 重置窗体大小void resizeWidget(const QPoint &gMousePos);// 移动窗体void moveWidget(const QPoint &gMousePos);// 处理鼠标按下void handleMousePressEvent(QMouseEvent *event);// 处理鼠标释放void handleMouseReleaseEvent(QMouseEvent *event);// 处理鼠标移动void handleMouseMoveEvent(QMouseEvent *event);// 处理鼠标离开void handleLeaveEvent(QEvent *event);// 处理鼠标进入void handleHoverMoveEvent(QHoverEvent *event);private:FramelessHelperPrivate *d;LinuxRubberBand *m_pRubberBand;QWidget *m_pWidget;QPoint m_ptDragPos;CursorPosCalculator m_pressedMousePos;CursorPosCalculator m_moveMousePos;bool m_bLeftButtonPressed;bool m_bCursorShapeChanged;bool m_bLeftButtonTitlePressed;Qt::WindowFlags m_windowFlags;
};#endif // FRAMELESSHELPER_H
2、实现代码

BaseWindow.cpp 

#include "BaseWindow.h"
#include <QDesktopWidget>
#include <QApplication>
#include <QPainter>
#include <QFile>BaseWindow::BaseWindow(QWidget *parent): QWidget(parent)
{// FramelessWindowHint属性设置窗口去除边框;// WindowMinimizeButtonHint 属性设置在窗口最小化时,点击任务栏窗口可以显示出原窗口;this->setWindowFlags(Qt::FramelessWindowHint | Qt::WindowMinimizeButtonHint);// 设置窗口背景透明;setAttribute(Qt::WA_TranslucentBackground);// 初始化标题栏;initTitleBar();
}BaseWindow::~BaseWindow()
{}void BaseWindow::initTitleBar()
{m_titleBar = new BaseTitleBar(this);m_titleBar->move(0, 0);connect(m_titleBar, SIGNAL(signalButtonMinClicked()), this, SLOT(onButtonMinClicked()));connect(m_titleBar, SIGNAL(signalButtonRestoreClicked()), this, SLOT(onButtonRestoreClicked()));connect(m_titleBar, SIGNAL(signalButtonMaxClicked()), this, SLOT(onButtonMaxClicked()));connect(m_titleBar, SIGNAL(signalButtonCloseClicked()), this, SLOT(onButtonCloseClicked()));}void BaseWindow::paintEvent(QPaintEvent* event)
{//设置背景色;QPainter painter(this);QPainterPath pathBack;pathBack.setFillRule(Qt::WindingFill);pathBack.addRoundedRect(QRect(0, 0, this->width(), this->height()), 3, 3);painter.setRenderHint(QPainter::SmoothPixmapTransform, true);painter.fillPath(pathBack, QBrush(QColor(127, 127, 127)));return QWidget::paintEvent(event);
}void BaseWindow::loadStyleSheet(const QString &sheetName)
{QFile file(":/Resources/" + sheetName + ".css");file.open(QFile::ReadOnly);if (file.isOpen()){QString styleSheet = this->styleSheet();styleSheet += QLatin1String(file.readAll());this->setStyleSheet(styleSheet);}
}void BaseWindow::onButtonMinClicked()
{if (Qt::Tool == (windowFlags() & Qt::Tool)){hide();    //设置了Qt::Tool 如果调用showMinimized()则窗口就销毁了???}else{showMinimized();}
}void BaseWindow::onButtonRestoreClicked()
{QPoint windowPos;QSize windowSize;m_titleBar->getRestoreInfo(windowPos, windowSize);this->setGeometry(QRect(windowPos, windowSize));
}void BaseWindow::onButtonMaxClicked()
{m_titleBar->saveRestoreInfo(this->pos(), QSize(this->width(), this->height()));QRect desktopRect = QApplication::desktop()->availableGeometry();QRect FactRect = QRect(desktopRect.x() - 3, desktopRect.y() - 3, desktopRect.width() + 6, desktopRect.height() + 6);setGeometry(FactRect);
}void BaseWindow::onButtonCloseClicked()
{close();
}

BaseTitleBar.cpp

#include "BaseTitleBar.h"
#include <QHBoxLayout>
#include <QPainter>
#include <QFile>
#include <QMouseEvent>#define BUTTON_HEIGHT 30		// 按钮高度;
#define BUTTON_WIDTH 30			// 按钮宽度;
#define TITLE_HEIGHT 30			// 标题栏高度;BaseTitleBar::BaseTitleBar(QWidget *parent): QWidget(parent), m_colorR(153), m_colorG(153), m_colorB(153), m_isPressed(false), m_buttonType(MIN_MAX_BUTTON)
{// 初始化;initControl();initConnections();loadStyleSheet("MyTitle");
}BaseTitleBar::~BaseTitleBar()
{}// 初始化控件;
void BaseTitleBar::initControl()
{m_pIcon = new QLabel;m_pTitleContent = new QLabel;m_pButtonMin = new QPushButton;m_pButtonRestore = new QPushButton;m_pButtonMax = new QPushButton;m_pButtonClose = new QPushButton;m_pButtonMin->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));m_pButtonRestore->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));m_pButtonMax->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));m_pButtonClose->setFixedSize(QSize(BUTTON_WIDTH, BUTTON_HEIGHT));m_pTitleContent->setObjectName("TitleContent");m_pButtonMin->setObjectName("ButtonMin");m_pButtonRestore->setObjectName("ButtonRestore");m_pButtonMax->setObjectName("ButtonMax");m_pButtonClose->setObjectName("ButtonClose");QHBoxLayout* mylayout = new QHBoxLayout(this);mylayout->addWidget(m_pIcon);mylayout->addWidget(m_pTitleContent);mylayout->addWidget(m_pButtonMin);mylayout->addWidget(m_pButtonRestore);mylayout->addWidget(m_pButtonMax);mylayout->addWidget(m_pButtonClose);mylayout->setContentsMargins(5, 0, 0, 0);m_pTitleContent->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);this->setFixedHeight(TITLE_HEIGHT);this->setWindowFlags(Qt::FramelessWindowHint);
}// 信号槽的绑定;
void BaseTitleBar::initConnections()
{connect(m_pButtonMin, SIGNAL(clicked()), this, SLOT(onButtonMinClicked()));connect(m_pButtonRestore, SIGNAL(clicked()), this, SLOT(onButtonRestoreClicked()));connect(m_pButtonMax, SIGNAL(clicked()), this, SLOT(onButtonMaxClicked()));connect(m_pButtonClose, SIGNAL(clicked()), this, SLOT(onButtonCloseClicked()));
}// 设置标题栏背景色,在paintEvent事件中进行绘制标题栏背景色;
//在构造函数中给了默认值,可以外部设置颜色值改变标题栏背景色;
void BaseTitleBar::setBackgroundColor(int r, int g, int b)
{m_colorR = r;m_colorG = g;m_colorB = b;// 重新绘制(调用paintEvent事件);update();
}// 设置标题栏图标;
void BaseTitleBar::setTitleIcon(QString filePath)
{QPixmap titleIcon(filePath);m_pIcon->setPixmap(titleIcon.scaled(25 , 25));
}// 设置标题内容;
void BaseTitleBar::setTitleContent(QString titleContent)
{m_pTitleContent->setText(titleContent);m_titleContent = titleContent;
}// 设置标题栏长度;
void BaseTitleBar::setTitleWidth(int width)
{this->setFixedWidth(width);
}// 设置标题栏上按钮类型;
// 由于不同窗口标题栏上的按钮都不一样,所以可以自定义标题栏中的按钮;
// 这里提供了四个按钮,分别为最小化、还原、最大化、关闭按钮,如果需要其他按钮可自行添加设置;
void BaseTitleBar::setButtonType(ButtonType buttonType)
{m_buttonType = buttonType;switch (buttonType){case MIN_BUTTON:{m_pButtonRestore->setVisible(false);m_pButtonMax->setVisible(false);}break;case MIN_MAX_BUTTON:{m_pButtonRestore->setVisible(false);}break;case ONLY_CLOSE_BUTTON:{m_pButtonMin->setVisible(false);m_pButtonRestore->setVisible(false);m_pButtonMax->setVisible(false);}break;default:break;}
}// 设置标题栏中的标题是否会自动滚动,跑马灯的效果;
// 一般情况下标题栏中的标题内容是不滚动的,但是既然自定义就看自己需要嘛,想怎么设计就怎么搞O(∩_∩)O!
void BaseTitleBar::setTitleRoll()
{connect(&m_titleRollTimer, SIGNAL(timeout()), this, SLOT(onRollTitle()));m_titleRollTimer.start(200);
}// 保存窗口最大化前窗口的位置以及大小;
void BaseTitleBar::saveRestoreInfo(const QPoint point, const QSize size)
{m_restorePos = point;m_restoreSize = size;
}// 获取窗口最大化前窗口的位置以及大小;
void BaseTitleBar::getRestoreInfo(QPoint& point, QSize& size)
{point = m_restorePos;size = m_restoreSize;
}// 绘制标题栏背景色;
void BaseTitleBar::paintEvent(QPaintEvent *event)
{//设置背景色;QPainter painter(this);QPainterPath pathBack;pathBack.setFillRule(Qt::WindingFill);pathBack.addRoundedRect(QRect(0, 0, this->width(), this->height()), 3, 3);painter.setRenderHint(QPainter::SmoothPixmapTransform, true);painter.fillPath(pathBack, QBrush(QColor(m_colorR, m_colorG, m_colorB)));// 当窗口最大化或者还原后,窗口长度变了,标题栏的长度应当一起改变;if (this->width() != this->parentWidget()->width()){this->setFixedWidth(this->parentWidget()->width());}QWidget::paintEvent(event);
}// 双击响应事件,主要是实现双击标题栏进行最大化和最小化操作;
void BaseTitleBar::mouseDoubleClickEvent(QMouseEvent *event)
{// 只有存在最大化、还原按钮时双击才有效;if (m_buttonType == MIN_MAX_BUTTON){// 通过最大化按钮的状态判断当前窗口是处于最大化还是原始大小状态;// 或者通过单独设置变量来表示当前窗口状态;if (m_pButtonMax->isVisible()){onButtonMaxClicked();}else{onButtonRestoreClicked();}}	return QWidget::mouseDoubleClickEvent(event);
}// 以下通过mousePressEvent、mouseMoveEvent、mouseReleaseEvent三个事件实现了鼠标拖动标题栏移动窗口的效果;
void BaseTitleBar::mousePressEvent(QMouseEvent *event)
{return QWidget::mousePressEvent(event);if (m_buttonType == MIN_MAX_BUTTON){// 在窗口最大化时禁止拖动窗口;if (m_pButtonMax->isVisible()){m_isPressed = true;m_startMovePos = event->globalPos();}}else{m_isPressed = true;m_startMovePos = event->globalPos();}//	return QWidget::mousePressEvent(event);
}void BaseTitleBar::mouseMoveEvent(QMouseEvent *event)
{if (m_isPressed){QPoint movePoint = event->globalPos() - m_startMovePos;QPoint widgetPos = this->parentWidget()->pos();m_startMovePos = event->globalPos();this->parentWidget()->move(widgetPos.x() + movePoint.x(), widgetPos.y() + movePoint.y());}return QWidget::mouseMoveEvent(event);
}void BaseTitleBar::mouseReleaseEvent(QMouseEvent *event)
{m_isPressed = false;return QWidget::mouseReleaseEvent(event);
}// 加载本地样式文件;
// 可以将样式直接写在文件中,程序运行时直接加载进来;
void BaseTitleBar::loadStyleSheet(const QString &sheetName)
{QFile file(":/TitleBarRc/" + sheetName + ".css");file.open(QFile::ReadOnly);if (file.isOpen()){QString styleSheet = this->styleSheet();styleSheet += QLatin1String(file.readAll());this->setStyleSheet(styleSheet);}
}// 以下为按钮操作响应的槽;
void BaseTitleBar::onButtonMinClicked()
{emit signalButtonMinClicked();
}void BaseTitleBar::onButtonRestoreClicked()
{m_pButtonRestore->setVisible(false);m_pButtonMax->setVisible(true);emit signalButtonRestoreClicked();
}void BaseTitleBar::onButtonMaxClicked()
{m_pButtonMax->setVisible(false);m_pButtonRestore->setVisible(true);emit signalButtonMaxClicked();
}void BaseTitleBar::onButtonCloseClicked()
{emit signalButtonCloseClicked();
}// 该方法主要是让标题栏中的标题显示为滚动的效果;
void BaseTitleBar::onRollTitle()
{static int nPos = 0;QString titleContent = m_titleContent;// 当截取的位置比字符串长时,从头开始;if (nPos > titleContent.length())nPos = 0;m_pTitleContent->setText(titleContent.mid(nPos));nPos++;
}

 framelesshelper.cpp

#include "framelesshelper.h"int CursorPosCalculator::m_nBorderWidth = 5;
int CursorPosCalculator::m_nTitleHeight = 30;
/***** CursorPosCalculator *****/
CursorPosCalculator::CursorPosCalculator()
{reset();
}void CursorPosCalculator::reset()
{m_bOnEdges = false;m_bOnLeftEdge = false;m_bOnRightEdge = false;m_bOnTopEdge = false;m_bOnBottomEdge = false;m_bOnTopLeftEdge = false;m_bOnBottomLeftEdge = false;m_bOnTopRightEdge  = false;m_bOnBottomRightEdge = false;
}void CursorPosCalculator::recalculate(const QPoint &gMousePos, const QRect &frameRect)
{int globalMouseX = gMousePos.x();int globalMouseY = gMousePos.y();int frameX = frameRect.x();int frameY = frameRect.y();int frameWidth = frameRect.width();int frameHeight = frameRect.height();m_bOnLeftEdge = (globalMouseX >= frameX &&globalMouseX <= frameX + m_nBorderWidth );m_bOnRightEdge = (globalMouseX >= frameX + frameWidth - m_nBorderWidth &&globalMouseX <= frameX + frameWidth);m_bOnTopEdge = (globalMouseY >= frameY &&globalMouseY <= frameY + m_nBorderWidth );m_bOnBottomEdge = (globalMouseY >= frameY + frameHeight - m_nBorderWidth &&globalMouseY <= frameY + frameHeight);m_bOnTopLeftEdge = m_bOnTopEdge && m_bOnLeftEdge;m_bOnBottomLeftEdge = m_bOnBottomEdge && m_bOnLeftEdge;m_bOnTopRightEdge = m_bOnTopEdge && m_bOnRightEdge;m_bOnBottomRightEdge = m_bOnBottomEdge && m_bOnRightEdge;m_bOnEdges = m_bOnLeftEdge || m_bOnRightEdge || m_bOnTopEdge || m_bOnBottomEdge;
}/***** WidgetData *****/
WidgetData::WidgetData(FramelessHelperPrivate *_d, QWidget *pTopLevelWidget)
{d = _d;m_pWidget = pTopLevelWidget;m_bLeftButtonPressed = false;m_bCursorShapeChanged = false;m_bLeftButtonTitlePressed = false;m_pRubberBand = NULL;m_windowFlags = m_pWidget->windowFlags();m_pWidget->setMouseTracking(true);m_pWidget->setAttribute(Qt::WA_Hover, true);updateRubberBandStatus();
}WidgetData::~WidgetData()
{m_pWidget->setMouseTracking(false);m_pWidget->setWindowFlags(m_windowFlags);m_pWidget->setAttribute(Qt::WA_Hover, false);delete m_pRubberBand;m_pRubberBand = NULL;
}QWidget* WidgetData::widget()
{return m_pWidget;
}void WidgetData::handleWidgetEvent(QEvent *event)
{switch (event->type()){default:break;case QEvent::MouseButtonPress:handleMousePressEvent(static_cast<QMouseEvent*>(event));break;case QEvent::MouseButtonRelease:handleMouseReleaseEvent(static_cast<QMouseEvent*>(event));break;case QEvent::MouseMove:handleMouseMoveEvent(static_cast<QMouseEvent*>(event));break;case QEvent::Leave:handleLeaveEvent(static_cast<QMouseEvent*>(event));break;case QEvent::HoverMove:handleHoverMoveEvent(static_cast<QHoverEvent*>(event));break;}
}void WidgetData::updateRubberBandStatus()
{if (d->m_bRubberBandOnMove || d->m_bRubberBandOnResize){if (NULL == m_pRubberBand) {m_pRubberBand = new LinuxRubberBand(QRubberBand::Rectangle);}}else{delete m_pRubberBand;m_pRubberBand = NULL;}
}void WidgetData::updateCursorShape(const QPoint &gMousePos)
{if (m_pWidget->isFullScreen() || m_pWidget->isMaximized()){if (m_bCursorShapeChanged){m_pWidget->unsetCursor();}return;}m_moveMousePos.recalculate(gMousePos, m_pWidget->frameGeometry());if(m_moveMousePos.m_bOnTopLeftEdge || m_moveMousePos.m_bOnBottomRightEdge){m_pWidget->setCursor( Qt::SizeFDiagCursor );m_bCursorShapeChanged = true;}else if(m_moveMousePos.m_bOnTopRightEdge || m_moveMousePos.m_bOnBottomLeftEdge){m_pWidget->setCursor( Qt::SizeBDiagCursor );m_bCursorShapeChanged = true;}else if(m_moveMousePos.m_bOnLeftEdge || m_moveMousePos.m_bOnRightEdge){m_pWidget->setCursor( Qt::SizeHorCursor );m_bCursorShapeChanged = true;}else if(m_moveMousePos.m_bOnTopEdge || m_moveMousePos.m_bOnBottomEdge){m_pWidget->setCursor( Qt::SizeVerCursor );m_bCursorShapeChanged = true;}else{if (m_bCursorShapeChanged){m_pWidget->unsetCursor();m_bCursorShapeChanged = false;}}
}void WidgetData::resizeWidget(const QPoint &gMousePos)
{QRect origRect;if (d->m_bRubberBandOnResize)origRect = m_pRubberBand->frameGeometry();elseorigRect = m_pWidget->frameGeometry();int left = origRect.left();int top = origRect.top();int right = origRect.right();int bottom = origRect.bottom();origRect.getCoords(&left, &top, &right, &bottom);//int minWidth = m_pWidget->minimumWidth();//int minHeight = m_pWidget->minimumHeight();int minWidth = 40;int minHeight = 40;if (m_pressedMousePos.m_bOnTopLeftEdge){left = gMousePos.x();top = gMousePos.y();}else if (m_pressedMousePos.m_bOnBottomLeftEdge){left = gMousePos.x();bottom = gMousePos.y();}else if (m_pressedMousePos.m_bOnTopRightEdge){right = gMousePos.x();top = gMousePos.y();}else if (m_pressedMousePos.m_bOnBottomRightEdge){right = gMousePos.x();bottom = gMousePos.y();}else if (m_pressedMousePos.m_bOnLeftEdge){left = gMousePos.x();}else if (m_pressedMousePos.m_bOnRightEdge){right = gMousePos.x();}else if (m_pressedMousePos.m_bOnTopEdge){top = gMousePos.y();}else if (m_pressedMousePos.m_bOnBottomEdge){bottom = gMousePos.y();}QRect newRect(QPoint(left, top), QPoint(right, bottom));if (newRect.isValid()){if (minWidth > newRect.width()){if (left != origRect.left())newRect.setLeft(origRect.left());elsenewRect.setRight(origRect.right());}if (minHeight > newRect.height()){if (top != origRect.top())newRect.setTop(origRect.top());elsenewRect.setBottom(origRect.bottom());}if (d->m_bRubberBandOnResize){m_pRubberBand->setGeometry(newRect);}else{m_pWidget->setGeometry(newRect);}}
}void WidgetData::moveWidget(const QPoint& gMousePos)
{if (d->m_bRubberBandOnMove){m_pRubberBand->move(gMousePos - m_ptDragPos);}else{m_pWidget->move(gMousePos - m_ptDragPos);}
}void WidgetData::handleMousePressEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton){m_bLeftButtonPressed = true;m_bLeftButtonTitlePressed = event->pos().y() < m_moveMousePos.m_nTitleHeight;QRect frameRect = m_pWidget->frameGeometry();QRect moveRect(frameRect.x(), frameRect.y(), frameRect.width(), 30);m_pressedMousePos.recalculate(event->globalPos(), frameRect);m_ptDragPos = event->globalPos() - frameRect.topLeft();if (m_pressedMousePos.m_bOnEdges){if (d->m_bRubberBandOnResize){m_pRubberBand->setGeometry(frameRect);m_pRubberBand->show();}}else if (d->m_bRubberBandOnMove){if (moveRect.contains(event->globalPos())) {m_pRubberBand->setGeometry(frameRect);m_pRubberBand->show();}}}
}void WidgetData::handleMouseReleaseEvent(QMouseEvent *event)
{if (event->button() == Qt::LeftButton){m_bLeftButtonPressed = false;m_bLeftButtonTitlePressed = false;m_pressedMousePos.reset();if (m_pRubberBand && m_pRubberBand->isVisible()){m_pRubberBand->hide();m_pWidget->setGeometry(m_pRubberBand->geometry());}}
}void WidgetData::handleMouseMoveEvent(QMouseEvent *event)
{if (m_bLeftButtonPressed){if (d->m_bWidgetResizable && m_pressedMousePos.m_bOnEdges){resizeWidget(event->globalPos());}else if (d->m_bWidgetMovable && m_bLeftButtonTitlePressed){moveWidget(event->globalPos());}}else if (d->m_bWidgetResizable){updateCursorShape(event->globalPos());}
}void WidgetData::handleLeaveEvent(QEvent *event)
{Q_UNUSED(event)if (!m_bLeftButtonPressed){m_pWidget->unsetCursor();}
}void WidgetData::handleHoverMoveEvent(QHoverEvent *event)
{if (d->m_bWidgetResizable){updateCursorShape(m_pWidget->mapToGlobal(event->pos()));}
}/*****FramelessHelper*****/
FramelessHelper::FramelessHelper(QObject *parent): QObject(parent),d(new FramelessHelperPrivate())
{d->m_bWidgetMovable = true;d->m_bWidgetResizable = true;d->m_bRubberBandOnResize = false;d->m_bRubberBandOnMove = false;
}FramelessHelper::~FramelessHelper()
{QList<QWidget*> keys = d->m_widgetDataHash.keys();int size = keys.size();for (int i = 0; i < size; ++i) {delete d->m_widgetDataHash.take(keys[i]);}delete d;
}bool FramelessHelper::eventFilter(QObject *obj, QEvent *event)
{switch (event->type()){case QEvent::MouseMove:case QEvent::HoverMove:case QEvent::MouseButtonPress:case QEvent::MouseButtonRelease:case QEvent::Leave:{WidgetData *data = d->m_widgetDataHash.value(static_cast<QWidget*>(obj));if (data){data->handleWidgetEvent(event);return true;}}}return QObject::eventFilter(obj, event);
}void FramelessHelper::activateOn(QWidget *topLevelWidget)
{if (!d->m_widgetDataHash.contains(topLevelWidget)){WidgetData *data = new WidgetData(d, topLevelWidget);d->m_widgetDataHash.insert(topLevelWidget, data);topLevelWidget->installEventFilter(this);}
}void FramelessHelper::removeFrom(QWidget *topLevelWidget)
{WidgetData *data = d->m_widgetDataHash.take(topLevelWidget);if (data){topLevelWidget->removeEventFilter(this);delete data;}
}void FramelessHelper::setRubberBandOnMove(bool movable)
{d->m_bRubberBandOnMove = movable;QList<WidgetData*> list = d->m_widgetDataHash.values();foreach (WidgetData *data, list){data->updateRubberBandStatus();}
}void FramelessHelper::setWidgetMovable(bool movable)
{d->m_bWidgetMovable = movable;
}void FramelessHelper::setWidgetResizable(bool resizable)
{d->m_bWidgetResizable = resizable;
}void FramelessHelper::setRubberBandOnResize(bool resizable)
{d->m_bRubberBandOnResize = resizable;QList<WidgetData*> list = d->m_widgetDataHash.values();foreach (WidgetData *data, list){data->updateRubberBandStatus();}
}void FramelessHelper::setBorderWidth(uint width)
{if (width > 0){CursorPosCalculator::m_nBorderWidth = width;}
}void FramelessHelper::setTitleHeight(uint height)
{if (height > 0){CursorPosCalculator::m_nTitleHeight = height;}
}bool FramelessHelper::widgetMovable()
{return d->m_bWidgetMovable;
}bool FramelessHelper::widgetResizable()
{return d->m_bWidgetResizable;
}bool FramelessHelper::rubberBandOnMove()
{return d->m_bRubberBandOnMove;
}bool FramelessHelper::rubberBandOnResisze()
{return d->m_bRubberBandOnResize;
}uint FramelessHelper::borderWidth()
{return CursorPosCalculator::m_nBorderWidth;
}uint FramelessHelper::titleHeight()
{return CursorPosCalculator::m_nTitleHeight;
}

        通过以上代码,我们可以实现一个具有自定义标题栏、窗口大小调整和可移动、可缩放属性的无边框窗口。这段代码提供了一种简单而有效的方法来实现自定义外观和交互方式的无边框窗口。可以根据自己的需求进行修改和定制,以满足不同应用程序的需要。

五、使用示例

以下是一个简单的示例代码,演示了如何在Qt中使用此控件:

CustomMainWindow.h
#ifndef CUSTOMMAINWINDOW_H
#define CUSTOMMAINWINDOW_H#include <QWidget>#include "BaseWindow.h"
#include "framelesshelper.h"namespace Ui {
class CustomMainWindow;
}class CustomMainWindow : public BaseWindow
{Q_OBJECTpublic:explicit CustomMainWindow(QWidget *parent = 0);~CustomMainWindow();private:void initTitleBar();void framelesshelperInit();Ui::CustomMainWindow *ui;
};#endif // CUSTOMMAINWINDOW_H
CustomMainWindow.cpp
#include "CustomMainWindow.h"
#include "ui_CustomMainWindow.h"CustomMainWindow::CustomMainWindow(QWidget *parent) :BaseWindow(parent),ui(new Ui::CustomMainWindow)
{initTitleBar();framelesshelperInit();ui->setupUi(this);
}CustomMainWindow::~CustomMainWindow()
{delete ui;
}void CustomMainWindow::initTitleBar()
{m_titleBar->setBackgroundColor(71,76,78);m_titleBar->setTitleIcon(":/TitleBarRc/icon.png");m_titleBar->setTitleContent(QStringLiteral("这是一个可以滚动的标题!"));m_titleBar->setTitleRoll();m_titleBar->setButtonType(MIN_MAX_BUTTON);m_titleBar->setTitleWidth(this->width());
}void CustomMainWindow::framelesshelperInit()
{//this指的是要处理的窗体FramelessHelper *pHelper = new FramelessHelper(this);pHelper->activateOn(this);  //激活当前窗体pHelper->setWidgetMovable(true);  //设置窗体可移动pHelper->setWidgetResizable(true);  //设置窗体可缩放pHelper->setRubberBandOnMove(false);  //设置橡皮筋效果-可移动pHelper->setRubberBandOnResize(true);  //设置橡皮筋效果-可缩放
}

        现在,我们可以编译和运行该项目。当窗口显示出来时,您将会看到一个自定义的无边框窗口,包含自定义的标题栏和窗口大小调整按钮。您可以尝试拖动标题栏或点击窗口大小调整按钮来调整窗口的大小和位置。需要注意的是,使用自定义标题栏时需留够空间,不要被窗口内控件遮挡。

        通过以上步骤,我们成功地实现了在QT中创建自定义无边框窗口的目标,包括自定义标题栏和窗口大小调整功能。这使得我们可以根据自己的需要创建独特和个性化的窗口界面。

        谢谢您的阅读,希望本文能为您带来一些帮助和启发。如果您有任何问题或意见,请随时与我联系。祝您度过美好的一天!

六、源代码下载

相关文章:

QT自定义无边框窗口(可移动控制和窗口大小调整)

QT是一个功能强大的跨平台开发框架&#xff0c;它提供了丰富的界面设计工具和组件。在界面开发中&#xff0c;QT窗口自带的标题栏无法满足我们的需求。我们就需要自定义无边框窗口&#xff0c;包括自定义标题栏和窗口大小调整功能。本文将介绍如何在QT中实现这些功能。 一、简…...

Typora 【最新1.8.6】版本安装下载教程 (轻量级 Markdown 编辑器),图文步骤详解,免费领取(软件可激活使用)

文章目录 软件介绍软件下载安装步骤激活步骤 软件介绍 Typora 是一款专为 Markdown 爱好者设计的文本编辑器&#xff0c;它结合了简洁的界面设计与强大的 Markdown 渲染能力&#xff0c;为用户提供了一个流畅、高效的写作环境。以下是对 Typora 更详细的介绍&#xff1a; 核心特…...

RxJava 面试题及其答案

以下是一个全面的 RxJava 面试题及其答案&#xff0c;涵盖了 RxJava 的各个方面&#xff0c;包括基本概念、操作符、线程管理、错误处理、背压处理等&#xff1a; 基本概念 1. RxJava 的基本概念和原理是什么&#xff1f; 答案&#xff1a; RxJava 是一个用于响应式编程的库…...

【Rust】所有权OwnerShip

什么是所有权 rust使用由编译器检查的一些规则构成的所有权系统来管理内存。且这不会影响程序的运行效率。 所有权规则 rust中每一个每一个值都有一个owner。在同一时刻&#xff0c;只能有一个owner。当这个owner超过范围&#xff0c;则该值会被丢弃。 String类型 为什么需…...

qt总结--翻金币案例

完成了一个小项目的在qt5.15.2环境下的运行,并使用NSIS editNSIS打包完成.有待改进之处:增加计时功能,随机且能通关功能,过关后选择下一关功能.打包后仅仅有安装包有图标 安装后应用图标并未改变 在qt .pro中有待改进对qt的基本操作和帮助文档有了基本的认识.对C制作小游戏有了…...

最清楚的 BIO、NIO、AIO 详解!

一、什么是 I/O&#xff1f; I/O 描述了计算机系统与外部设备&#xff08;磁盘&#xff09;之间通信的过程。 为了保证操作系统的稳定性和安全性&#xff0c;一个进程的地址空间划分为 用户空间&#xff08;User space&#xff09; 和 内核空间&#xff08;Kernel space &…...

八股文学习第二天| HTTP请求报文和响应报文是怎样的,有哪些常见的字段?,HTTP有哪些请求方式?,GET请求和POST请求的区别?

1、HTTP请求报文和响应报文是怎样的&#xff0c;有哪些常见的字段&#xff1f; 答&#xff1a; HTTP报文分为请求报文和响应报文。 &#xff08;1&#xff09; 请求报文 请求报文主要由请求行、请求头、空行、请求体构成。 请求行包括如下字段&#xff1a; 方法&#xff08…...

C++初阶学习第四弹——类与对象(中)

目录 一. 类的默认成员函数 二.六种默认成员函数 1、构造函数 1.1 构造函数的作用 1.2 特性 1.3 默认构造函数 2、析构函数 2.1 析构函数的作用 2.2 析构函数的用法 3、拷贝构造函数 3.1 拷贝构造函数的作用 3.2 特征 3.3 默认拷贝构造函数 三.总结 类与对象&…...

【计算机网络】期末实验答辩

注意事项&#xff1a; 1&#xff09;每位同学要在下面做过的实验列表中选取三个实验进行答辩准备&#xff0c;并将自己的姓名&#xff0c;学号以及三个实验序号填入共享文档"1&#xff08;2&#xff09;班答辩名单"中。 2&#xff09;在答辩当日每位同学由老师在表…...

一步步教你学会如何安装VMare虚拟机(流程参考图)

前言&#xff1a;一步步教你安装VMare虚拟机&#xff08;此版本为17.5。2版本&#xff09;。 1、安装 2、确认协议 3、选择位置存放 4、选择第二个 5、都不选。 6、都选提供便捷操作 7、点击许可证&#xff0c;将密钥输入&#xff08;可以在网络寻找自己版本的密钥&#xff…...

WebGoC题解(14) 151.(2017dloi小乙)第5题 巧克力甜度(sweet)

题目描述 妈妈买了n颗甜度不同的巧克力&#xff0c;规定小C只能吃最大甜度之和是S。 例如&#xff1a;有5颗巧克力&#xff0c;s6&#xff0c;每个的甜度分别为&#xff1a; 4 2 3 1 1&#xff0c;那么小C最多可以吃3颗。 请问你能帮小C计算一下最多能吃多少颗巧克力吗? 输入格…...

深入探索PHP框架:Symfony框架全面解析

1. 引言 在现代Web开发领域&#xff0c;PHP作为一种广泛使用的服务器端脚本语言&#xff0c;其框架的选择对于项目的成功至关重要。PHP框架不仅能够提高开发效率&#xff0c;还能确保代码的质量和可维护性。本文将深入探讨Symfony框架&#xff0c;这是一个功能强大且灵活的PHP…...

内卷的利与弊

“内卷”原指一类文化模式达到了某种最终的形态以后&#xff0c;既没有办法稳定下来&#xff0c;也没有办法转变为新的形态&#xff0c;而只能不断地在内部变得更加复杂的现象。经网络流传&#xff0c;很多大学生用其来指代非理性的内部竞争或“被自愿”竞争。现指同行间竞相付…...

用Java手写jvm之实现查找class

写在前面 完成类加载器加载class的三阶段&#xff0c;加载&#xff0c;解析&#xff0c;初始化中的加载&#x1f600;&#x1f600;&#x1f600; 源码 。 jvm想要运行class&#xff0c;是根据类全限定名称来从特定的位置基于类加载器来查找的&#xff0c;分别如下&#xff1a;…...

【React】组件:全面解析现代前端开发的基石

文章目录 一、什么是组件&#xff1f;二、组件的类型三、组件的生命周期四、状态管理五、属性传递六、组合与继承七、最佳实践 在现代前端开发中&#xff0c;React 已成为开发者构建用户界面的首选框架之一。React 的强大之处在于其组件化设计&#xff0c;允许开发者将 UI 拆分…...

java学习--包装类

包装类 Boolean的关系图 Character关系图 其他关系图 包装类和基本数据转换 Debug进入之后可以看到底层代码如下 例题&#xff1a; 三元运算符是一个整体返回的数的类型看其中所含类型最高的那个是谁就会转成哪个 想要掌握这个这个知识&#xff0c;就要多看源码&#xff0c;直接…...

Python Django功能强大的扩展库之channels使用详解

概要 随着实时 web 应用程序的兴起,传统的同步 web 框架已经无法满足高并发和实时通信的需求。Django Channels 是 Django 的一个扩展,旨在将 Django 从一个同步 HTTP 框架转变为一个支持 WebSockets、HTTP2 和其他协议的异步框架。它不仅能够处理传统的 HTTP 请求,还可以处…...

推荐3款将相片变为动漫风格的免费AI工具推荐

toonme ToonMe是一款功能强大的在线和移动端应用&#xff0c;专门用于将照片转换成卡通风格图像。该工具利用先进的AI技术&#xff0c;能够快速识别照片中的面部特征&#xff0c;并进行智能处理&#xff0c;生成高清晰度的卡通肖像。 功能特点 ToonMe通过其内置的人工智能算法…...

【职业学习】高效工作法

文章目录 01 时间拳击02 非同步沟通03 批量处理04. 80/20法则05. 一次只做一件事 01 时间拳击 时间拳击&#xff08;Time Boxing&#xff09;核心是给每项任务创造一个时间限制&#xff0c;然后在固定的时间段内专注地完成这个任务。 不同于传统的待办事项清单&#xff1a;8点…...

【iOS】Tagged Pointer

目录 前言什么是Tagged Pointer&#xff1f;引入Tagged Pointer技术之前引入Tagged Pointer之后总结 Tagged Pointer原理&#xff08;TagData分析&#xff09;关闭数据混淆MacOS分析NSNumberNSString iOS分析 判断Tagged PointerTagged Pointer应用Tagged Pointer 注意点 Tagge…...

Mysql explain 优化解析

explain 解释 select_type 效率对比 MySQL 中 EXPLAIN 语句的 select_type 列描述了查询的类型,不同的 select_type 类型在效率上会有所差异。下面我们来比较一下各种 select_type 的效率: SIMPLE: 这是最简单的查询类型,表示查询不包含子查询或 UNION 操作。 这种查询通常是…...

wget下载github文件得到html文件

从github/gitee下载源文件&#xff0c;本来是22M下载下来只有11k 原因&#xff1a; Github会提供html页面&#xff0c;包括指定的文件、上下文与相关操作。通过wget或者curl下载时&#xff0c;会下载该页面 解决方式&#xff1a; github点击Code一栏的raw按钮&#xff0c;获得源…...

【es】elasticsearch 自定义排序-按关键字位置排序

一 背景 要求es查询的结果按关键字位置排序&#xff0c;位置越靠前优先级越高。 es版本7.14.0&#xff0c;项目是thrift&#xff0c;也可以平替springboot&#xff0c;使用easyes连接es。 二 easyes使用 配easyes按官方文档就差不多了 排序 | Easy-Es 主要的一个问题是easy…...

堆的相关知识点

目录 大小堆 堆的实现 堆的创建 堆的销毁 交换 向上调整 向下调整 弹出首个元素 取出首个元素 判空 堆插入 大小堆 大堆&#xff1a;最上面的数字是最小的&#xff0c;越往下越大 小堆&#xff1a;最上面的数字是最大的&#xff0c;越往下越小 堆的复杂程度&#…...

【Sass】常用全局sass高级函数,可使用原子化CSS减轻代码量,方便快速开发

文章目录 前言一、安装二、样式custom.scssflex.scsscolor.scssmargin-padding.scssorther 总结 前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 针对style的预编译器为scss 转载自git前端知识库 原博主是B站up程序员郑清&#xff0c;可以看他的v3教程…...

MYSQL 第四次作业

任务要求&#xff1a; 具体操作&#xff1a; 新建数据库&#xff1a; mysql> CREATE DATABASE mydb15_indexstu; Query OK, 1 row affected (0.01 sec) mysql> USE mydb15_indexstu; Database changed 新建表&#xff1a; mysql> CREATE TABLE student( ->…...

depcheck 前端依赖检查

介绍 depcheck 是一款用于检测项目中 未使用依赖项 的工具。 depcheck 通过扫描项目文件&#xff0c;帮助你找出未被引用的依赖&#xff0c;从而优化项目。 优势&#xff1a; 简单易用: 仅需几个简单的命令&#xff0c;就能够扫描并列出未使用的依赖项&#xff0c;让你快速了…...

Qt/C++音视频开发79-采集websocket视频流/打开ws开头的地址/音视频同步/保存到MP4文件/视频回放

一、前言 随着音视频的爆发式的增长,各种推拉流应用场景应运而生,基本上都要求各个端都能查看实时视频流,比如PC端、手机端、网页端,在网页端用websocket来接收并解码实时视频流显示,是一个非常常规的场景,单纯的http-flv模式受限于最大6个通道同时显示,一般会选择ws-f…...

网络安全等级保护制度1.0与2.0的演进与变革

等保1.0概述 等保1.0是我国在网络安全领域迈出的重要一步&#xff0c;它于2008年正式发布。该版本的等保制度以《信息安全技术 信息系统安全等级保护基本要求》为核心标准&#xff0c;主要聚焦于信息系统的物理安全、网络安全、主机安全、应用安全和数据安全等方面的基础防护。…...

多线程优化API请求:CountDownLatch与PriorityBlockingQueue的应用

目录 前言 CountDownLatch是什么&#xff1f; PriorityBlockingQueue是什么&#xff1f; 场景描述 解决方案 定义统一工厂制造类 定义制造厂 定义客户请求实现 定义控制器 定义启动类 结果呈现 启动项目 请求制造操作 总结 前言 写这篇文章的缘由是因为之前在面…...