環境:Desktop Qt 5.4.1 MSVC2013 32bit
需要的庫:dwmapi.lib
、user32.lib
需要標頭檔案:<dwmapi.h>
、<windowsx.h>
只顯示重要程式碼
1、去除原邊框、加上陰影、Aero Snap以及其他動畫特效
(1)標頭檔案
#include "Windows.h"
#include "uxtheme.h"
#include "dwmapi.h"
#include "titlebar.h"//自定義類
(2)去除標題、原邊框
初始化,去除邊框
void MainWindow::init()
{
setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
#ifdef Q_OS_WIN
HWND hwnd = reinterpret_cast<HWND>(this->winId());
const LONG style = ( WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CLIPCHILDREN );
SetWindowLongPtr(hwnd, GWL_STYLE, style);
const MARGINS shadow = {1, 1, 1, 1};
DwmExtendFrameIntoClientArea(hwnd, &shadow);
SetWindowPos(hwnd, 0, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE);
#endif
// 標題拖動、雙擊事件
MyTitleBar *title = new MyTitleBar(this);
qobject_cast<QBoxLayout *>(ui->centralwidget->layout())->insertWidget(0, title);
}
同時在最大化時,增加了邊界,可以自行刪除
bool MainWindow::nativeEvent(const QByteArray& eventType, void* message, qintptr* result)
{
MSG* msg = (MSG*)message;
switch (msg->message)
{
case WM_NCCALCSIZE:
{
// this kills the window frame and title bar we added with WS_THICKFRAME and WS_CAPTION
*result = 0;
return true;
}
// 若full時邊框不合適,可以去除此case
case WM_GETMINMAXINFO:
{
if (::IsZoomed(msg->hwnd)) {
// 最大化時會超出螢幕,所以填充邊框間距
RECT frame = { 0, 0, 0, 0 };
AdjustWindowRectEx(&frame, WS_OVERLAPPEDWINDOW, FALSE, 0);
frame.left = abs(frame.left);
frame.top = abs(frame.bottom);
this->setContentsMargins(frame.left, frame.top, frame.right, frame.bottom);
}
else {
this->setContentsMargins(0, 0, 0, 0);
}
*result = ::DefWindowProc(msg->hwnd, msg->message, msg->wParam, msg->lParam);
return true;
}
break;
default:
return QMainWindow::nativeEvent(eventType, message, result);
}
}
(3)支援手動修改視窗
需要實現一個QAbstractNativeEventFilter
類,內容如下:
標頭檔案
#include <QAbstractNativeEventFilter>
#include <QWidget>
#include "Windows.h"
#define GET_X_LPARAM(lp) ((int)(short)LOWORD(lp))
#define GET_Y_LPARAM(lp) ((int)(short)HIWORD(lp))
class NativeEventFilter : public QAbstractNativeEventFilter
{
public:
virtual bool nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)Q_DECL_OVERRIDE;
};
cpp檔案
bool NativeEventFilter::nativeEventFilter(const QByteArray &eventType, void *message, qintptr *result)
{
#ifdef Q_OS_WIN
if (eventType != "windows_generic_MSG")
return false;
MSG* msg = static_cast<MSG*>(message);
QWidget* widget = QWidget::find(reinterpret_cast<WId>(msg->hwnd));
if (!widget)
return false;
switch (msg->message) {
case WM_NCHITTEST: {
const LONG borderWidth = 9;
RECT winrect;
GetWindowRect(msg->hwnd, &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
// bottom left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMLEFT;
return true;
}
// bottom right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOMRIGHT;
return true;
}
// top left
if (x >= winrect.left && x < winrect.left + borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPLEFT;
return true;
}
// top right
if (x < winrect.right && x >= winrect.right - borderWidth &&
y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOPRIGHT;
return true;
}
// left
if (x >= winrect.left && x < winrect.left + borderWidth)
{
*result = HTLEFT;
return true;
}
// right
if (x < winrect.right && x >= winrect.right - borderWidth)
{
*result = HTRIGHT;
return true;
}
// bottom
if (y < winrect.bottom && y >= winrect.bottom - borderWidth)
{
*result = HTBOTTOM;
return true;
}
// top
if (y >= winrect.top && y < winrect.top + borderWidth)
{
*result = HTTOP;
return true;
}
return false;
}
default:
break;
}
return false;
#else
return false;
#endif
};
應用
然後在視窗建立之前,使用QApplication::installNativeEventFilter
方法把監聽器註冊給主程式。
int main(int argc, char *argv[])
{
NativeEventFilter f;
QApplication a(argc, argv);
a.installNativeEventFilter(&f);//支援手動修改視窗大小
MainWindow w;
w.show();
return a.exec();
}
2、自定義標題欄
- 需要過載
QWidget::mousePressEvent
方法 - 儲存 Window 控制代碼操作原視窗
class MyTitleBar : public QFrame {
Q_OBJECT
public:
explicit MyTitleBar(QWidget *parent = nullptr);
protected:
void mousePressEvent(QMouseEvent* ev);
private:
QWidget *Window = nullptr; // 儲存主視窗的指標
QVBoxLayout *verticalLayout;
QHBoxLayout *horizontalLayout;
QSpacerItem *horizontalSpacer;
QPushButton *pushButton_min;
QPushButton *pushButton_normal;
QPushButton *pushButton_max;
QPushButton *pushButton_full;
QSpacerItem *horizontalSpacer_6;
QPushButton *pushButton_close;
};
建立ui
MyTitleBar::MyTitleBar(QWidget *parent): QFrame (parent), Window(parent)
{
// 邊緣貼合
parent->setContentsMargins(0, 0, 0, 0);
setObjectName("title");
setMaximumHeight(50);
this->setStyleSheet("#title{background-color: rgb(255, 255, 255);}");
verticalLayout = new QVBoxLayout(this);
verticalLayout->setObjectName("verticalLayout");
verticalLayout->setContentsMargins(0, 0, 0, 0);
horizontalLayout = new QHBoxLayout();
horizontalLayout->setObjectName("horizontalLayout");
horizontalSpacer = new QSpacerItem(40, 20, QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Minimum);
horizontalLayout->addItem(horizontalSpacer);
pushButton_min = new QPushButton(this);
pushButton_min->setObjectName("pushButton_min");
horizontalLayout->addWidget(pushButton_min);
pushButton_normal = new QPushButton(this);
pushButton_normal->setObjectName("pushButton_normal");
horizontalLayout->addWidget(pushButton_normal);
pushButton_max = new QPushButton(this);
pushButton_max->setObjectName("pushButton_max");
horizontalLayout->addWidget(pushButton_max);
pushButton_full = new QPushButton(this);
pushButton_full->setObjectName("pushButton_full");
horizontalLayout->addWidget(pushButton_full);
horizontalSpacer_6 = new QSpacerItem(40, 20, QSizePolicy::Policy::Expanding, QSizePolicy::Policy::Minimum);
horizontalLayout->addItem(horizontalSpacer_6);
pushButton_close = new QPushButton(this);
pushButton_close->setObjectName("pushButton_close");
horizontalLayout->addWidget(pushButton_close);
horizontalLayout->setStretch(0, 1);
verticalLayout->addLayout(horizontalLayout);
pushButton_min->setText(QCoreApplication::translate("MainWindow", "MIN", nullptr));
pushButton_normal->setText(QCoreApplication::translate("MainWindow", "NORMAL", nullptr));
pushButton_max->setText(QCoreApplication::translate("MainWindow", "MAX", nullptr));
pushButton_full->setText(QCoreApplication::translate("MainWindow", "FULL", nullptr));
pushButton_close->setText(QCoreApplication::translate("MainWindow", "CLOSE", nullptr));
}
連線主視窗的變化
最大化和關閉按扭,正常呼叫QWidget::showMaximized()
和QWidget::close()
等Qt自帶方法即可。
connect(pushButton_min, &QPushButton::clicked, Window, &QWidget::showMinimized);
connect(pushButton_close, &QPushButton::clicked, Window, &QWidget::close);
connect(pushButton_normal, &QPushButton::clicked, Window, &QWidget::showNormal);
connect(pushButton_full, &QPushButton::clicked, Window, &QWidget::showFullScreen);
connect(pushButton_max, &QPushButton::clicked, Window, &QWidget::showMaximized);
重截QWidget::mousePressEvent
方法
#include "Windows.h"
void MyTitleBar::mousePressEvent(QMouseEvent* ev)
{
QWidget::mousePressEvent(ev);
if (Window == nullptr) return;
if (!ev->isAccepted()) {
if (ev->type() == QEvent::MouseButtonDblClick) {
// Toggle maximize/restore on double-click
if (Window->isMaximized()) {
Window->showNormal(); // Restore
}
else {
Window->showMaximized(); // Maximize
}
return; // Prevent further processing
}
#ifdef Q_OS_WIN
ReleaseCapture();
SendMessage(reinterpret_cast<HWND>(Window->winId()), WM_SYSCOMMAND, SC_MOVE + HTCAPTION, 0);
#endif
}
}
新增
// 標題拖動、雙擊事件
MyTitleBar *title = new MyTitleBar(this);
qobject_cast<QBoxLayout *>(ui->centralwidget->layout())->insertWidget(0, title);
3、最終實現效果
參考連結:https://github.com/deimos1877/BorderlessWindow