Qt編寫自定義控件67-通用無邊框

1、前言

在以前的一篇文章中寫過一個通用的移動控件,做用就是用來傳入任意的widget控件,能夠在父類容器中自由移動。本篇文章要寫的是一個通用的無邊框類,確切的說這不叫控件應該叫組件纔對,控件是要看得見的東西,有繪製需求的,而這個須要依附在控件上。在咱們平時作的項目中,爲了U界面的美觀,不少會採用自定義無邊框窗體來處理,本身美化標題欄等,都會面臨一個相同的問題,就是設置好自定義無邊框窗體之後,要本身處理窗體的移動和拉伸,若是有多個無邊框窗體,不少人會想到每一個地方都寫重複的代碼來實現移動或者拉伸,何不封裝一個類來完成這個功能呢,直接傳入窗體便可。在QDialog窗體中能夠經過設置一個屬性sizeGripEnabled來實現右下角的拉伸,這個還不足以知足全部的需求,不少時候咱們還須要在四個角和上下左右都能拉伸大小,這個就須要重寫了,安裝事件過濾器,識別到鼠標移動到某個區域,鼠標形狀自動變化,而後識別是否已經按下,按下的話作對應的處理,對應的處理核心就是從新設置窗體的XY軸座標和大小。linux

開源地址:https://gitee.com/feiyangqingyun/QWidgetDemo https://github.com/feiyangqingyun/QWidgetDemoc++

2、實現的功能

  • 1:能夠指定須要無邊框的widget
  • 2:邊框四周八個方位均可以自由拉伸
  • 3:可設置對應位置的邊距,以便識別更大區域
  • 4:可設置是否容許拖動
  • 5:可設置是否容許拉伸

3、效果圖

4、頭文件代碼

#ifndef FRAMELESSWIDGET_H
#define FRAMELESSWIDGET_H

/**
 * 無邊框窗體類 做者:feiyangqingyun(QQ:517216493) 2019-10-03
 * 1:能夠指定須要無邊框的widget
 * 2:邊框四周八個方位均可以自由拉伸
 * 3:可設置對應位置的邊距,以便識別更大區域
 * 4:可設置是否容許拖動
 * 5:可設置是否容許拉伸
 */

#include <QWidget>

#ifdef quc
#if (QT_VERSION < QT_VERSION_CHECK(5,7,0))
#include <QtDesigner/QDesignerExportWidget>
#else
#include <QtUiPlugin/QDesignerExportWidget>
#endif

class QDESIGNER_WIDGET_EXPORT FramelessWidget : public QObject
#else
class FramelessWidget : public QObject
#endif

{
    Q_OBJECT
public:
    explicit FramelessWidget(QObject *parent = 0);

protected:
    bool eventFilter(QObject *watched, QEvent *event);

private:
    int padding;                    //邊距
    bool moveEnable;                //可移動
    bool resizeEnable;              //可拉伸
    QWidget *widget;                //無邊框窗體

    bool pressed;                   //鼠標按下
    bool pressedLeft;               //鼠標按下左側
    bool pressedRight;              //鼠標按下右側
    bool pressedTop;                //鼠標按下上側
    bool pressedBottom;             //鼠標按下下側
    bool pressedLeftTop;            //鼠標按下左上側
    bool pressedRightTop;           //鼠標按下右上側
    bool pressedLeftBottom;         //鼠標按下左下側
    bool pressedRightBottom;        //鼠標按下右下側

    int rectX, rectY, rectW, rectH; //窗體座標+寬高
    QPoint lastPos;                 //鼠標按下處座標

    QRect rectLeft;                 //左側區域
    QRect rectRight;                //右側區域
    QRect rectTop;                  //上側區域
    QRect rectBottom;               //下側區域
    QRect rectLeftTop;              //左上側區域
    QRect rectRightTop;             //右上側區域
    QRect rectLeftBottom;           //左下側區域
    QRect rectRightBottom;          //右下側區域

public Q_SLOTS:
    //設置邊距
    void setPadding(int padding);
    //設置是否可拖動+拉伸
    void setMoveEnable(bool moveEnable);
    void setResizeEnable(bool resizeEnable);

    //設置要無邊框的窗體
    void setWidget(QWidget *widget);
};

#endif // FRAMELESSWIDGET_H

5、核心代碼

#include "framelesswidget.h"
#include "qevent.h"
#include "qdebug.h"

FramelessWidget::FramelessWidget(QObject *parent) : QObject(parent)
{
    padding = 8;
    moveEnable = true;
    resizeEnable = true;
    widget = 0;

    pressed = false;
    pressedLeft = false;
    pressedRight = false;
    pressedTop = false;
    pressedBottom = false;
    pressedLeftTop = false;
    pressedRightTop = false;
    pressedLeftBottom = false;
    pressedRightBottom = false;

    //若是父類是窗體則直接設置
    if (parent->isWidgetType()) {
        setWidget((QWidget *)parent);
    }
}

