Qt FramelessWidget自定义无边框可伸缩窗口

效果如上所示, 系统默认的标题栏不太好看,如果要自定义样式的话还是要自己重写无边框的widget,但是自己写的没有缩放功能,这里就介绍如何自己实现拉伸功能

目录

FramelessWidget

1.重写FramelessWidget,继承于QWidget,我们需要定义一些枚举值来判断当前鼠标位置

2.设置鼠标追踪,并且设置距离窗口边缘多少个像素的时候触发

3.判断鼠标类型,当前处在什么位置

4.鼠标按下的时候记录坐标

5.根据鼠标类型改变鼠标样式

 6.设置窗口最大最小值

7.拉伸窗口

8.鼠标离开窗口范围

MainWindow

1.初始化

2.实现窗口拖动

3.最大化按钮

4.关联窗口大小改变事件

注意事项


FramelessWidget

1.重写FramelessWidget,继承于QWidget,我们需要定义一些枚举值来判断当前鼠标位置

    enum EAreaType
    {
        Area_Invalid,   // 窗口范围外
        Area_Middle,    // 窗口中间区域
        Area_Left,      // 窗口左边拉伸区域
        Area_Right,     // 窗口右边拉伸区域
        Area_Top,       // 窗口上方拉伸区域
        Area_TopLeft,   // 窗口左上角拉伸区域
        Area_TopRight,  // 窗口右上角拉伸区域
        Area_Bottom,        // 窗口底部拉伸区域
        Area_BottomLeft,    // 窗口左下角拉伸区域
        Area_BottomRight,   // 窗口右下角拉伸区域
    };

头文件

#ifndef FRAMELESSWIDGET_H
#define FRAMELESSWIDGET_H

#include <QWidget>
#include <Windows.h>
#include <QDebug>

class FramelessWidget : public QWidget
{
    Q_OBJECT
public:
    explicit FramelessWidget(QWidget *parent = nullptr);
    ~FramelessWidget();
    enum EAreaType
    {
        Area_Invalid,   // 窗口范围外
        Area_Middle,    // 窗口中间区域
        Area_Left,      // 窗口左边拉伸区域
        Area_Right,     // 窗口右边拉伸区域
        Area_Top,       // 窗口上方拉伸区域
        Area_TopLeft,   // 窗口左上角拉伸区域
        Area_TopRight,  // 窗口右上角拉伸区域
        Area_Bottom,        // 窗口底部拉伸区域
        Area_BottomLeft,    // 窗口左下角拉伸区域
        Area_BottomRight,   // 窗口右下角拉伸区域
    };
    void setStrethDelta(int delta);// 设置拉伸区域大小(距离窗口边缘多少个像素)
    void setMinAndMaxSize(QSize minSize,QSize maxSize);

protected:
    void mousePressEvent(QMouseEvent* e) override;  // 重写鼠标按下事件
    void mouseReleaseEvent(QMouseEvent* e) override;// 重写鼠释放事件
    void mouseMoveEvent(QMouseEvent* e) override;   // 重写鼠标移动事件
    void mouseMove(QPoint point);

private:
    EAreaType getAreaType(QPoint);// 获取位置所在的鼠标区域类型
    void printfAreaType(EAreaType);

signals:
    void resizeWindow(QRect rect);
    void moveWindow(QPoint point);
private:
    int _strethArea{};// 在离窗口边缘多少个像素会触发拉伸
    QPoint _clickPos;// 鼠标左键点击位置
    bool isClicked = false;
    Qt::CursorShape _cursorShape;// 记录原始鼠标类型
    EAreaType _areaType{Area_Invalid};// 鼠标当前所在区域类型
    bool isMaxWindow = true;//记录是否最大窗口

};

#endif // FRAMEWIDGET_H

2.设置鼠标追踪,并且设置距离窗口边缘多少个像素的时候触发

鼠标追踪这个非常重要,每一个子控件都需要设置,不然会出现鼠标移动进来样式没有恢复的情况,我这里设置距离3个像素的时候触发判断

    setMouseTracking(true);// 设置鼠标跟踪,不然只会在鼠标按下时才会触发鼠标移动事件
    setStrethDelta(3);

3.判断鼠标类型,当前处在什么位置

