Sometimes it is required for us to create some of the advanced controls apart from the basics which are provided by the QML. Qt provides a class called QQuickPaintedItem which can be used to create a custom item for the QML using QPainter API in the Scene Graph.
We can see that nowadays Qt and QML used widely in the automotive industry for both infotainment and instrument cluster. Currently Qt is offering custom Dial item only in the commercial version of Qt which is part of Enterprise controls.
In the past I have worked on similar kind of project in which I have created custom dial item using QDeclarativeItem in Qt 4. here in this post I am going to recreate similar kind of Dial item with minimal functionality.
We have to follow few steps to create a custom item and to use it with QML.
- Create a new class by sub classing QQuickPaintedItem.
- Define all the properties which has to be exposed to the QML as part of this Item.
- Re-implement the void paint(QPainter *painter) method and perform all the drawing for our custom dial item.
- Expose the new Item to the QML by using qmlRegisterType
- import our new module in QML file. Use this new item like any other item in QML
The dial item which I have created only has the background dial, tick marks and numbers. It does not provide the needle. For needle I am using an image which I am placing over my Dial item in the QML and set the rotation property for it.
Here is my implementation for the custom dial item.
dialitem.h
#ifndef DIALITEM_H #define DIALITEM_H #include <QQuickPaintedItem> class DialItem : public QQuickPaintedItem { Q_OBJECT Q_PROPERTY(int startAngle READ getStartAngle WRITE setStartAngle NOTIFY startAngleChanged) Q_PROPERTY(qreal spanAngle READ getSpanAngle WRITE setSpanAngle NOTIFY spanAngleChanged) Q_PROPERTY(int dialWidth READ getDialWidth WRITE setDialWidth NOTIFY dialWidthChanged) Q_PROPERTY(QColor dialColor READ getDialColor WRITE setDialColor NOTIFY dialColorChanged) public: DialItem(QQuickItem *parent = 0); void paint(QPainter *painter); int getStartAngle() {return m_StartAngle;} qreal getSpanAngle() {return m_SpanAngle;} int getDialWidth() {return m_DialWidth;} QColor getDialColor() {return m_DialColor;} void setStartAngle(int angle) {m_StartAngle = angle; this->update();} void setSpanAngle(qreal angle) {m_SpanAngle = angle; this->update();} void setDialWidth(int angle) {m_DialWidth = angle; this->update();} void setDialColor(QColor color) {m_DialColor = color; this->update();} signals: void startAngleChanged(); void spanAngleChanged(); void dialWidthChanged(); void dialColorChanged(); private: int m_StartAngle; qreal m_SpanAngle; int m_DialWidth; QColor m_DialColor; }; #endif // DIALITEM_H
dialitem.cpp
#include <QTime> #include <QPainter> #include "dialitem.h" #define PI 3.14159265 DialItem::DialItem(QQuickItem *parent) : QQuickPaintedItem(parent) { m_StartAngle = 0; m_SpanAngle = 180; m_DialWidth = 4; m_DialColor = QColor(0, 0, 0); connect(this, SIGNAL(startAngleChanged()), this, SLOT(update())); connect(this, SIGNAL(spanAngleChanged()), this, SLOT(update())); connect(this, SIGNAL(dialWidthChanged()), this, SLOT(update())); connect(this, SIGNAL(dialColorChanged()), this, SLOT(update())); } void DialItem::paint(QPainter *painter) { QRectF rect = this->boundingRect(); painter->setRenderHint(QPainter::Antialiasing); QPen pen = painter->pen(); pen.setColor(Qt::black); //Draw outer dial painter->save(); pen.setWidth(m_DialWidth); pen.setColor(m_DialColor); painter->setPen(pen); double startAngle = -90 - m_StartAngle; double spanAngle = 0 - m_SpanAngle; qreal offset = m_DialWidth / 2; painter->drawArc(rect.adjusted(offset, offset, -offset, -offset), startAngle * 16, spanAngle * 16); painter->restore(); //Draw major ticks painter->save(); pen.setWidth(4); painter->translate(width() / 2, height() / 2); painter->rotate(-startAngle); painter->setPen(pen); qreal angleStep = m_SpanAngle / 10; for(int i = 0; i < 11; ++i) { painter->drawLine(rect.width()/2 - 30, 0, rect.width()/2 - 2, 0); painter->rotate(angleStep); } painter->translate(-width() / 2, -height() / 2); painter->restore(); //Draw minor ticks painter->save(); pen.setWidth(2); painter->translate(width() / 2, height() / 2); painter->rotate(-startAngle); painter->setPen(pen); int minorTicks = 8* 10; qreal angleStepMin = m_SpanAngle / minorTicks; for(int i = 0; i < minorTicks; ++i) { painter->drawLine(rect.width()/2 - 15, 0, rect.width()/2 - 2, 0); painter->rotate(angleStepMin); } painter->translate(-width() / 2, -height() / 2); painter->restore(); //Draw numbers painter->save(); pen.setColor(Qt::white); painter->setPen(pen); const int radius = rect.width()/2 - 45; QFontMetrics fontMat(painter->font()); QString numbr; for (double i = 0; i <= m_SpanAngle; i += m_SpanAngle/10) { QPoint p(rect.center().x() + radius * cos(-90-i * PI / 180.0), rect.center().y() - radius * sin(-90-i * PI / 180.0)); numbr = QString::number(i); QRect bound = fontMat.boundingRect(numbr); painter->drawText(QPointF((p.x() - bound.width()/2), (p.y() + bound.height()/2)), numbr); } painter->restore(); }
And in the main.cpp we will expose the new item inside the QML.
main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "dialitem.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); //register new item to the QML qmlRegisterType<DialItem>("IVIControls", 1, 0, "DialItem"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }
And here is my demo usage of my DialItem. Iam using a Slider item to modify the value of the Dial.
main.qml
import QtQuick 2.7 import QtQuick.Controls 2.0 import QtQuick.Controls 1.2 import IVIControls 1.0 ApplicationWindow { visible: true width: 800 height: 480 Rectangle { anchors.fill: parent color: "#303030" } DialItem { id: speed objectName: "speed" width: 350 height: width anchors.centerIn: parent startAngle: 30 spanAngle: 300 dialWidth: 6 dialColor: "#4646FF" Image { id: needle source: "qrc:/images/needle.png" anchors.centerIn: parent rotation: 39 + 30 + slider.value Behavior on rotation { SpringAnimation { spring: 2; damping: 0.2 } } } } Slider { id: slider anchors.top: speed.bottom anchors.horizontalCenter: parent.horizontalCenter width: 400 minimumValue: 0 maximumValue: 300 } }
And finally here is my application running.
Source code:
https://github.com/arunpkqt/IVIDialDemo
2 responses to “Custom Dial Item using QQuickPaintedItem”
Hello Arun, is there a way to change the numbering values to a show a different scale, say
0 – 100 while dividing itself equally and occupying the entire dial ?
LikeLike
Hi Sujith. Sorry for the delay. If I remember correctly I divide the range with the Major tick number.So It should be possible. I couldn’t give it a try.
LikeLike