天天看點

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

一、前言

在之前的一篇文章中寫過一個通用的移動控件,作用就是用來傳入任意的widget控件,可以在父類容器中自由移動。本篇文章要寫的是一個通用的無邊框類,确切的說這不叫控件應該叫元件才對,控件是要看得見的東西,有繪制需求的,而這個需要依附在控件上。在我們平時做的項目中,為了U界面的美觀,很多會采用自定義無邊框窗體來處理,自己美化标題欄等,都會面臨一個相同的問題,就是設定好自定義無邊框窗體以後,要自己處理窗體的移動和拉伸,如果有多個無邊框窗體,很多人會想到每個地方都寫重複的代碼來實作移動或者拉伸,何不封裝一個類來完成這個功能呢,直接傳入窗體即可。在QDialog窗體中可以通過設定一個屬性sizeGripEnabled來實作右下角的拉伸,這個還不足以滿足所有的需求,很多時候我們還需要在四個角和上下左右都能拉伸大小,這個就需要重寫了,安裝事件過濾器,識别到滑鼠移動到某個區域,滑鼠形狀自動變化,然後識别是否已經按下,按下的話做對應的處理,對應的處理核心就是重新設定窗體的XY軸坐标和大小。

開源位址:

https://gitee.com/feiyangqingyun/QWidgetDemo https://github.com/feiyangqingyun/QWidgetDemo

二、實作的功能

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

三、效果圖

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

四、頭檔案代碼

#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

           

五、核心代碼

#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);        
    }
}

           

六、控件介紹

  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