CpuWidget using QCharts
Now that the base class SysInfoWidget is ready, let's implement its first child class: CpuWidget. We will now use the Qt Charts API to display a good-looking widget. The average CPU load will be displayed in a pie graph with a hole in the center, like a partly eaten donut where the eaten part is the percentage of the CPU used. The first step is to add a new C++ class named CpuWidget and make it inherit SysInfoWidget:
#include "SysInfoWidget.h" class CpuWidget : public SysInfoWidget { public: explicit CpuWidget(QWidget* parent = 0); };
In the constructor, the only parameter needed is a QWidget* parent. Since we provided default values for the startDelayMs and updateSeriesDelayMs variables in SysInfoWidget class, we get the best possible behavior; there is no need to remember it when subclassing SysInfoWidget, but it is still easy to override it if need be.
The next step is to override the updateSeries() function from the SysInfoWidget class and start using the Qt Charts API:
#include <QtCharts/QpieSeries> #include "SysInfoWidget.h" class CpuWidget : public SysInfoWidget { Q_OBJECT public: explicit CpuWidget(QWidget* parent = 0); protected slots: void updateSeries() override; private: QtCharts::QPieSeries* mSeries; };
Since we overrode the SysInfoWidget::updateSeries() slot, we have to include the Q_OBJECT macro to allow CPUWidget to respond to the SysInfoWidgetmRefreshTimer::timeout() signal.
We include QPieSeries from the Qt Charts module so that we can create a member QPieSeries* named mSeries. The QPieSeries is a subclass of QAbstractSeries, which is the base class of all Qt Charts series (QLineSeries, QAreaSeries, QPieSeries, and so on). In Qt Charts, a QAbstractSeries subclass holds the data you want to display and defines how it should be drawn, but it does not define where the data should be displayed inside your layout.
We can now proceed to CpuWidget.cpp to investigate how we can tell Qt where the drawing takes place:
using namespace QtCharts; CpuWidget::CpuWidget(QWidget* parent) : SysInfoWidget(parent), mSeries(new QPieSeries(this)) { mSeries->setHoleSize(0.35); mSeries->append("CPU Load", 30.0); mSeries->append("CPU Free", 70.0); QChart* chart = chartView().chart(); chart->addSeries(mSeries); chart->setTitle("CPU average load"); }
All Qt Charts classes are defined in the QtCharts namespace. This is why we start with using namespace QtCharts.
First, we initialize mSeries in the constructor initializer list. We then proceed to configure it. We carve the donut with mSeries->setHoleSize(0.35) and we append two data sets to mSeries: a fake CPU Load and Cpu Free, which are expressed in percentages. The mSeries function is now ready to be linked to the class managing its drawing: QChart.
The QChart class is retrieved from the SysInfoWidget::chartView() function. When calling chart->addSeries(mSeries), chart takes the ownership of mSeries and will draw it according to the series type--in our case, a QPieSeries. QChart is not a QWidget: it is a subclass of QGraphicsWidget. QGraphicsWidget can be described as a lighter QWidget with some differences (its coordinates and geometry are defined with doubles or floats instead of integers, a subset of QWidget attributes are supported: custom drag, drop framework, and so on). The QGraphicsWidget class is designed to be added in a QGraphicsScene class, a high-performance Qt component used to draw hundreds of items on screen at the same time.
In our SysInfo application, the QChart has to be displayed in a QVBoxLayout in SysInfoWidget. Here, the QChartView class comes in very handy. It lets us add chart in a QWidget layout.
Up to now, QPieSeries has seemed rather abstract. Let's add it to the MainWindow file to see how it looks:
// In MainWindow.h #include "CpuWidget.h" ... private: Ui::MainWindow *ui; CpuWidget mCpuWidget; }; // In MainWindow.cpp MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), mCpuWidget(this) { ui->setupUi(this); SysInfo::instance().init(); ui->centralWidget->layout()->addWidget(&mCpuWidget); }
We simply declare mCpuWidget in the MainWindow.h file, initialize it, and add it to MainWindow->centralWidget->layout. If you now run the application, you should see something like this:
Even though it looks cool, this donut is a bit static and does not reflect the CPU usage. Thanks to the architecture we built with the SysInfo and SysInfoWidget classes, the remaining part will be implemented swiftly.
Switch back to the CpuWidget.cpp file and implement the updateSeries() function with the following body:
void CpuWidget::updateSeries() { double cpuLoadAverage = SysInfo::instance().cpuLoadAverage(); mSeries->clear(); mSeries->append("Load", cpuLoadAverage); mSeries->append("Free", 100.0 - cpuLoadAverage); }
First, we get a reference to our SysInfo singleton. We then retrieve the current average CPU load in the cpuLoadAverage variable. We have to feed this data to our mSeries. The mSeries object is a QPieCharts, which implies that we just want a snapshot of the current CPU average load. Past history is not meaningful with this kind of graph; that's why we clear the mSeries data with the mSeries->clear() syntax, and append the cpuLoadAverage variable and then the free part (100.0 - cpuLoadAverage).
The nice thing to note is that, in the CpuWidget class, we don't have to worry about refreshing. All the work is done in the SysInfoWidget subclass with all the whistles and bells of the QTimer class. In a SysInfoWidget subclass, we only have to concentrate on the valuable specific code: what data should be displayed and what kind of graph is used to display it. If you look at the whole CpuWidget class, it is very short. The next SysInfoWidget subclass, MemoryWidget, will also be very concise, as well as quick to implement.