zoukankan      html  css  js  c++  java
  • Qt之自定义界面(窗体缩放-跨平台终极版)

    简述

    通过上一节内容,我们实现了窗体的缩放,功能很不错,但是很遗憾-不支持跨平台!如果对于多平台来说,这是一个硬伤,所以,我们急需要一个能够支持跨平台的实现方案。

    在网上看到过很多不同的实现方式,多多少少会存在一些问题-要么融合度太高、要么不能很好地进行移动、缩放。基于前人的分享与总结,最后,我花了很长时间来完善。独乐乐不如众乐乐,既然纯开源-那就全部分享出来。

    效果

    这里写图片描述

    窗体缩放

    实现

    frameless_helper.h

    #ifndef FRAMELESS_HELPER_H
    #define FRAMELESS_HELPER_H
    
    #include <QObject>
    
    class QWidget;
    class FramelessHelperPrivate;
    
    class FramelessHelper : public QObject
    {
        Q_OBJECT
    
    public:
        explicit FramelessHelper(QObject *parent = 0);
        ~FramelessHelper();
        // 激活窗体
        void activateOn(QWidget *topLevelWidget);
        // 移除窗体
        void removeFrom(QWidget *topLevelWidget);
        // 设置窗体移动
        void setWidgetMovable(bool movable);
        // 设置窗体缩放
        void setWidgetResizable(bool resizable);
        // 设置橡皮筋移动
        void setRubberBandOnMove(bool movable);
        // 设置橡皮筋缩放
        void setRubberBandOnResize(bool resizable);
        // 设置边框的宽度
        void setBorderWidth(uint width);
        // 设置标题栏高度
        void setTitleHeight(uint height);
        bool widgetResizable();
        bool widgetMovable();
        bool rubberBandOnMove();
        bool rubberBandOnResisze();
        uint borderWidth();
        uint titleHeight();
    
    protected:
        // 事件过滤,进行移动、缩放等
        virtual bool eventFilter(QObject *obj, QEvent *event);
    
    private:
        FramelessHelperPrivate *d;
    };
    
    #endif //FRAMELESS_HELPER_H
    

    frameless_helper.cpp

    FramelessHelperPrivate类

    /*****
     * FramelessHelperPrivate
     * 存储界面对应的数据集合,以及是否可移动、可缩放属性
    *****/
    class FramelessHelperPrivate
    {
    public:
        QHash<QWidget*, WidgetData*> m_widgetDataHash;
        bool m_bWidgetMovable        : true;
        bool m_bWidgetResizable      : true;
        bool m_bRubberBandOnResize   : true;
        bool m_bRubberBandOnMove     : true;
    };

    CursorPosCalculator类

    /*****
     * CursorPosCalculator
     * 计算鼠标是否位于左、上、右、下、左上角、左下角、右上角、右下角
    *****/
    class CursorPosCalculator
    {
    public:
        explicit CursorPosCalculator();
        void reset();
        void recalculate(const QPoint &globalMousePos, const QRect &frameRect);
    
    public:
        bool m_bOnEdges              : true;
        bool m_bOnLeftEdge           : true;
        bool m_bOnRightEdge          : true;
        bool m_bOnTopEdge            : true;
        bool m_bOnBottomEdge         : true;
        bool m_bOnTopLeftEdge        : true;
        bool m_bOnBottomLeftEdge     : true;
        bool m_bOnTopRightEdge       : true;
        bool m_bOnBottomRightEdge    : true;
    
        static int m_nBorderWidth;
        static int m_nTitleHeight;
    };
    int CursorPosCalculator::m_nBorderWidth = 5;
    int CursorPosCalculator::m_nTitleHeight = 30;
    
    /***** CursorPosCalculator *****/
    CursorPosCalculator::CursorPosCalculator()
    {
        reset();
    }
    
    void CursorPosCalculator::reset()
    {
        m_bOnEdges = false;
        m_bOnLeftEdge = false;
        m_bOnRightEdge = false;
        m_bOnTopEdge = false;
        m_bOnBottomEdge = false;
        m_bOnTopLeftEdge = false;
        m_bOnBottomLeftEdge = false;
        m_bOnTopRightEdge  = false;
        m_bOnBottomRightEdge = false;
    }
    
    void CursorPosCalculator::recalculate(const QPoint &gMousePos, const QRect &frameRect)
    {
        int globalMouseX = gMousePos.x();
        int globalMouseY = gMousePos.y();
    
        int frameX = frameRect.x();
        int frameY = frameRect.y();
    
        int frameWidth = frameRect.width();
        int frameHeight = frameRect.height();
    
        m_bOnLeftEdge = (globalMouseX >= frameX &&
                      globalMouseX <= frameX + m_nBorderWidth );
    
    
        m_bOnRightEdge = (globalMouseX >= frameX + frameWidth - m_nBorderWidth &&
                       globalMouseX <= frameX + frameWidth);
    
        m_bOnTopEdge = (globalMouseY >= frameY &&
                     globalMouseY <= frameY + m_nBorderWidth );
    
        m_bOnBottomEdge = (globalMouseY >= frameY + frameHeight - m_nBorderWidth &&
                        globalMouseY <= frameY + frameHeight);
    
        m_bOnTopLeftEdge = m_bOnTopEdge && m_bOnLeftEdge;
        m_bOnBottomLeftEdge = m_bOnBottomEdge && m_bOnLeftEdge;
        m_bOnTopRightEdge = m_bOnTopEdge && m_bOnRightEdge;
        m_bOnBottomRightEdge = m_bOnBottomEdge && m_bOnRightEdge;
    
        m_bOnEdges = m_bOnLeftEdge || m_bOnRightEdge || m_bOnTopEdge || m_bOnBottomEdge;
    }

    WidgetData类

    /*****
     * WidgetData
     * 更新鼠标样式、移动窗体、缩放窗体
    *****/
    class WidgetData
    {
    public:
        explicit WidgetData(FramelessHelperPrivate *d, QWidget *pTopLevelWidget);
        ~WidgetData();
        QWidget* widget();
        // 处理鼠标事件-划过、按下、释放、移动
        void handleWidgetEvent(QEvent *event);
        // 更新橡皮筋状态
        void updateRubberBandStatus();
    
    private:
        // 更新鼠标样式
        void updateCursorShape(const QPoint &gMousePos);
        // 重置窗体大小
        void resizeWidget(const QPoint &gMousePos);
        // 移动窗体
        void moveWidget(const QPoint &gMousePos);
        // 处理鼠标按下
        void handleMousePressEvent(QMouseEvent *event);
        // 处理鼠标释放
        void handleMouseReleaseEvent(QMouseEvent *event);
        // 处理鼠标移动
        void handleMouseMoveEvent(QMouseEvent *event);
        // 处理鼠标离开
        void handleLeaveEvent(QEvent *event);
        // 处理鼠标进入
        void handleHoverMoveEvent(QHoverEvent *event);
    
    private:
        FramelessHelperPrivate *d;
        QRubberBand *m_pRubberBand;
        QWidget *m_pWidget;
        QPoint m_ptDragPos;
        CursorPosCalculator m_pressedMousePos;
        CursorPosCalculator m_moveMousePos;
        bool m_bLeftButtonPressed;
        bool m_bCursorShapeChanged;
        bool m_bLeftButtonTitlePressed;
        Qt::WindowFlags m_windowFlags;
    };
    /***** WidgetData *****/
    WidgetData::WidgetData(FramelessHelperPrivate *_d, QWidget *pTopLevelWidget)
    {
        d = _d;
        m_pWidget = pTopLevelWidget;
        m_bLeftButtonPressed = false;
        m_bCursorShapeChanged = false;
        m_bLeftButtonTitlePressed = false;
        m_pRubberBand = NULL;
    
        m_windowFlags = m_pWidget->windowFlags();
        m_pWidget->setMouseTracking(true);
        m_pWidget->setAttribute(Qt::WA_Hover, true);
    
        updateRubberBandStatus();
    }
    
    WidgetData::~WidgetData()
    {
        m_pWidget->setMouseTracking(false);
        m_pWidget->setWindowFlags(m_windowFlags);
        m_pWidget->setAttribute(Qt::WA_Hover, false);
    
        delete m_pRubberBand;
        m_pRubberBand = NULL;
    }
    
    QWidget* WidgetData::widget()
    {
        return m_pWidget;
    }
    
    void WidgetData::handleWidgetEvent(QEvent *event)
    {
        switch (event->type())
        {
        default:
            break;
        case QEvent::MouseButtonPress:
            handleMousePressEvent(static_cast<QMouseEvent*>(event));
            break;
        case QEvent::MouseButtonRelease:
            handleMouseReleaseEvent(static_cast<QMouseEvent*>(event));
            break;
        case QEvent::MouseMove:
            handleMouseMoveEvent(static_cast<QMouseEvent*>(event));
            break;
        case QEvent::Leave:
            handleLeaveEvent(static_cast<QMouseEvent*>(event));
            break;
        case QEvent::HoverMove:
            handleHoverMoveEvent(static_cast<QHoverEvent*>(event));
            break;
        }
    }
    
    void WidgetData::updateRubberBandStatus()
    {
        if (d->m_bRubberBandOnMove || d->m_bRubberBandOnResize)
        {
            if (NULL == m_pRubberBand)
                m_pRubberBand = new QRubberBand(QRubberBand::Rectangle);
        }
        else
        {
            delete m_pRubberBand;
            m_pRubberBand = NULL;
        }
    }
    
    void WidgetData::updateCursorShape(const QPoint &gMousePos)
    {
        if (m_pWidget->isFullScreen() || m_pWidget->isMaximized())
        {
            if (m_bCursorShapeChanged)
            {
                m_pWidget->unsetCursor();
            }
            return;
        }
    
        m_moveMousePos.recalculate(gMousePos, m_pWidget->frameGeometry());
    
        if(m_moveMousePos.m_bOnTopLeftEdge || m_moveMousePos.m_bOnBottomRightEdge)
        {
            m_pWidget->setCursor( Qt::SizeFDiagCursor );
            m_bCursorShapeChanged = true;
        }
        else if(m_moveMousePos.m_bOnTopRightEdge || m_moveMousePos.m_bOnBottomLeftEdge)
        {
            m_pWidget->setCursor( Qt::SizeBDiagCursor );
            m_bCursorShapeChanged = true;
        }
        else if(m_moveMousePos.m_bOnLeftEdge || m_moveMousePos.m_bOnRightEdge)
        {
            m_pWidget->setCursor( Qt::SizeHorCursor );
            m_bCursorShapeChanged = true;
        }
        else if(m_moveMousePos.m_bOnTopEdge || m_moveMousePos.m_bOnBottomEdge)
        {
            m_pWidget->setCursor( Qt::SizeVerCursor );
            m_bCursorShapeChanged = true;
        }
        else
        {
            if (m_bCursorShapeChanged)
            {
                m_pWidget->unsetCursor();
                m_bCursorShapeChanged = false;
            }
        }
    }
    
    void WidgetData::resizeWidget(const QPoint &gMousePos)
    {
        QRect origRect;
    
        if (d->m_bRubberBandOnResize)
            origRect = m_pRubberBand->frameGeometry();
        else
            origRect = m_pWidget->frameGeometry();
    
        int left = origRect.left();
        int top = origRect.top();
        int right = origRect.right();
        int bottom = origRect.bottom();
        origRect.getCoords(&left, &top, &right, &bottom);
    
        int minWidth = m_pWidget->minimumWidth();
        int minHeight = m_pWidget->minimumHeight();
    
        if (m_pressedMousePos.m_bOnTopLeftEdge)
        {
            left = gMousePos.x();
            top = gMousePos.y();
        }
        else if (m_pressedMousePos.m_bOnBottomLeftEdge)
        {
            left = gMousePos.x();
            bottom = gMousePos.y();
        }
        else if (m_pressedMousePos.m_bOnTopRightEdge)
        {
            right = gMousePos.x();
            top = gMousePos.y();
        }
        else if (m_pressedMousePos.m_bOnBottomRightEdge)
        {
            right = gMousePos.x();
            bottom = gMousePos.y();
        }
        else if (m_pressedMousePos.m_bOnLeftEdge)
        {
            left = gMousePos.x();
        }
        else if (m_pressedMousePos.m_bOnRightEdge)
        {
            right = gMousePos.x();
        }
        else if (m_pressedMousePos.m_bOnTopEdge)
        {
            top = gMousePos.y();
        }
        else if (m_pressedMousePos.m_bOnBottomEdge)
        {
            bottom = gMousePos.y();
        }
    
        QRect newRect(QPoint(left, top), QPoint(right, bottom));
    
        if (newRect.isValid())
        {
            if (minWidth > newRect.width())
            {
                if (left != origRect.left())
                    newRect.setLeft(origRect.left());
                else
                    newRect.setRight(origRect.right());
            }
            if (minHeight > newRect.height())
            {
                if (top != origRect.top())
                    newRect.setTop(origRect.top());
                else
                    newRect.setBottom(origRect.bottom());
            }
    
            if (d->m_bRubberBandOnResize)
            {
                m_pRubberBand->setGeometry(newRect);
            }
            else
            {
                m_pWidget->setGeometry(newRect);
            }
        }
    }
    
    void WidgetData::moveWidget(const QPoint& gMousePos)
    {
        if (d->m_bRubberBandOnMove)
        {
            m_pRubberBand->move(gMousePos - m_ptDragPos);
        }
        else
        {
            m_pWidget->move(gMousePos - m_ptDragPos);
        }
    }
    
    void WidgetData::handleMousePressEvent(QMouseEvent *event)
    {
        if (event->button() == Qt::LeftButton)
        {
            m_bLeftButtonPressed = true;
            m_bLeftButtonTitlePressed = event->pos().y() < m_moveMousePos.m_nTitleHeight;
    
            QRect frameRect = m_pWidget->frameGeometry();
            m_pressedMousePos.recalculate(event->globalPos(), frameRect);
    
            m_ptDragPos = event->globalPos() - frameRect.topLeft();
    
            if (m_pressedMousePos.m_bOnEdges)
            {
                if (d->m_bRubberBandOnResize)
                {
                    m_pRubberBand->setGeometry(frameRect);
                    m_pRubberBand->show();
                }
            }
            else if (d->m_bRubberBandOnMove)
            {
                m_pRubberBand->setGeometry(frameRect);
                m_pRubberBand->show();
            }
        }
    }
    
    void WidgetData::handleMouseReleaseEvent(QMouseEvent *event)
    {
        if (event->button() == Qt::LeftButton)
        {
            m_bLeftButtonPressed = false;
            m_bLeftButtonTitlePressed = false;
            m_pressedMousePos.reset();
            if (m_pRubberBand && m_pRubberBand->isVisible())
            {
                m_pRubberBand->hide();
                m_pWidget->setGeometry(m_pRubberBand->geometry());
            }
        }
    }
    
    void WidgetData::handleMouseMoveEvent(QMouseEvent *event)
    {
        if (m_bLeftButtonPressed)
        {
            if (d->m_bWidgetResizable && m_pressedMousePos.m_bOnEdges)
            {
                resizeWidget(event->globalPos());
            }
            else if (d->m_bWidgetMovable && m_bLeftButtonTitlePressed)
            {
                moveWidget(event->globalPos());
            }
        }
        else if (d->m_bWidgetResizable)
        {
            updateCursorShape(event->globalPos());
        }
    }
    
    void WidgetData::handleLeaveEvent(QEvent *event)
    {
        Q_UNUSED(event)
        if (!m_bLeftButtonPressed)
        {
            m_pWidget->unsetCursor();
        }
    }
    
    void WidgetData::handleHoverMoveEvent(QHoverEvent *event)
    {
        if (d->m_bWidgetResizable)
        {
            updateCursorShape(m_pWidget->mapToGlobal(event->pos()));
        }
    }

    FramelessHelper类

    #include <QRect>
    #include <QRubberBand>
    #include <QMouseEvent>
    #include <QHoverEvent>
    #include <QApplication>
    #include "frameless_helper.h"
    
    class WidgetData;
    
    /*****FramelessHelper*****/
    FramelessHelper::FramelessHelper(QObject *parent)
        : QObject(parent),
          d(new FramelessHelperPrivate())
    {
        d->m_bWidgetMovable = true;
        d->m_bWidgetResizable = true;
        d->m_bRubberBandOnResize = false;
        d->m_bRubberBandOnMove = false;
    }
    
    FramelessHelper::~FramelessHelper()
    {
        QList<QWidget*> keys = d->m_widgetDataHash.keys();
        int size = keys.size();
        for (int i = 0; i < size; ++i)
        {
            delete d->m_widgetDataHash.take(keys[i]);
        }
    
        delete d;
    }
    
    bool FramelessHelper::eventFilter(QObject *obj, QEvent *event)
    {
        switch (event->type())
        {
        case QEvent::MouseMove:
        case QEvent::HoverMove:
        case QEvent::MouseButtonPress:
        case QEvent::MouseButtonRelease:
        case QEvent::Leave:
        {
            WidgetData *data = d->m_widgetDataHash.value(static_cast<QWidget*>(obj));
            if (data)
            {
                data->handleWidgetEvent(event);
                return true;
            }
        }
        }
        return QObject::eventFilter(obj, event);
    }
    
    void FramelessHelper::activateOn(QWidget *topLevelWidget)
    {
        if (!d->m_widgetDataHash.contains(topLevelWidget))
        {
            WidgetData *data = new WidgetData(d, topLevelWidget);
            d->m_widgetDataHash.insert(topLevelWidget, data);
    
            topLevelWidget->installEventFilter(this);
        }
    }
    
    void FramelessHelper::removeFrom(QWidget *topLevelWidget)
    {
        WidgetData *data = d->m_widgetDataHash.take(topLevelWidget);
        if (data)
        {
            topLevelWidget->removeEventFilter(this);
            delete data;
        }
    }
    
    void FramelessHelper::setRubberBandOnMove(bool movable)
    {
        d->m_bRubberBandOnMove = movable;
        QList<WidgetData*> list = d->m_widgetDataHash.values();
        foreach (WidgetData *data, list)
        {
            data->updateRubberBandStatus();
        }
    }
    
    void FramelessHelper::setWidgetMovable(bool movable)
    {
        d->m_bWidgetMovable = movable;
    }
    
    void FramelessHelper::setWidgetResizable(bool resizable)
    {
        d->m_bWidgetResizable = resizable;
    }
    
    void FramelessHelper::setRubberBandOnResize(bool resizable)
    {
        d->m_bRubberBandOnResize = resizable;
        QList<WidgetData*> list = d->m_widgetDataHash.values();
        foreach (WidgetData *data, list)
        {
            data->updateRubberBandStatus();
        }
    }
    
    void FramelessHelper::setBorderWidth(uint width)
    {
        if (width > 0)
        {
            CursorPosCalculator::m_nBorderWidth = width;
        }
    }
    
    void FramelessHelper::setTitleHeight(uint height)
    {
        if (height > 0)
        {
            CursorPosCalculator::m_nTitleHeight = height;
        }
    }
    
    bool FramelessHelper::widgetMovable()
    {
        return d->m_bWidgetMovable;
    }
    
    bool FramelessHelper::widgetResizable()
    {
        return d->m_bWidgetResizable;
    }
    
    bool FramelessHelper::rubberBandOnMove()
    {
        return d->m_bRubberBandOnMove;
    }
    
    bool FramelessHelper::rubberBandOnResisze()
    {
        return d->m_bRubberBandOnResize;
    }
    
    uint FramelessHelper::borderWidth()
    {
        return CursorPosCalculator::m_nBorderWidth;
    }
    
    uint FramelessHelper::titleHeight()
    {
        return CursorPosCalculator::m_nTitleHeight;
    }

    接口说明

    • FramelessHelperPrivate

      存储界面对应的数据集合,以及是否可移动、可缩放属性

    • CursorPosCalculator

      计算鼠标是否位于左、上、右、下、左上角、左下角、右上角、右下角

    • WidgetData

      更新鼠标样式、移动窗体、缩放窗体

    • FramelessHelper

      激活窗体、移除窗体、设置窗体移动、窗体缩放、橡皮筋移动、橡皮筋缩放、边框的宽度、标题栏高度等

    代码很多,我就不详细解答了,里面主要的接口我都添加了注释。其它接口的命名也是比较规范的-见名知义。

    使用方式

    这里的this指的是要处理的窗体。

    FramelessHelper *pHelper = new FramelessHelper(this);
    pHelper->activateOn(this);  //激活当前窗体
    pHelper->setTitleHeight(m_pTitleBar->height());  //设置窗体的标题栏高度
    pHelper->setWidgetMovable(true);  //设置窗体可移动
    pHelper->setWidgetResizable(true);  //设置窗体可缩放
    pHelper->setRubberBandOnMove(true);  //设置橡皮筋效果-可移动
    pHelper->setRubberBandOnResize(true);  //设置橡皮筋效果-可缩放

    平台支持

    因为使用的是纯Qt实现,所以支持跨平台!Win7、Win10、Redhat7.0已测试通过,其它平台尚未测试,有需要的童鞋可自行实验。

  • 相关阅读:
    TextView 显示内容时出现 ArrayIndexOutOfBoundsException 的解决方法(Android 4.1)
    Android Activity 启动模式和任务栈
    Android 坐标系和 MotionEvent 分析、滑动
    Android 控件架构及View、ViewGroup的测量
    Android Studio 3.0 及以上版本使用技巧总结
    移动端开发网络优化建议
    Netty系列之Netty高性能之道
    并发框架Disruptor译文
    Python轻量Web框架Flask使用
    修改MySQL的时区,涉及参数time_zone
  • 原文地址:https://www.cnblogs.com/itrena/p/5938392.html
Copyright © 2011-2022 走看看