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

四、頭檔案代碼
#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);
}
}
六、控件介紹
- 超過160個精美控件,涵蓋了各種儀表盤、進度條、進度球、指南針、曲線圖、标尺、溫度計、導覽列、導航欄,flatui、高亮按鈕、滑動選擇器、農曆等。遠超qwt內建的控件數量。
- 每個類都可以獨立成一個單獨的控件,零耦合,每個控件一個頭檔案和一個實作檔案,不依賴其他檔案,友善單個控件以源碼形式內建到項目中,較少代碼量。qwt的控件類環環相扣,高度耦合,想要使用其中一個控件,必須包含所有的代碼。
- 全部純Qt編寫,QWidget+QPainter繪制,支援Qt4.6到Qt5.13的任何Qt版本,支援mingw、msvc、gcc等編譯器,支援任意作業系統比如windows+linux+mac+嵌入式linux等,不亂碼,可直接內建到Qt Creator中,和自帶的控件一樣使用,大部分效果隻要設定幾個屬性即可,極為友善。
- 每個控件都有一個對應的單獨的包含該控件源碼的DEMO,友善參考使用。同時還提供一個所有控件使用的內建的DEMO。
- 每個控件的源代碼都有詳細中文注釋,都按照統一設計規範編寫,友善學習自定義控件的編寫。
- 每個控件預設配色和demo對應的配色都非常精美。
- 超過130個可見控件,6個不可見控件。
- 部分控件提供多種樣式風格選擇,多種訓示器樣式選擇。
- 所有控件自适應窗體拉伸變化。
- 內建自定義控件屬性設計器,支援拖曳設計,所見即所得,支援導入導出xml格式。
- 自帶activex控件demo,所有控件可以直接運作在ie浏覽器中。
- 內建fontawesome圖形字型+阿裡巴巴iconfont收藏的幾百個圖形字型,享受圖形字型帶來的樂趣。
- 所有控件最後生成一個動态庫檔案(dll或者so等),可以直接內建到qtcreator中拖曳設計使用。
- 目前已經有qml版本,後期會考慮出pyqt版本,如果使用者需求量很大的話。
- 自定義控件插件開放動态庫使用(永久免費),無任何後門和限制,請放心使用。
- 目前已提供32個版本的dll,其中qt_5_7_0_mingw530_32這個版本會一直保證最新的完整的。
- 不定期增加控件和完善控件,不定期更新SDK,歡迎各位提出建議,謝謝!
- Qt入門書籍推薦霍亞飛的《Qt Creator快速入門》《Qt5程式設計入門》,Qt進階書籍推薦官方的《C++ GUI Qt4程式設計》。
- 強烈推薦程式員自我修養和規劃系列書《大話程式員》《程式員的成長課》《解憂程式員》,受益匪淺,受益終生!
- SDK位址: https://gitee.com/feiyangqingyun/QUCSDK https://github.com/feiyangqingyun/qucsdk