一、前言
做过安防视频监控的同学都清楚,在视频监控系统软件上都可以看到一个云台控制区域,可以对球机进行下下左右等八个方位的运动控制,还可以进行复位,一般都是美工作图好,然后贴图的形式加入到软件中,好处是程序简单,界面美工,主要取决于美工的美图能力,缺点是对于各种分辨率的适应性稍微差点,需要不同的图片切图贴图,除非默认做好的是大图自适应看不出差别,可能大部分人所在的公司都是小公司,一般美工人员比较少甚至没有,都需要程序员一人负责,甚至一开始就要考虑到各种分辨率的应用场景以及后期可能的换肤换色等。
之前做过很多自定义控件,大部分都采用了qpainter的形式绘制,有个好处就是自适应任意分辨率,所以思考着这个云台控制仪表盘也采用纯painter绘制的形式,据说纯painter绘制还可以轻松移植到qml中,这又坚定了我用qpainter绘制的决心。所谓心中有坐标系,万物皆painter。
观察云台仪表盘下来,基本上就这几部分组成,圆形底盘,八个角,中间部分按钮,整个的控件的难点就在于八个角的定位,中间部分很好定位,而且八个角不是绝对的位置,都是相对于界面的宽高按照等比例自适应排列的。八个角的鼠标按下要做出对应的反应,发送出对应型号,网上大部分人都是切图或者放置label或者按钮来贴图实现,绑定事件过滤器过滤鼠标按下然后再发出信号。我这里为了提升逼格,直接采用位置坐标计算法。
二、实现的功能
- 1:可设置背景颜色
- 2:可设置基准颜色
- 3:可设置边框颜色
- 4:可设置文本颜色
- 5:可识别每个角度+中间 鼠标按下并发出信号
- 6:可设置八个角的图标和中间图标,随便换
- 7:内置4种云台风格 黑色+白色+蓝色+紫色
- 8:支持拓展鼠标进入离开时的切换
- 9:精准识别内圆区域鼠标按下,而不是圆的矩形区域
- 10:支持长按连续触发,支持设定延时间隔和执行间隔
三、效果图
四、头文件代码
#ifndef GAUGECLOUD_H
#define GAUGECLOUD_H
/**
* 云台仪表盘控件 作者:feiyangqingyun(QQ:517216493) 2018-9-2
* 1:可设置背景颜色
* 2:可设置基准颜色
* 3:可设置边框颜色
* 4:可设置文本颜色
* 5:可识别每个角度+中间 鼠标按下并发出信号
* 6:可设置八个角的图标和中间图标,随便换
* 7:内置4种云台风格 黑色+白色+蓝色+紫色
* 8:支持拓展鼠标进入离开时的切换
* 9:精准识别内圆区域鼠标按下,而不是圆的矩形区域
* 10:支持长按连续触发,支持设定延时间隔和执行间隔
*/
#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 enterColor READ getEnterColor WRITE setEnterColor)
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)
Q_PROPERTY(bool autoRepeat READ getAutoRepeat WRITE setAutoRepeat)
Q_PROPERTY(int autoRepeatDelay READ getAutoRepeatDelay WRITE setAutoRepeatDelay)
Q_PROPERTY(int autoRepeatInterval READ getAutoRepeatInterval WRITE setAutoRepeatInterval)
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 mouseMoveEvent(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 enterColor; //悬停文字颜色
QColor pressColor; //按下文字颜色
QString iconText; //八个角图标
QString centerText; //中间图标
CloudStyle cloudStyle; //云台样式
bool autoRepeat; //是否重复执行按下
int autoRepeatDelay; //延时执行按下动作
int autoRepeatInterval; //重复执行间隔
bool enter; //鼠标是否进入
bool pressed; //鼠标是否按下
bool inCenter; //是否在内圆中
QPoint lastPoint; //鼠标按下处的坐标
QRectF centerRect; //中间区域
QRectF leftRect; //左侧图标区域
QRectF topRect; //上侧图标区域
QRectF rightRect; //右侧图标区域
QRectF bottomRect; //下侧图标区域
QRectF leftTopRect; //左上角图标区域
QRectF rightTopRect; //右上角图标区域
QRectF leftBottomRect; //左下角图标区域
QRectF rightBottomRect; //右下角图标区域
QFont iconFont; //图形字体
int position; //最后按下的位置
QTimer *timer; //定时器触发长按
private slots:
void checkPressed();
private:
double twoPtDistance(const QPointF &pt1, const QPointF &pt2);
public:
QColor getBgColor() const;
QColor getBaseColor() const;
QColor getArcColor() const;
QColor getBorderColor() const;
QColor getTextColor() const;
QColor getEnterColor() const;
QColor getPressColor() const;
QString getIconText() const;
QString getCenterText() const;
CloudStyle getCloudStyle() const;
bool getAutoRepeat() const;
int getAutoRepeatDelay() const;
int getAutoRepeatInterval() 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 setEnterColor(const QColor &enterColor);
//设置按下文本颜色
void setPressColor(const QColor &pressColor);
//设置八个角图标
void setIconText(const QString &iconText);
//设置中间图标
void setCenterText(const QString ¢erText);
//设置云台样式
void setCloudStyle(const CloudStyle &cloudStyle);
//设置是否启用长按重复执行
void setAutoRepeat(bool autoRepeat);
//设置延时执行长按时间
void setAutoRepeatDelay(int autoRepeatDelay);
//设置长按重复执行间隔
void setAutoRepeatInterval(int autoRepeatInterval);
Q_SIGNALS:
//鼠标按下的区域,共9个,从0-8依次表示底部/左下角/左侧/左上角/顶部/右上角/右侧/右下角/中间
void mousePressed(int position);
//鼠标松开
void mouseReleased(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;
//计算当前按下点到中心点的距离,如果小于内圆的半径则认为在内圆中
double offset = twoPtDistance(lastPoint, this->rect().center());
inCenter = (offset <= centerSize / 2);
//中间区域
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, inCenter ? 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, inCenter ? QColor(255, 255, 255) : QColor(245, 245, 245));
} else if (cloudStyle == CloudStyle_Blue) {
//设置圆锥渐变
QConicalGradient gradient(0, 0, 100);
gradient.setColorAt(0, QColor(79, 163, 219));
gradient.setColorAt(0.4, QColor(227, 183, 106));
gradient.setColorAt(0.7, QColor(217, 178, 109));
gradient.setColorAt(1, QColor(79, 163, 219));
//绘制色彩外圆
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, inCenter ? 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, inCenter ? 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 (inCenter) {
if (pressed) {
position = 8;
if (autoRepeat) {
QTimer::singleShot(autoRepeatDelay, timer, SLOT(start()));
}
emit mousePressed(position);
}
painter->setPen(pressed ? pressColor : enterColor);
} else {
painter->setPen(textColor);
}
QFont font;
font.setPixelSize(30);
#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(28);
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++) {
//判断鼠标按下的是哪个区域
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) {
if (pressed) {
position = i;
if (autoRepeat) {
QTimer::singleShot(autoRepeatDelay, timer, SLOT(start()));
}
emit mousePressed(position);
}
painter->setPen(pressed ? pressColor : enterColor);
} else {
painter->setPen(textColor);
}
painter->drawText(iconRect, Qt::AlignCenter, iconChar);
painter->rotate(angleStep);
}
painter->restore();
}
六、控件介绍
- 超过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动态库文件,可以直接集成到qtcreator中拖曳设计使用。
- 目前已经有qml版本,后期会考虑出pyqt版本,如果用户需求量很大的话。
七、SDK下载
- SDK下载链接:https://pan.baidu.com/s/1A5Gd77kExm8Co5ckT51vvQ 提取码:877p
- 下载链接中包含了各个版本的动态库文件,所有控件的头文件,使用demo,自定义控件+属性设计器。
- 自定义控件插件开放动态库dll使用(永久免费),无任何后门和限制,请放心使用。
- 目前已提供26个版本的dll,其中包括了qt5.12.3 msvc2017 32+64 mingw 32+64 的。
- 不定期增加控件和完善控件,不定期更新SDK,欢迎各位提出建议,谢谢!
- widget版本(QQ:517216493)qml版本(QQ:373955953)三峰驼(QQ:278969898)。
- 涛哥的知乎专栏 Qt进阶之路 https://zhuanlan.zhihu.com/TaoQt
- 欢迎关注微信公众号【高效程序员】,C++/Python、学习方法、写作技巧、热门技术、职场发展等内容,干货多多,福利多多!