bool FramelessWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (widget != 0 && watched == widget) {
        if (event->type() == QEvent::Resize) {
            //從新計算八個描點的區域,描點區域的做用還有就是計算鼠標座標是否在某一個區域內
            int width = widget->width();
            int height = widget->height();

            //左側描點區域
            rectLeft = QRect(0, padding, padding, height - padding * 2);
            //上側描點區域
            rectTop = QRect(padding, 0, width - padding * 2, padding);
            //右側描點區域
            rectRight = QRect(width - padding, padding, padding, height - padding * 2);
            //下側描點區域
            rectBottom = QRect(padding, height - padding, width - padding * 2, padding);

            //左上角描點區域
            rectLeftTop = QRect(0, 0, padding, padding);
            //右上角描點區域
            rectRightTop = QRect(width - padding, 0, padding, padding);
            //左下角描點區域
            rectLeftBottom = QRect(0, height - padding, padding, padding);
            //右下角描點區域
            rectRightBottom = QRect(width - padding, height - padding, padding, padding);
        } else if (event->type() == QEvent::HoverMove) {
            //設置對應鼠標形狀,這個必須放在這裏而不是下面,由於能夠在鼠標沒有按下的時候識別
            QHoverEvent *hoverEvent = (QHoverEvent *)event;
            QPoint point = hoverEvent->pos();
            if (resizeEnable) {
                if (rectLeft.contains(point)) {
                    widget->setCursor(Qt::SizeHorCursor);
                } else if (rectRight.contains(point)) {
                    widget->setCursor(Qt::SizeHorCursor);
                } else if (rectTop.contains(point)) {
                    widget->setCursor(Qt::SizeVerCursor);
                } else if (rectBottom.contains(point)) {
                    widget->setCursor(Qt::SizeVerCursor);
                } else if (rectLeftTop.contains(point)) {
                    widget->setCursor(Qt::SizeFDiagCursor);
                } else if (rectRightTop.contains(point)) {
                    widget->setCursor(Qt::SizeBDiagCursor);
                } else if (rectLeftBottom.contains(point)) {
                    widget->setCursor(Qt::SizeBDiagCursor);
                } else if (rectRightBottom.contains(point)) {
                    widget->setCursor(Qt::SizeFDiagCursor);
                } else {
                    widget->setCursor(Qt::ArrowCursor);
                }
            }

            //根據當前鼠標位置,計算XY軸移動了多少
            int offsetX = point.x() - lastPos.x();
            int offsetY = point.y() - lastPos.y();

            //根據按下處的位置判斷是不是移動控件仍是拉伸控件
            if (moveEnable) {
                if (pressed) {
                    widget->move(widget->x() + offsetX, widget->y() + offsetY);
                }
            }

            if (resizeEnable) {
                if (pressedLeft) {
                    int resizeW = widget->width() - offsetX;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, rectY, resizeW, rectH);
                    }
                } else if (pressedRight) {
                    widget->setGeometry(rectX, rectY, rectW + offsetX, rectH);
                } else if (pressedTop) {
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(rectX, widget->y() + offsetY, rectW, resizeH);
                    }
                } else if (pressedBottom) {
                    widget->setGeometry(rectX, rectY, rectW, rectH + offsetY);
                } else if (pressedLeftTop) {
                    int resizeW = widget->width() - offsetX;
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, widget->y(), resizeW, resizeH);
                    }
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y() + offsetY, resizeW, resizeH);
                    }
                } else if (pressedRightTop) {
                    int resizeW = rectW + offsetX;
                    int resizeH = widget->height() - offsetY;
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y() + offsetY, resizeW, resizeH);
                    }
                } else if (pressedLeftBottom) {
                    int resizeW = widget->width() - offsetX;
                    int resizeH = rectH + offsetY;
                    if (widget->minimumWidth() <= resizeW) {
                        widget->setGeometry(widget->x() + offsetX, widget->y(), resizeW, resizeH);
                    }
                    if (widget->minimumHeight() <= resizeH) {
                        widget->setGeometry(widget->x(), widget->y(), resizeW, resizeH);
                    }
                } else if (pressedRightBottom) {
                    int resizeW = rectW + offsetX;
                    int resizeH = rectH + offsetY;
                    widget->setGeometry(widget->x(), widget->y(), resizeW, resizeH);
                }
            }
        } else if (event->type() == QEvent::MouseButtonPress) {
            //記住當前控件座標和寬高以及鼠標按下的座標
            QMouseEvent *mouseEvent = (QMouseEvent *)event;
            rectX = widget->x();
            rectY = widget->y();
            rectW = widget->width();
            rectH = widget->height();
            lastPos = mouseEvent->pos();

            //判斷按下的手柄的區域位置
            if (rectLeft.contains(lastPos)) {
                pressedLeft = true;
            } else if (rectRight.contains(lastPos)) {
                pressedRight = true;
            } else if (rectTop.contains(lastPos)) {
                pressedTop = true;
            } else if (rectBottom.contains(lastPos)) {
                pressedBottom = true;
            } else if (rectLeftTop.contains(lastPos)) {
                pressedLeftTop = true;
            } else if (rectRightTop.contains(lastPos)) {
                pressedRightTop = true;
            } else if (rectLeftBottom.contains(lastPos)) {
                pressedLeftBottom = true;
            } else if (rectRightBottom.contains(lastPos)) {
                pressedRightBottom = true;
            } else {
                pressed = true;
            }
        } else if (event->type() == QEvent::MouseMove) {
            //改爲用HoverMove識別
        } else if (event->type() == QEvent::MouseButtonRelease) {
            //恢復全部
            pressed = false;
            pressedLeft = false;
            pressedRight = false;
            pressedTop = false;
            pressedBottom = false;
            pressedLeftTop = false;
            pressedRightTop = false;
            pressedLeftBottom = false;
            pressedRightBottom = false;
            widget->setCursor(Qt::ArrowCursor);
        }
    }

    return QObject::eventFilter(watched, event);
}