通过计算鼠标坐标距离边距的位置,设置对应的鼠标类型

FramelessWidget::EAreaType FramelessWidget::getAreaType(QPoint point)
{
    bool bTop = (point.y() <= _strethArea && point.y() >= -_strethArea);
    bool bBottom = (point.y() >= height()-_strethArea && point.y() <= height()+_strethArea);
    bool bLeft = (point.x() <= _strethArea && point.x() >= -_strethArea);
    bool bRight = (point.x() >= width()-_strethArea && point.x() <= width()+_strethArea);
    bool bVMid = (point.y() > _strethArea && point.y() < height()-_strethArea);
    bool bHMid = (point.x() > _strethArea && point.x() < width()-_strethArea);
    if (bHMid)
    {
        if (bVMid)
            return Area_Middle;
        else if (bTop)
            return Area_Top;
        else if (bBottom)
            return Area_Bottom;
    }
    else if (bLeft)
    {
        if (bVMid)
            return Area_Left;
        else if (bTop)
            return Area_TopLeft;
        else if (bBottom)
            return Area_BottomLeft;
    }
    else if (bRight)
    {
        if (bVMid)
            return Area_Right;
        else if (bTop)
            return Area_TopRight;
        else if (bBottom)
            return Area_BottomRight;
    }
    return Area_Invalid;
}

4.鼠标按下的时候记录坐标

void FramelessWidget::mousePressEvent(QMouseEvent *e)
{
    this->setFocus();
    if(e->button()==Qt::LeftButton)
    {
        _clickPos=e->pos();
    }
}

5.根据鼠标类型改变鼠标样式

如果为鼠标左键,则判断为鼠标按下并且拖动的情况,否则则是鼠标移动只改变样式

void FramelessWidget::mouseMoveEvent(QMouseEvent *e)
{
    if(e->buttons() & Qt::LeftButton)
    {
        mouseMove(e->pos());
    }
    else
    {
        _areaType = getAreaType(e->pos());
//        printfAreaType(_areaType);
        switch (_areaType)
        {
        case Area_TopLeft: {
            this->setCursor(Qt::SizeFDiagCursor);
        }break;
        case Area_Left: {
            this->setCursor(Qt::SizeHorCursor);
        }break;
        case Area_BottomLeft: {
            this->setCursor(Qt::SizeBDiagCursor);
        }break;
        case Area_TopRight: {
            this->setCursor(Qt::SizeBDiagCursor);
        }break;
        case Area_Right: {
            this->setCursor(Qt::SizeHorCursor);
        }break;
        case Area_BottomRight: {
            this->setCursor(Qt::SizeFDiagCursor);
        }break;
        case Area_Top: {
            this->setCursor(Qt::SizeVerCursor);
        }break;
        case Area_Middle: {
            this->setCursor(Qt::ArrowCursor);
        }break;
        case Area_Bottom: {
            this->setCursor(Qt::SizeVerCursor);
        }break;
        default: {
            this->setCursor(Qt::ArrowCursor);
        }break;
        }
    }
}

 6.设置窗口最大最小值

void FramelessWidget::setMinAndMaxSize(QSize minSize,QSize maxSize)
{
    setMinimumSize(minSize);
    setMaximumSize(maxSize);
}

7.拉伸窗口

这里加上了最大最小值的判断,让窗口不会无限增大和缩小

