├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README_ch.md ├── Sample ├── .gitignore ├── CMakeLists.txt ├── DialogSample.cpp ├── DialogSample.h ├── MainWindowSample.cpp ├── MainWindowSample.h ├── QtFramelessWindowSample.qrc ├── Resources │ ├── DialogSample.css │ ├── close.svg │ ├── close_h.svg │ └── main.css └── main.cpp ├── frameless.gif └── include └── FramelessWindow.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | build/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.16) 2 | 3 | project(Qt-FramelessWindow VERSION 0.2 LANGUAGES CXX) 4 | 5 | add_subdirectory(Sample) 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 winsoft666 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [>>> 中文版](README_ch.md) 2 | 3 | # 1. FramelessWindow 4 | When I started using Qt as UI library, I found that Qt didn't provide a perfect borderless window solution to support the following functions, and I have to implement these functions by myself: 5 | 6 | 1. Support to change the position and size of the window by dragging the mouse; ✅ 7 | 2. Support double-click the title bar to maximize/restore the window; ✅ 8 | 3. Support `Windows Areo Snap` feature; 9 | 4. Support system shadow; 10 | 5. Support dragging across screens that have different DPI; ✅ 11 | 6. Automatically adapt to changes in resolution and DPI; ✅ 12 | 7. Automatically near to screen edge; ✅ 13 | 14 | # 2. Implementation scheme comparison 15 | There are two schemes for implementing borderless windows in Qt. 16 | 17 | ## 2.1 Scheme 1 18 | By overriding `nativeEvent` function to hook windows message(such as 'WM_NCHITTEST`), the general steps are as follows: 19 | 1. Set the `WS_THICKFRAME | WS_CAPTION` property to restore the window border and title, so that the window can receive the` WM_NCHITTEST` message. 20 | 21 | 2. Then remove the border and title in the `WM_NCCALCSIZE` message processing. 22 | 23 | 3. Set the mouse behavior (`HTLEFT`,` HTRIGHT`, etc.) by judging the mouse position in the `WM_NCHITTEST` message processing. 24 | 25 | The advantage of this solution is that it can support the `Windows Areo Snap` and `System Shodow` feature, but it is very complicated for message processing and needs to be compatible with various versions of Qt. At present, I have not found a solution to achieve a perfect borderless through this solution. 26 | 27 | As far as I know, the following open source projects are implemented in this way, but there are some problems, such as not supporting drag across screens that have different DPI screens, unable to adapt to resolution and DPI changes, and `WM_NCHITTEST` sometimes not responding, etc. In addition, after setting the background transparency property (such as `Qt :: WA_TranslucentBackground`), the system shadow feature will also disappear. 28 | 29 | - qtdevs/FramelessHelper: [https://github.com/qtdevs/FramelessHelper](https://github.com/qtdevs/FramelessHelper) 30 | - wangwenx190/framelesshelper: [https://github.com/wangwenx190/framelesshelper](https://github.com/wangwenx190/framelesshelper) 31 | 32 | ## 2.2 Scheme 2 33 | This solution not hook windows `WM_NCHITTEST`,`WM_NCCALCSIZE` messages, nor does it change the window style, and is implemented by pure Qt. 34 | 35 | By setting MouseTracking for each Widget, make sure each Widget can respond to mouse events (mouseMoveEvent, mousePressEvent, mouseReleaseEvent, etc.), and then determine the mouse position in these events to set the shape and behavior of the mouse. 36 | 37 | Although the logic for judging the position of the mouse is cumbersome in this way, the compatibility is better and pure, and it does not need to process various messages of Windows. 38 | 39 | 👉 **This project is implemented in this way, and can support all the features of the borderless form except "Windows Areo Snap" and "System Shadow" features mentioned above.** 40 | 41 | 42 | # Screenshot On Windows 43 | ![screenshot on windows](frameless.gif) 44 | 45 | # 3. Sponsor 46 | Thank you for using this project. It would be a great pleasure for me if this project can be of help to you. 47 | 48 | **You can go to my Github [homepage](https://github.com/winsoft666) to make a donation.** -------------------------------------------------------------------------------- /README_ch.md: -------------------------------------------------------------------------------- 1 | [>>> Enginsh Version](README.md) 2 | 3 | # 1. FramelessWindow介绍 4 | 当我开始用Qt作为界面库时,我发现Qt没有提供一个完美的无边框窗体解决方案来支持如下功能,需要自己来实现这些功能: 5 | 1. 支持通过使用鼠标拖拽来改变窗体位置和大小;✅ 6 | 2. 支持双击标题栏最大化窗体和还原窗体;✅ 7 | 3. 支持`Windows Areo Snap`特性; 8 | 4. 支持系统阴影; 9 | 5. 支持跨不同DPI的屏幕拖拽;✅ 10 | 6. 适应分辨率和DPI改变;✅ 11 | 7. 自动贴边;✅ 12 | 13 | # 2. 实现方案比较 14 | 15 | 在Qt中实现无边框窗体有2种方案: 16 | 17 | ## 2.1 方案一 18 | 通过重载`nativeEvent`函数拦截Windows消息(如`WM_NCHITTEST`)来实现,大致步骤如下: 19 | 1. 给窗体设置`WS_THICKFRAME | WS_CAPTION`属性从而还原窗体边框和标题栏,这样窗体就可以接收到`WM_NCHITTEST`消息。 20 | 2. 在`WM_NCCALCSIZE`消息处理中再移除边框和标题栏。 21 | 3. 在`WM_NCHITTEST`消息处理中通过判断鼠标位置来设置鼠标行为(`HTLEFT`, `HTRIGHT`等)。 22 | 23 | 这种方案的优点是可以支持`Windows Areo Snap`和`系统阴影`的特性,但是针对消息处理起来很复杂而且需要兼容Qt的各个版本,目前我还没有找到一个通过这种方案来完美实现无边框的解决方案。 24 | 25 | 据我所知,有如下的开源项目是通过这种方式来实现的,但都有些许问题,如不支持跨不同DPI屏幕拖拽、不能适应分辨率和DPI改变、`WM_NCHITTEST`有时无响应等。另外,在设置了背景透明属性之后(如`Qt::WA_TranslucentBackground`),系统阴影特性也将消失。 26 | 27 | - qtdevs/FramelessHelper: [https://github.com/qtdevs/FramelessHelper](https://github.com/qtdevs/FramelessHelper) 28 | - wangwenx190/framelesshelper: [https://github.com/wangwenx190/framelesshelper](https://github.com/wangwenx190/framelesshelper) 29 | 30 | ## 2.2 方案二 31 | 32 | 这种方案不Hook windows `WM_NCHITTEST`、`WM_NCCALCSIZE`消息,也不改变窗体样式,通过纯Qt方式实现。通过对每个Widget设置MouseTracking,来使每个Widget都可以响应鼠标事件(mouseMoveEvent、mousePressEvent、mouseReleaseEvent等),然后这些事件中判断鼠标位置来设置鼠标的形状和行为。 33 | 34 | 这种方式虽然对鼠标位置的判断逻辑比较繁琐,但兼容性较好,较纯粹,不需要处理Windows的各个消息。 35 | 36 | 👉 **本项目就是通过这种方式来实现的,可以支持上述除`“Windows Areo Snap”和“系统阴影”`特性之外的所有无边框窗体的特性**。 37 | 38 | # Screenshot On Windows 39 | ![screenshot on windows](frameless.gif) 40 | 41 | # 3. 赞助 42 | 43 | 感谢您能使用本项目,如果这个项目能对您产生帮助,对我而言也是一件非常开心的事情。 44 | 45 | **可以前往我的 Github [主页](https://github.com/winsoft666) 进行赞助。** -------------------------------------------------------------------------------- /Sample/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | CMakeLists.txt.user* 41 | 42 | # xemacs temporary files 43 | *.flc 44 | 45 | # Vim temporary files 46 | .*.swp 47 | 48 | # Visual Studio generated files 49 | *.ib_pdb_index 50 | *.idb 51 | *.ilk 52 | *.pdb 53 | *.sln 54 | *.suo 55 | *.vcproj 56 | *vcproj.*.*.user 57 | *.ncb 58 | *.sdf 59 | *.opensdf 60 | *.vcxproj 61 | *vcxproj.* 62 | 63 | # MinGW generated files 64 | *.Debug 65 | *.Release 66 | 67 | # Python byte code 68 | *.pyc 69 | 70 | # Binaries 71 | # -------- 72 | *.dll 73 | *.exe 74 | 75 | -------------------------------------------------------------------------------- /Sample/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(CMAKE_AUTOUIC ON) 2 | set(CMAKE_AUTOMOC ON) 3 | set(CMAKE_AUTORCC ON) 4 | 5 | set(CMAKE_CXX_STANDARD 17) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | 8 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets) 9 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets) 10 | 11 | set(PROJECT_SOURCES 12 | ../include/FramelessWindow.hpp 13 | main.cpp 14 | DialogSample.h 15 | DialogSample.cpp 16 | MainWindowSample.h 17 | MainWindowSample.cpp 18 | QtFramelessWindowSample.qrc 19 | ) 20 | 21 | if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) 22 | qt_add_executable(Sample 23 | MANUAL_FINALIZATION 24 | ${PROJECT_SOURCES} 25 | ) 26 | 27 | set_target_properties(Sample PROPERTIES 28 | VS_DEBUGGER_ENVIRONMENT "PATH=${Qt6_DIR}/../../../bin;%PATH%" 29 | ) 30 | # Define target properties for Android with Qt 6 as: 31 | # set_property(TARGET Sample APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR 32 | # ${CMAKE_CURRENT_SOURCE_DIR}/android) 33 | # For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation 34 | else() 35 | if(ANDROID) 36 | add_library(Sample SHARED ${PROJECT_SOURCES}) 37 | # Define properties for Android with Qt 5 after find_package() calls as: 38 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 39 | else() 40 | add_executable(Sample ${PROJECT_SOURCES}) 41 | 42 | set_target_properties(Sample PROPERTIES 43 | VS_DEBUGGER_ENVIRONMENT "PATH=${Qt5_DIR}/../../../bin;%PATH%" 44 | ) 45 | endif() 46 | endif() 47 | 48 | target_link_libraries(Sample PRIVATE Qt${QT_VERSION_MAJOR}::Widgets) 49 | 50 | set_target_properties(Sample PROPERTIES 51 | MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com 52 | MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION} 53 | MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR} 54 | MACOSX_BUNDLE TRUE 55 | WIN32_EXECUTABLE TRUE 56 | ) 57 | 58 | install(TARGETS Sample 59 | BUNDLE DESTINATION . 60 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) 61 | 62 | if(QT_VERSION_MAJOR EQUAL 6) 63 | qt_finalize_executable(Sample) 64 | endif() 65 | -------------------------------------------------------------------------------- /Sample/DialogSample.cpp: -------------------------------------------------------------------------------- 1 | #include "DialogSample.h" 2 | #include 3 | 4 | namespace { 5 | void loadStyleSheetFile(const QString& sheetName, QWidget* widget) { 6 | if (widget) { 7 | QString qss; 8 | QFile qssFile(sheetName); 9 | qssFile.open(QFile::ReadOnly); 10 | if (qssFile.isOpen()) { 11 | qss = QString::fromUtf8(qssFile.readAll()); 12 | widget->setStyleSheet(qss); 13 | qssFile.close(); 14 | } 15 | } 16 | } 17 | } 18 | 19 | DialogSample::DialogSample(QWidget* parent /*= nullptr*/) 20 | : FramelessWindow(true, parent, true), shadow_(nullptr) { 21 | setAttribute(Qt::WA_DeleteOnClose, true); 22 | setupUi(); 23 | setWindowTitle("Dialog Sample"); 24 | 25 | setTitlebar({centralWidget_, labelTitle_}); 26 | loadStyleSheetFile(":/QtFramelessWindowSample/Resources/DialogSample.css", this); 27 | 28 | setShadowEnable(true); 29 | 30 | connect(pushButtonClose_, &QPushButton::clicked, [this]() { close(); }); 31 | } 32 | 33 | DialogSample::~DialogSample() {} 34 | 35 | void DialogSample::setupUi() { 36 | this->setObjectName("SettingWnd"); 37 | 38 | centralWidget_ = new QWidget(); 39 | centralWidget_->setObjectName("centralWidget"); 40 | 41 | pushButtonClose_ = new QPushButton(); 42 | pushButtonClose_->setObjectName("pushButtonClose"); 43 | pushButtonClose_->setFixedSize(30, 30); 44 | pushButtonClose_->setCursor(QCursor(Qt::PointingHandCursor)); 45 | 46 | labelSaveDir_ = new QLabel(); 47 | labelSaveDir_->setObjectName("labelWallpaperSaveDir"); 48 | labelSaveDir_->setText("Directory: "); 49 | 50 | pushButtonSaveDir_ = new QPushButton(); 51 | pushButtonSaveDir_->setObjectName("pushButtonWallpaperSaveDir"); 52 | pushButtonSaveDir_->setText("C:\\Users\\JEFFERY\\AppData\\Local"); 53 | pushButtonSaveDir_->setCursor(QCursor(Qt::PointingHandCursor)); 54 | pushButtonSaveDir_->setToolTip(pushButtonSaveDir_->text()); 55 | 56 | pushButtonChangeSaveDir_ = new QPushButton(); 57 | pushButtonChangeSaveDir_->setObjectName("pushButtonChangeWallpaperSaveDir"); 58 | pushButtonChangeSaveDir_->setText("Update Directory"); 59 | pushButtonChangeSaveDir_->setFixedSize(150, 35); 60 | pushButtonChangeSaveDir_->setCursor(QCursor(Qt::PointingHandCursor)); 61 | 62 | labelTitle_ = new QLabel(); 63 | labelTitle_->setObjectName("labelTitle"); 64 | labelTitle_->setText("Dialog Sample"); 65 | 66 | QHBoxLayout* hlTitle = new QHBoxLayout(); 67 | hlTitle->setContentsMargins(0, 0, 0, 0); 68 | hlTitle->setSpacing(0); 69 | hlTitle->addWidget(labelTitle_); 70 | hlTitle->addStretch(); 71 | hlTitle->addWidget(pushButtonClose_); 72 | 73 | QHBoxLayout* hlSD = new QHBoxLayout(); 74 | hlSD->setContentsMargins(0, 0, 0, 0); 75 | hlSD->setSpacing(0); 76 | hlSD->addWidget(labelSaveDir_); 77 | hlSD->addWidget(pushButtonSaveDir_); 78 | hlSD->addStretch(); 79 | 80 | QVBoxLayout* vLayout = new QVBoxLayout(); 81 | vLayout->setSpacing(20); 82 | vLayout->setContentsMargins(20, 0, 10, 0); 83 | vLayout->addLayout(hlTitle); 84 | vLayout->addLayout(hlSD); 85 | vLayout->addWidget(pushButtonChangeSaveDir_); 86 | vLayout->addStretch(); 87 | 88 | centralWidget_->setLayout(vLayout); 89 | 90 | QHBoxLayout* hMain = new QHBoxLayout(); 91 | hMain->setContentsMargins(0, 0, 0, 0); 92 | hMain->addWidget(centralWidget_); 93 | this->setLayout(hMain); 94 | 95 | this->setContentsMargins(25, 25, 25, 25); 96 | 97 | this->setMinimumSize(450, 350); 98 | } 99 | 100 | void DialogSample::setShadowEnable(bool enable) { 101 | if (!shadow_) { 102 | shadow_ = new QGraphicsDropShadowEffect(); 103 | shadow_->setColor(QColor(2, 122, 255, 255)); 104 | shadow_->setBlurRadius(20); 105 | shadow_->setOffset(0.0); 106 | } 107 | 108 | centralWidget_->setGraphicsEffect(enable ? shadow_ : nullptr); 109 | } 110 | -------------------------------------------------------------------------------- /Sample/DialogSample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../include/FramelessWindow.hpp" 5 | 6 | class QGraphicsDropShadowEffect; 7 | 8 | class DialogSample : public FramelessWindow { 9 | Q_OBJECT 10 | public: 11 | DialogSample(QWidget* parent = nullptr); 12 | ~DialogSample(); 13 | 14 | protected: 15 | void setupUi(); 16 | 17 | void setShadowEnable(bool enable); 18 | 19 | protected: 20 | QWidget* centralWidget_; 21 | QPushButton* pushButtonClose_; 22 | QLabel* labelSaveDir_; 23 | QPushButton* pushButtonChangeSaveDir_; 24 | QLabel* labelTitle_; 25 | QPushButton* pushButtonSaveDir_; 26 | 27 | QGraphicsDropShadowEffect* shadow_; 28 | }; 29 | -------------------------------------------------------------------------------- /Sample/MainWindowSample.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindowSample.h" 2 | #include "DialogSample.h" 3 | 4 | namespace { 5 | void loadStyleSheetFile(const QString& sheetName, QWidget* widget) { 6 | if (widget) { 7 | QString qss; 8 | QFile qssFile(sheetName); 9 | qssFile.open(QFile::ReadOnly); 10 | if (qssFile.isOpen()) { 11 | qss = QString::fromUtf8(qssFile.readAll()); 12 | widget->setStyleSheet(qss); 13 | qssFile.close(); 14 | } 15 | } 16 | } 17 | } 18 | 19 | MainWindowSample::MainWindowSample(QWidget* parent) 20 | : FramelessWindow(true, parent, true) { 21 | setupUi(); 22 | setTitlebar({widgetTitle_, labelTitle_}); 23 | setResizeable(true, QMargins(2, 2, 2, 2)); 24 | loadStyleSheetFile(":/QtFramelessWindowSample/Resources/main.css", this); 25 | 26 | connect(pushButtonClose_, &QPushButton::clicked, [this]() { this->close(); }); 27 | 28 | connect(pushButtonDialog_, &QPushButton::clicked, [this]() { 29 | DialogSample* pWnd = new DialogSample(this); 30 | pWnd->setResizeable(false, QMargins(0, 0, 0, 0)); 31 | pWnd->setModal(true); 32 | pWnd->show(); 33 | }); 34 | 35 | connect(chkNearToScreenEdge_, &QCheckBox::stateChanged, this, [this](int state) { 36 | this->setNearToScreenEdge(state == Qt::Checked); 37 | }); 38 | 39 | connect(pushButtonResizableDialog_, &QPushButton::clicked, [this]() { 40 | DialogSample* pWnd = new DialogSample(this); 41 | pWnd->setResizeable(true, QMargins(0, 0, 0, 0)); 42 | pWnd->setModal(true); 43 | pWnd->show(); 44 | }); 45 | } 46 | 47 | void MainWindowSample::resizeEvent(QResizeEvent* event) { 48 | labelTitle_->setText( 49 | QString("Frameless Window (Title Height: %1px)").arg(widgetTitle_->height())); 50 | pushButtonH_->setText(QString::number(pushButtonH_->width())); 51 | pushButtonV_->setText(QString::number(pushButtonV_->height())); 52 | 53 | QMainWindow::resizeEvent(event); 54 | } 55 | 56 | void MainWindowSample::setupUi() { 57 | this->setObjectName("FramelessWnd"); 58 | 59 | widgetCentral_ = new QWidget(); 60 | widgetCentral_->setObjectName("widgetCentral"); 61 | 62 | widgetTitle_ = new QWidget(); 63 | widgetTitle_->setObjectName("widgetTitle"); 64 | 65 | labelTitle_ = new QLabel(); 66 | labelTitle_->setObjectName("labelTitle"); 67 | labelTitle_->setText("Frameless Window"); 68 | 69 | pushButtonClose_ = new QPushButton(); 70 | pushButtonClose_->setObjectName("pushButtonClose"); 71 | pushButtonClose_->setCursor(QCursor(Qt::PointingHandCursor)); 72 | pushButtonClose_->setFixedSize(30, 30); 73 | 74 | pushButtonH_ = new QPushButton(); 75 | pushButtonH_->setObjectName("pushButtonH"); 76 | pushButtonH_->setText("--"); 77 | pushButtonH_->setCursor(QCursor(Qt::PointingHandCursor)); 78 | pushButtonH_->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 79 | pushButtonH_->setFixedHeight(30); 80 | 81 | pushButtonV_ = new QPushButton(); 82 | pushButtonV_->setObjectName("pushButtonV"); 83 | pushButtonV_->setText("--"); 84 | pushButtonV_->setCursor(QCursor(Qt::PointingHandCursor)); 85 | pushButtonV_->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); 86 | pushButtonV_->setFixedWidth(30); 87 | 88 | pushButtonDialog_ = new QPushButton(); 89 | pushButtonDialog_->setObjectName("pushButtonDialog"); 90 | pushButtonDialog_->setText("Dialog"); 91 | pushButtonDialog_->setCursor(QCursor(Qt::PointingHandCursor)); 92 | pushButtonDialog_->setFixedSize(80, 26); 93 | 94 | chkNearToScreenEdge_ = new QCheckBox("Near to Screen Edge"); 95 | chkNearToScreenEdge_->setChecked(this->nearToScreenEdge()); 96 | 97 | pushButtonResizableDialog_ = new QPushButton(); 98 | pushButtonResizableDialog_->setObjectName("pushButtonResizableDialog"); 99 | pushButtonResizableDialog_->setText("Resizable Dialog"); 100 | pushButtonResizableDialog_->setCursor(QCursor(Qt::PointingHandCursor)); 101 | pushButtonResizableDialog_->setFixedSize(160, 26); 102 | 103 | QHBoxLayout* hlTitle = new QHBoxLayout(); 104 | hlTitle->setContentsMargins(10, 0, 10, 0); 105 | hlTitle->setSpacing(0); 106 | hlTitle->addWidget(labelTitle_); 107 | hlTitle->addStretch(2); 108 | hlTitle->addWidget(pushButtonDialog_); 109 | hlTitle->addSpacing(12); 110 | hlTitle->addWidget(chkNearToScreenEdge_); 111 | hlTitle->addStretch(1); 112 | hlTitle->addWidget(pushButtonClose_); 113 | 114 | widgetTitle_->setLayout(hlTitle); 115 | widgetTitle_->setFixedHeight(30); 116 | 117 | QGridLayout* gridBody = new QGridLayout(); 118 | gridBody->setSpacing(0); 119 | gridBody->setContentsMargins(0, 0, 0, 0); 120 | gridBody->addWidget(pushButtonResizableDialog_, 0, 0, Qt::AlignCenter); 121 | gridBody->addWidget(pushButtonH_, 1, 0, 1, 3, Qt::AlignVCenter); 122 | gridBody->addWidget(pushButtonV_, 0, 2, 3, 1, Qt::AlignHCenter); 123 | 124 | QVBoxLayout* hlCenteral = new QVBoxLayout(); 125 | hlCenteral->setSpacing(0); 126 | hlCenteral->setContentsMargins(2, 2, 2, 2); // 2px border 127 | hlCenteral->addWidget(widgetTitle_); 128 | hlCenteral->addLayout(gridBody); 129 | widgetCentral_->setLayout(hlCenteral); 130 | 131 | this->setCentralWidget(widgetCentral_); 132 | 133 | this->setMinimumSize(800, 600); 134 | } 135 | -------------------------------------------------------------------------------- /Sample/MainWindowSample.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "../include/FramelessWindow.hpp" 4 | #include 5 | 6 | class MainWindowSample : public FramelessWindow { 7 | Q_OBJECT 8 | 9 | public: 10 | MainWindowSample(QWidget* parent = Q_NULLPTR); 11 | 12 | protected: 13 | void resizeEvent(QResizeEvent* event) override; 14 | 15 | private: 16 | void setupUi(); 17 | 18 | QWidget* widgetCentral_; 19 | QWidget* widgetTitle_; 20 | QLabel* labelTitle_; 21 | QPushButton* pushButtonClose_; 22 | QPushButton* pushButtonDialog_; 23 | QCheckBox* chkNearToScreenEdge_; 24 | QPushButton* pushButtonResizableDialog_; 25 | QPushButton* pushButtonH_; 26 | QPushButton* pushButtonV_; 27 | }; 28 | -------------------------------------------------------------------------------- /Sample/QtFramelessWindowSample.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | Resources/main.css 4 | Resources/close.svg 5 | Resources/close_h.svg 6 | Resources/DialogSample.css 7 | 8 | 9 | -------------------------------------------------------------------------------- /Sample/Resources/DialogSample.css: -------------------------------------------------------------------------------- 1 | * { 2 | font-family: Microsoft YaHei; 3 | } 4 | 5 | QWidget#centralWidget { 6 | background-color: rgba(37,37,37,1); 7 | border-radius: 4px; 8 | border: 1px solid rgba(37,37,37,1); 9 | } 10 | 11 | #pushButtonClose { 12 | border: none; 13 | image: url(:/QtFramelessWindowSample/Resources/close.svg); 14 | } 15 | 16 | #pushButtonClose:hover { 17 | border: none; 18 | image: url(:/QtFramelessWindowSample/Resources/close_h.svg); 19 | } 20 | 21 | #labelWallpaperSaveDir { 22 | color: rgba(255, 255, 255, 1); 23 | font-size: 14px; 24 | } 25 | 26 | #labelCurVer { 27 | color: rgba(255, 255, 255, 1); 28 | font-size: 12px; 29 | } 30 | 31 | #labelTitle { 32 | color: rgba(255, 255, 255, 1); 33 | font-size: 14px; 34 | font-weight: bold; 35 | } 36 | 37 | #checkboxAutoStart { 38 | color: rgba(255, 255, 255, 1); 39 | } 40 | 41 | QPushButton#pushButtonChangeWallpaperSaveDir { 42 | border-radius: 4px; 43 | background-color: rgba(2,122,255,1); 44 | color: rgba(255, 255, 255, 1); 45 | font-size: 12px; 46 | } 47 | 48 | QPushButton#pushButtonChangeWallpaperSaveDir:hover { 49 | border-radius: 4px; 50 | background-color: rgba(2,122,255,1); 51 | color: rgba(255, 255, 255, 1); 52 | font-size: 12px; 53 | } 54 | 55 | #pushButtonWallpaperSaveDir { 56 | color: rgba(255, 255, 255, 1); 57 | font-size: 12px; 58 | text-align: left; 59 | border-style: none none none none; 60 | } 61 | 62 | #pushButtonWallpaperSaveDir:hover { 63 | color: rgba(2,122,255,1); 64 | font-size: 12px; 65 | text-align: left; 66 | border-style: none none none none; 67 | } -------------------------------------------------------------------------------- /Sample/Resources/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 矩形备份 4 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Sample/Resources/close_h.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 矩形备份 4 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Sample/Resources/main.css: -------------------------------------------------------------------------------- 1 | #widgetCentral { 2 | background-color: rgb(255,255,255); 3 | border-radius: 8px; 4 | border: 2px solid rgb(79, 133, 233); 5 | } 6 | 7 | 8 | #widgetTitle { 9 | background-color: rgb(79, 133, 233); 10 | } 11 | 12 | #labelTitle { 13 | color: rgb(255, 255, 255); 14 | font-size: 14px; 15 | } 16 | 17 | #pushButtonClose { 18 | border: none; 19 | image: url(:/QtFramelessWindowSample/Resources/close.svg); 20 | } 21 | 22 | #pushButtonClose:hover { 23 | border: none; 24 | image: url(:/QtFramelessWindowSample/Resources/close_h.svg); 25 | } -------------------------------------------------------------------------------- /Sample/main.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindowSample.h" 2 | #include 3 | 4 | int main(int argc, char* argv[]) { 5 | QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 6 | QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); 7 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 8 | QGuiApplication::setHighDpiScaleFactorRoundingPolicy(Qt::HighDpiScaleFactorRoundingPolicy::PassThrough); 9 | #endif 10 | 11 | QApplication a(argc, argv); 12 | 13 | MainWindowSample w; 14 | 15 | w.show(); 16 | 17 | return a.exec(); 18 | } 19 | -------------------------------------------------------------------------------- /frameless.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/winsoft666/Qt-FramelessWindow/326a15011a87d776a4a40f4bb395b62b15dd1e22/frameless.gif -------------------------------------------------------------------------------- /include/FramelessWindow.hpp: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | * Copyright (C) 2020 - 2024, winsoft666, . 3 | * 4 | * THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, 5 | * EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED 6 | * WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. 7 | * 8 | * Expect bugs 9 | * 10 | * Please use and enjoy. Please let me know of any bugs/improvements 11 | * that you have found/implemented and I will fix/incorporate them into this 12 | * file. 13 | *******************************************************************************/ 14 | 15 | #ifndef FRAMELESS_WINDOW_HPP_ 16 | #define FRAMELESS_WINDOW_HPP_ 17 | #pragma once 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | template 29 | class FramelessWindow : public T { 30 | public: 31 | enum class Direction { 32 | UP = 0, 33 | DOWN = 1, 34 | LEFT, 35 | RIGHT, 36 | LEFTTOP, 37 | LEFTBOTTOM, 38 | RIGHTBOTTOM, 39 | RIGHTTOP, 40 | NONE 41 | }; 42 | 43 | FramelessWindow(bool translucentBackground, QWidget* parent = Q_NULLPTR, bool ignoreReturn = false) : 44 | T(parent), 45 | m_ignoreReturn(ignoreReturn), 46 | m_bLeftPressed(false), 47 | m_bResizeable(false), 48 | m_bUseSystemMove(true), 49 | m_bCurUseSystemMove(false), 50 | m_bNearToScreenEdge(false), 51 | m_bDisableTitlebarDoubleClick(false), 52 | m_Direction(Direction::NONE), 53 | m_iResizeRegionPadding(4) { 54 | m_iResizeRegionPadding = m_iResizeRegionPadding * this->devicePixelRatioF(); 55 | this->installEventFilter(this); 56 | 57 | Qt::WindowFlags flags = this->windowFlags(); 58 | this->setWindowFlags(flags | Qt::FramelessWindowHint); 59 | 60 | if (translucentBackground) { 61 | this->setAttribute(Qt::WA_TranslucentBackground); 62 | } 63 | } 64 | 65 | virtual ~FramelessWindow() = default; 66 | 67 | public: 68 | void setTitlebar(QVector titleBar) { 69 | m_titlebarWidget = titleBar; 70 | } 71 | 72 | void setResizeable(bool b, const QMargins& transparentMargins) { 73 | m_bResizeable = b; 74 | m_transparentMargsins = transparentMargins; 75 | } 76 | 77 | bool resizeable() const { 78 | return m_bResizeable; 79 | } 80 | 81 | void setResizeRegionPadding(int padding) { 82 | m_iResizeRegionPadding = padding * this->devicePixelRatioF(); 83 | } 84 | 85 | int resizeRegionPadding() const { 86 | return m_iResizeRegionPadding / this->devicePixelRatioF(); 87 | } 88 | 89 | void setNearToScreenEdge(bool yes) { 90 | m_bNearToScreenEdge = yes; 91 | } 92 | 93 | bool nearToScreenEdge() const { 94 | return m_bNearToScreenEdge; 95 | } 96 | 97 | QMargins transparentMargins() const { 98 | return m_transparentMargsins; 99 | } 100 | 101 | bool useSystemMove() const { 102 | return m_bUseSystemMove; 103 | } 104 | 105 | void setUseSystemMove(bool b) { 106 | m_bUseSystemMove = b; 107 | } 108 | 109 | bool titlebarDoubleClickDisabled() const { 110 | return m_bDisableTitlebarDoubleClick; 111 | } 112 | 113 | void setDisableTitlebarDoubleClicked(bool b) { 114 | m_bDisableTitlebarDoubleClick = b; 115 | } 116 | 117 | void setAllWidgetMouseTracking(QWidget* widget) { 118 | if (!widget) 119 | return; 120 | 121 | widget->setMouseTracking(true); 122 | QObjectList list = widget->children(); 123 | foreach(QObject * obj, list) { 124 | QWidget* w = dynamic_cast(obj); 125 | if (w) { 126 | setAllWidgetMouseTracking(w); 127 | } 128 | } 129 | } 130 | 131 | protected: 132 | virtual bool eventFilter(QObject* target, QEvent* event) { 133 | if (event->type() == QEvent::Paint) { 134 | static bool first = false; 135 | if (!first) { 136 | first = true; 137 | setAllWidgetMouseTracking(this); 138 | } 139 | } 140 | else if (event->type() == QEvent::KeyPress) { 141 | if (m_ignoreReturn) { 142 | QKeyEvent* keyEvent = static_cast(event); 143 | if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) 144 | return true; // mark the event as handled 145 | } 146 | } 147 | else if (event->type() == QEvent::Move) { 148 | if (m_bLeftPressed) { 149 | onManualMoving(this->geometry().topLeft()); 150 | } 151 | 152 | if (m_bNearToScreenEdge) { 153 | const int NEAR_SPACE = 20; 154 | QRect curGeo = geometry(); 155 | 156 | bool isNearEdge = false; 157 | QPoint nearToPoint; 158 | QList screens = QGuiApplication::screens(); 159 | for (int i = 0; i < screens.size(); i++) { 160 | QRect geo = screens[i]->geometry(); 161 | if (qAbs(geo.left() - curGeo.left()) <= NEAR_SPACE) { 162 | isNearEdge = true; 163 | nearToPoint = QPoint(geo.left(), curGeo.top()); 164 | break; 165 | } 166 | else if (qAbs(geo.top() - curGeo.top()) <= NEAR_SPACE) { 167 | isNearEdge = true; 168 | nearToPoint = QPoint(curGeo.left(), geo.top()); 169 | break; 170 | } 171 | else if (qAbs(geo.right() - curGeo.right()) <= NEAR_SPACE) { 172 | isNearEdge = true; 173 | nearToPoint = QPoint(geo.right() - curGeo.width(), curGeo.top()); 174 | break; 175 | } 176 | else if (qAbs(geo.bottom() - curGeo.bottom()) <= NEAR_SPACE) { 177 | isNearEdge = true; 178 | nearToPoint = QPoint(curGeo.left(), geo.bottom() - curGeo.height()); 179 | break; 180 | } 181 | } 182 | 183 | if (isNearEdge) { 184 | move(nearToPoint); 185 | return true; 186 | } 187 | } 188 | } 189 | else if (event->type() == QEvent::MouseMove) { 190 | QMouseEvent* mouseEvent = static_cast(event); 191 | handleMouseMoveEvent(mouseEvent); 192 | } 193 | else if (event->type() == QEvent::MouseButtonPress) { 194 | QMouseEvent* mouseEvent = static_cast(event); 195 | handleMousePressEvent(mouseEvent); 196 | } 197 | else if (event->type() == QEvent::MouseButtonRelease) { 198 | QMouseEvent* mouseEvent = static_cast(event); 199 | handleMouseReleaseEvent(mouseEvent); 200 | } 201 | else if (event->type() == QEvent::MouseButtonDblClick) { 202 | QMouseEvent* mouseEvent = static_cast(event); 203 | handleMouseDoubleClickEvent(mouseEvent); 204 | } 205 | return T::eventFilter(target, event); 206 | } 207 | 208 | void handleMouseDoubleClickEvent(QMouseEvent* event) { 209 | if (m_bResizeable && !m_bDisableTitlebarDoubleClick) { 210 | QWidget* actionWidget = QApplication::widgetAt(event->globalPos()); 211 | if (actionWidget) { 212 | bool inTitlebar = false; 213 | for (auto& item : m_titlebarWidget) { 214 | if (actionWidget == item) { 215 | inTitlebar = true; 216 | break; 217 | } 218 | } 219 | if (inTitlebar) { 220 | if (this->isMaximized()) { 221 | this->showNormal(); 222 | } 223 | else { 224 | this->showMaximized(); 225 | } 226 | } 227 | } 228 | } 229 | } 230 | 231 | void handleMousePressEvent(QMouseEvent* event) { 232 | m_MousePressedGeo = this->geometry(); 233 | if (event->button() == Qt::LeftButton) { 234 | if (m_Direction != Direction::NONE) { 235 | m_bLeftPressed = true; 236 | this->grabMouse(); 237 | } 238 | else { 239 | QWidget* action = QApplication::widgetAt(event->globalPos()); 240 | if (action) { 241 | bool inTitlebar = false; 242 | for (auto& item : m_titlebarWidget) { 243 | if (action == item) { 244 | inTitlebar = true; 245 | break; 246 | } 247 | } 248 | if (inTitlebar) { 249 | m_bLeftPressed = true; 250 | m_DragPos = event->globalPos() - this->frameGeometry().topLeft(); 251 | if (m_bUseSystemMove) { 252 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) 253 | m_bCurUseSystemMove = this->windowHandle()->startSystemMove(); 254 | Q_ASSERT_X(m_bCurUseSystemMove, "mousePressEvent()", 255 | "this->windowHandle()->startSystemMove() failed"); 256 | #endif 257 | } 258 | } 259 | } 260 | } 261 | } 262 | } 263 | 264 | void handleMouseMoveEvent(QMouseEvent* event) { 265 | QPoint globalPoint = event->globalPos(); 266 | if (m_bLeftPressed) { 267 | bool bIgnore = true; 268 | QList screens = QGuiApplication::screens(); 269 | for (int i = 0; i < screens.size(); i++) { 270 | QScreen* pScreen = screens[i]; 271 | QRect geometryRect = pScreen->availableGeometry(); 272 | if (geometryRect.contains(globalPoint)) { 273 | bIgnore = false; 274 | break; 275 | } 276 | } 277 | 278 | if (bIgnore) { 279 | event->ignore(); 280 | return; 281 | } 282 | 283 | if (m_Direction != Direction::NONE) { 284 | QRect rect = this->rect(); 285 | QPoint tl = this->mapToGlobal(rect.topLeft()); 286 | QPoint rb = this->mapToGlobal(rect.bottomRight()); 287 | 288 | QRect rMove(tl, rb); 289 | 290 | switch (m_Direction) { 291 | case Direction::LEFT: 292 | if (rb.x() - globalPoint.x() <= this->minimumWidth()) 293 | rMove.setX(tl.x()); 294 | else 295 | rMove.setX(globalPoint.x()); 296 | break; 297 | case Direction::RIGHT: 298 | rMove.setWidth(globalPoint.x() - tl.x()); 299 | break; 300 | case Direction::UP: 301 | if (rb.y() - globalPoint.y() <= this->minimumHeight()) 302 | rMove.setY(tl.y()); 303 | else 304 | rMove.setY(globalPoint.y()); 305 | break; 306 | case Direction::DOWN: 307 | rMove.setHeight(globalPoint.y() - tl.y()); 308 | break; 309 | case Direction::LEFTTOP: 310 | if (rb.x() - globalPoint.x() <= this->minimumWidth()) 311 | rMove.setX(tl.x()); 312 | else 313 | rMove.setX(globalPoint.x()); 314 | if (rb.y() - globalPoint.y() <= this->minimumHeight()) 315 | rMove.setY(tl.y()); 316 | else 317 | rMove.setY(globalPoint.y()); 318 | break; 319 | case Direction::RIGHTTOP: 320 | rMove.setWidth(globalPoint.x() - tl.x()); 321 | rMove.setY(globalPoint.y()); 322 | break; 323 | case Direction::LEFTBOTTOM: 324 | rMove.setX(globalPoint.x()); 325 | rMove.setHeight(globalPoint.y() - tl.y()); 326 | break; 327 | case Direction::RIGHTBOTTOM: 328 | rMove.setWidth(globalPoint.x() - tl.x()); 329 | rMove.setHeight(globalPoint.y() - tl.y()); 330 | break; 331 | default: 332 | break; 333 | } 334 | this->setGeometry(rMove); 335 | onManualResizing(rMove); 336 | } 337 | else { 338 | QPoint newPoint = event->globalPos() - m_DragPos; 339 | if (!m_bCurUseSystemMove) { 340 | this->move(newPoint); 341 | } 342 | event->accept(); 343 | } 344 | } 345 | else { 346 | region(globalPoint); 347 | } 348 | } 349 | 350 | void region(const QPoint& cursorGlobalPoint) { 351 | if (!m_bResizeable) 352 | return; 353 | 354 | QRect rect = this->contentsRect(); 355 | rect.setLeft(rect.left() + m_transparentMargsins.left()); 356 | rect.setTop(rect.top() + m_transparentMargsins.top()); 357 | rect.setRight(rect.right() - m_transparentMargsins.right()); 358 | rect.setBottom(rect.bottom() - m_transparentMargsins.bottom()); 359 | 360 | QPoint tl = this->mapToGlobal(rect.topLeft()); 361 | QPoint rb = this->mapToGlobal(rect.bottomRight()); 362 | int x = cursorGlobalPoint.x(); 363 | int y = cursorGlobalPoint.y(); 364 | 365 | if (tl.x() + m_iResizeRegionPadding >= x && tl.x() <= x && 366 | tl.y() + m_iResizeRegionPadding >= y && tl.y() <= y) { 367 | m_Direction = Direction::LEFTTOP; 368 | this->setCursor(QCursor(Qt::SizeFDiagCursor)); 369 | } 370 | else if (x >= rb.x() - m_iResizeRegionPadding && x <= rb.x() && 371 | y >= rb.y() - m_iResizeRegionPadding && y <= rb.y()) { 372 | m_Direction = Direction::RIGHTBOTTOM; 373 | this->setCursor(QCursor(Qt::SizeFDiagCursor)); 374 | } 375 | else if (x <= tl.x() + m_iResizeRegionPadding && x >= tl.x() && 376 | y >= rb.y() - m_iResizeRegionPadding && y <= rb.y()) { 377 | m_Direction = Direction::LEFTBOTTOM; 378 | this->setCursor(QCursor(Qt::SizeBDiagCursor)); 379 | } 380 | else if (x <= rb.x() && x >= rb.x() - m_iResizeRegionPadding && y >= tl.y() && 381 | y <= tl.y() + m_iResizeRegionPadding) { 382 | m_Direction = Direction::RIGHTTOP; 383 | this->setCursor(QCursor(Qt::SizeBDiagCursor)); 384 | } 385 | else if (x <= tl.x() + m_iResizeRegionPadding && x >= tl.x()) { 386 | m_Direction = Direction::LEFT; 387 | this->setCursor(QCursor(Qt::SizeHorCursor)); 388 | } 389 | else if (x <= rb.x() && x >= rb.x() - m_iResizeRegionPadding) { 390 | m_Direction = Direction::RIGHT; 391 | this->setCursor(QCursor(Qt::SizeHorCursor)); 392 | } 393 | else if (y >= tl.y() && y <= tl.y() + m_iResizeRegionPadding) { 394 | m_Direction = Direction::UP; 395 | this->setCursor(QCursor(Qt::SizeVerCursor)); 396 | } 397 | else if (y <= rb.y() && y >= rb.y() - m_iResizeRegionPadding) { 398 | m_Direction = Direction::DOWN; 399 | this->setCursor(QCursor(Qt::SizeVerCursor)); 400 | } 401 | else { 402 | m_Direction = Direction::NONE; 403 | this->setCursor(QCursor(Qt::ArrowCursor)); 404 | } 405 | } 406 | 407 | void handleMouseReleaseEvent(QMouseEvent* event) { 408 | m_bLeftPressed = false; 409 | if (m_Direction != Direction::NONE) { 410 | m_Direction = Direction::NONE; 411 | this->releaseMouse(); 412 | this->setCursor(QCursor(Qt::ArrowCursor)); 413 | } 414 | 415 | QRect curGeo = this->geometry(); 416 | if (m_MousePressedGeo != curGeo) { 417 | onManualMoved(curGeo.topLeft()); 418 | } 419 | if (m_MousePressedGeo.size() != curGeo.size()) { 420 | onManualResized(this->rect()); 421 | } 422 | } 423 | 424 | virtual void onManualResizing(const QRect& rc) {} 425 | virtual void onManualResized(const QRect& rc) {} 426 | virtual void onManualMoving(const QPoint& pt) {} 427 | virtual void onManualMoved(const QPoint& pt) {} 428 | 429 | protected: 430 | const bool m_ignoreReturn; 431 | bool m_bLeftPressed; 432 | bool m_bResizeable; 433 | bool m_bUseSystemMove; 434 | bool m_bCurUseSystemMove; 435 | bool m_bNearToScreenEdge; 436 | bool m_bDisableTitlebarDoubleClick; 437 | Direction m_Direction; 438 | int m_iResizeRegionPadding; 439 | QPoint m_DragPos; 440 | QRect m_MousePressedGeo; 441 | QMargins m_transparentMargsins; 442 | QVector m_titlebarWidget; 443 | }; 444 | #endif 445 | --------------------------------------------------------------------------------