Qt去掉标题栏之后添加边框阴影的解决方案

前言

  我们经常需要自定义标题栏,那么去掉标题栏是非常有必要。但是去掉标题栏之后边框阴影也会消失,感觉光秃秃的,不太舒服。接下来我们将讨论添加边框阴影的几种解决方案。
Qt去掉标题栏之后添加边框阴影的解决方案_第1张图片

解决方案

  1. 如果是Windows平台,那么可以调用Windows相关API。
  2. 使用Qt的QGraphicsDropShadowEffect类来实现。
  3. 使用Qt的qDrawBorderPixmap函数来实现。
  4. 自己构造出边框阴影QImage并绘制(参考为知笔记源码)。

源码实现

一、调用Windows相关API

  我们调用的是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"));
}

效果图:
Qt去掉标题栏之后添加边框阴影的解决方案_第2张图片

二、使用Qt的QGraphicsDropShadowEffect类来实现

  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);
}

效果图:
Qt去掉标题栏之后添加边框阴影的解决方案_第3张图片

三、使用Qt的qDrawBorderPixmap函数来实现

  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));
}

效果图:
Qt去掉标题栏之后添加边框阴影的解决方案_第4张图片

四、自己构造出边框阴影QImage并绘制

  这种方式稍微麻烦点,但是比较灵活,效率也很可观,推荐使用。代码有点多,先上主要的。

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());
}

效果图:
Qt去掉标题栏之后添加边框阴影的解决方案_第5张图片

源码下载

CSDN: https://download.csdn.net/download/a844651990/10841366
GitHub: https://github.com/FlyWM/ShadowWidget

你可能感兴趣的:(qt)