void FramelessWidget::mouseMove(QPoint point)
{
    QPoint globalPoint = mapToGlobal(QPoint(this->geometry().x(),this->geometry().y()));
    QRect rect(globalPoint.x(),globalPoint.y(),width(),height());
    QPoint delta = point - _clickPos;
    switch (_areaType)
    {
    case Area_Middle: {
//        move(pos() + delta);
//        emit moveWindow(globalPoint + delta);
    } break;
    case Area_Left: {
        int newWidth = rect.width() - delta.x();
        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setLeft(rect.left() + delta.x());
            rect.setWidth(newWidth);
            emit resizeWindow(rect);
        }
    } break;
    case Area_Right: {
        int newWidth = rect.width() + delta.x();
        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setRight(rect.right() + delta.x());
            rect.setWidth(newWidth);
            _clickPos.setX(_clickPos.x() + delta.x());
            emit resizeWindow(rect);
        }
    } break;
    case Area_Top: {
        int newHeight = rect.height() - delta.y();
        if (newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setTop(rect.top() + delta.y());
            rect.setHeight(newHeight);
            emit resizeWindow(rect);
        }
    } break;
    case Area_Bottom: {
        int newHeight = rect.height() + delta.y();
        if (newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setBottom(rect.bottom() + delta.y());
            rect.setHeight(newHeight);
            _clickPos.setY(_clickPos.y() + delta.y());
            emit resizeWindow(rect);
        }
    } break;
    case Area_TopLeft: {
        int newWidth = rect.width() - delta.x();
        int newHeight = rect.height() - delta.y();

        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setLeft(rect.left() + delta.x());
            rect.setWidth(newWidth);
        }
        if(newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setTop(rect.top() + delta.y());
            rect.setHeight(newHeight);
        }
        emit resizeWindow(rect);
    } break;
    case Area_TopRight: {
        int newWidth = rect.width() + delta.x();
        int newHeight = rect.height() - delta.y();
        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setRight(rect.right() + delta.x());
            rect.setWidth(newWidth);
            _clickPos.setX(_clickPos.x() + delta.x());
        }
        if(newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setTop(rect.top() + delta.y());
            rect.setHeight(newHeight);
        }
        emit resizeWindow(rect);
    }break;
    case Area_BottomLeft: {
        int newWidth = rect.width() - delta.x();
        int newHeight = rect.height() + delta.y();
        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setLeft(rect.left() + delta.x());
            rect.setWidth(newWidth);
        }
        if(newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setBottom(rect.bottom() + delta.y());
            rect.setHeight(newHeight);
            _clickPos.setY(_clickPos.y() + delta.y());
        }
        emit resizeWindow(rect);
    }break;
    case Area_BottomRight: {
        int newWidth = rect.width() + delta.x();
        int newHeight = rect.height() + delta.y();
        if (newWidth >= minimumWidth() && newWidth <= maximumWidth())
        {
            rect.setRight(rect.right()  + delta.x());
            rect.setWidth(newWidth);
            _clickPos.setX(_clickPos.x() + delta.x());
        }
        if(newHeight >= minimumHeight() && newHeight <= maximumHeight())
        {
            rect.setBottom(rect.bottom() + delta.y());
            rect.setHeight(newHeight);
            _clickPos.setY(_clickPos.y() + delta.y());
        }
        emit resizeWindow(rect);
    }break;
    default: {
        this->setCursor(Qt::ArrowCursor);
    } break;
    }
}

8.鼠标离开窗口范围

需要将鼠标恢复默认样式

void FramelessWidget::mouseReleaseEvent(QMouseEvent *e)
{
    if(e->button()==Qt::LeftButton)
    {
        _clickPos = e->pos();
        this->setCursor(Qt::ArrowCursor);
        _areaType = Area_Middle;
    }
    else
    {
        _areaType = getAreaType(e->pos());
//        printfAreaType(_areaType);
        switch (_areaType)
        {
        case Area_TopLeft: {
            this->setCursor(Qt::SizeFDiagCursor);
        }break;
        case Area_Left: {
            this->setCursor(Qt::SizeHorCursor);
        }break;
        case Area_BottomLeft: {
            this->setCursor(Qt::SizeBDiagCursor);
        }break;
        case Area_TopRight: {
            this->setCursor(Qt::SizeBDiagCursor);
        }break;
        case Area_Right: {
            this->setCursor(Qt::SizeHorCursor);
        }break;
        case Area_BottomRight: {
            this->setCursor(Qt::SizeFDiagCursor);
        }break;
        case Area_Top: {
            this->setCursor(Qt::SizeVerCursor);
        }break;
        case Area_Middle: {
            this->setCursor(Qt::ArrowCursor);
        }break;
        case Area_Bottom: {
            this->setCursor(Qt::SizeVerCursor);
        }break;
        default: {
            this->setCursor(Qt::ArrowCursor);
        }break;
        }
    }
}

MainWindow

1.初始化

将ui中的centralwidget提升为FramelessWidget,并且设置窗口为无边框窗口