void FramelessWidget::setPadding(int padding)
{
    this->padding = padding;
}

void FramelessWidget::setMoveEnable(bool moveEnable)
{
    this->moveEnable = moveEnable;
}

void FramelessWidget::setResizeEnable(bool resizeEnable)
{
    this->resizeEnable = resizeEnable;
}

void FramelessWidget::setWidget(QWidget *widget)
{
    if (this->widget == 0) {
        this->widget = widget;
        //設置鼠標追蹤爲真
        this->widget->setMouseTracking(true);
        //綁定事件過濾器
        this->widget->installEventFilter(this);
        //設置懸停爲真,必須設置這個,否則當父窗體裏邊還有子窗體所有遮擋了識別不到MouseMove,須要識別HoverMove
        this->widget->setAttribute(Qt::WA_Hover, true);        
    }
}

6、控件介紹

  1. 超過160個精美控件,涵蓋了各類儀表盤、進度條、進度球、指南針、曲線圖、標尺、溫度計、導航條、導航欄,flatui、高亮按鈕、滑動選擇器、農曆等。遠超qwt集成的控件數量。
  2. 每一個類均可以獨立成一個單獨的控件,零耦合,每一個控件一個頭文件和一個實現文件,不依賴其餘文件,方便單個控件以源碼形式集成到項目中,較少代碼量。qwt的控件類環環相扣,高度耦合,想要使用其中一個控件,必須包含全部的代碼。
  3. 所有純Qt編寫,QWidget+QPainter繪製,支持Qt4.6到Qt5.13的任何Qt版本,支持mingw、msvc、gcc等編譯器,支持任意操做系統好比windows+linux+mac+嵌入式linux等,不亂碼,可直接集成到Qt Creator中,和自帶的控件同樣使用,大部分效果只要設置幾個屬性便可,極爲方便。
  4. 每一個控件都有一個對應的單獨的包含該控件源碼的DEMO,方便參考使用。同時還提供一個全部控件使用的集成的DEMO。
  5. 每一個控件的源代碼都有詳細中文註釋,都按照統一設計規範編寫,方便學習自定義控件的編寫。
  6. 每一個控件默認配色和demo對應的配色都很是精美。
  7. 超過130個可見控件,6個不可見控件。
  8. 部分控件提供多種樣式風格選擇,多種指示器樣式選擇。
  9. 全部控件自適應窗體拉伸變化。
  10. 集成自定義控件屬性設計器,支持拖曳設計,所見即所得,支持導入導出xml格式。
  11. 自帶activex控件demo,全部控件能夠直接運行在ie瀏覽器中。
  12. 集成fontawesome圖形字體+阿里巴巴iconfont收藏的幾百個圖形字體,享受圖形字體帶來的樂趣。
  13. 全部控件最後生成一個動態庫文件(dll或者so等),能夠直接集成到qtcreator中拖曳設計使用。
  14. 目前已經有qml版本,後期會考慮出pyqt版本,若是用戶需求量很大的話。
  15. 自定義控件插件開放動態庫使用(永久免費),無任何後門和限制,請放心使用。
  16. 目前已提供32個版本的dll,其中qt_5_7_0_mingw530_32這個版本會一直保證最新的完整的。
  17. 不按期增長控件和完善控件,不按期更新SDK,歡迎各位提出建議,謝謝!
  18. Qt入門書籍推薦霍亞飛的《Qt Creator快速入門》《Qt5編程入門》,Qt進階書籍推薦官方的《C++ GUI Qt4編程》。
  19. 強烈推薦程序員自我修養和規劃系列書《大話程序員》《程序員的成長課》《解憂程序員》,受益不淺,受益終生!
  20. SDK地址:https://gitee.com/feiyangqingyun/QUCSDK https://github.com/feiyangqingyun/qucsdk
相關文章
相關標籤/搜索