做過安防視頻監控的同學都清楚,在視頻監控系統軟件上都可以看到一個雲台控制區域,可以對球機進行下下左右等八個方位的運動控制,還可以進行復位,一般都是美工作圖好,然后貼圖的形式加入到軟件中,好處是程序簡單,界面美工,主要取決於美工的美圖能力,缺點是對於各種分辨率的適應性稍微差點,需要不同的圖片切圖貼圖,除非默認做好的是大圖自適應看不出差別,可能大部分人所在的公司都是小公司,一般美工人員比較少甚至沒有,都需要程序員一人負責,甚至一開始就要考慮到各種分辨率的應用場景以及后期可能的換膚換色等。
之前做過很多自定義控件,大部分都采用了qpainter的形式繪制,有個好處就是自適應任意分辨率,所以思考着這個雲台控制儀表盤也采用純painter繪制的形式,據說純painter繪制還可以輕松移植到qml中,這又堅定了我用qpainter繪制的決心。所謂心中有坐標系,萬物皆painter。
觀察雲台儀表盤下來,基本上就這幾部分組成,圓形底盤,八個角,中間部分按鈕,整個的控件的難點就在於八個角的定位,中間部分很好定位,而且八個角不是絕對的位置,都是相對於界面的寬高按照等比例自適應排列的。八個角的鼠標按下要做出對應的反應,發送出對應型號,網上大部分人都是切圖或者放置label或者按鈕來貼圖實現,綁定事件過濾器過濾鼠標按下然后再發出信號。我這里為了提升逼格,直接采用位置坐標計算法。
設計師designer完整源碼(僅限Qt4):https://pan.baidu.com/s/1t9uKOgi7PW34Kdj7rgTlrA
設計師designer可執行文件:https://pan.baidu.com/s/1h3oUjqBun2_YD68gry84wQ
自定義控件Qt4封裝版本:https://pan.baidu.com/s/1JnpCwIW5sY9VtViqHSCi1g
自定義控件Qt5封裝版本:https://pan.baidu.com/s/1xMGlK0PN-5yckLJI8koSmQ
自定義控件屬性設計器:https://pan.baidu.com/s/1iZvQe7L0Dfif_p50qodZ8Q

