在现代桌面应用程序开发中,用户界面的美观性和灵活性显得越来越重要。传统的窗口边框在某些情况下可能会显得过于呆板和限制性,特别是当开发者追求提供无缝和沉浸式用户体验时。Qt Quick (QML) 为开发者提供了强大的工具,以自定义和增强用户界面的外观和感觉。在本篇博客中,我们将探讨如何在Qt QML中创建无边框窗口,并讨论实现该功能的关键步骤及注意事项。
- 增强的视觉吸引力:无边框窗口可以去除传统窗口边界的干扰,使应用看起来更现代化,更干净。
- 自定义布局与控制:开发者可以完全控制窗口的外观和布局,包括窗口的大小、形状和行为。
- 提升用户体验:通过提供定制的窗口控制(如最小化、最大化、关闭按钮),可以更好地融入应用程序的整体设计语言中。
//frameless.h
#ifndef FRAMELESS_H
#define FRAMELESS_H
#include <QQuickWindow>
class Frameless : public QQuickWindow {
Q_OBJECT
public:
explicit Frameless(QWindow *parent = nullptr);
protected:
bool nativeEvent(const QByteArray &eventType, void *message, qintptr *result) override;
};
#endif //FRAMELESS_H
//frameless.cpp
#include "btframeless.h"
#ifdef Q_OS_WIN
#pragma comment(lib, "dwmapi")
#pragma comment(lib, "user32.lib")
#include <Windows.h>
#include <windowsx.h>
#include <dwmapi.h>
#endif
Frameless::Frameless(QWindow *parent) : QQuickWindow(parent) {
setFlags(Qt::Window
| Qt::WindowMaximizeButtonHint
| Qt::WindowMinimizeButtonHint
| Qt::FramelessWindowHint);
HWND hWnd = (HWND) winId();
LONG style = GetWindowLongW(hWnd, GWL_STYLE);
SetWindowLong(
hWnd,
GWL_STYLE,
style
| WS_MINIMIZEBOX
| WS_MAXIMIZEBOX
| WS_CAPTION
| CS_DBLCLKS
| WS_THICKFRAME
);
}
bool Frameless::nativeEvent(const QByteArray &eventType, void *message, qintptr *result) {
const static int boundary = 5;
MSG *msg = static_cast<MSG *>(message);
switch (msg->message) {
// 无边框 最大化时增加边距
case WM_NCCALCSIZE: {
if (IsZoomed(msg->hwnd)) {
auto *params = reinterpret_cast<NCCALCSIZE_PARAMS *>(msg->lParam);
int border = GetSystemMetrics(SM_CXSIZEFRAME);
params->rgrc[0].top += border;
params->rgrc[0].left += border;
params->rgrc[0].right -= border;
params->rgrc[0].bottom -= border;
}
*result = HTNOWHERE;
return true;
}
// win11 圆角
case WM_ACTIVATE: {
MARGINS margins = {1, 1, 0, 1};
HRESULT hr = DwmExtendFrameIntoClientArea(msg->hwnd, &margins);
*result = hr;
return true;
}
case WM_NCHITTEST: {
POINT mouse = {GET_X_LPARAM(msg->lParam), GET_Y_LPARAM(msg->lParam)};
RECT rc;
GetWindowRect(msg->hwnd, &rc);
bool left = (mouse.x >= rc.left && mouse.x < rc.left + boundary);
bool right = (mouse.x < rc.right && mouse.x >= rc.right - boundary);
bool top = (mouse.y >= rc.top && mouse.y < rc.top + boundary);
bool bottom = (mouse.y < rc.bottom && mouse.y >= rc.bottom - boundary);
if (top && left) {
*result = HTTOPLEFT;
return true;
} else if (top && right) {
*result = HTTOPRIGHT;
return true;
} else if (bottom && left) {
*result = HTBOTTOMLEFT;
return true;
} else if (bottom && right) {
*result = HTBOTTOMRIGHT;
return true;
} else if (top) {
*result = HTTOP;
return true;
} else if (left) {
*result = HTLEFT;
return true;
} else if (right) {
*result = HTRIGHT;
return true;
} else if (bottom) {
*result = HTBOTTOM;
return true;
}
*result = HTCLIENT;
return false;
}
}
return false;
}
如何使用:
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QQuickStyle>
#include "frameless.h"
int main(int argc, char *argv[]) {
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL);
#else
QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication::setAttribute(Qt::AA_UseHighDpiPixmaps);
#endif
QGuiApplication app(argc, argv);
QQuickStyle::setStyle("Fusion"); //Fusion Imagine Universal Material
qmlRegisterType<Frameless>("Frameless", 1, 0, "Frameless");
QQmlApplicationEngine engine;
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.load(QUrl("qrc:qml/Main.qml"));
return QGuiApplication::exec();
}
// main.qml
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import Frameless
Frameless {
height: 480
title: " New UI"
visible: true
width: 640
ColumnLayout {
anchors.fill: parent
spacing: 0
Rectangle {
id: titleBar
Layout.fillWidth: true
height: 30
}
Rectangle {
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}