我们经常需要自定义标题栏,那么去掉标题栏是非常有必要。但是去掉标题栏之后边框阴影也会消失,感觉光秃秃的,不太舒服。接下来我们将讨论添加边框阴影的几种解决方案。
我们调用的是dwmapi.dll即Microsoft Desktop Window Manager API(桌面窗口管理器DWM 的公用界面)的动态链接库的相关函数。
#include "WinAPIShadowWidget.h"
#include "windwmapi.h"
WinAPIShadowWidget::WinAPIShadowWidget(QWidget *parent)
: QWidget(parent)
{
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
HWND hwnd = (HWND)this->winId();
DWORD style = ::GetWindowLong(hwnd, GWL_STYLE);
// 此行代码可以带回Aero效果,同时也带回了标题栏和边框,在nativeEvent()会再次去掉标题栏
::SetWindowLong(hwnd, GWL_STYLE, style | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_CAPTION);
//we better left 1 piexl width of border untouch, so OS can draw nice shadow around it
const MARGINS shadow = { 1, 1, 1, 1 };
WinDwmapi::instance()->DwmExtendFrameIntoClientArea(HWND(winId()), &shadow);
}
bool WinAPIShadowWidget::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;
}
default:
return QWidget::nativeEvent(eventType, message, result);
}
}
...
HRESULT WinDwmapi::DwmExtendFrameIntoClientArea(HWND hWnd, const MARGINS *pMarInset) const
{
if (dwm_extendframe_into_client_area_) {
return dwm_extendframe_into_client_area_(hWnd, pMarInset);
}
return E_NOTIMPL;
}
...
if (dwmapi_dll_) {
dwm_is_composition_enabled_ = \
reinterpret_cast<DwmIsCompositionEnabledPtr>(GetProcAddress(dwmapi_dll_, "DwmIsCompositionEnabled"));
dwm_extendframe_into_client_area_ = \
reinterpret_cast<DwmExtendFrameIntoClientAreaPtr>(GetProcAddress(dwmapi_dll_, "DwmExtendFrameIntoClientArea"));
}
QGraphicsDropShadowEffect类提供了一个投影效果。可以使用setColor()函数修改投影的颜色。可以使用setOffset()函数修改阴影偏移量,使用setBlurRadius()函数修改阴影的半径。默认情况下,投影是半透明的深灰色(QColor(63, 63, 63, 180)阴影,模糊半径为1,向右下角偏移8个像素。将一个QWidget嵌入到另一个QWidget中,被嵌入的QWidget背景透明。
ShadowEffectWidget::ShadowEffectWidget(QWidget *parent)
: QWidget(parent)
{
resize(400, 300);
setWindowFlags(windowFlags() | Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
QWidget *pCentralWidget = new QWidget(this);
pCentralWidget->setStyleSheet("background-color: white");
QHBoxLayout *pLayout = new QHBoxLayout(this);
pLayout->addWidget(pCentralWidget);
pLayout->setContentsMargins(20, 20, 20, 20);
QGraphicsDropShadowEffect *pEffect = new QGraphicsDropShadowEffect(pCentralWidget);
pEffect->setOffset(0, 0);
pEffect->setColor(QColor(QStringLiteral("black")));
pEffect->setBlurRadius(30);
pCentralWidget->setGraphicsEffect(pEffect);
}
qDrawBorderPixmap函数用于将一个像素图绘制到一个矩形的边缘。使用给定的绘图器将给定的像素映射绘制到给定的目标矩形中。pixmap将被分割成九个部分,并根据边缘结构绘制。需要我们提前做好边框阴影的图片。但是据说这种方式效率并不高,有待考证。
DrawBorderPixmapWidget::DrawBorderPixmapWidget(QWidget *parent)
: QWidget(parent)
{
resize(800, 600);
setWindowFlags(Qt::FramelessWindowHint);
setAttribute(Qt::WA_TranslucentBackground);
}
void DrawBorderPixmapWidget::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e)
QPainter painter(this);
QPixmap pixmap(":/client-shadow.png");
qDrawBorderPixmap(&painter, this->rect(), QMargins(8, 8, 8, 8), pixmap);
// 绘制中心区域的背景色(不然会是透明的)
QRect rect(this->rect().x()+8, this->rect().y()+8, this->rect().width()-16, this->rect().height()-16);
painter.fillRect(rect, QColor(255, 255, 255));
}
这种方式稍微麻烦点,但是比较灵活,效率也很可观,推荐使用。代码有点多,先上主要的。
inline unsigned char MakeAlpha(int i, double f, int nSize)
{
if (i == nSize)
f *= 1.2;
//
double f2 = 1 - cos((double)i / nSize * 3.14 / 2);
//
return int(fabs((i * f) * f2));
}
QImage MakeShadowImage(int shadowSize, bool activated)
{
int size = shadowSize * 2 + 10;
QImage image(size, size, QImage::Format_ARGB32);
image.fill(QColor(Qt::black));
//
double f = activated ? 4.0 : 1.0;
//
QSize szImage = image.size();
//
//left
for (int y = shadowSize; y < szImage.height() - shadowSize; y++) {
for (int x = 0; x < shadowSize; x++) {
int i = x + 1;
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//right
for (int y = shadowSize; y < szImage.height() - shadowSize; y++) {
for (int x = szImage.width() - shadowSize - 1; x < szImage.width(); x++) {
int i = szImage.width() - x;
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//top
for (int y = 0; y < shadowSize; y++) {
int i = y + 1;
for (int x = shadowSize; x < szImage.width() - shadowSize; x++) {
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
//
}
//bottom
for (int y = szImage.height() - shadowSize - 1; y < szImage.height(); y++) {
int i = szImage.height() - y;
for (int x = shadowSize; x < szImage.width() - shadowSize; x++) {
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//
int parentRoundSize = 3;
//
for (int x = 0; x < shadowSize + parentRoundSize; x++) {
for (int y = 0; y < shadowSize + parentRoundSize; y++) {
int xx = (shadowSize + parentRoundSize) - x;
int yy = (shadowSize + parentRoundSize) - y;
int i = int(sqrt(double(xx * xx + yy * yy)));
i = std::min<int>(shadowSize + parentRoundSize, i);
i -= parentRoundSize;
i = shadowSize - i;
//
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//
for (int x = szImage.width() - shadowSize - parentRoundSize; x < szImage.width(); x++) {
for (int y = 0; y < shadowSize + parentRoundSize; y++) {
int xx = (shadowSize + parentRoundSize) - (szImage.width() - x);
int yy = (shadowSize + parentRoundSize) - y;
int i = int(sqrt(double(xx * xx + yy * yy)));
i = std::min<int>(shadowSize + parentRoundSize, i);
i -= parentRoundSize;
i = shadowSize - i;
//
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//
for (int x = 0; x < shadowSize + parentRoundSize; x++) {
for (int y = szImage.height() - shadowSize - parentRoundSize; y < szImage.height(); y++) {
int xx = (shadowSize + parentRoundSize) - x;
int yy = (shadowSize + parentRoundSize) - (szImage.height() - y);
int i = int(sqrt(double(xx * xx + yy * yy)));
i = std::min<int>(shadowSize + parentRoundSize, i);
i -= parentRoundSize;
i = shadowSize - i;
//
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//
for (int x = szImage.width() - shadowSize - parentRoundSize; x < szImage.width(); x++) {
for (int y = szImage.height() - shadowSize - parentRoundSize; y < szImage.height(); y++) {
int xx = (shadowSize + parentRoundSize) - (szImage.width() - x);
int yy = (shadowSize + parentRoundSize) - (szImage.height() - y);
int i = int(sqrt(double(xx * xx + yy * yy)));
i = std::min<int>(shadowSize + parentRoundSize, i);
i -= parentRoundSize;
i = shadowSize - i;
//
int alpha = MakeAlpha(i, f, shadowSize);
image.setPixelColor(x, y, QColor(0, 0, 0, alpha));
}
}
//
int borderR = 165;
int borderG = 165;
int borderB = 165;
//
if (activated) {
borderR = 68;
borderG = 138;
borderB = 255;
// borderR = 0;
// borderG = 0;
// borderB = 0;
}
//
int borderSize = 1;
//left
for (int i = 0; i < borderSize; i++) {
for (int y = shadowSize - 1; y < szImage.height() - shadowSize + 1; y++) {
int x = shadowSize - i - 1;
image.setPixelColor(x, y, QColor(borderR, borderG, borderB, 255));
}
}
//right
for (int i = 0; i < borderSize; i++) {
for (int y = shadowSize - 1; y < szImage.height() - shadowSize + 1; y++) {
int x = szImage.width() - shadowSize - 1 + i + 1;
image.setPixelColor(x, y, QColor(borderR, borderG, borderB, 255));
}
}
//top
for (int i = 0; i < borderSize; i++) {
for (int x = shadowSize; x < szImage.width() - shadowSize; x++) {
int y = shadowSize - i - 1;
image.setPixelColor(x, y, QColor(borderR, borderG, borderB, 255));
}
}
//bottom
for (int i = 0; i < borderSize; i++) {
for (int x = shadowSize; x < szImage.width() - shadowSize; x++) {
int y = szImage.height() - shadowSize - 1 + i + 1;
image.setPixelColor(x, y, QColor(borderR, borderG, borderB, 255));
}
}
//
return image;
}
调节阴影大小时,只需要调节shadowSize的大小即可。
ShadowWidget::ShadowWidget(int shadowSize, QWidget *parent)
: m_shadowSize(shadowSize)
, QWidget(parent)
, m_shadow(new Skin9GridImage())
{
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlag(Qt::FramelessWindowHint);
setMouseTracking(true);
//
QImage image = MakeShadowImage(shadowSize, true);
m_shadow->setImage(image, QPoint(shadowSize + 1, shadowSize + 1));
}
void ShadowWidget::paintEvent(QPaintEvent *e)
{
Q_UNUSED(e)
QPainter painter(this);
m_shadow->drawBorder(&painter, rect());
}
CSDN: https://download.csdn.net/download/a844651990/10841366
GitHub: https://github.com/FlyWM/ShadowWidget