一、前言
插件控件加載了,拖曳控件也實現了,接下來就是一個最難點了,跟QtDesigner或者其他開發環境一樣,能夠任意自由的拉伸控件大小,移動位置,為了這個功能,還特別編寫了一個控件來實現這個功能,名字叫SelectWidget描點跟隨窗體控件,大致的原理就是安裝事件過濾器,在生成控件的時候將該控件傳入描點跟隨控件,自動識別鼠標的位置,按下拉動的距離來改變控件的大小,繪制描點指示器以便用戶拉伸使用。
描點跟隨控件可設置是否繪制描點、邊距、描點顏色、描點尺寸、描點樣式 正方形+圓形、選中邊框寬度,支持上下左右按鍵移動窗體,支持delete鍵刪除窗體,支持八個描點改變窗體大小尺寸。
體驗地址:https://gitee.com/feiyangqingyun/QUCSDK
https://github.com/feiyangqingyun/qucsdk
二、實現的功能
- 自動加載插件文件中的所有控件生成列表,默認自帶的控件超過120個。
- 拖曳到畫布自動生成對應的控件,所見即所得。
- 右側中文屬性欄,改變對應的屬性立即應用到對應選中控件,直觀簡潔,非常適合小白使用。
- 獨創屬性欄文字翻譯映射機制,效率極高,可以非常方便拓展其他語言的屬性欄。
- 所有控件的屬性自動提取並顯示在右側屬性欄,包括枚舉值下拉框等。
- 支持手動選擇插件文件,外部導入插件文件。
- 可以將當前畫布的所有控件配置信息導出到xml文件。
- 可以手動選擇xml文件打開控件布局,自動根據xml文件加載控件。
- 可拉動滑動條、勾選模擬數據復選框、文本框輸入,三種方式來生成數據應用所有控件。
- 控件支持八個方位拉動調整大小,自適應任意分辨率,可鍵盤上下左右微調位置。
- 打通了串口采集、網絡采集、數據庫采集三種方式設置數據。
- 代碼極其精簡,注釋非常詳細,可以作為組態的雛形,自行拓展更多的功能。
- 純Qt編寫,支持任意Qt版本+任意編譯器+任意系統。
三、效果圖

