Create the timer

Our app's UI is finished, so now we'll create a timer and implement our countdown.

Using the QTimer class

Let's take a minute to review the behavior that we defined for our finished app. The app has a button that, when clicked, changes the traffic signal from red to green. The button also starts a countdown timer, which is indicated visually in the text area of the UI. When the timer reaches 0, the traffic light changes to yellow, pauses briefly, and then changes to red.

We need a way to keep track of how much time is left before the traffic light should change to yellow. Fortunately, the Qt framework includes a class called QTimer that we can use to do just that. You can set a timer interval for a QTimer object and a signal, timeout(), will be emitted at that interval. For example, if you set an interval of 2000 milliseconds, the QTimer object emits its timeout() signal every two seconds while the timer is active. You can call start() to start the timer, and stop() to stop it.

We can use the functionality of QTimer in our traffic light app. We can use one QTimer object, with an interval of one second, to keep track of the countdown until the traffic light should turn yellow. When this QTimer emits its timeout() signal, we update the text in the countdown TextArea. We can use a second QTimer object, with an interval of about two seconds, to pause the traffic light in the yellow state before changing to the red state.

There's a problem with this plan, though: we can't use the QTimer class (and other Qt and C++ classes) in QML automatically. We need a way to provide access to the features of QTimer directly from QML. There are several ways to do this, but here are two possible approaches that we could choose to use in our app:

  • Use the attachedObjects list in a QML control to specify the C++ objects that you want to use (in this case, QTimer).
  • Create a new class that extends the  CustomControl class, and provide access to an underlying QTimer object. Then, register this new class for use in QML.

To demonstrate more Cascades features in this tutorial, we'll use the second approach for our traffic light app. One of the benefits of extending CustomControl is the ability to create a visual representation for the new control. Because CustomControl inherits from Control (which itself inherits from VisualNode ), you can use the properties of these inherited classes to define how your control looks (for example, preferredWidth, opacityscaleX, and so on). For the purposes of this tutorial, we won't create a visual representation for the timer that we make, but we'll leave that as an optional extension to our app.

Create the header for the Timer class

To declare our class elements, create a new C++ header file called timer.hpp in the src folder of the project (right-click the src folder and click New > Header File). In this file, keep the pre-populated code, and in between the #define and #endif statements, add the appropriate #include statements. We need to include both QObject and CustomControl. We also need to use a forward declaration of the QTimer class:

 #include <QObject>
#include <bb/cascades/CustomControl> 
class QTimer; 

Next, we create the Timer class, extending CustomControl. We also use the Q_OBJECT macro to indicate that this class should be preprocessed by the Meta-Object Compiler (moc) tool to compile properly. For more information about the moc tool in Qt, see Using the Meta-Object Compiler (moc) on the Qt website.

class Timer : public bb::cascades::CustomControl
{
    Q_OBJECT

The QTimer class includes a couple of properties that we want to expose in our own Timer class. The active property indicates whether the timer is currently running, and the interval property specifies the current interval that the timeout() signals are emitted at. We expose these properties by using the Q_PROPERTY macro, and we use the READ and WRITE keywords inside this macro to specify functions that access and modify each property. The NOTIFY keyword is also important, and specifies the signal that's emitted when the property changes. For more information about properties in Qt and the Q_PROPERTY macro, see The Property System on the Qt website.

Q_PROPERTY(bool active READ isActive NOTIFY activeChanged)
Q_PROPERTY(int interval READ interval WRITE setInterval
           NOTIFY intervalChanged)

We now declare the public functions of the Timer class, including the constructor. We need functions to get and set the interval of the timer, and we should also have a way to access the state of the timer (active or inactive):

public:
    explicit Timer(QObject* parent = 0);
 
    bool isActive();
    void setInterval(int m_sec);
    int interval();

This class includes two slots, start() and stop(), to start and stop the timer. Remember that slots are just normal member functions, but are declared in a public slots: section so that they can be connected to signals if needed:

public slots:
    void start();
    void stop();

Our class also includes three signals: timeout(), intervalChanged(), and activeChanged(). These signals are emitted in the definitions of our class functions, which we'll add soon when we create our timer.cpp file. We declare our signals in a signals: section:

signals:
    void timeout();
    void intervalChanged();
    void activeChanged();

Finally, we declare the underlying QTimer object that our Timer class uses:

private:
    QTimer* _timer;
};

Create the source for the Timer class

To define our class elements, create a new C++ source file called timer.cpp in the src folder of the project (right-click the src folder and click New > Source File). This source file doesn't include any pre-populated code, so we start by adding the appropriate #include statements. We need to include QTimer, as well as timer.hpp that we just created:

 #include <QTimer>
#include "timer.hpp" 

Next, we create the constructor for Timer. The constructor creates a QTimer object to represent our timer. We also call QObject::connect() to connect the timeout() signal in QTimer to the timeout()signal of our Timer class. This way, when the QTimer emits the timeout() signal, our timer also emits its timeout() signal and we can handle it using a signal handler in QML. We use the setVisible() function to indicate that our timer shouldn't be visible on the screen, because it has no visual representation defined for it. The constructor also includes a Q_UNUSED macro, which simply means that the parent parameter isn't used in the body of the constructor.

Timer::Timer(QObject* parent) 
     : bb::cascades::CustomControl(),
     _timer(new QTimer(this))
{
    Q_UNUSED(parent);
    
    // If any Q_ASSERT statement(s) indicate that the slot 
    // failed to connect to the signal, make sure you know exactly
    // why this has happened. This is not normal, and will cause your 
    // app to stop working
    bool connectResult;
    
    // Since the variable is not used in the app, this is 
    // added to avoid a compiler warning
    Q_UNUSED(connectResult);
    
    connectResult = connect(_timer, 
                            SIGNAL(timeout()), 
                            this, 
                            SIGNAL(timeout()));
    
    // This is only available in Debug builds.
    Q_ASSERT(connectResult);
    
    setVisible(false);
}

The isActive() function just returns the value of the active property by calling isActive() of the underlying QTimer object. Similarly, the interval() function calls interval() of the QTimer object to return the interval of the timer:

bool Timer::isActive()
{
    return _timer->isActive();
}
 
int Timer::interval()
{
    return _timer->interval();
}

The setInterval() function calls setInterval() of QTimer to set the interval of the timer, and also emits the intervalChanged() signal by using the emit keyword:

void Timer::setInterval(int m_sec)
{
    // If the timer already has the specified interval, do nothing
    if (_timer->interval() == m_sec)
        return;
         
    // Otherwise, set the interval of the timer and emit the
    // intervalChanged() signal
    _timer->setInterval(m_sec);
    emit intervalChanged();
}

The last functions to implement are start() and stop(). Once again, these functions just call the corresponding functions of the underlying QTimer object. They also emit the activeChanged() signal:

void Timer::start()
{
    // If the timer has already been started, do nothing
    if (_timer->isActive())
        return;
         
    // Otherwise, start the timer and emit the activeChanged()
    // signal
    _timer->start();
    emit activeChanged();
}
 
void Timer::stop()
{
    // If the timer has already been stopped, do nothing
    if (!_timer->isActive())
        return;
     
    // Otherwise, stop the timer and emit the activeChanged()
    // signal
    _timer->stop();
    emit activeChanged();
}

Our Timer class is now ready for us to use.

Last modified: 2013-12-21

comments powered by Disqus