頭文件代碼:
#ifndef GAUGECLOUD_H
#define GAUGECLOUD_H
/**
* 雲台儀表盤控件 作者:feiyangqingyun(QQ:517216493) 2018-9-2
* 1:可設置背景顏色
* 2:可設置基准顏色
* 3:可設置邊框顏色
* 4:可設置文本顏色
* 5:可識別每個角度+中間 鼠標按下並發出信號
* 6:可設置八個角的圖標和中間圖標,隨便換
* 7:內置4種雲台風格 黑色+白色+藍色+紫色
* 8:支持拓展鼠標進入離開時的切換
*/
#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 GaugeCloud : public QWidget
#else
class GaugeCloud : public QWidget
#endif
{
Q_OBJECT
Q_ENUMS(CloudStyle)
Q_PROPERTY(QColor baseColor READ getBaseColor WRITE setBaseColor)
Q_PROPERTY(QColor bgColor READ getBgColor WRITE setBgColor)
Q_PROPERTY(QColor arcColor READ getArcColor WRITE setArcColor)
Q_PROPERTY(QColor borderColor READ getBorderColor WRITE setBorderColor)
Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor)
Q_PROPERTY(QColor pressColor READ getPressColor WRITE setPressColor)
Q_PROPERTY(QString iconText READ getIconText WRITE setIconText)
Q_PROPERTY(QString centerText READ getCenterText WRITE setCenterText)
Q_PROPERTY(CloudStyle cloudStyle READ getCloudStyle WRITE setCloudStyle)
public:
enum CloudStyle {
CloudStyle_Black = 0, //黑色風格
CloudStyle_White = 1, //白色風格
CloudStyle_Blue = 2, //藍色風格
CloudStyle_Purple = 3 //紫色風格
};
explicit GaugeCloud(QWidget *parent = 0);
~GaugeCloud();
protected:
void enterEvent(QEvent *);
void leaveEvent(QEvent *);
void mousePressEvent(QMouseEvent *);
void mouseReleaseEvent(QMouseEvent *);
void paintEvent(QPaintEvent *);
void drawCircle(QPainter *painter, int radius, const QBrush &brush);
void drawArc(QPainter *painter);
void drawText(QPainter *painter);
private:
QColor bgColor; //背景顏色
QColor baseColor; //基准顏色
QColor arcColor; //圓弧顏色
QColor borderColor; //邊框顏色
QColor textColor; //文字顏色
QColor pressColor; //按下文字顏色
QString iconText; //八個角圖標
QString centerText; //中間圖標
CloudStyle cloudStyle; //雲台樣式
bool enter; //鼠標是否進入
bool pressed; //鼠標是否按下
QPoint lastPoint; //鼠標按下處的坐標
QRectF centerRect; //中間區域
QRectF leftRect; //左側圖標區域
QRectF topRect; //上側圖標區域
QRectF rightRect; //右側圖標區域
QRectF bottomRect; //下側圖標區域
QRectF leftTopRect; //左上角圖標區域
QRectF rightTopRect; //右上角圖標區域
QRectF leftBottomRect; //左下角圖標區域
QRectF rightBottomRect; //右下角圖標區域
QFont iconFont; //圖形字體
public:
QColor getBgColor() const;
QColor getBaseColor() const;
QColor getArcColor() const;
QColor getBorderColor() const;
QColor getTextColor() const;
QColor getPressColor() const;
QString getIconText() const;
QString getCenterText() const;
CloudStyle getCloudStyle() const;
QSize sizeHint() const;
QSize minimumSizeHint() const;
public Q_SLOTS:
//設置背景顏色
void setBgColor(const QColor &bgColor);
//設置基准顏色
void setBaseColor(const QColor &baseColor);
//設置圓弧顏色
void setArcColor(const QColor &arcColor);
//設置邊框顏色
void setBorderColor(const QColor &borderColor);
//設置文本顏色
void setTextColor(const QColor &textColor);
//設置按下文本顏色
void setPressColor(const QColor &pressColor);
//設置八個角圖標
void setIconText(const QString &iconText);
//設置中間圖標
void setCenterText(const QString ¢erText);
//設置雲台樣式
void setCloudStyle(const CloudStyle &cloudStyle);
Q_SIGNALS:
//鼠標按下的區域,共9個,從0-8依次表示底部/左下角/左側/左上角/頂部/右上角/右側/右下角/中間
void mousePressed(int position);
};
#endif //GAUGECLOUD_H
核心代碼:
void GaugeCloud::paintEvent(QPaintEvent *)
{
int width = this->width();
int height = this->height();
int side = qMin(width, height);
//以中心點為基准,分別計算八方位區域和中間區域
QPointF center = this->rect().center();
double centerSize = (double)side / ((double)100 / 30);
double iconSize = (double)side / ((double)100 / 10);
double offset1 = 3.6;
double offset2 = 2.65;
//中間區域
centerRect = QRectF(center.x() - centerSize / 2, center.y() - centerSize / 2, centerSize, centerSize);
//左側圖標區域
leftRect = QRectF(center.x() - iconSize * offset1, center.y() - iconSize / 2, iconSize, iconSize);
//上側圖標區域
topRect = QRectF(center.x() - iconSize / 2, center.y() - iconSize * offset1, iconSize, iconSize);
//右側圖標區域
rightRect = QRectF(center.x() + iconSize * (offset1 - 1), center.y() - iconSize / 2, iconSize, iconSize);
//下側圖標區域
bottomRect = QRectF(center.x() - iconSize / 2, center.y() + iconSize * (offset1 - 1), iconSize, iconSize);
//左上角圖標區域
leftTopRect = QRectF(center.x() - iconSize * offset2, center.y() - iconSize * offset2, iconSize, iconSize);
//右上角圖標區域
rightTopRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() - iconSize * offset2, iconSize, iconSize);
//左下角圖標區域
leftBottomRect = QRectF(center.x() - iconSize * offset2, center.y() + iconSize * (offset2 - 1), iconSize, iconSize);
//右下角圖標區域
rightBottomRect = QRectF(center.x() + iconSize * (offset2 - 1), center.y() + iconSize * (offset2 - 1), iconSize, iconSize);
//繪制准備工作,啟用反鋸齒,平移坐標軸中心,等比例縮放
QPainter painter(this);
painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
painter.translate(width / 2, height / 2);
painter.scale(side / 200.0, side / 200.0);
if (cloudStyle == CloudStyle_Black) {
//繪制外圓背景
drawCircle(&painter, 99, bgColor);
//繪制圓弧
drawArc(&painter);
//繪制中間圓盤背景
drawCircle(&painter, 83, baseColor);
//繪制內圓背景
drawCircle(&painter, 40, arcColor);
//繪制內圓邊框
drawCircle(&painter, 33, borderColor);
//繪制內圓
drawCircle(&painter, 30, (pressed && centerRect.contains(lastPoint)) ? bgColor : baseColor);
} else if (cloudStyle == CloudStyle_White) {
//繪制外圓背景
drawCircle(&painter, 99, QColor(249, 249, 249));
//設置圓錐漸變
QConicalGradient gradient(0, 0, 100);
gradient.setColorAt(0, QColor(34, 163, 169));
gradient.setColorAt(0.4, QColor(240, 201, 136));
gradient.setColorAt(0.7, QColor(211, 77, 37));
gradient.setColorAt(1, QColor(34, 163, 169));
//繪制彩色外圓
drawCircle(&painter, 90, gradient);
//繪制中間圓盤背景
drawCircle(&painter, 83, QColor(245, 245, 245));
//繪制內圓背景
drawCircle(&painter, 33, QColor(208, 208, 208));
//繪制內圓邊框
drawCircle(&painter, 32, QColor(208, 208, 208));
//繪制內圓
drawCircle(&painter, 30, (pressed && centerRect.contains(lastPoint)) ? QColor(255, 255, 255) : QColor(245, 245, 245));
} else if (cloudStyle == CloudStyle_Blue) {
//設置圓錐漸變
QConicalGradient gradient(0, 0, 100);
gradient.setColorAt(0, QColor(34, 163, 169));
gradient.setColorAt(0.4, QColor(240, 201, 136));
gradient.setColorAt(0.7, QColor(211, 77, 37));
gradient.setColorAt(1, QColor(34, 163, 169));
//繪制色彩外圓
drawCircle(&painter, 99, gradient);
//繪制中間圓盤背景
drawCircle(&painter, 91, QColor(31, 66, 98));
//繪制內圓背景
drawCircle(&painter, 33, QColor(23, 54, 81));
//繪制內圓邊框
drawCircle(&painter, 30, QColor(150, 150, 150));
//繪制內圓
drawCircle(&painter, 30, (pressed && centerRect.contains(lastPoint)) ? QColor(35, 82, 133) : QColor(34, 73, 115));
} else if (cloudStyle == CloudStyle_Purple) {
//設置圓錐漸變
QConicalGradient gradient(0, 0, 100);
gradient.setColorAt(0, QColor(87, 87, 155));
gradient.setColorAt(0.4, QColor(129, 82, 130));
gradient.setColorAt(0.7, QColor(54, 89, 166));
gradient.setColorAt(1, QColor(87, 87, 155));
//繪制色彩外圓
drawCircle(&painter, 99, gradient);
//繪制中間圓盤背景
drawCircle(&painter, 91, QColor(55, 55, 92));
//繪制內圓背景
drawCircle(&painter, 33, QColor(49, 48, 82));
//繪制內圓邊框
drawCircle(&painter, 30, QColor(82, 78, 131));
//繪制內圓
drawCircle(&painter, 30, (pressed && centerRect.contains(lastPoint)) ? QColor(85, 81, 137) : QColor(62, 59, 103));
}
//繪制八方位+中間圖標
drawText(&painter);
#if 0
//重置坐標系,並繪制八方位區域及中間區域,判斷是否正確
painter.resetMatrix();
painter.resetTransform();
painter.setPen(Qt::white);
painter.drawRect(centerRect);
painter.drawRect(leftRect);
painter.drawRect(topRect);
painter.drawRect(rightRect);
painter.drawRect(bottomRect);
painter.drawRect(leftTopRect);
painter.drawRect(rightTopRect);
painter.drawRect(leftBottomRect);
painter.drawRect(rightBottomRect);
#endif
}
void GaugeCloud::drawCircle(QPainter *painter, int radius, const QBrush &brush)
{
painter->save();
painter->setPen(Qt::NoPen);
painter->setBrush(brush);
//繪制圓
painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);
painter->restore();
}
void GaugeCloud::drawArc(QPainter *painter)
{
int radius = 91;
painter->save();
painter->setBrush(Qt::NoBrush);
QPen pen;
pen.setWidthF(10);
pen.setColor(arcColor);
painter->setPen(pen);
QRectF rect = QRectF(-radius, -radius, radius * 2, radius * 2);
painter->drawArc(rect, 0 * 16, 360 * 16);
painter->restore();
}
void GaugeCloud::drawText(QPainter *painter)
{
bool ok;
int radius = 100;
painter->save();
//判斷當前按下坐標是否在中心區域,按下則文本不同顏色
if (pressed && centerRect.contains(lastPoint)) {
emit mousePressed(8);
painter->setPen(pressColor);
} else {
painter->setPen(textColor);
}
QFont font;
font.setPixelSize(25);
#if (QT_VERSION >= QT_VERSION_CHECK(4,8,0))
font.setHintingPreference(QFont::PreferNoHinting);
#endif
painter->setFont(font);
//繪制中間圖標
QRectF centerRect(-radius, -radius, radius * 2, radius * 2);
QString centerText = this->centerText.replace("0x", "");
QChar centerChar = QChar(centerText.toInt(&ok, 16));
painter->drawText(centerRect, Qt::AlignCenter, centerChar);
//繪制八方位圖標
radius = 70;
int offset = 15;
int steps = 8;
double angleStep = 360.0 / steps;
font.setPixelSize(20);
painter->setFont(font);
//從下側圖標開始繪制,順時針旋轉
QRect iconRect(-offset / 2, radius - offset, offset, offset);
QString iconText = this->iconText.replace("0x", "");
QChar iconChar = QChar(iconText.toInt(&ok, 16));
for (int i = 0; i < steps; i++) {
//判斷鼠標按下的是哪個區域
if (pressed) {
bool contains = false;
if (bottomRect.contains(lastPoint) && i == 0) {
contains = true;
} else if (leftBottomRect.contains(lastPoint) && i == 1) {
contains = true;
} else if (leftRect.contains(lastPoint) && i == 2) {
contains = true;
} else if (leftTopRect.contains(lastPoint) && i == 3) {
contains = true;
} else if (topRect.contains(lastPoint) && i == 4) {
contains = true;
} else if (rightTopRect.contains(lastPoint) && i == 5) {
contains = true;
} else if (rightRect.contains(lastPoint) && i == 6) {
contains = true;
} else if (rightBottomRect.contains(lastPoint) && i == 7) {
contains = true;
}
if (contains) {
painter->setPen(pressColor);
emit mousePressed(i);
} else {
painter->setPen(textColor);
}
} else {
painter->setPen(textColor);
}
painter->drawText(iconRect, Qt::AlignCenter, iconChar);
painter->rotate(angleStep);
}
painter->restore();
}