四、核心代碼
bool SelectWidget::eventFilter(QObject *watched, QEvent *event)
{
if (watched == widget) {
if (event->type() == QEvent::Resize) {
//設置當前窗體大小為跟隨窗體的大小增加部分
this->resize(this->widget->size() + QSize(padding * 2, padding * 2));
} else if (event->type() == QEvent::Move) {
//將當前窗體移到偏移位置
this->move(this->widget->pos() - QPoint(padding, padding));
}
} else {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = dynamic_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Left) {
this->move(this->pos() - QPoint(1, 0));
} else if (keyEvent->key() == Qt::Key_Right) {
this->move(this->pos() + QPoint(1, 0));
} else if (keyEvent->key() == Qt::Key_Up) {
this->move(this->pos() - QPoint(0, 1));
} else if (keyEvent->key() == Qt::Key_Down) {
this->move(this->pos() + QPoint(0, 1));
} else if (keyEvent->key() == Qt::Key_Delete) {
emit widgetDelete(widget);
widget->deleteLater();
this->deleteLater();
widget = 0;
}
//重新設置附帶窗體的位置和大小
if (widget != 0) {
widget->setGeometry(this->x() + padding, this->y() + padding, this->width() - padding * 2, this->height() - padding * 2);
}
return QWidget::eventFilter(watched, event);
}
QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
if (mouseEvent->type() == QEvent::MouseButtonPress) {
//記住當前控件坐標和寬高以及鼠標按下的坐標
rectX = this->x();
rectY = this->y();
rectW = this->width();
rectH = this->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;
}
if (widget != 0) {
emit widgetPressed(widget);
}
} else if (mouseEvent->type() == QEvent::MouseMove) {
//根據當前鼠標位置,計算XY軸移動了多少
QPoint pos = mouseEvent->pos();
int dx = pos.x() - lastPos.x();
int dy = pos.y() - lastPos.y();
//根據按下處的位置判斷是否是移動控件還是拉伸控件
if (pressed) {
this->move(this->x() + dx, this->y() + dy);
} else if (pressedLeft) {
int resizeW = this->width() - dx;
if (this->minimumWidth() <= resizeW) {
this->setGeometry(this->x() + dx, rectY, resizeW, rectH);
}
} else if (pressedRight) {
this->setGeometry(rectX, rectY, rectW + dx, rectH);
} else if (pressedTop) {
int resizeH = this->height() - dy;
if (this->minimumHeight() <= resizeH) {
this->setGeometry(rectX, this->y() + dy, rectW, resizeH);
}
} else if (pressedBottom) {
this->setGeometry(rectX, rectY, rectW, rectH + dy);
} else if (pressedLeftTop) {
int resizeW = this->width() - dx;
int resizeH = this->height() - dy;
if (this->minimumWidth() <= resizeW) {
this->setGeometry(this->x() + dx, this->y(), resizeW, resizeH);
}
if (this->minimumHeight() <= resizeH) {
this->setGeometry(this->x(), this->y() + dy, resizeW, resizeH);
}
} else if (pressedRightTop) {
int resizeW = rectW + dx;
int resizeH = this->height() - dy;
if (this->minimumHeight() <= resizeH) {
this->setGeometry(this->x(), this->y() + dy, resizeW, resizeH);
}
} else if (pressedLeftBottom) {
int resizeW = this->width() - dx;
int resizeH = rectH + dy;
if (this->minimumWidth() <= resizeW) {
this->setGeometry(this->x() + dx, this->y(), resizeW, resizeH);
}
if (this->minimumHeight() <= resizeH) {
this->setGeometry(this->x(), this->y(), resizeW, resizeH);
}
} else if (pressedRightBottom) {
int resizeW = rectW + dx;
int resizeH = rectH + dy;
this->setGeometry(this->x(), this->y(), resizeW, resizeH);
}
//重新設置附帶窗體的位置和大小
if (widget != 0) {
widget->setGeometry(this->x() + padding, this->y() + padding, this->width() - padding * 2, this->height() - padding * 2);
}
} else if (mouseEvent->type() == QEvent::MouseButtonRelease) {
pressed = false;
pressedLeft = false;
pressedRight = false;
pressedTop = false;
pressedBottom = false;
pressedLeftTop = false;
pressedRightTop = false;
pressedLeftBottom = false;
pressedRightBottom = false;
if (widget != 0) {
emit widgetRelease(widget);
}
}
}
return QWidget::eventFilter(watched, event);
}
void SelectWidget::resizeEvent(QResizeEvent *)
{
//重新計算八個描點的區域,描點區域的作用還有就是計算鼠標坐標是否在某一個區域內
int width = this->width();
int height = this->height();
//左側描點區域
rectLeft = QRectF(0, height / 2 - pointSize / 2, pointSize, pointSize);
//上側描點區域
rectTop = QRectF(width / 2 - pointSize / 2, 0, pointSize, pointSize);
//右側描點區域
rectRight = QRectF(width - pointSize, height / 2 - pointSize / 2, pointSize, pointSize);
//下側描點區域
rectBottom = QRectF(width / 2 - pointSize / 2, height - pointSize, pointSize, pointSize);
//左上角描點區域
rectLeftTop = QRectF(0, 0, pointSize, pointSize);
//右上角描點區域
rectRightTop = QRectF(width - pointSize, 0, pointSize, pointSize);
//左下角描點區域
rectLeftBottom = QRectF(0, height - pointSize, pointSize, pointSize);
//右下角描點區域
rectRightBottom = QRectF(width - pointSize, height - pointSize, pointSize, pointSize);
}
void SelectWidget::mouseMoveEvent(QMouseEvent *e)
{
//計算當前鼠標位置是否在某個區域內,自動更新鼠標形狀
QPoint p = e->pos();
if (rectLeft.contains(p)) {
this->setCursor(Qt::SizeHorCursor);
} else if (rectTop.contains(p)) {
this->setCursor(Qt::SizeVerCursor);
} else if (rectRight.contains(p)) {
this->setCursor(Qt::SizeHorCursor);
} else if (rectBottom.contains(p)) {
this->setCursor(Qt::SizeVerCursor);
} else if (rectLeftTop.contains(p)) {
this->setCursor(Qt::SizeFDiagCursor);
} else if (rectRightTop.contains(p)) {
this->setCursor(Qt::SizeBDiagCursor);
} else if (rectLeftBottom.contains(p)) {
this->setCursor(Qt::SizeBDiagCursor);
} else if (rectRightBottom.contains(p)) {
this->setCursor(Qt::SizeFDiagCursor);
} else {
this->setCursor(Qt::ArrowCursor);
}
}
五、控件介紹
- 超過150個精美控件,涵蓋了各種儀表盤、進度條、進度球、指南針、曲線圖、標尺、溫度計、導航條、導航欄,flatui、高亮按鈕、滑動選擇器、農歷等。遠超qwt集成的控件數量。
- 每個類都可以獨立成一個單獨的控件,零耦合,每個控件一個頭文件和一個實現文件,不依賴其他文件,方便單個控件以源碼形式集成到項目中,較少代碼量。qwt的控件類環環相扣,高度耦合,想要使用其中一個控件,必須包含所有的代碼。
- 全部純Qt編寫,QWidget+QPainter繪制,支持Qt4.6到Qt5.12的任何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版本,如果用戶需求量很大的話。
- 自定義控件插件開放動態庫使用(永久免費),無任何后門和限制,請放心使用。
- 目前已提供26個版本的dll,其中包括了qt5.12.3 msvc2017 32+64 mingw 32+64 的。
- 不定期增加控件和完善控件,不定期更新SDK,歡迎各位提出建議,謝謝!
- Qt入門書籍推薦霍亞飛的《Qt Creator快速入門》《Qt5編程入門》,Qt進階書籍推薦官方的《C++ GUI Qt4編程》。
- 強烈推薦程序員自我修養和規划系列書《大話程序員》《程序員的成長課》《解憂程序員》,受益匪淺,受益終生!
- SDK下載鏈接:https://pan.baidu.com/s/1A5Gd77kExm8Co5ckT51vvQ 提取碼:877p
