Qt之使用QPainter自绘实现窗口阴影边框

一、简述

今天要说的是如何通过自绘的方式实现窗口的阴影边框,在之前的文章里Qt之使用QGraphicsDropShadowEffect添加窗口边框以及文字阴影效果 ,讲述到使用Qt的 QGraphicsDropShadowEffect类给窗口或者控件加阴影效果,在显示上效果也不错,但是使用上貌似会有一些问题,之前在项目中使用QGraphicsDropShadowEffect还挺多的,但是会遇到窗口中控件输入框不自动刷新的问题,不知道有没有小伙伴遇到这样的问题。

虽然QGraphicsDropShadowEffect在效果上和参数设置上还是很不错的,但是会存在窗口控件不刷新的问题(不知道是不是个例),索性就自己绘制一个得了。


二、代码之路

方案一

第一种:简单而通用的方法就是通过QPainterdrawRoundedRect方法,给画笔设置成半透明的灰色,然后一圈一圈进行阴影的绘制,具体效果也不错,阴影边框也可自由设置宽度,颜色。可以绘制圆角的阴影边框和方角的阴影边框

方角阴影边框

Qt之使用QPainter自绘实现窗口阴影边框_第1张图片

圆角阴影边框

Qt之使用QPainter自绘实现窗口阴影边框_第2张图片

绘制方角就用drawRect,绘制圆角就用drawRounderRect

#define SHADOW_WIDTH 10		// 阴影边框宽度;

MyShadowWidget::MyShadowWidget(QWidget *parent)
    : QWidget(parent)
{
    this->setWindowFlags(Qt::FramelessWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground);
}

void MyShadowWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing, true);
    painter.fillRect(QRect(SHADOW_WIDTH, SHADOW_WIDTH, this->width() - 2 * SHADOW_WIDTH, this->height() - 2 * SHADOW_WIDTH), QBrush(Qt::white));

    QColor color(50, 50, 50, 30);
    for (int i = 0; i < SHADOW_WIDTH; i++)
    {
        color.setAlpha(120 - qSqrt(i) * 40);
        painter.setPen(color);
        // 方角阴影边框;
     //   painter.drawRect(SHADOW_WIDTH - i, SHADOW_WIDTH - i, this->width() - (SHADOW_WIDTH - i) * 2, this->height() - (SHADOW_WIDTH - i) * 2);
        // 圆角阴影边框;
        painter.drawRoundedRect(SHADOW_WIDTH - i, SHADOW_WIDTH - i, this->width() - (SHADOW_WIDTH - i) * 2, this->height() - (SHADOW_WIDTH - i) * 2, 4, 4);
    }
}

Qt之使用QPainter自绘实现窗口阴影边框_第3张图片
Qt之使用QPainter自绘实现窗口阴影边框_第4张图片


方案二

使用九宫图法,一去二三里也曾使用过,不过他是用了九张图片绘制窗口边框的九个部位,具体文章可见 Qt之阴影边框,而下面要讲的是用一张图绘制。

(1)、一去二三里九图拼凑法

Qt之使用QPainter自绘实现窗口阴影边框_第5张图片


(2)、九宫格缩放阴影图片法

Qt之使用QPainter自绘实现窗口阴影边框_第6张图片Qt之使用QPainter自绘实现窗口阴影边框_第7张图片

其实原理就是只需要一张阴影边框的图片,就可以给不同尺寸的窗口绘制出同样效果的阴影边框来,具体九宫格缩放阴影图片的方法参考了灿哥哥的博客(Qt使用九宫格原理缩放图片)。

原理如下图,说白了就是把阴影边框图中带阴影的部分进行一定的放缩。上图只是改变了窗口的大小,用了同一张阴影边框图片,解决了因为不同尺寸窗口需要制作不同尺寸阴影图片的问题。

Qt之使用QPainter自绘实现窗口阴影边框_第8张图片