void MainWindow::initUi()
{
    ui->title_lab->installEventFilter(this);
    QRect availableGeometry = QGuiApplication::primaryScreen()->availableGeometry();
    // top-padding:5    bottom-padding:5
    int maxHeight = availableGeometry.height()-(ui->title_widget->height()+10);
    int maxWidth = 1920;

    QSize maxSize(1920,availableGeometry.height());
    QSize minSize(800,(availableGeometry.height()/2));// 1/4屏幕
    setMaximumSize(maxSize);
    setMinimumSize(minSize);
    ui->centralwidget->setMinAndMaxSize(minSize,maxSize);
    ui->scrollAreaWidgetContents->setFixedSize(maxWidth,maxHeight);
    resize(maximumSize());
    setWindowFlags(Qt::FramelessWindowHint);//无边框
    connect(ui->centralwidget,&FramelessWidget::resizeWindow,this,[=](QRect rect){setGeometry(rect);});

}

2.实现窗口拖动

初始化的时候给标题安装了事件管理器,这里在事件管理器中实现窗口移动,以及双击最大化或恢复正常窗口

bool MainWindow::eventFilter(QObject *object, QEvent *evt)
{
    static QPoint mousePoint;
    static bool mousePressed = false;

    QMouseEvent *event = static_cast<QMouseEvent *>(evt);
    if(object->objectName()=="title_lab" && event->type() == QEvent::MouseButtonPress)
    {
        if (event->button() == Qt::LeftButton)
        {
            mousePressed = true;
            mousePoint = event->globalPos() - this->pos();
        }
    }
    else if (object->objectName()=="title_lab" && event->type() == QEvent::MouseButtonRelease)
    {
        mousePressed = false;
    }
    else if (object->objectName()=="title_lab" && event->type() == QEvent::MouseMove)
    {
        if (mousePressed && (event->buttons() & Qt::LeftButton))
        {
            this->move(event->globalPos() - mousePoint);
        }
    }
    else if (object->objectName()=="title_lab" && event->type() == QEvent::MouseButtonDblClick)
    {
        on_max_bt_clicked();
    }
    return QWidget::eventFilter(object, event);
}

3.最大化按钮

判断窗口是否是最大化的状态,不是的话就最大化,是的话就恢复默认

void MainWindow::on_max_bt_clicked()
{
    if(isMaxWindow)
    {
        // 获取屏幕的几何信息
        QScreen *primaryScreen = QGuiApplication::primaryScreen();
        QRect screenGeometry = primaryScreen->geometry();

        // 计算主窗口的位置
        int x = (screenGeometry.width() - 1280) / 2 ;
        int y = (screenGeometry.height() - 720) / 2 ;

        setGeometry(x,y,1280,720);
        ui->scrollArea->horizontalScrollBar()->setValue(ui->scrollArea->horizontalScrollBar()->maximum()/2);
    }
    else
    {
        setGeometry(0,0,maximumWidth(),maximumHeight());
    }
}

4.关联窗口大小改变事件

void MainWindow::resizeEvent(QResizeEvent *event)
{
    int maxHeight = QGuiApplication::primaryScreen()->availableGeometry().height() - (ui->title_widget->height() + 10) ;

    ui->max_bt->setIcon((width() < maximumWidth() || height() < maximumHeight()) ? QIcon(":/pic/max_window_white.png") : QIcon(":/pic/white_window.png"));
    isMaxWindow = (size() == maximumSize());
    if(isMaxWindow)ui->max_bt->setText("还原");
    else ui->max_bt->setText("最大化");

    ui->scrollArea->setHorizontalScrollBarPolicy( (width() < 1920 ) ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);
    ui->scrollArea->setVerticalScrollBarPolicy((height() < maxHeight) ? Qt::ScrollBarAsNeeded : Qt::ScrollBarAlwaysOff);

}

注意事项

  • 如果没有设置setMouseTracking(true);的话,鼠标会出现样式不对的情况,所有的子控件都要设置
  • 右边拉伸和右下角拉伸窗口不会有抖动,但是左上角的坐标如果改变了就可能会有抖动,这好像是qt自己的原因,不过就算用其他软件拉伸的时候也会有这个抖动现象

demo链接

【免费】QtFramelessWidget自定义无边框可伸缩窗口资源资源-CSDN文库

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值