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中创建自定义无边框窗口的目标,包括自定义标题栏和窗口大小调整功能。这使得我们可以根据自己的需要创建独特和个性化的窗口界面。

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

六、源代码下载

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/49626.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java代码基础算法练习-计算握手次数-2024.07.27

任务描述&#xff1a; 有n(0<n<50)个同学聚会&#xff0c;如果见面时&#xff0c;每个人都要跟其他人握手1次&#xff0c;请计算一共握手多少次? 解决思路&#xff1a; 为了计算在一次聚会上 n 个人相互之间握手的总次数&#xff0c;我们可以采用组合数学的方法。给定 …

【QT】TCP

目录 核心API 示例&#xff1a;服务器和客户端信息互发 服务器代码实现 第一步&#xff1a;创建QTcpServer对象的实例 第二步&#xff1a;绑定信号槽&#xff0c;处理新的连接 第三步&#xff1a;绑定并监听端口号 客户端代码实现 第一步&#xff1a;创建socket对象的实…

financial是“财务”吗-《分析模式》漫谈14

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的Preface&#xff08;前言&#xff09;有这么一句&#xff1a; David Creager, Steve Shepherd, and their team at Citibank worked with me in developing t…

鱼哥好书分享活动第27期:看完这篇《云原生安全》了解云原生环境安全攻防实战技巧!

鱼哥好书分享活动第27期&#xff1a;看完这篇《云原生安全》了解云原生安全攻防实战技巧&#xff01; 主要内容&#xff1a;读者对象&#xff1a;本书目录&#xff1a;了解更多&#xff1a;赠书抽奖规则: 当前全球数字化的发展逐步进入深水区&#xff0c;云计算模式已经广泛应用…

免费【2024】springboot 超市在线销售系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

【MATLAB源码-第238期】基于simulink的三输出单端反激flyback仿真,通过PWM和PID控制能够得到稳定电压。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 概述 反激变换器是一种广泛应用于电源管理的拓扑结构&#xff0c;特别是在需要隔离输入和输出的应用中。它的工作原理是利用变压器的储能和释放能量来实现电压转换和隔离。该图展示了一个通过脉宽调制&#xff08;PWM&#…

7.24 补题

C 小w和大W的决斗 链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 题目描述 小w和大W为了比出谁更聪明。决定进行一场游戏。游戏内容如下: 两人轮流操作&#xff0c;小w先进行操作&#xff0c;每次操作可以选择下列两个其一: 选择数组中的一…

深度解析Linux-C——结构体(初始化,结构体数组,结构体大小,位段操作,联合体,内存对齐,C的预处理,宏和带参宏,条件编译)

目录 结构体的三种初始化 结构体的两种引用 结构体数组 结构体大小 结构体实现位段操作 联合体 内存对齐 C的预处理 带参宏 条件编译 结构体的三种初始化 定义如下结构体 struct student {char name[100]; int age; float height; } ; 1、定义变量时初始化 s…

tof系统标定流程步骤详解

1、tof标定概述 系统校准是一个减少ToF系统中系统误差影响的过程,如图1.1所示。本文件旨在介绍校准方法、设备和软件 1.1 系统误差 1.1.1 周期误差 谐波失真导致的相位(距离)相关误差。 1.1.2 固定相位模式噪声 由于解调信号的时延取决于可见像素位置以及VCSEL和传感器…

【Golang 面试基础题】每日 5 题(十)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/UWz06 &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 Golang 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏…

【机器学习】解开反向传播算法的奥秘

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 解开反向传播算法的奥秘反向传播算法的概述反向传播算法的数学推导1. 前向传播2…

Linux进程——程序地址空间详解

文章目录 程序地址空间地址空间与物理内存什么是程序地址空间管理程序地址空间虚拟地址与物理地址的映射页表的结构及其作用程序地址空间的作用 程序地址空间 我们之前学习内存的时候&#xff0c;有说内存的分布大概是这样的 其中堆由下而上&#xff0c;栈由上而下 除此之外&…

LeetCode:删除排序链表中的重复元素(C语言)

1、问题概述&#xff1a;给定一个已排序链表的头&#xff0c;删除重复元素&#xff0c;返回已排序的链表 2、示例 示例 1&#xff1a; 输入&#xff1a;head [1,1,2] 输出&#xff1a;[1,2] 示例 2&#xff1a; 输入&#xff1a;head [1,1,2,3,3] 输出&#xff1a;[1,2,3] 3…

2024年国际高校数学建模大赛(IMMCHE)问题A:金字塔石的运输成品文章分享(仅供学习)

2024 International Mathematics Molding Contest for Higher Education Problem A: Transportation of Pyramid Stones&#xff08;2024年国际高校数学建模大赛&#xff08;IMMCHE&#xff09;问题A&#xff1a;金字塔石的运输&#xff09; 古埃及金字塔石材运输优化模型研究…

2024最新Selenium面试题(附带答案),建议收藏备用

一.你在TestNG中使用了哪些注解&#xff1f; TestBeforeSuiteAfterSuiteBeforeTestAfterTestBeforeClassAfterClassBeforeMethodAfterMethod 二.如何从Excel中读取数据&#xff1f; FileInputStream fs new FileInputStream(“excel文件路径”); Workbook wb WorkbookFact…

探索智慧校园资产入库功能,构建高效校园资产管理体系

在智慧校园的资产管理框架下&#xff0c;资产入库功能作为资产生命周期管理的开端&#xff0c;扮演着至关重要的角色。这一功能确保了新购置或转入的资产能够迅速而准确地被记录在系统中&#xff0c;从而无缝融入日常的管理流程。当资产入库时&#xff0c;系统要求详细登记一系…

2024最新前端学习路线指南!

2024最新前端学习路线指南&#xff01; 如果你正在寻找一份全面的前端学习路线图&#xff0c;那么这份精心打造的学习大纲恰好符合您的需求。无论您是新手还是经验丰富的开发者&#xff0c;这份路线图都能够帮助您系统地掌握前端开发的关键知识点&#xff0c;并在实践中不断提…

<PLC><HMI><汇川>在汇川HMI画面中,如何为UI设置全局样式?

前言 汇川的HMI软件是使用了Qt来编写的,因此在汇川的HMI程序编写过程,是支持使用qt的样式来自定义部件样式的,即qss格式。 概述 汇川的软件本身提供三个系统的style样式,我们可以直接使用,但是,如果系统提供的样式不符合你的需求,那么你可以对其进行修改,或者自己新建…

修改linux服务器上的mariadb/mysql数据库的密码

文章目录 一、查看数据库的状态二、修改密码 可能我们在最初安装数据库时没有设置密码或者已经设置了但是又想修改另一个密码&#xff0c;可以这样操作来修改我们的密码。 以数据库 mariadb 为例。 一、查看数据库的状态 使用命令 systemctl is-active mariadb 查看当前数据库…

Github 2024-07-27开源项目日报 Top10

根据Github Trendings的统计,今日(2024-07-27统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量非开发语言项目2C++项目2C项目2TypeScript项目1JavaScript项目1Java项目1Python项目1C#项目1免费编程学习平台:freeCodeCamp.org 创建周期:33…