说明

我们知道QWidget等设置了this->setWindowFlags(Qt::FramelessWindowHint);后无法移动和调整大小,但实际项目中是需要窗口能够调整大小的。所以以实现FrameLess弹窗调整大小需求,以此类推,移动窗口也就很简单了(这里没有实现)。
并且这里,还实现了QWidget的窗口阴影,因为FramelessWindow也没有窗口阴影。

代码

#ifndef SHADOWWIDGET_H
#define SHADOWWIDGET_H

#include <QWidget>

enum MouseDirection {
    DirectionLeft,
    DirectionRight,
    DirectionTop,
    DirectionBottom,
    DirectionTopLeft,
    DirectionTopRight,
    DirectionBottomLeft,
    DirectionBottomRight
};

class ShadowWidget : public QWidget
{
    Q_OBJECT
public:
    explicit ShadowWidget(QWidget *parent = nullptr, QWidget *subWidget = nullptr);

protected:
    bool event(QEvent *event) override;

    void paintEvent(QPaintEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;

    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
//    void mouseMoveEvent(QMouseEvent *event) override;

    void responseHoverMoveEvent(QHoverEvent *event);

signals:

public:
    uint32_t m_shadowWidthMargin;
    uint32_t m_shadowHeightMargin;
    uint32_t m_minWidth;
    uint32_t m_minHeight;
    uint32_t m_maxWidth;
    uint32_t m_maxHeight;
    QWidget *m_subWidget;

private:
    QPixmap m_shadowPixmap;
    bool m_bCanDrag;
    bool m_bMousePress;
    QPoint m_globalPointPressed;
    QRect m_geoPressed;
    MouseDirection m_curMousePressMoveDirection;
};

#endif // SHADOWWIDGET_H
#include "shadowwidget.h"

#include <QPainter>
#include <QResizeEvent>
#include <QApplication>
#include <QScreen>
#include <QDebug>

static QPixmap ninePatchScalePixmap(QPixmap *pix, int iHorzSplit, int iVertSplit, int DstWidth, int DstHeight)// 参考[1]
{
    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;
}

ShadowWidget::ShadowWidget(QWidget *parent, QWidget *subWidget) :
    QWidget(parent),
    m_shadowWidthMargin(5),
    m_shadowHeightMargin(5),
    m_subWidget(subWidget),
    m_shadowPixmap(":resource/shadow.png"),
    m_bCanDrag(false),
    m_bMousePress(false),
    m_globalPointPressed(QPoint()),
    m_minWidth(120),
    m_minHeight(120),
    m_maxWidth(INT32_MAX),
    m_maxHeight(INT32_MAX)
{
    this->setWindowFlags(Qt::Window | Qt::FramelessWindowHint);
    this->setAttribute(Qt::WA_TranslucentBackground, true);

    this->setAttribute(Qt::WA_Hover, true); // 开启Hover事件记录,替代mouseMoveEvent,但CPU代价更高
}

bool ShadowWidget::event(QEvent *event)
{
    if (QEvent::HoverMove == event->type())
    {
        QHoverEvent *hoverEvent = static_cast<QHoverEvent *>(event);
        this->responseHoverMoveEvent(hoverEvent);
        event->accept();
        return true;
    }
    return QWidget::event(event);
}

void ShadowWidget::paintEvent(QPaintEvent *event)
{
    QWidget::paintEvent(event);
    QPainter painter(this);
    painter.drawPixmap(this->rect(), m_shadowPixmap);
}

void ShadowWidget::resizeEvent(QResizeEvent *event)
{
    // 重新生成geometry()大小的图片
    m_shadowPixmap = ::ninePatchScalePixmap(&m_shadowPixmap, 10, 10,
                                            event->size().width(), event->size().height());

    // 重置subWidget大小
    if (m_subWidget)
    {
        QRect rect;
        rect.setTopLeft(QPoint(m_shadowWidthMargin, m_shadowWidthMargin));
        rect.setSize(QSize(this->geometry().width() - 2 * m_shadowWidthMargin,
                           this->geometry().height() - 2 * m_shadowHeightMargin));
        m_subWidget->setGeometry(rect);
    }

    QWidget::resizeEvent(event);
    this->repaint();
}

void ShadowWidget::mousePressEvent(QMouseEvent *event)
{
    if (m_bCanDrag)
    {
        m_bMousePress = true;
        m_globalPointPressed = event->globalPos();
        m_geoPressed = this->geometry();
    }
    return QWidget::mousePressEvent(event);
}

void ShadowWidget::mouseReleaseEvent(QMouseEvent *event)
{
    m_bMousePress = false;
    m_globalPointPressed = QPoint();
    m_geoPressed = QRect();

    return QWidget::mouseReleaseEvent(event);
}

void ShadowWidget::responseHoverMoveEvent(QHoverEvent *event)
{
    QPoint pt = event->pos();

    // 可拖拽区域鼠标拖拽,调整窗口大小
    if (m_bMousePress)
    {
        QPoint globalPt = QPoint(this->x() + pt.x(), this->y() + pt.y());
        double xDiff = globalPt.x() - m_globalPointPressed.x();
        double yDiff = globalPt.y() - m_globalPointPressed.y();
        QRect rect = m_geoPressed;

        if (m_curMousePressMoveDirection == DirectionLeft)
            rect.setX(rect.x() + xDiff);
        else if (m_curMousePressMoveDirection == DirectionRight)
            rect.setWidth(rect.width() + xDiff);
        else if (m_curMousePressMoveDirection == DirectionTop)
            rect.setY(rect.y() + yDiff);
        else if (m_curMousePressMoveDirection == DirectionBottom)
            rect.setHeight(rect.height() + yDiff);
        else if (m_curMousePressMoveDirection == DirectionTopLeft)
        {
            rect.setX(rect.x() + xDiff);
            rect.setY(rect.y() + yDiff);
        }
        else if (m_curMousePressMoveDirection == DirectionTopRight)
        {
            rect.setY(rect.y() + yDiff);
            rect.setWidth(rect.width() + xDiff);
        }
        else if (m_curMousePressMoveDirection == DirectionBottomLeft)
        {
            rect.setHeight(rect.height() + yDiff);
            rect.setX(rect.x() + xDiff);
        }
        else if (m_curMousePressMoveDirection == DirectionBottomRight)
        {
            rect.setHeight(rect.height() + yDiff);
            rect.setWidth(rect.width() + xDiff);
        }
        rect.setWidth(qMax<int>(qMin<int>(m_maxWidth, rect.width()), m_minWidth));
        rect.setHeight(qMax<int>(qMin<int>(m_maxHeight, rect.height()), m_minHeight));
        this->setGeometry(rect);
        return;
    }

    // 若鼠标在subWidget中则重置鼠标
    QRect subWidgetCannotDragRect = m_subWidget->geometry().adjusted(m_shadowWidthMargin,
                                                                     m_shadowHeightMargin,
                                                                     -m_shadowWidthMargin,
                                                                     -m_shadowHeightMargin);
    if (subWidgetCannotDragRect.contains(pt))
    {
        m_bCanDrag = false;
        QApplication::setOverrideCursor(QCursor(Qt::ArrowCursor));
        return;
    }

    m_bCanDrag = true;

    // 根据当前鼠标位置确定应该选择那种伸缩图标
    MouseDirection direction;
    if (pt.x() < 2 * m_shadowWidthMargin)
    {
        direction = DirectionLeft;
        if (pt.y() < 2 * m_shadowHeightMargin)
            direction = DirectionTopLeft;
        else if ((this->height() - pt.y()) < 2 * m_shadowHeightMargin)
            direction = DirectionBottomLeft;
    }
    else if ((this->width() - pt.x()) < 2 * m_shadowWidthMargin)
    {
        direction = DirectionRight;
        if (pt.y() < 2 * m_shadowHeightMargin)
            direction = DirectionTopRight;
        else if ((this->height() - pt.y()) < 2 * m_shadowHeightMargin)
            direction = DirectionBottomRight;
    }
    else if (pt.y() < 2 * m_shadowHeightMargin)
    {
        direction = DirectionTop;
        if (pt.x() < 2 * m_shadowWidthMargin)
            direction = DirectionTopLeft;
        else if ((this->width() - pt.x()) < 2 * m_shadowWidthMargin)
            direction = DirectionTopRight;
    }
    else if ((this->height() - pt.y()) < 2 * m_shadowHeightMargin)
    {
        direction = DirectionBottom;
        if (pt.x() < 2 * m_shadowWidthMargin)
            direction = DirectionBottomLeft;
        else if ((this->width() - pt.x()) < 2 * m_shadowWidthMargin)
            direction = DirectionBottomRight;
    }

    m_curMousePressMoveDirection = direction;

    switch (direction)
    {
    case DirectionLeft:
    case DirectionRight:
        QApplication::setOverrideCursor(QCursor(Qt::SizeHorCursor));
        break;
    case DirectionTop:
    case DirectionBottom:
        QApplication::setOverrideCursor(QCursor(Qt::SizeVerCursor));
        break;
    case DirectionTopLeft:
    case DirectionBottomRight:
        QApplication::setOverrideCursor(QCursor(Qt::SizeFDiagCursor));
        break;
    case DirectionTopRight:
    case DirectionBottomLeft:
        QApplication::setOverrideCursor(QCursor(Qt::SizeBDiagCursor));
        break;
    }
}

解释

  • m_minWidth、m_minHeight、m_maxWidth、m_maxHeight:防止窗口太大或太小。
  • m_shadowWidthMargin、m_shadowHeightMargin:记录窗口阴影的宽和高。
  • m_subWidget:用于中间放置主Widget(阴影弹窗作为后面的一个widget)。
  • m_shadowPixmap:阴影图。
  • m_bCanDrag:记录是否可调整大小。
  • m_bMousePress:记录是否在可调整大小的情况下,鼠标按下了。
  • m_globalPointPressed:用于记录按下时的全局坐标点。
  • m_geoPressed:记录按下时,阴影窗口的geometry。
  • m_curMousePressMoveDirection:记录拖拽方向。
  • enum MouseDirection:标记窗口方向,共有8个方向。

参考:
1

08-04 13:30