说明
我们知道QWidget等设置了this->setWindowFlags(Qt::FramelessWindowHint);
后无法移动和调整大小,但实际项目中是需要窗口能够调整大小的。所以以实现FrameLess弹窗调整大小及移动弹窗需求,并且在Windows 10上有Aero效果。
先看一下效果:
代码
大部分参考这个github。然后自己修改了一下,因为github上面的,在设置了qss后怎么也实现不了窗口圆角以阴影。下面修改版的代码可以实现圆角,但也没有阴影,只能在Widget中自己实现阴影了。
如果不需要圆角,github上面的也是会自带阴影的。不用下面的调整版实现方案。
#ifndef AEROMAINWINDOW_H
#define AEROMAINWINDOW_H
#include <QMainWindow>
class AeroMainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit AeroMainWindow(QWidget *parent = nullptr);
~AeroMainWindow();
//设置是否可以通过鼠标调整窗口大小
//if resizeable is set to false, then the window can not be resized by mouse
//but still can be resized programtically
void setResizeable(bool resizeable=true);
bool isResizeable(){return m_bResizeable;}
//设置可调整大小区域的宽度,在此区域内,可以使用鼠标调整窗口大小
//set border width, inside this aera, window can be resized by mouse
void setResizeableAreaWidth(int width = 5);
protected:
//设置一个标题栏widget,此widget会被当做标题栏对待
//set a widget which will be treat as SYSTEM titlebar
void setTitleBar(QWidget* titlebar);
//在标题栏控件内,也可以有子控件如标签控件“label1”,此label1遮盖了标题栏,导致不能通过label1拖动窗口
//要解决此问题,使用addIgnoreWidget(label1)
//generally, we can add widget say "label1" on titlebar, and it will cover the titlebar under it
//as a result, we can not drag and move the MainWindow with this "label1" again
//we can fix this by add "label1" to a ignorelist, just call addIgnoreWidget(label1)
void addIgnoreWidget(QWidget* widget);
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
void resizeEvent(QResizeEvent *event);
public slots:
private slots:
void onTitleBarDestroyed();
private:
QWidget *m_titleBar;
QList<QWidget*> m_whiteList;
int m_borderWidth;
bool m_bResizeable;
};
#endif // AEROMAINWINDOW_H
#include "aeromainwindow.h"
#include <QGraphicsDropShadowEffect>
#include <QDesktopServices>
#include <QUrl>
#include <QGridLayout>
#include <QStyle>
#include <QDebug>
#include <QPushButton>
#ifdef Q_OS_WIN
#include <windows.h>
#include <WinUser.h>
#include <windowsx.h>
#include <dwmapi.h>
#include <objidl.h> // Fixes error C2504: 'IUnknown' : base class undefined
#include <gdiplus.h>
#include <GdiPlusColor.h>
#pragma comment (lib,"Dwmapi.lib") // Adds missing library, fixes error LNK2019: unresolved external symbol __imp__DwmExtendFrameIntoClientArea
#pragma comment (lib,"user32.lib")
#endif
AeroMainWindow::AeroMainWindow(QWidget *parent) :
QMainWindow(parent),
m_titleBar(Q_NULLPTR),
m_borderWidth(5),
m_bResizeable(true)
{
this->setAttribute(Qt::WA_TranslucentBackground);//设置窗口背景透明
this->setWindowFlags(Qt::FramelessWindowHint); //设置无边框窗口
this->setResizeable(true);
}
AeroMainWindow::~AeroMainWindow()
{
}
void AeroMainWindow::setResizeable(bool resizeable)
{
bool visible = isVisible();
m_bResizeable = resizeable;
if (m_bResizeable)
{
setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint);
//此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏
//
//this line will get titlebar/thick frame/Aero back, which is exactly what we want
//we will get rid of titlebar and thick frame again in nativeEvent() later
HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
}
else
{
setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint);
HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
::SetWindowLong(hwnd, GWL_STYLE, style & ~WS_MAXIMIZEBOX & ~WS_CAPTION);
}
//保留一个像素的边框宽度,否则系统不会绘制边框阴影
const MARGINS shadow = { 1, 1, 1, 1 };
DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
setVisible(visible);
}
void AeroMainWindow::setResizeableAreaWidth(int width)
{
if (1 > width) width = 1;
m_borderWidth = width;
}
void AeroMainWindow::setTitleBar(QWidget* titlebar)
{
m_titleBar = titlebar;
if (!titlebar) return;
connect(titlebar, SIGNAL(destroyed(QObject*)), this, SLOT(onTitleBarDestroyed()));
}
void AeroMainWindow::onTitleBarDestroyed()
{
if (m_titleBar == QObject::sender())
{
m_titleBar = Q_NULLPTR;
}
}
void AeroMainWindow::addIgnoreWidget(QWidget* widget)
{
if (!widget) return;
if (m_whiteList.contains(widget)) return;
m_whiteList.append(widget);
}
bool AeroMainWindow::nativeEvent(const QByteArray &eventType, void *message, long *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;
} // WM_NCCALCSIZE
case WM_NCHITTEST:
{
*result = 0;
const LONG border_width = m_borderWidth; //in pixels
RECT winrect;
GetWindowRect(HWND(winId()), &winrect);
long x = GET_X_LPARAM(msg->lParam);
long y = GET_Y_LPARAM(msg->lParam);
if(m_bResizeable)
{
bool resizeWidth = minimumWidth() != maximumWidth();
bool resizeHeight = minimumHeight() != maximumHeight();
if(resizeWidth)
{
//left border
if (x >= winrect.left && x < winrect.left + border_width)
{
*result = HTLEFT;
}
//right border
if (x < winrect.right && x >= winrect.right - border_width)
{
*result = HTRIGHT;
}
}
if(resizeHeight)
{
//bottom border
if (y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOM;
}
//top border
if (y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOP;
}
}
if(resizeWidth && resizeHeight)
{
//bottom left corner
if (x >= winrect.left && x < winrect.left + border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMLEFT;
}
//bottom right corner
if (x < winrect.right && x >= winrect.right - border_width &&
y < winrect.bottom && y >= winrect.bottom - border_width)
{
*result = HTBOTTOMRIGHT;
}
//top left corner
if (x >= winrect.left && x < winrect.left + border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPLEFT;
}
//top right corner
if (x < winrect.right && x >= winrect.right - border_width &&
y >= winrect.top && y < winrect.top + border_width)
{
*result = HTTOPRIGHT;
}
}
}
if (0 != *result) return true;
//*result still equals 0, that means the cursor locate OUTSIDE the frame area
//but it may locate in titlebar area
if (!m_titleBar) return false;
//support highdpi
double dpr = this->devicePixelRatioF();
QPoint pos = m_titleBar->mapFromGlobal(QPoint(x/dpr,y/dpr));
if (!m_titleBar->rect().contains(pos)) return false;
QWidget* child = m_titleBar->childAt(pos);
if (!child)
{
*result = HTCAPTION;
return true;
}
else
{
if (m_whiteList.contains(child))
{
*result = HTCAPTION;
return true;
}
}
return false;
} // WM_NCHITTEST
default:
return QMainWindow::nativeEvent(eventType, msg, result);
}
}
void AeroMainWindow::resizeEvent(QResizeEvent *event)
{
if (m_titleBar)
m_titleBar->setGeometry(QRect(0, 0, this->rect().width(), m_titleBar->rect().height()));
QMainWindow::resizeEvent(event);
}
测试代码
生成一个类,继承上面的类。然后实现下面的内容。很简单:
#include "testmainwindow.h"
#include "ui_testmainwindow.h"
TestMainWindow::TestMainWindow(QWidget *parent) :
AeroMainWindow(parent),
ui(new Ui::TestMainWindow)
{
ui->setupUi(this);
QWidget *titleBar = new QWidget(this);
titleBar->setGeometry(QRect(0, 0, this->rect().width(), 25));
this->setTitleBar(titleBar);
this->setStyleSheet("background-color: red;\
border-radius: 8px;");
}
TestMainWindow::~TestMainWindow()
{
delete ui;
}