九宫格缩放图片接口
QPixmap ninePatchScalePixmap(QString picName, int iHorzSplit, int iVertSplit, int DstWidth, int DstHeight)
{
    QPixmap* pix = new QPixmap(picName);

    int pixWidth = pix->width();
    int pixHeight = pix->height();

    QPixmap pix_1 = pix->copy(0, 0, iHorzSplit, iVertSplit);
    QPixmap pix_2 = pix->copy(iHorzSplit, 0, pixWidth - iHorzSplit * 2, iVertSplit);
    QPixmap pix_3 = pix->copy(pixWidth - iHorzSplit, 0, iHorzSplit, iVertSplit);

    QPixmap pix_4 = pix->copy(0, iVertSplit, iHorzSplit, pixHeight - iVertSplit * 2);
    QPixmap pix_5 = pix->copy(iHorzSplit, iVertSplit, pixWidth - iHorzSplit * 2, pixHeight - iVertSplit * 2);
    QPixmap pix_6 = pix->copy(pixWidth - iHorzSplit, iVertSplit, iHorzSplit, pixHeight - iVertSplit * 2);

    QPixmap pix_7 = pix->copy(0, pixHeight - iVertSplit, iHorzSplit, iVertSplit);
    QPixmap pix_8 = pix->copy(iHorzSplit, pixHeight - iVertSplit, pixWidth - iHorzSplit * 2, pixWidth - iHorzSplit * 2);
    QPixmap pix_9 = pix->copy(pixWidth - iHorzSplit, pixHeight - iVertSplit, iHorzSplit, iVertSplit);

    pix_2 = pix_2.scaled(DstWidth - iHorzSplit * 2, iVertSplit, Qt::IgnoreAspectRatio);//保持高度拉宽;
    pix_4 = pix_4.scaled(iHorzSplit, DstHeight - iVertSplit * 2, Qt::IgnoreAspectRatio);//保持宽度拉高;
    pix_5 = pix_5.scaled(DstWidth - iHorzSplit * 2, DstHeight - iVertSplit * 2, Qt::IgnoreAspectRatio);//宽高都缩放;
    pix_6 = pix_6.scaled(iHorzSplit, DstHeight - iVertSplit * 2, Qt::IgnoreAspectRatio);//保持宽度拉高;
    pix_8 = pix_8.scaled(DstWidth - iHorzSplit * 2, iVertSplit);//保持高度拉宽;


    QPixmap resultImg(DstWidth, DstHeight);
    // 需设置背景透明;
    resultImg.fill(Qt::transparent);
    QPainter* painter = new QPainter(&resultImg);
    if (!resultImg.isNull()) {
        painter->drawPixmap(0, 0, pix_1);
        painter->drawPixmap(iHorzSplit, 0, pix_2);
        painter->drawPixmap(DstWidth - iHorzSplit, 0, pix_3);

        painter->drawPixmap(0, iVertSplit, pix_4);
        painter->drawPixmap(iHorzSplit, iVertSplit, pix_5);
        painter->drawPixmap(DstWidth - iHorzSplit, iVertSplit, pix_6);

        painter->drawPixmap(0, DstHeight - iVertSplit, pix_7);
        painter->drawPixmap(iHorzSplit, DstHeight - iVertSplit, pix_8);
        painter->drawPixmap(DstWidth - iHorzSplit, DstHeight - iVertSplit, pix_9);
        painter->end();
    }

    return resultImg;
}
绘制代码

代码比较简单,直接获取对应阴影边框图片,Then Draw It !!!

// 每次窗口发生变化,就重新生成一张背景图,适用于可拉伸的窗口,也适用于未指定大小的窗口;
void MyShadowWidget::resizeEvent(QResizeEvent *event)
{
    m_shadowBackPixmap = ninePatchScalePixmap(":/MyShadowWidget/Resources/ShadowImage.png",
        SHADOW_WIDTH, SHADOW_WIDTH, this->width(), this->height());

    return QWidget::resizeEvent(event);
}

// 直接绘制阴影边框即可;
void MyShadowWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.drawPixmap(this->rect(), m_shadowBackPixmap);
    // 绘制白色背景,因为窗口背景设置为透明;
    painter.fillRect(this->rect().adjusted(SHADOW_WIDTH, SHADOW_WIDTH, -SHADOW_WIDTH, -SHADOW_WIDTH) , QBrush(Qt::white));
}

注意事项

1、注意阴影边框图片的阴影宽度要还是要和 SHADOW_WIDTH大致相同最好,这样放缩出来效果肯定比较好的啦。我这里使用的阴影边框图片大小为 1000 * 500,阴影边框为15的样子。

2、如果给窗口使用布局的时候要注意设置最外层布局的 margin >= SHADOW_WIDTH,否则控件就在阴影边框上了。

	QVBoxLayout* vMainLayout = new QVBoxLayout(this);
	vMainLayout->setMargin(SHADOW_WIDTH);

下面把这个方法应用到可拉伸的窗口上,显示效果上没有什么问题,也没有什么卡顿。但是重新绘制出一张图片的时候确实需要消耗一点cpu,不过也是在每次窗口尺寸发生变化的时候才会重新制作一个阴影边框。

Qt之使用QPainter自绘实现窗口阴影边框_第9张图片


综上

第一种方案直接使用Qt自身进行绘制,不依赖任何图片,边框颜色,宽度都可自由设置,但是显示效果与第二种方案相比确实会差一些。

第二种方案优点就是显示效果上会好一些,只需一张图片即可为不同尺寸的窗口服务,前提是要有一张合适的阴影边框图片,不过这也不是难事,美工分分钟估计就能做好一张。

具体哪种方案小伙伴可自由选择,也可加群311750285或者下方评论进行交流。

你可能感兴趣的:(Qt,Qt自绘)