Qt 自定义控件及使用浅谈:控件个性化
1 摘要
在Qt开发中,自定义控件是一个非常重要的概念。通过自定义控件,开发者可以封装属于自己特定的功能和界面,以便在项目开发中多个地方复用。通过平时的积累封装更多于系统控件,开发出更精美的UI界面。本文将介绍如何创建自定义控件,并讨论其在Qt项目中的使用。
2 自定义控件
自定义控件是通过继承Qt现有控件(如QWidget、QPushButton等)或QAbstractItemView等基类创建的控件。这些控件可以具有特定的外观和行为,以满足应用程序的需求。Qt实现自定义控件的方法主要有两种,一是提升法,二是插件法。本文介绍两种方法的使用,并讨论两种方法使用的优势。
2.1 提升法
提升法实现一个圆形进度条,圆形进度条继承Qt现有控件QWidget,根据需要重写事件处理函数,以实现自定义的行为。如重写paintEvent来绘制控件的外观,或重写mousePressEvent来处理鼠标点击事件及槽函数实现控件相关功能。这里先上一张效果图,后续介绍提升法的简单应用。
核心代码.h文件
#ifndef QROUNDPROGRESSBAR_H
#define QROUNDPROGRESSBAR_H
#include <QWidget>
class QRoundProgressBar : public QWidget
{Q_OBJECT
public: explicit QRoundProgressBar(QWidget *parent = 0);static const int PositionLeft = 180;static const int PositionTop = 90;static const int PositionRight = 0;static const int PositionBottom = -90;double nullPosition() const { return m_nullPosition; }void setNullPosition(double position);enum BarStyle{/// Donut style (filled torus around the text)StyleDonut,/// Pie style (filled pie segment with the text in center)StylePie,/// Line style (thin round line around the text)StyleLine};void setBarStyle(BarStyle style);BarStyle barStyle() const { return m_barStyle; }void setOutlinePenWidth(double penWidth);double outlinePenWidth() const { return m_outlinePenWidth; }void setDataPenWidth(double penWidth);double dataPenWidth() const { return m_dataPenWidth; }void setDataColors(const QGradientStops& stopPoints);void setFormat(const QString& format);void resetFormat();QString format() const { return m_format; }void setDecimals(int count);int decimals() const { return m_decimals; }double value() const { return m_value; }double minimum() const { return m_min; }double maximum() const { return m_max; }public Q_SLOTS:void setRange(double min, double max);void setMinimum(double min);void setMaximum(double max);void setValue(double val);void setValue(int val);protected:virtual void paintEvent(QPaintEvent *event);virtual void drawBackground(QPainter& p, const QRectF& baseRect);virtual void drawBase(QPainter& p, const QRectF& baseRect);virtual void drawValue(QPainter& p, const QRectF& baseRect, double value, double arcLength);virtual void calculateInnerRect(const QRectF& baseRect, double outerRadius, QRectF& innerRect, double& innerRadius);virtual void drawInnerBackground(QPainter& p, const QRectF& innerRect);virtual void drawText(QPainter& p, const QRectF& innerRect, double innerRadius, double value);virtual QString valueToText(double value) const;virtual void valueFormatChanged();virtual QSize minimumSizeHint() const { return QSize(32,32); }virtual bool hasHeightForWidth() const { return true; }virtual int heightForWidth(int w) const { return w; }void rebuildDataBrushIfNeeded();double m_min, m_max;double m_value;double m_nullPosition;BarStyle m_barStyle;double m_outlinePenWidth, m_dataPenWidth;QGradientStops m_gradientData;bool m_rebuildBrush;QString m_format;int m_decimals;static const int UF_VALUE = 1;static const int UF_PERCENT = 2;static const int UF_MAX = 4;int m_updateFlags;
};#endif // QROUNDPROGRESSBAR_H
核心代码.c文件
#include "QRoundProgressBar.h"#include <QtGui/QPainter>QRoundProgressBar::QRoundProgressBar(QWidget *parent) :QWidget(parent),m_min(0), m_max(100),m_value(0),m_nullPosition(PositionTop),m_barStyle(StyleDonut),m_outlinePenWidth(1),m_dataPenWidth(1),m_rebuildBrush(false),m_format("%p%"),m_decimals(1),m_updateFlags(UF_PERCENT)
{
}void QRoundProgressBar::setRange(double min, double max)
{m_min = min;m_max = max;if (m_max < m_min)qSwap(m_max, m_min);if (m_value < m_min)m_value = m_min;else if (m_value > m_max)m_value = m_max;if (!m_gradientData.isEmpty())m_rebuildBrush = true;update();
}void QRoundProgressBar::setMinimum(double min)
{setRange(min, m_max);
}void QRoundProgressBar::setMaximum(double max)
{setRange(m_min, max);
}void QRoundProgressBar::setValue(double val)
{if (m_value != val){if (val < m_min)m_value = m_min;else if (val > m_max)m_value = m_max;elsem_value = val;update();}
}void QRoundProgressBar::setValue(int val)
{setValue(double(val));
}void QRoundProgressBar::setNullPosition(double position)
{if (position != m_nullPosition){m_nullPosition = position;if (!m_gradientData.isEmpty())m_rebuildBrush = true;update();}
}void QRoundProgressBar::setBarStyle(QRoundProgressBar::BarStyle style)
{if (style != m_barStyle){m_barStyle = style;update();}
}void QRoundProgressBar::setOutlinePenWidth(double penWidth)
{if (penWidth != m_outlinePenWidth){m_outlinePenWidth = penWidth;update();}
}void QRoundProgressBar::setDataPenWidth(double penWidth)
{if (penWidth != m_dataPenWidth){m_dataPenWidth = penWidth;update();}
}void QRoundProgressBar::setDataColors(const QGradientStops &stopPoints)
{if (stopPoints != m_gradientData){m_gradientData = stopPoints;m_rebuildBrush = true;update();}
}void QRoundProgressBar::setFormat(const QString &format)
{if (format != m_format){m_format = format;valueFormatChanged();}
}void QRoundProgressBar::resetFormat()
{m_format = QString::null;valueFormatChanged();
}void QRoundProgressBar::setDecimals(int count)
{if (count >= 0 && count != m_decimals){m_decimals = count;valueFormatChanged();}
}void QRoundProgressBar::paintEvent(QPaintEvent* /*event*/)
{double outerRadius = qMin(width(), height());QRectF baseRect(1, 1, outerRadius-2, outerRadius-2);QImage buffer(outerRadius, outerRadius, QImage::Format_ARGB32_Premultiplied);QPainter p(&buffer);p.setRenderHint(QPainter::Antialiasing);// data brushrebuildDataBrushIfNeeded();// backgrounddrawBackground(p, buffer.rect());// base circledrawBase(p, baseRect);// data circledouble arcStep = 360.0 / (m_max - m_min) * m_value;drawValue(p, baseRect, m_value, arcStep);// center circledouble innerRadius(0);QRectF innerRect;calculateInnerRect(baseRect, outerRadius, innerRect, innerRadius);drawInnerBackground(p, innerRect);// textdrawText(p, innerRect, innerRadius, m_value);// finally draw the barp.end();QPainter painter(this);painter.fillRect(baseRect, palette().background());painter.drawImage(0,0, buffer);
}void QRoundProgressBar::drawBackground(QPainter &p, const QRectF &baseRect)
{p.fillRect(baseRect, palette().background());
}void QRoundProgressBar::drawBase(QPainter &p, const QRectF &baseRect)
{switch (m_barStyle){case StyleDonut:p.setPen(QPen(palette().shadow().color(), m_outlinePenWidth));p.setBrush(palette().base());p.drawEllipse(baseRect);break;case StylePie:p.setPen(QPen(palette().base().color(), m_outlinePenWidth));p.setBrush(palette().base());p.drawEllipse(baseRect);break;case StyleLine:p.setPen(QPen(palette().base().color(), m_outlinePenWidth));p.setBrush(Qt::NoBrush);p.drawEllipse(baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2));break;default:;}}void QRoundProgressBar::drawValue(QPainter &p, const QRectF &baseRect, double value, double arcLength)
{// nothing to drawif (value == m_min)return;// for Line styleif (m_barStyle == StyleLine){p.setPen(QPen(palette().highlight().color(), m_dataPenWidth));p.setBrush(Qt::NoBrush);p.drawArc(baseRect.adjusted(m_outlinePenWidth/2, m_outlinePenWidth/2, -m_outlinePenWidth/2, -m_outlinePenWidth/2),m_nullPosition * 16,-arcLength * 16);return;}// for Pie and Donut stylesQPainterPath dataPath;dataPath.setFillRule(Qt::WindingFill);// pie segment outerdataPath.moveTo(baseRect.center());dataPath.arcTo(baseRect, m_nullPosition, -arcLength);dataPath.lineTo(baseRect.center());p.setBrush(palette().highlight());p.setPen(QPen(palette().shadow().color(), m_dataPenWidth));p.drawPath(dataPath);
}void QRoundProgressBar::calculateInnerRect(const QRectF &/*baseRect*/, double outerRadius, QRectF &innerRect, double &innerRadius)
{// for Line styleif (m_barStyle == StyleLine){innerRadius = outerRadius - m_outlinePenWidth;}else // for Pie and Donut styles{innerRadius = outerRadius * 0.75;}double delta = (outerRadius - innerRadius) / 2;innerRect = QRectF(delta, delta, innerRadius, innerRadius);
}void QRoundProgressBar::drawInnerBackground(QPainter &p, const QRectF &innerRect)
{if (m_barStyle == StyleDonut){p.setBrush(palette().alternateBase());p.drawEllipse(innerRect);}
}void QRoundProgressBar::drawText(QPainter &p, const QRectF &innerRect, double innerRadius, double value)
{if (m_format.isEmpty())return;// !!! to reviseQFont f(font());f.setPixelSize(innerRadius * qMax(0.05, (0.35 - (double)m_decimals * 0.08)));p.setFont(f);QRectF textRect(innerRect);p.setPen(palette().text().color());p.drawText(textRect, Qt::AlignCenter, valueToText(value));
}QString QRoundProgressBar::valueToText(double value) const
{QString textToDraw(m_format);if (m_updateFlags & UF_VALUE)textToDraw.replace("%v", QString::number(value, 'f', m_decimals));if (m_updateFlags & UF_PERCENT){double procent = (value - m_min) / (m_max - m_min) * 100.0;textToDraw.replace("%p", QString::number(procent, 'f', m_decimals));}if (m_updateFlags & UF_MAX)textToDraw.replace("%m", QString::number(m_max - m_min + 1, 'f', m_decimals));return textToDraw;
}void QRoundProgressBar::valueFormatChanged()
{m_updateFlags = 0;if (m_format.contains("%v"))m_updateFlags |= UF_VALUE;if (m_format.contains("%p"))m_updateFlags |= UF_PERCENT;if (m_format.contains("%m"))m_updateFlags |= UF_MAX;update();
}void QRoundProgressBar::rebuildDataBrushIfNeeded()
{if (m_rebuildBrush){m_rebuildBrush = false;QConicalGradient dataBrush;dataBrush.setCenter(0.5,0.5);dataBrush.setCoordinateMode(QGradient::StretchToDeviceMode);// invert colorsfor (int i = 0; i < m_gradientData.count(); i++){dataBrush.setColorAt(1.0 - m_gradientData.at(i).first, m_gradientData.at(i).second);}// angledataBrush.setAngle(m_nullPosition);QPalette p(palette());p.setBrush(QPalette::Highlight, dataBrush);setPalette(p);}
}
提升法相对比较简单,将.h文件和.c文件添加到功能目录下。并在工程中包含头文件,接下来就可以直接在项目工程中通过提升QWidget方法直接使用。
2.2 插件法
插件法实现一个圆形仪表盘,圆形仪表盘继承Qt现有控件QWidget,根据需要重写事件处理函数,以实现自定义的行为。如重写paintEvent来绘制控件的外观,或重写mousePressEvent来处理鼠标点击事件及槽函数实现控件相关功能。这里也同样先上一张效果图,后续介绍插件法的简单应用。
核心代码.h文件
#ifndef GAUGECAR_H
#define GAUGECAR_H#include <QWidget>
#include <QtDesigner/QtDesigner>
#include <QtUiPlugin/QDesignerCustomWidgetCollectionInterface>class QDESIGNER_WIDGET_EXPORT GaugeCar : public QWidget
{Q_OBJECTQ_ENUMS(PieStyle)Q_ENUMS(PointerStyle)Q_PROPERTY(double value READ getValue WRITE setValue)Q_PROPERTY(double minValue READ getMinValue WRITE setMinValue)Q_PROPERTY(double maxValue READ getMaxValue WRITE setMaxValue)Q_PROPERTY(int precision READ getPrecision WRITE setPrecision)Q_PROPERTY(int scaleMajor READ getScaleMajor WRITE setScaleMajor)Q_PROPERTY(int scaleMinor READ getScaleMinor WRITE setScaleMinor)Q_PROPERTY(int startAngle READ getStartAngle WRITE setStartAngle)Q_PROPERTY(int endAngle READ getEndAngle WRITE setEndAngle)Q_PROPERTY(bool animation READ getAnimation WRITE setAnimation)Q_PROPERTY(double animationStep READ getAnimationStep WRITE setAnimationStep)Q_PROPERTY(QColor outerCircleColor READ getOuterCircleColor WRITE setOuterCircleColor)Q_PROPERTY(QColor innerCircleColor READ getInnerCircleColor WRITE setInnerCircleColor)Q_PROPERTY(QColor pieColorStart READ getPieColorStart WRITE setPieColorStart)Q_PROPERTY(QColor pieColorMid READ getPieColorMid WRITE setPieColorMid)Q_PROPERTY(QColor pieColorEnd READ getPieColorEnd WRITE setPieColorEnd)Q_PROPERTY(QColor coverCircleColor READ getCoverCircleColor WRITE setCoverCircleColor)Q_PROPERTY(QColor scaleColor READ getScaleColor WRITE setScaleColor)Q_PROPERTY(QColor pointerColor READ getPointerColor WRITE setPointerColor)Q_PROPERTY(QColor centerCircleColor READ getCenterCircleColor WRITE setCenterCircleColor)Q_PROPERTY(QColor textColor READ getTextColor WRITE setTextColor)Q_PROPERTY(bool showOverlay READ getShowOverlay WRITE setShowOverlay)Q_PROPERTY(QColor overlayColor READ getOverlayColor WRITE setOverlayColor)Q_PROPERTY(PieStyle pieStyle READ getPieStyle WRITE setPieStyle)Q_PROPERTY(PointerStyle pointerStyle READ getPointerStyle WRITE setPointerStyle)public:enum PieStyle {PieStyle_Three = 0, //三色圆环PieStyle_Current = 1 //当前圆环};enum PointerStyle {PointerStyle_Circle = 0, //圆形指示器PointerStyle_Indicator = 1, //指针指示器PointerStyle_IndicatorR = 2, //圆角指针指示器PointerStyle_Triangle = 3 //三角形指示器};explicit GaugeCar(QWidget *parent = 0);~GaugeCar();protected:void paintEvent(QPaintEvent *);void drawOuterCircle(QPainter *painter);void drawInnerCircle(QPainter *painter);void drawColorPie(QPainter *painter);void drawCoverCircle(QPainter *painter);void drawScale(QPainter *painter);void drawScaleNum(QPainter *painter);void drawPointerCircle(QPainter *painter);void drawPointerIndicator(QPainter *painter);void drawPointerIndicatorR(QPainter *painter);void drawPointerTriangle(QPainter *painter);void drawRoundCircle(QPainter *painter);void drawCenterCircle(QPainter *painter);void drawText(QPainter *painter);void drawOverlay(QPainter *painter);private slots:void updateValue();private:double maxValue; //最小值double minValue; //最大值double value; //目标值int precision; //精确度,小数点后几位int scaleMajor; //大刻度数量int scaleMinor; //小刻度数量int startAngle; //开始旋转角度int endAngle; //结束旋转角度bool animation; //是否启用动画显示double animationStep; //动画显示时步长QColor outerCircleColor; //外圆背景颜色QColor innerCircleColor; //内圆背景颜色QColor pieColorStart; //饼圆开始颜色QColor pieColorMid; //饼圆中间颜色QColor pieColorEnd; //饼圆结束颜色QColor coverCircleColor; //覆盖圆背景颜色QColor scaleColor; //刻度尺颜色QColor pointerColor; //指针颜色QColor centerCircleColor; //中心圆颜色QColor textColor; //文字颜色bool showOverlay; //显示遮罩层QColor overlayColor; //遮罩层颜色PieStyle pieStyle; //饼图样式PointerStyle pointerStyle; //指针样式bool reverse; //是否往回走double currentValue; //当前值QTimer *timer; //定时器绘制动画public:double getMinValue() const;double getMaxValue() const;double getValue() const;int getPrecision() const;int getScaleMajor() const;int getScaleMinor() const;int getStartAngle() const;int getEndAngle() const;bool getAnimation() const;double getAnimationStep() const;QColor getOuterCircleColor() const;QColor getInnerCircleColor() const;QColor getPieColorStart() const;QColor getPieColorMid() const;QColor getPieColorEnd() const;QColor getCoverCircleColor() const;QColor getScaleColor() const;QColor getPointerColor() const;QColor getCenterCircleColor() const;QColor getTextColor() const;bool getShowOverlay() const;QColor getOverlayColor() const;PieStyle getPieStyle() const;PointerStyle getPointerStyle() const;QSize sizeHint() const;QSize minimumSizeHint() const;public Q_SLOTS://设置范围值void setRange(double minValue, double maxValue);void setRange(int minValue, int maxValue);//设置最大最小值void setMinValue(double minValue);void setMaxValue(double maxValue);//设置目标值void setValue(double value);void setValue(int value);//设置精确度void setPrecision(int precision);//设置主刻度数量void setScaleMajor(int scaleMajor);//设置小刻度数量void setScaleMinor(int scaleMinor);//设置开始旋转角度void setStartAngle(int startAngle);//设置结束旋转角度void setEndAngle(int endAngle);//设置是否启用动画显示void setAnimation(bool animation);//设置动画显示的步长void setAnimationStep(double animationStep);//设置外圆背景颜色void setOuterCircleColor(const QColor &outerCircleColor);//设置内圆背景颜色void setInnerCircleColor(const QColor &innerCircleColor);//设置饼圆三种颜色void setPieColorStart(const QColor &pieColorStart);void setPieColorMid(const QColor &pieColorMid);void setPieColorEnd(const QColor &pieColorEnd);//设置覆盖圆背景颜色void setCoverCircleColor(const QColor &coverCircleColor);//设置刻度尺颜色void setScaleColor(const QColor &scaleColor);//设置指针颜色void setPointerColor(const QColor &pointerColor);//设置中心圆颜色void setCenterCircleColor(const QColor ¢erCircleColor);//设置文本颜色void setTextColor(const QColor &textColor);//设置是否显示遮罩层void setShowOverlay(bool showOverlay);//设置遮罩层颜色void setOverlayColor(const QColor &overlayColor);//设置饼图样式void setPieStyle(PieStyle pieStyle);//设置指针样式void setPointerStyle(PointerStyle pointerStyle);Q_SIGNALS:void valueChanged(int value);
};#endif //GAUGECAR_H
核心代码.c文件
#pragma execution_character_set("utf-8")#include "gaugecar.h"
#include "qpainter.h"
#include "qmath.h"
#include "qtimer.h"
#include "qdebug.h"GaugeCar::GaugeCar(QWidget *parent) : QWidget(parent)
{minValue = 0;maxValue = 100;value = 0;precision = 0;scaleMajor = 10;scaleMinor = 10;startAngle = 45;endAngle = 45;animation = false;animationStep = 0.5;outerCircleColor = QColor(80, 80, 80);innerCircleColor = QColor(60, 60, 60);pieColorStart = QColor(24, 189, 155);pieColorMid = QColor(218, 218, 0);pieColorEnd = QColor(255, 107, 107);coverCircleColor = QColor(100, 100, 100);scaleColor = QColor(255, 255, 255);pointerColor = QColor(255, 107, 107);centerCircleColor = QColor(200, 200, 200);textColor = QColor(0, 0, 0);showOverlay = false;overlayColor = QColor(255, 255, 255, 60);pieStyle = PieStyle_Three;pointerStyle = PointerStyle_Indicator;reverse = false;currentValue = 0;timer = new QTimer(this);timer->setInterval(10);connect(timer, SIGNAL(timeout()), this, SLOT(updateValue()));setFont(QFont("Arial", 7));
}GaugeCar::~GaugeCar()
{if (timer->isActive()) {timer->stop();}
}void GaugeCar::paintEvent(QPaintEvent *)
{int width = this->width();int height = this->height();int side = qMin(width, height);//绘制准备工作,启用反锯齿,平移坐标轴中心,等比例缩放QPainter painter(this);painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);painter.translate(width / 2, height / 2);painter.scale(side / 200.0, side / 200.0);//绘制外圆drawOuterCircle(&painter);//绘制内圆drawInnerCircle(&painter);//绘制饼圆drawColorPie(&painter);//绘制覆盖圆 用以遮住饼圆多余部分drawCoverCircle(&painter);//绘制刻度线drawScale(&painter);//绘制刻度值drawScaleNum(&painter);//根据指示器形状绘制指示器if (pointerStyle == PointerStyle_Circle) {drawPointerCircle(&painter);} else if (pointerStyle == PointerStyle_Indicator) {drawPointerIndicator(&painter);} else if (pointerStyle == PointerStyle_IndicatorR) {drawPointerIndicatorR(&painter);} else if (pointerStyle == PointerStyle_Triangle) {drawPointerTriangle(&painter);}//绘制指针中心圆外边框drawRoundCircle(&painter);//绘制指针中心圆drawCenterCircle(&painter);//绘制当前值drawText(&painter);//绘制遮罩层drawOverlay(&painter);
}void GaugeCar::drawOuterCircle(QPainter *painter)
{int radius = 99;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(outerCircleColor);painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawInnerCircle(QPainter *painter)
{int radius = 90;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(innerCircleColor);painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawColorPie(QPainter *painter)
{int radius = 60;painter->save();painter->setPen(Qt::NoPen);QRectF rect(-radius, -radius, radius * 2, radius * 2);if (pieStyle == PieStyle_Three) {//计算总范围角度,根据占比例自动计算三色圆环范围角度double angleAll = 360.0 - startAngle - endAngle;double angleStart = angleAll * 0.7;double angleMid = angleAll * 0.15;double angleEnd = angleAll * 0.15;//绘制开始饼圆painter->setBrush(pieColorStart);painter->drawPie(rect, (270 - startAngle - angleStart) * 16, angleStart * 16);//绘制中间饼圆painter->setBrush(pieColorMid);painter->drawPie(rect, (270 - startAngle - angleStart - angleMid) * 16, angleMid * 16);//绘制结束饼圆painter->setBrush(pieColorEnd);painter->drawPie(rect, (270 - startAngle - angleStart - angleMid - angleEnd) * 16, angleEnd * 16);} else if (pieStyle == PieStyle_Current) {//计算总范围角度,当前值范围角度,剩余值范围角度double angleAll = 360.0 - startAngle - endAngle;double angleCurrent = angleAll * ((currentValue - minValue) / (maxValue - minValue));double angleOther = angleAll - angleCurrent;//绘制当前值饼圆painter->setBrush(pieColorStart);painter->drawPie(rect, (270 - startAngle - angleCurrent) * 16, angleCurrent * 16);//绘制剩余值饼圆painter->setBrush(pieColorEnd);painter->drawPie(rect, (270 - startAngle - angleCurrent - angleOther) * 16, angleOther * 16);}painter->restore();
}void GaugeCar::drawCoverCircle(QPainter *painter)
{int radius = 50;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(coverCircleColor);painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawScale(QPainter *painter)
{int radius = 72;painter->save();painter->setPen(scaleColor);painter->rotate(startAngle);int steps = (scaleMajor * scaleMinor);double angleStep = (360.0 - startAngle - endAngle) / steps;QPen pen = painter->pen();pen.setCapStyle(Qt::RoundCap);for (int i = 0; i <= steps; i++) {if (i % scaleMinor == 0) {pen.setWidthF(1.5);painter->setPen(pen);painter->drawLine(0, radius - 10, 0, radius);} else {pen.setWidthF(0.5);painter->setPen(pen);painter->drawLine(0, radius - 5, 0, radius);}painter->rotate(angleStep);}painter->restore();
}void GaugeCar::drawScaleNum(QPainter *painter)
{int radius = 82;painter->save();painter->setPen(scaleColor);double startRad = (360 - startAngle - 90) * (M_PI / 180);double deltaRad = (360 - startAngle - endAngle) * (M_PI / 180) / scaleMajor;for (int i = 0; i <= scaleMajor; i++) {double sina = sin(startRad - i * deltaRad);double cosa = cos(startRad - i * deltaRad);double value = 1.0 * i * ((maxValue - minValue) / scaleMajor) + minValue;QString strValue = QString("%1").arg((double)value, 0, 'f', precision);double textWidth = fontMetrics().width(strValue);double textHeight = fontMetrics().height();int x = radius * cosa - textWidth / 2;int y = -radius * sina + textHeight / 4;painter->drawText(x, y, strValue);}painter->restore();
}void GaugeCar::drawPointerCircle(QPainter *painter)
{int radius = 12;int offset = 10;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(pointerColor);painter->rotate(startAngle);double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue);painter->rotate(degRotate);painter->drawEllipse(-radius, radius + offset, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawPointerIndicator(QPainter *painter)
{int radius = 75;painter->save();painter->setOpacity(0.8);painter->setPen(Qt::NoPen);painter->setBrush(pointerColor);QPolygon pts;pts.setPoints(3, -5, 0, 5, 0, 0, radius);painter->rotate(startAngle);double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue);painter->rotate(degRotate);painter->drawConvexPolygon(pts);painter->restore();
}void GaugeCar::drawPointerIndicatorR(QPainter *painter)
{int radius = 75;painter->save();painter->setOpacity(1.0);painter->setPen(pointerColor);painter->setBrush(pointerColor);QPolygon pts;pts.setPoints(3, -5, 0, 5, 0, 0, radius);painter->rotate(startAngle);double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue);painter->rotate(degRotate);painter->drawConvexPolygon(pts);//增加绘制圆角直线,与之前三角形重叠,形成圆角指针QPen pen = painter->pen();pen.setCapStyle(Qt::RoundCap);pen.setWidthF(4);painter->setPen(pen);painter->drawLine(0, 0, 0, radius);painter->restore();
}void GaugeCar::drawPointerTriangle(QPainter *painter)
{int radius = 10;int offset = 38;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(pointerColor);QPolygon pts;pts.setPoints(3, -5, 0 + offset, 5, 0 + offset, 0, radius + offset);painter->rotate(startAngle);double degRotate = (360.0 - startAngle - endAngle) / (maxValue - minValue) * (currentValue - minValue);painter->rotate(degRotate);painter->drawConvexPolygon(pts);painter->restore();
}void GaugeCar::drawRoundCircle(QPainter *painter)
{int radius = 18;painter->save();painter->setOpacity(0.8);painter->setPen(Qt::NoPen);painter->setBrush(pointerColor);painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawCenterCircle(QPainter *painter)
{int radius = 15;painter->save();painter->setPen(Qt::NoPen);painter->setBrush(centerCircleColor);painter->drawEllipse(-radius, -radius, radius * 2, radius * 2);painter->restore();
}void GaugeCar::drawText(QPainter *painter)
{int radius = 100;painter->save();painter->setPen(textColor);painter->setFont(QFont("Arial", 9));QRectF textRect(-radius, -radius, radius * 2, radius * 2);QString strValue = QString("%1").arg((double)currentValue, 0, 'f', precision);painter->drawText(textRect, Qt::AlignCenter, strValue);painter->restore();
}void GaugeCar::drawOverlay(QPainter *painter)
{if (!showOverlay) {return;}int radius = 90;painter->save();painter->setPen(Qt::NoPen);QPainterPath smallCircle;QPainterPath bigCircle;radius -= 1;smallCircle.addEllipse(-radius, -radius, radius * 2, radius * 2);radius *= 2;bigCircle.addEllipse(-radius, -radius + 140, radius * 2, radius * 2);//高光的形状为小圆扣掉大圆的部分QPainterPath highlight = smallCircle - bigCircle;QLinearGradient linearGradient(0, -radius / 2, 0, 0);overlayColor.setAlpha(100);linearGradient.setColorAt(0.0, overlayColor);overlayColor.setAlpha(30);linearGradient.setColorAt(1.0, overlayColor);painter->setBrush(linearGradient);painter->rotate(-20);painter->drawPath(highlight);painter->restore();
}void GaugeCar::updateValue()
{if (!reverse) {if (currentValue >= value) {timer->stop();} else {currentValue += animationStep;}} else {if (currentValue <= value) {timer->stop();} else {currentValue -= animationStep;}}update();
}double GaugeCar::getMinValue() const
{return this->minValue;
}double GaugeCar::getMaxValue() const
{return this->maxValue;
}double GaugeCar::getValue() const
{return this->value;
}int GaugeCar::getPrecision() const
{return this->precision;
}int GaugeCar::getScaleMajor() const
{return this->scaleMajor;
}int GaugeCar::getScaleMinor() const
{return this->scaleMinor;
}int GaugeCar::getStartAngle() const
{return this->startAngle;
}int GaugeCar::getEndAngle() const
{return this->endAngle;
}bool GaugeCar::getAnimation() const
{return this->animation;
}double GaugeCar::getAnimationStep() const
{return this->animationStep;
}QColor GaugeCar::getOuterCircleColor() const
{return this->outerCircleColor;
}QColor GaugeCar::getInnerCircleColor() const
{return this->innerCircleColor;
}QColor GaugeCar::getPieColorStart() const
{return this->pieColorStart;
}QColor GaugeCar::getPieColorMid() const
{return this->pieColorMid;
}QColor GaugeCar::getPieColorEnd() const
{return this->pieColorEnd;
}QColor GaugeCar::getCoverCircleColor() const
{return this->coverCircleColor;
}QColor GaugeCar::getScaleColor() const
{return this->scaleColor;
}QColor GaugeCar::getPointerColor() const
{return this->pointerColor;
}QColor GaugeCar::getCenterCircleColor() const
{return this->centerCircleColor;
}QColor GaugeCar::getTextColor() const
{return this->textColor;
}bool GaugeCar::getShowOverlay() const
{return this->showOverlay;
}QColor GaugeCar::getOverlayColor() const
{return this->overlayColor;
}GaugeCar::PieStyle GaugeCar::getPieStyle() const
{return this->pieStyle;
}GaugeCar::PointerStyle GaugeCar::getPointerStyle() const
{return this->pointerStyle;
}QSize GaugeCar::sizeHint() const
{return QSize(200, 200);
}QSize GaugeCar::minimumSizeHint() const
{return QSize(50, 50);
}void GaugeCar::setRange(double minValue, double maxValue)
{//如果最小值大于或者等于最大值则不设置if (minValue >= maxValue) {return;}this->minValue = minValue;this->maxValue = maxValue;//如果目标值不在范围值内,则重新设置目标值if (value < minValue || value > maxValue) {setValue(value);}update();
}void GaugeCar::setRange(int minValue, int maxValue)
{setRange((double)minValue, (double)maxValue);
}void GaugeCar::setMinValue(double minValue)
{setRange(minValue, maxValue);
}void GaugeCar::setMaxValue(double maxValue)
{setRange(minValue, maxValue);
}void GaugeCar::setValue(double value)
{//值小于最小值或者值大于最大值或者值和当前值一致则无需处理if (value < minValue || value > maxValue || value == this->value) {return;}if (value > this->value) {reverse = false;} else if (value < this->value) {reverse = true;}this->value = value;emit valueChanged(value);if (!animation) {currentValue = this->value;update();} else {timer->start();}
}void GaugeCar::setValue(int value)
{setValue((double)value);
}void GaugeCar::setPrecision(int precision)
{//最大精确度为 3if (precision <= 3 && this->precision != precision) {this->precision = precision;update();}
}void GaugeCar::setScaleMajor(int scaleMajor)
{if (this->scaleMajor != scaleMajor) {this->scaleMajor = scaleMajor;update();}
}void GaugeCar::setScaleMinor(int scaleMinor)
{if (this->scaleMinor != scaleMinor) {this->scaleMinor = scaleMinor;update();}
}void GaugeCar::setStartAngle(int startAngle)
{if (this->startAngle != startAngle) {this->startAngle = startAngle;update();}
}void GaugeCar::setEndAngle(int endAngle)
{if (this->endAngle != endAngle) {this->endAngle = endAngle;update();}
}void GaugeCar::setAnimation(bool animation)
{if (this->animation != animation) {this->animation = animation;update();}
}void GaugeCar::setAnimationStep(double animationStep)
{if (this->animationStep != animationStep) {this->animationStep = animationStep;update();}
}void GaugeCar::setOuterCircleColor(const QColor &outerCircleColor)
{if (this->outerCircleColor != outerCircleColor) {this->outerCircleColor = outerCircleColor;update();}
}void GaugeCar::setInnerCircleColor(const QColor &innerCircleColor)
{if (this->innerCircleColor != innerCircleColor) {this->innerCircleColor = innerCircleColor;update();}
}void GaugeCar::setPieColorStart(const QColor &pieColorStart)
{if (this->pieColorStart != pieColorStart) {this->pieColorStart = pieColorStart;update();}
}void GaugeCar::setPieColorMid(const QColor &pieColorMid)
{if (this->pieColorMid != pieColorMid) {this->pieColorMid = pieColorMid;update();}
}void GaugeCar::setPieColorEnd(const QColor &pieColorEnd)
{if (this->pieColorEnd != pieColorEnd) {this->pieColorEnd = pieColorEnd;update();}
}void GaugeCar::setCoverCircleColor(const QColor &coverCircleColor)
{if (this->coverCircleColor != coverCircleColor) {this->coverCircleColor = coverCircleColor;update();}
}void GaugeCar::setScaleColor(const QColor &scaleColor)
{if (this->scaleColor != scaleColor) {this->scaleColor = scaleColor;update();}
}void GaugeCar::setPointerColor(const QColor &pointerColor)
{if (this->pointerColor != pointerColor) {this->pointerColor = pointerColor;update();}
}void GaugeCar::setCenterCircleColor(const QColor ¢erCircleColor)
{if (this->centerCircleColor != centerCircleColor) {this->centerCircleColor = centerCircleColor;update();}
}void GaugeCar::setTextColor(const QColor &textColor)
{if (this->textColor != textColor) {this->textColor = textColor;update();}
}void GaugeCar::setShowOverlay(bool showOverlay)
{if (this->showOverlay != showOverlay) {this->showOverlay = showOverlay;update();}
}void GaugeCar::setOverlayColor(const QColor &overlayColor)
{if (this->overlayColor != overlayColor) {this->overlayColor = overlayColor;update();}
}void GaugeCar::setPieStyle(GaugeCar::PieStyle pieStyle)
{if (this->pieStyle != pieStyle) {this->pieStyle = pieStyle;update();}
}void GaugeCar::setPointerStyle(GaugeCar::PointerStyle pointerStyle)
{if (this->pointerStyle != pointerStyle) {this->pointerStyle = pointerStyle;update();}
}
选择项目新建Qt设计师自定义控件,设置项目名称、类名以及控件图标
设置类及图标
类封装工程结构如下
设置好编译器对类封装进行编译,编译生成如下
将生成的库文件添加至Qt安装的Designer目录下,这里使用的是Mingw73_64编译器
在Qt设计器 Qt Designer中直接拖拽自定义控件到UI界面中,运行显示
2.3 对比
(1)提升法:
提升法使用自定义控件相对简单,直接使用源文件对控件进行提升。但是具有局限性,只能应用于当前工程,其他工程需要重新加载源文件或者类。
(2)插件法:
插件法使用自定义控件相对繁琐写,将源文件封装成类。通过Qt中plugins插件方式直接可视化加载,不同工程都可以重复使用,即一次封装,重复使用插件,这种方法相对于提升法更适合于开发者。
3 总结
本文介绍了Qt中的自定义进阶应用,通过提升或插件的方式使用该控件,通过平时的积累及在项目需求应用中对Qt设计控件库的封装,可为后续开发提供更丰富于IDE本身的控件,开发出更好用灵活精美的UI设计,可为自己在开发过程中提供了快速高效的开发环境。后续则继续总结分享Qt应用开发中的其他应用,开发不易珍惜每一分原创和劳动成果,同时注意平时开发过程中的经验积累总结。