Busy-Indicator-for-QML: Difference between revisions

From Qt Wiki
Jump to navigation Jump to search
No edit summary
No edit summary
Line 1: Line 1:
[[Category:Snippets]]<br />[[Category:HowTo]]<br />[[Category:Developing_with_Qt::Qt Quick]]<br />[[Category:Developing_with_Qt::Qt Quick::QML]]
[[Category:Snippets]]
[[Category:HowTo]]
[[Category:Developing_with_Qt::Qt Quick]]
[[Category:Developing_with_Qt::Qt Quick::QML]]


[toc align_right=&quot;yes&amp;quot; depth=&quot;4&amp;quot;]
[toc align_right="yes" depth="4"]


'''English''' | [[Busy-Indicator-for-QML_German|Deutsch]]
'''English''' | [[Busy-Indicator-for-QML_German|Deutsch]]
Line 11: Line 14:
'''QtQuick.Controls 1.3''' come with the '''BusyIndicator'''. It is a simple and ready to use component. Here is a short example for how to use it:
'''QtQuick.Controls 1.3''' come with the '''BusyIndicator'''. It is a simple and ready to use component. Here is a short example for how to use it:


<code><br />import QtQuick 2.4<br />import QtQuick.Controls 1.3<br />import QtQuick.Window 2.2
<code>
import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2


ApplicationWindow {<br /> title: qsTr(&quot;Hello World&amp;quot;)<br /> width: 640<br /> height: 480<br /> visible: true
ApplicationWindow {
title: qsTr("Hello World")
width: 640
height: 480
visible: true


BusyIndicator {<br /> id: busyIndication<br /> anchors.centerIn: parent<br /> // 'running' defaults to 'true'<br /> }
BusyIndicator {
id: busyIndication
anchors.centerIn: parent
// 'running' defaults to 'true'
}


Button {<br /> anchors.horizontalCenter: parent.horizontalCenter<br /> anchors.bottom: parent.bottom<br /> text: busyIndication.running ? &quot;Stop Busy Indicator&amp;quot; : &quot;Start Busy Indicator&amp;quot;<br /> checkable: true<br /> checked: busyIndication.running<br /> onClicked: busyIndication.running = !busyIndication.running<br /> }<br />}<br /></code>
Button {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
text: busyIndication.running ? "Stop Busy Indicator" : "Start Busy Indicator"
checkable: true
checked: busyIndication.running
onClicked: busyIndication.running = !busyIndication.running
}
}
</code>


'''The following describes the implementation of a custom busy indicator for QtQuick 1.'''
'''The following describes the implementation of a custom busy indicator for QtQuick 1.'''


Certain graphical assets may take a while to load or you may wish to show that some other processing is going on. This custom BusyIndicator shows one way in which visual feedback can be provided. This busy indicator has been implemented as a custom QDeclarativeItem in C++ since it uses a conical gradient which it is not possible to represent in an SVG (which only have support for linear and radial gradients). We do take care to minimise the amount of expensive imperative drawing operations. My inspiration for this design comes from StarCraft 2 ;<s>)
Certain graphical assets may take a while to load or you may wish to show that some other processing is going on. This custom BusyIndicator shows one way in which visual feedback can be provided. This busy indicator has been implemented as a custom QDeclarativeItem in C++ since it uses a conical gradient which it is not possible to represent in an SVG (which only have support for linear and radial gradients). We do take care to minimise the amount of expensive imperative drawing operations. My inspiration for this design comes from StarCraft 2 ;-)
<br />[[Image:http://gallery.theharmers.co.uk/upload/2011/04/30/20110430160839-0a540a36.png|Busy Indicator]]
 
<br />h2. Implementation
[[Image:http://gallery.theharmers.co.uk/upload/2011/04/30/20110430160839-0a540a36.png|Busy Indicator]]
<br />First, here is the class declaration:
 
<br /><code><br />#ifndef BUSYINDICATOR_H<br />#define BUSYINDICATOR_H
h2. Implementation
<br />#include &lt;QDeclarativeItem&amp;gt;
 
<br />class BusyIndicator : public QDeclarativeItem<br />{<br /> Q_OBJECT<br /> Q_PROPERTY( qreal innerRadius READ innerRadius WRITE setInnerRadius NOTIFY innerRadiusChanged )<br /> Q_PROPERTY( qreal outerRadius READ outerRadius WRITE setOuterRadius NOTIFY outerRadiusChanged )<br /> Q_PROPERTY( QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged )<br /> Q_PROPERTY( QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY foregroundColorChanged )<br /> Q_PROPERTY( qreal actualInnerRadius READ actualInnerRadius NOTIFY actualInnerRadiusChanged )<br /> Q_PROPERTY( qreal actualOuterRadius READ actualOuterRadius NOTIFY actualOuterRadiusChanged )
First, here is the class declaration:
<br />public:<br /> explicit BusyIndicator( QDeclarativeItem* parent = 0 );
 
<br /> void setInnerRadius( const qreal&amp;amp; innerRadius );<br /> qreal innerRadius() const;
<code>
<br /> void setOuterRadius( const qreal&amp;amp; outerRadius );<br /> qreal outerRadius() const;
#ifndef BUSYINDICATOR_H
<br /> void setBackgroundColor( const QColor&amp;amp; color );<br /> QColor backgroundColor() const;
#define BUSYINDICATOR_H
<br /> void setForegroundColor( const QColor&amp;amp; color );<br /> QColor foregroundColor() const;
 
<br /> qreal actualInnerRadius() const;<br /> qreal actualOuterRadius() const;
#include <QDeclarativeItem>
<br /> virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0 );
 
<br />signals:<br /> void innerRadiusChanged();<br /> void outerRadiusChanged();<br /> void backgroundColorChanged();<br /> void foregroundColorChanged();<br /> void actualInnerRadiusChanged();<br /> void actualOuterRadiusChanged();
class BusyIndicator : public QDeclarativeItem
<br />protected slots:<br /> virtual void updateSpinner();
{
<br />private:<br /> // User settable properties<br /> qreal m_innerRadius; // In range (0, m_outerRadius]<br /> qreal m_outerRadius; // (m_innerRadius, 1]<br /> QColor m_backgroundColor;<br /> QColor m_foregroundColor;
Q_OBJECT
<br /> // The calculated size, inner and outer radii<br /> qreal m_size;<br /> qreal m_actualInnerRadius;<br /> qreal m_actualOuterRadius;
Q_PROPERTY( qreal innerRadius READ innerRadius WRITE setInnerRadius NOTIFY innerRadiusChanged )
<br /> QString m_cacheKey;<br />};
Q_PROPERTY( qreal outerRadius READ outerRadius WRITE setOuterRadius NOTIFY outerRadiusChanged )
<br />#endif // BUSYINDICATOR_H<br /></code>
Q_PROPERTY( QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged )
<br />This is quite a simple sub-class of QDeclarativeItem with only a handful of properties for setting the inner and outer radii of the busy indicator's ring as a fraction of the item's size. In this case the item's size is defined to be min( width, height ) so as to preserve the 1:1 aspect ratio of the ring.
Q_PROPERTY( QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY foregroundColorChanged )
<br />Now for the implementation:
Q_PROPERTY( qreal actualInnerRadius READ actualInnerRadius NOTIFY actualInnerRadiusChanged )
<br /><code><br />#include &quot;busyindicator.h&amp;quot;
Q_PROPERTY( qreal actualOuterRadius READ actualOuterRadius NOTIFY actualOuterRadiusChanged )
<br />#include &lt;QConicalGradient&amp;gt;<br />#include &lt;QPainter&amp;gt;<br />#include &lt;QPainterPath&amp;gt;<br />#include &lt;QPixmapCache&amp;gt;
 
<br />BusyIndicator::BusyIndicator( QDeclarativeItem* parent )<br /> : QDeclarativeItem( parent ),<br /> m_innerRadius( 0.8 ),<br /> m_outerRadius( 1.0 ),<br /> m_backgroundColor( 177, 210, 143, 70 ),<br /> m_foregroundColor( 119, 183, 83, 255 ),<br /> m_actualInnerRadius( 90.0 ),<br /> m_actualOuterRadius( 100.0 ),<br /> m_cacheKey()<br />{<br /> setFlag( QGraphicsItem::ItemHasNoContents, false );<br /> setWidth( 100.0 );<br /> setHeight( 100.0 );
public:
<br /> updateSpinner();
explicit BusyIndicator( QDeclarativeItem* parent = 0 );
<br /> connect( this, SIGNAL( widthChanged() ), SLOT( updateSpinner() ) );<br /> connect( this, SIGNAL( heightChanged() ), SLOT( updateSpinner() ) );<br />}
 
<br />void BusyIndicator::setInnerRadius( const qreal&amp;amp; innerRadius )<br />{<br /> if ( qFuzzyCompare( m_innerRadius, innerRadius ) )<br /> return;<br /> m_innerRadius = innerRadius;<br /> updateSpinner();<br /> emit innerRadiusChanged();<br />}
void setInnerRadius( const qreal&amp;amp; innerRadius );
<br />qreal BusyIndicator::innerRadius() const<br />{<br /> return m_innerRadius;<br />}
qreal innerRadius() const;
<br />void BusyIndicator::setOuterRadius( const qreal&amp;amp; outerRadius )<br />{<br /> if ( qFuzzyCompare( m_outerRadius, outerRadius ) )<br /> return;<br /> m_outerRadius = outerRadius;<br /> updateSpinner();<br /> emit outerRadiusChanged();<br />}
 
<br />qreal BusyIndicator::outerRadius() const<br />{<br /> return m_outerRadius;<br />}
void setOuterRadius( const qreal&amp;amp; outerRadius );
<br />void BusyIndicator::setBackgroundColor( const QColor&amp;amp; color )<br />{<br /> if ( m_backgroundColor == color )<br /> return;<br /> m_backgroundColor = color;<br /> updateSpinner();<br /> emit backgroundColorChanged();<br />}
qreal outerRadius() const;
<br />QColor BusyIndicator::backgroundColor() const<br />{<br /> return m_backgroundColor;<br />}
 
<br />void BusyIndicator::setForegroundColor( const QColor&amp;amp; color )<br />{<br /> if ( m_foregroundColor == color )<br /> return;<br /> m_foregroundColor = color;<br /> updateSpinner();<br /> emit foregroundColorChanged();<br />}
void setBackgroundColor( const QColor&amp;amp; color );
<br />QColor BusyIndicator::foregroundColor() const<br />{<br /> return m_foregroundColor;<br />}
QColor backgroundColor() const;
<br />qreal BusyIndicator::actualInnerRadius() const<br />{<br /> return m_actualInnerRadius;<br />}
 
<br />qreal BusyIndicator::actualOuterRadius() const<br />{<br /> return m_actualOuterRadius;<br />}
void setForegroundColor( const QColor&amp;amp; color );
<br />void BusyIndicator::updateSpinner()<br />{<br /> // Calculate new inner and outer radii<br /> m_size = qMin( width(), height() );<br /> qreal nCoef = 0.5 * m_size;<br /> m_actualInnerRadius = nCoef * m_innerRadius;<br /> m_actualOuterRadius = nCoef * m_outerRadius;
QColor foregroundColor() const;
<br /> // Calculate a new key<br /> m_cacheKey = m_backgroundColor.name();<br /> m_cacheKey ''= &quot;<s>&quot;;<br /> m_cacheKey ''= m_foregroundColor.name();<br /> m_cacheKey''= &quot;</s>&quot;;<br /> m_cacheKey''= QString::number(m_actualOuterRadius);<br /> m_cacheKey += &quot;</s>&quot;;<br /> m_cacheKey ''= QString::number(m_actualInnerRadius);
 
<br /> emit actualInnerRadiusChanged();<br /> emit actualOuterRadiusChanged();<br />}
qreal actualInnerRadius() const;
<br />void BusyIndicator::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )<br />{<br /> Q_UNUSED( option );<br /> Q_UNUSED( widget );
qreal actualOuterRadius() const;
<br /> QPixmap pixmap;<br /> if ( !QPixmapCache::find( m_cacheKey, pixmap ) )<br /> {<br /> // Set up a convenient path<br /> QPainterPath path;<br /> path.setFillRule( Qt::OddEvenFill );<br /> path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualOuterRadius, m_actualOuterRadius );<br /> path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualInnerRadius, m_actualInnerRadius );
 
<br /> qreal nActualDiameter = 2 * m_actualOuterRadius;<br /> pixmap = QPixmap( nActualDiameter, nActualDiameter );<br /> pixmap.fill( Qt::transparent );<br /> QPainter p( &amp;pixmap );
virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0 );
<br /> // Draw the ring background<br /> p.setPen( Qt::NoPen );<br /> p.setBrush( m_backgroundColor );<br /> p.setRenderHint( QPainter::Antialiasing );<br /> p.drawPath( path );
 
<br /> // Draw the ring foreground<br /> QConicalGradient gradient( QPointF( m_actualOuterRadius, m_actualOuterRadius ), 0.0 );<br /> gradient.setColorAt( 0.0, Qt::transparent );<br /> gradient.setColorAt( 0.05, m_foregroundColor );<br /> gradient.setColorAt( 0.8, Qt::transparent );<br /> p.setBrush( gradient );<br /> p.drawPath( path );<br /> p.end();
signals:
<br /> QPixmapCache::insert( m_cacheKey, pixmap );<br /> }
void innerRadiusChanged();
<br /> // Draw pixmap at center of item<br /> painter-&gt;drawPixmap( 0.5 * ( width() - m_size ), 0.5 * ( height() - m_size ), pixmap );<br />}<br /></code>
void outerRadiusChanged();
<br />In the constructor we set up a default size of 100x100 pixels for the indicator and call the updateSpinner() function. This function is also called whenever one of the affecting properties changes. These are:
void backgroundColorChanged();
<br />* Height<br />* Width<br />* Inner radius<br />* Outer radius<br />* Background color<br />* Foreground color
void foregroundColorChanged();
<br />The implementation of the updateSpinner() function only calculates a new QString value which is later used in paint() as a key in the global QPixmapCache. In the paint() function we check to see if the QPixmapCache already contains a matching pixmap or not. If it does we paint it. If it does not we first generate it, store it in the cache and then paint it.  
void actualInnerRadiusChanged();
<br />This approach minimises the amount of expensive painting calls and key constructions.
void actualOuterRadiusChanged();
<br />h2. Usage
 
<br />Before we can use our custom item in any QML scene we need to expose it to the QML world. We do this with something along these lines:
protected slots:
<br /><code><br />qmlRegisterType&amp;lt;BusyIndicator&amp;gt;( &quot;ZapBsComponents&amp;quot;, 1, 0, &quot;BusyIndicator&amp;quot; );<br /></code>
virtual void updateSpinner();
<br />Then in your QML scene you need to instruct the QML backend to import this collection (of 1) components with:
 
<br /><code><br />import ZapBsComponents 1.0<br /></code>
private:
<br />You are now ready to roll.
// User settable properties
<br />h3. Independent Usage
qreal m_innerRadius; // In range (0, m_outerRadius]
<br /><code><br />import QtQuick 1.0<br />import ZapBsComponents 1.0
qreal m_outerRadius; // (m_innerRadius, 1]
<br />Rectangle {<br /> id: root<br /> width: 640<br /> height: 360
QColor m_backgroundColor;
<br /> // Trying out the new BusyIndicator custom item<br /> BusyIndicator {<br /> id: busy1<br /> anchors.centerIn: parent
QColor m_foregroundColor;
<br /> // Make the ring do something interesting<br /> RotationAnimation<br /> {<br /> target: busy1<br /> property: &quot;rotation&amp;quot; // Suppress a warning<br /> from: 0<br /> to: 360<br /> direction: RotationAnimation.Clockwise<br /> duration: 1000<br /> loops: Animation.Infinite<br /> running: true<br /> }<br /> }<br />}<br /></code>
 
<br />The above should provide you with a nicely spinning busy indicator. Obviously the size and colors can be varied using the properties we declared in the header file.
// The calculated size, inner and outer radii
<br />h3. Compound Usage Within Another Component
qreal m_size;
<br />It is also easy to include the BusyIndicator into compound components. One example might be for slow to load images:
qreal m_actualInnerRadius;
<br /><code><br />import QtQuick 1.0<br />import ZapBsComponents 1.0
qreal m_actualOuterRadius;
<br />Item {<br /> id: container<br /> property alias source: image.source<br /> property alias fillMode: image.fillMode
 
<br /> Image {<br /> id: image<br /> anchors.fill: parent<br /> }
QString m_cacheKey;
<br /> BusyIndicator {<br /> id: busyIndicator<br /> anchors.fill: parent<br /> visible: image.status != Image.Ready
};
<br /> RotationAnimation<br /> {<br /> target: busyIndicator<br /> property: &quot;rotation&amp;quot; // Suppress a warning<br /> from: 0<br /> to: 360<br /> direction: RotationAnimation.Clockwise<br /> duration: 1000<br /> loops: Animation.Infinite<br /> running: image.status != Image.Ready<br /> }<br /> }<br />}<br /></code>
 
<br />This will show a nice spinning busy indicator until the image is loaded. Of course you can expose more of the properties to the outside world if you like - this is just a simple example after all.
#endif // BUSYINDICATOR_H
<br />Another example using this Busy Indicator component along with a small progress bar in the center of the spinning ring to show loading progress for e.g. is shown in this &quot;snippet&amp;quot;:http://developer.qt.nokia.com/wiki/QML_Progress_Spinner.
</code>
<br />h3. Independent Usage as Widget
 
<br />This implementation of a busy indicator can also be used without QML. It can be added as widget through &quot;QGraphicsView&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsview.html and &quot;QGraphicsScene&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsscene.html to a &quot;layout&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.7/qlayout.html and animated with &quot;QTimeLine&amp;quot;:http://developer.qt.nokia.com/doc/qt-4.7/qtimeline.html as shown at the following example. It is important to note that the viewport must be set in order to display the busy indicator.
This is quite a simple sub-class of QDeclarativeItem with only a handful of properties for setting the inner and outer radii of the busy indicator's ring as a fraction of the item's size. In this case the item's size is defined to be min( width, height ) so as to preserve the 1:1 aspect ratio of the ring.
<br />h4. Example
 
<br />* pro file
Now for the implementation:
<br /><code>QT''= declarative<code>
 
<code>
#include "busyindicator.h"
 
#include <QConicalGradient>
#include <QPainter>
#include <QPainterPath>
#include <QPixmapCache>
 
BusyIndicator::BusyIndicator( QDeclarativeItem* parent )
: QDeclarativeItem( parent ),
m_innerRadius( 0.8 ),
m_outerRadius( 1.0 ),
m_backgroundColor( 177, 210, 143, 70 ),
m_foregroundColor( 119, 183, 83, 255 ),
m_actualInnerRadius( 90.0 ),
m_actualOuterRadius( 100.0 ),
m_cacheKey()
{
setFlag( QGraphicsItem::ItemHasNoContents, false );
setWidth( 100.0 );
setHeight( 100.0 );
 
updateSpinner();
 
connect( this, SIGNAL( widthChanged() ), SLOT( updateSpinner() ) );
connect( this, SIGNAL( heightChanged() ), SLOT( updateSpinner() ) );
}
 
void BusyIndicator::setInnerRadius( const qreal&amp;amp; innerRadius )
{
if ( qFuzzyCompare( m_innerRadius, innerRadius ) )
return;
m_innerRadius = innerRadius;
updateSpinner();
emit innerRadiusChanged();
}
 
qreal BusyIndicator::innerRadius() const
{
return m_innerRadius;
}
 
void BusyIndicator::setOuterRadius( const qreal&amp;amp; outerRadius )
{
if ( qFuzzyCompare( m_outerRadius, outerRadius ) )
return;
m_outerRadius = outerRadius;
updateSpinner();
emit outerRadiusChanged();
}
 
qreal BusyIndicator::outerRadius() const
{
return m_outerRadius;
}
 
void BusyIndicator::setBackgroundColor( const QColor&amp;amp; color )
{
if ( m_backgroundColor == color )
return;
m_backgroundColor = color;
updateSpinner();
emit backgroundColorChanged();
}
 
QColor BusyIndicator::backgroundColor() const
{
return m_backgroundColor;
}
 
void BusyIndicator::setForegroundColor( const QColor&amp;amp; color )
{
if ( m_foregroundColor == color )
return;
m_foregroundColor = color;
updateSpinner();
emit foregroundColorChanged();
}
 
QColor BusyIndicator::foregroundColor() const
{
return m_foregroundColor;
}
 
qreal BusyIndicator::actualInnerRadius() const
{
return m_actualInnerRadius;
}
 
qreal BusyIndicator::actualOuterRadius() const
{
return m_actualOuterRadius;
}
 
void BusyIndicator::updateSpinner()
{
// Calculate new inner and outer radii
m_size = qMin( width(), height() );
qreal nCoef = 0.5 * m_size;
m_actualInnerRadius = nCoef * m_innerRadius;
m_actualOuterRadius = nCoef * m_outerRadius;
 
// Calculate a new key
m_cacheKey = m_backgroundColor.name();
m_cacheKey ''= "-";
m_cacheKey ''= m_foregroundColor.name();
m_cacheKey''= "-";
m_cacheKey''= QString::number(m_actualOuterRadius);
m_cacheKey += "-";
m_cacheKey ''= QString::number(m_actualInnerRadius);
 
emit actualInnerRadiusChanged();
emit actualOuterRadiusChanged();
}
 
void BusyIndicator::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
{
Q_UNUSED( option );
Q_UNUSED( widget );
 
QPixmap pixmap;
if ( !QPixmapCache::find( m_cacheKey, pixmap ) )
{
// Set up a convenient path
QPainterPath path;
path.setFillRule( Qt::OddEvenFill );
path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualOuterRadius, m_actualOuterRadius );
path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualInnerRadius, m_actualInnerRadius );
 
qreal nActualDiameter = 2 * m_actualOuterRadius;
pixmap = QPixmap( nActualDiameter, nActualDiameter );
pixmap.fill( Qt::transparent );
QPainter p( &amp;pixmap );
 
// Draw the ring background
p.setPen( Qt::NoPen );
p.setBrush( m_backgroundColor );
p.setRenderHint( QPainter::Antialiasing );
p.drawPath( path );
 
// Draw the ring foreground
QConicalGradient gradient( QPointF( m_actualOuterRadius, m_actualOuterRadius ), 0.0 );
gradient.setColorAt( 0.0, Qt::transparent );
gradient.setColorAt( 0.05, m_foregroundColor );
gradient.setColorAt( 0.8, Qt::transparent );
p.setBrush( gradient );
p.drawPath( path );
p.end();
 
QPixmapCache::insert( m_cacheKey, pixmap );
}
 
// Draw pixmap at center of item
painter->drawPixmap( 0.5 * ( width() - m_size ), 0.5 * ( height() - m_size ), pixmap );
}
</code>
 
In the constructor we set up a default size of 100x100 pixels for the indicator and call the updateSpinner() function. This function is also called whenever one of the affecting properties changes. These are:
 
* Height
* Width
* Inner radius
* Outer radius
* Background color
* Foreground color
 
The implementation of the updateSpinner() function only calculates a new QString value which is later used in paint() as a key in the global QPixmapCache. In the paint() function we check to see if the QPixmapCache already contains a matching pixmap or not. If it does we paint it. If it does not we first generate it, store it in the cache and then paint it.  
 
This approach minimises the amount of expensive painting calls and key constructions.
 
h2. Usage
 
Before we can use our custom item in any QML scene we need to expose it to the QML world. We do this with something along these lines:
 
<code>
qmlRegisterType<BusyIndicator>( "ZapBsComponents", 1, 0, "BusyIndicator" );
</code>
 
Then in your QML scene you need to instruct the QML backend to import this collection (of 1) components with:
 
<code>
import ZapBsComponents 1.0
</code>
 
You are now ready to roll.
 
h3. Independent Usage
 
<code>
import QtQuick 1.0
import ZapBsComponents 1.0
 
Rectangle {
id: root
width: 640
height: 360
 
// Trying out the new BusyIndicator custom item
BusyIndicator {
id: busy1
anchors.centerIn: parent
 
// Make the ring do something interesting
RotationAnimation
{
target: busy1
property: "rotation" // Suppress a warning
from: 0
to: 360
direction: RotationAnimation.Clockwise
duration: 1000
loops: Animation.Infinite
running: true
}
}
}
</code>
 
The above should provide you with a nicely spinning busy indicator. Obviously the size and colors can be varied using the properties we declared in the header file.
 
h3. Compound Usage Within Another Component
 
It is also easy to include the BusyIndicator into compound components. One example might be for slow to load images:
 
<code>
import QtQuick 1.0
import ZapBsComponents 1.0
 
Item {
id: container
property alias source: image.source
property alias fillMode: image.fillMode
 
Image {
id: image
anchors.fill: parent
}
 
BusyIndicator {
id: busyIndicator
anchors.fill: parent
visible: image.status != Image.Ready
 
RotationAnimation
{
target: busyIndicator
property: "rotation" // Suppress a warning
from: 0
to: 360
direction: RotationAnimation.Clockwise
duration: 1000
loops: Animation.Infinite
running: image.status != Image.Ready
}
}
}
</code>
 
This will show a nice spinning busy indicator until the image is loaded. Of course you can expose more of the properties to the outside world if you like - this is just a simple example after all.
 
Another example using this Busy Indicator component along with a small progress bar in the center of the spinning ring to show loading progress for e.g. is shown in this "snippet":http://developer.qt.nokia.com/wiki/QML_Progress_Spinner.
 
h3. Independent Usage as Widget
 
This implementation of a busy indicator can also be used without QML. It can be added as widget through "QGraphicsView":http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsview.html and "QGraphicsScene":http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsscene.html to a "layout":http://developer.qt.nokia.com/doc/qt-4.7/qlayout.html and animated with "QTimeLine":http://developer.qt.nokia.com/doc/qt-4.7/qtimeline.html as shown at the following example. It is important to note that the viewport must be set in order to display the busy indicator.
 
h4. Example
 
* pro file
 
<code>QT''= declarative<code>
 
* mainwindow.h
</code>
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
 
#include <QtGui/QMainWindow>
#include "busyindicator.h"
 
#include <QGraphicsScene>
#include <QTimeLine>
 
namespace Ui {
class MainWindow;
}


* mainwindow.h<br /></code><br />#ifndef MAINWINDOW_H<br />#define MAINWINDOW_H
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
enum ScreenOrientation {
ScreenOrientationLockPortrait,
ScreenOrientationLockLandscape,
ScreenOrientationAuto
};


#include &lt;QtGui/QMainWindow&amp;gt;<br />#include &quot;busyindicator.h&amp;quot;
explicit MainWindow(QWidget '''parent = 0);
virtual ~MainWindow();


#include &lt;QGraphicsScene&amp;gt;<br />#include &lt;QTimeLine&amp;gt;
private slots:


namespace Ui {<br /> class MainWindow;<br />}
void rotateSpinner(int nValue);


class MainWindow : public QMainWindow<br />{<br /> Q_OBJECT<br />public:<br /> enum ScreenOrientation {<br /> ScreenOrientationLockPortrait,<br /> ScreenOrientationLockLandscape,<br /> ScreenOrientationAuto<br /> };
private:


explicit MainWindow(QWidget '''parent = 0);<br /> virtual ~MainWindow();
BusyIndicator''' m_pBusyIndicator;
<br />private slots:
<br /> void rotateSpinner(int nValue);
<br />private:
<br /> BusyIndicator''' m_pBusyIndicator;


QGraphicsScene* m_scene;
QGraphicsScene* m_scene;
Line 125: Line 439:
<code>
<code>


* mainwindow.cpp<br /></code><br />#include &quot;mainwindow.h&amp;quot;
* mainwindow.cpp
</code>
#include "mainwindow.h"


#include &lt;QtCore/QCoreApplication&amp;gt;
#include <QtCore/QCoreApplication>


#include &lt;QGraphicsView&amp;gt;<br />#include &lt;QVBoxLayout&amp;gt;
#include <QGraphicsView>
#include <QVBoxLayout>


MainWindow::MainWindow(QWidget '''parent)<br /> : QMainWindow(parent), m_pBusyIndicator(NULL), m_pTimeLine(NULL)<br />{<br /> QLayout''' pLayout = new QVBoxLayout();
MainWindow::MainWindow(QWidget '''parent)
: QMainWindow(parent), m_pBusyIndicator(NULL), m_pTimeLine(NULL)
{
QLayout''' pLayout = new QVBoxLayout();


QGraphicsScene* pScene = new QGraphicsScene(this);<br /> m_pBusyIndicator = new BusyIndicator();<br /> pScene-&gt;addItem(dynamic_cast&amp;lt;QGraphicsItem*&gt;(m_pBusyIndicator));
QGraphicsScene* pScene = new QGraphicsScene(this);
m_pBusyIndicator = new BusyIndicator();
pScene->addItem(dynamic_cast<QGraphicsItem*>(m_pBusyIndicator));


QGraphicsView* pView = new QGraphicsView(pScene, this);<br /> pView-&gt;setViewport(this);<br /> pView-&gt;setMinimumHeight(200);<br /> pView-&gt;show();
QGraphicsView* pView = new QGraphicsView(pScene, this);
pView->setViewport(this);
pView->setMinimumHeight(200);
pView->show();


pLayout-&gt;addWidget(pView);<br /> setLayout(pLayout);
pLayout->addWidget(pView);
setLayout(pLayout);


m_pTimeLine = new QTimeLine(1000, this);<br /> m_pTimeLine-&gt;setLoopCount(0);<br /> m_pTimeLine-&gt;setFrameRange(0, 36);
m_pTimeLine = new QTimeLine(1000, this);
m_pTimeLine->setLoopCount(0);
m_pTimeLine->setFrameRange(0, 36);


connect(m_pTimeLine, SIGNAL (frameChanged(int)), this, SLOT (rotateSpinner(int)));<br /> m_pTimeLine-&gt;start();
connect(m_pTimeLine, SIGNAL (frameChanged(int)), this, SLOT (rotateSpinner(int)));
m_pTimeLine->start();


}<br />//——————————————————————————
}
//——————————————————————————


MainWindow::~MainWindow()<br />{<br /> //Nothing to do<br />}<br />//——————————————————————————
MainWindow::~MainWindow()
{
//Nothing to do
}
//——————————————————————————


void MainWindow::rotateSpinner(int nValue)<br />{<br /> qreal nTransX = m_pBusyIndicator-&gt;actualOuterRadius();<br /> m_pBusyIndicator-&gt;setTransform(QTransform().translate(nTransX, nTransX).<br /> rotate(nValue*10).translate(–1*nTransX, –1*nTransX));<br />}<br />//——————————————————————————
void MainWindow::rotateSpinner(int nValue)
{
qreal nTransX = m_pBusyIndicator->actualOuterRadius();
m_pBusyIndicator->setTransform(QTransform().translate(nTransX, nTransX).
rotate(nValue*10).translate(–1*nTransX, –1*nTransX));
}
//——————————————————————————


<code>
<code>

Revision as of 08:59, 25 February 2015


[toc align_right="yes" depth="4"]

English | Deutsch

Busy Indicator

Introduction

QtQuick.Controls 1.3 come with the BusyIndicator. It is a simple and ready to use component. Here is a short example for how to use it:

import QtQuick 2.4
import QtQuick.Controls 1.3
import QtQuick.Window 2.2

ApplicationWindow {
 title: qsTr("Hello World")
 width: 640
 height: 480
 visible: true

BusyIndicator {
 id: busyIndication
 anchors.centerIn: parent
 // 'running' defaults to 'true'
 }

Button {
 anchors.horizontalCenter: parent.horizontalCenter
 anchors.bottom: parent.bottom
 text: busyIndication.running ? "Stop Busy Indicator" : "Start Busy Indicator"
 checkable: true
 checked: busyIndication.running
 onClicked: busyIndication.running = !busyIndication.running
 }
}

The following describes the implementation of a custom busy indicator for QtQuick 1.

Certain graphical assets may take a while to load or you may wish to show that some other processing is going on. This custom BusyIndicator shows one way in which visual feedback can be provided. This busy indicator has been implemented as a custom QDeclarativeItem in C++ since it uses a conical gradient which it is not possible to represent in an SVG (which only have support for linear and radial gradients). We do take care to minimise the amount of expensive imperative drawing operations. My inspiration for this design comes from StarCraft 2 ;-)

Busy Indicator

h2. Implementation

First, here is the class declaration:

#ifndef BUSYINDICATOR_H
#define BUSYINDICATOR_H

#include <QDeclarativeItem>

class BusyIndicator : public QDeclarativeItem
{
 Q_OBJECT
 Q_PROPERTY( qreal innerRadius READ innerRadius WRITE setInnerRadius NOTIFY innerRadiusChanged )
 Q_PROPERTY( qreal outerRadius READ outerRadius WRITE setOuterRadius NOTIFY outerRadiusChanged )
 Q_PROPERTY( QColor backgroundColor READ backgroundColor WRITE setBackgroundColor NOTIFY backgroundColorChanged )
 Q_PROPERTY( QColor foregroundColor READ foregroundColor WRITE setForegroundColor NOTIFY foregroundColorChanged )
 Q_PROPERTY( qreal actualInnerRadius READ actualInnerRadius NOTIFY actualInnerRadiusChanged )
 Q_PROPERTY( qreal actualOuterRadius READ actualOuterRadius NOTIFY actualOuterRadiusChanged )

public:
 explicit BusyIndicator( QDeclarativeItem* parent = 0 );

 void setInnerRadius( const qreal&amp;amp; innerRadius );
 qreal innerRadius() const;

 void setOuterRadius( const qreal&amp;amp; outerRadius );
 qreal outerRadius() const;

 void setBackgroundColor( const QColor&amp;amp; color );
 QColor backgroundColor() const;

 void setForegroundColor( const QColor&amp;amp; color );
 QColor foregroundColor() const;

 qreal actualInnerRadius() const;
 qreal actualOuterRadius() const;

 virtual void paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0 );

signals:
 void innerRadiusChanged();
 void outerRadiusChanged();
 void backgroundColorChanged();
 void foregroundColorChanged();
 void actualInnerRadiusChanged();
 void actualOuterRadiusChanged();

protected slots:
 virtual void updateSpinner();

private:
 // User settable properties
 qreal m_innerRadius; // In range (0, m_outerRadius]
 qreal m_outerRadius; // (m_innerRadius, 1]
 QColor m_backgroundColor;
 QColor m_foregroundColor;

 // The calculated size, inner and outer radii
 qreal m_size;
 qreal m_actualInnerRadius;
 qreal m_actualOuterRadius;

 QString m_cacheKey;
};

#endif // BUSYINDICATOR_H

This is quite a simple sub-class of QDeclarativeItem with only a handful of properties for setting the inner and outer radii of the busy indicator's ring as a fraction of the item's size. In this case the item's size is defined to be min( width, height ) so as to preserve the 1:1 aspect ratio of the ring.

Now for the implementation:

#include "busyindicator.h"

#include <QConicalGradient>
#include <QPainter>
#include <QPainterPath>
#include <QPixmapCache>

BusyIndicator::BusyIndicator( QDeclarativeItem* parent )
 : QDeclarativeItem( parent ),
 m_innerRadius( 0.8 ),
 m_outerRadius( 1.0 ),
 m_backgroundColor( 177, 210, 143, 70 ),
 m_foregroundColor( 119, 183, 83, 255 ),
 m_actualInnerRadius( 90.0 ),
 m_actualOuterRadius( 100.0 ),
 m_cacheKey()
{
 setFlag( QGraphicsItem::ItemHasNoContents, false );
 setWidth( 100.0 );
 setHeight( 100.0 );

 updateSpinner();

 connect( this, SIGNAL( widthChanged() ), SLOT( updateSpinner() ) );
 connect( this, SIGNAL( heightChanged() ), SLOT( updateSpinner() ) );
}

void BusyIndicator::setInnerRadius( const qreal&amp;amp; innerRadius )
{
 if ( qFuzzyCompare( m_innerRadius, innerRadius ) )
 return;
 m_innerRadius = innerRadius;
 updateSpinner();
 emit innerRadiusChanged();
}

qreal BusyIndicator::innerRadius() const
{
 return m_innerRadius;
}

void BusyIndicator::setOuterRadius( const qreal&amp;amp; outerRadius )
{
 if ( qFuzzyCompare( m_outerRadius, outerRadius ) )
 return;
 m_outerRadius = outerRadius;
 updateSpinner();
 emit outerRadiusChanged();
}

qreal BusyIndicator::outerRadius() const
{
 return m_outerRadius;
}

void BusyIndicator::setBackgroundColor( const QColor&amp;amp; color )
{
 if ( m_backgroundColor == color )
 return;
 m_backgroundColor = color;
 updateSpinner();
 emit backgroundColorChanged();
}

QColor BusyIndicator::backgroundColor() const
{
 return m_backgroundColor;
}

void BusyIndicator::setForegroundColor( const QColor&amp;amp; color )
{
 if ( m_foregroundColor == color )
 return;
 m_foregroundColor = color;
 updateSpinner();
 emit foregroundColorChanged();
}

QColor BusyIndicator::foregroundColor() const
{
 return m_foregroundColor;
}

qreal BusyIndicator::actualInnerRadius() const
{
 return m_actualInnerRadius;
}

qreal BusyIndicator::actualOuterRadius() const
{
 return m_actualOuterRadius;
}

void BusyIndicator::updateSpinner()
{
 // Calculate new inner and outer radii
 m_size = qMin( width(), height() );
 qreal nCoef = 0.5 * m_size;
 m_actualInnerRadius = nCoef * m_innerRadius;
 m_actualOuterRadius = nCoef * m_outerRadius;

 // Calculate a new key
 m_cacheKey = m_backgroundColor.name();
 m_cacheKey ''= "-";
 m_cacheKey ''= m_foregroundColor.name();
 m_cacheKey''= "-";
 m_cacheKey''= QString::number(m_actualOuterRadius);
 m_cacheKey += "-";
 m_cacheKey ''= QString::number(m_actualInnerRadius);

 emit actualInnerRadiusChanged();
 emit actualOuterRadiusChanged();
}

void BusyIndicator::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget )
{
 Q_UNUSED( option );
 Q_UNUSED( widget );

 QPixmap pixmap;
 if ( !QPixmapCache::find( m_cacheKey, pixmap ) )
 {
 // Set up a convenient path
 QPainterPath path;
 path.setFillRule( Qt::OddEvenFill );
 path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualOuterRadius, m_actualOuterRadius );
 path.addEllipse( QPointF( m_actualOuterRadius, m_actualOuterRadius ), m_actualInnerRadius, m_actualInnerRadius );

 qreal nActualDiameter = 2 * m_actualOuterRadius;
 pixmap = QPixmap( nActualDiameter, nActualDiameter );
 pixmap.fill( Qt::transparent );
 QPainter p( &amp;pixmap );

 // Draw the ring background
 p.setPen( Qt::NoPen );
 p.setBrush( m_backgroundColor );
 p.setRenderHint( QPainter::Antialiasing );
 p.drawPath( path );

 // Draw the ring foreground
 QConicalGradient gradient( QPointF( m_actualOuterRadius, m_actualOuterRadius ), 0.0 );
 gradient.setColorAt( 0.0, Qt::transparent );
 gradient.setColorAt( 0.05, m_foregroundColor );
 gradient.setColorAt( 0.8, Qt::transparent );
 p.setBrush( gradient );
 p.drawPath( path );
 p.end();

 QPixmapCache::insert( m_cacheKey, pixmap );
 }

 // Draw pixmap at center of item
 painter->drawPixmap( 0.5 * ( width() - m_size ), 0.5 * ( height() - m_size ), pixmap );
}

In the constructor we set up a default size of 100x100 pixels for the indicator and call the updateSpinner() function. This function is also called whenever one of the affecting properties changes. These are:

  • Height
  • Width
  • Inner radius
  • Outer radius
  • Background color
  • Foreground color

The implementation of the updateSpinner() function only calculates a new QString value which is later used in paint() as a key in the global QPixmapCache. In the paint() function we check to see if the QPixmapCache already contains a matching pixmap or not. If it does we paint it. If it does not we first generate it, store it in the cache and then paint it.

This approach minimises the amount of expensive painting calls and key constructions.

h2. Usage

Before we can use our custom item in any QML scene we need to expose it to the QML world. We do this with something along these lines:

qmlRegisterType<BusyIndicator>( "ZapBsComponents", 1, 0, "BusyIndicator" );

Then in your QML scene you need to instruct the QML backend to import this collection (of 1) components with:

import ZapBsComponents 1.0

You are now ready to roll.

h3. Independent Usage

import QtQuick 1.0
import ZapBsComponents 1.0

Rectangle {
 id: root
 width: 640
 height: 360

 // Trying out the new BusyIndicator custom item
 BusyIndicator {
 id: busy1
 anchors.centerIn: parent

 // Make the ring do something interesting
 RotationAnimation
 {
 target: busy1
 property: "rotation" // Suppress a warning
 from: 0
 to: 360
 direction: RotationAnimation.Clockwise
 duration: 1000
 loops: Animation.Infinite
 running: true
 }
 }
}

The above should provide you with a nicely spinning busy indicator. Obviously the size and colors can be varied using the properties we declared in the header file.

h3. Compound Usage Within Another Component

It is also easy to include the BusyIndicator into compound components. One example might be for slow to load images:

import QtQuick 1.0
import ZapBsComponents 1.0

Item {
 id: container
 property alias source: image.source
 property alias fillMode: image.fillMode

 Image {
 id: image
 anchors.fill: parent
 }

 BusyIndicator {
 id: busyIndicator
 anchors.fill: parent
 visible: image.status != Image.Ready

 RotationAnimation
 {
 target: busyIndicator
 property: "rotation" // Suppress a warning
 from: 0
 to: 360
 direction: RotationAnimation.Clockwise
 duration: 1000
 loops: Animation.Infinite
 running: image.status != Image.Ready
 }
 }
}

This will show a nice spinning busy indicator until the image is loaded. Of course you can expose more of the properties to the outside world if you like - this is just a simple example after all.

Another example using this Busy Indicator component along with a small progress bar in the center of the spinning ring to show loading progress for e.g. is shown in this "snippet":http://developer.qt.nokia.com/wiki/QML_Progress_Spinner.

h3. Independent Usage as Widget

This implementation of a busy indicator can also be used without QML. It can be added as widget through "QGraphicsView":http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsview.html and "QGraphicsScene":http://developer.qt.nokia.com/doc/qt-4.7/qgraphicsscene.html to a "layout":http://developer.qt.nokia.com/doc/qt-4.7/qlayout.html and animated with "QTimeLine":http://developer.qt.nokia.com/doc/qt-4.7/qtimeline.html as shown at the following example. It is important to note that the viewport must be set in order to display the busy indicator.

h4. Example

  • pro file
QT''= declarative<code>

* mainwindow.h
  1. ifndef MAINWINDOW_H
  2. define MAINWINDOW_H
  1. include <QtGui/QMainWindow>
  2. include "busyindicator.h"
  1. include <QGraphicsScene>
  2. include <QTimeLine>

namespace Ui {

class MainWindow;

}

class MainWindow : public QMainWindow {

Q_OBJECT

public:

enum ScreenOrientation {
ScreenOrientationLockPortrait,
ScreenOrientationLockLandscape,
ScreenOrientationAuto
};

explicit MainWindow(QWidget parent = 0);

virtual ~MainWindow();

private slots:

void rotateSpinner(int nValue);

private:

BusyIndicator m_pBusyIndicator;

QGraphicsScene* m_scene;

QTimeLine* m_pTimeLine;

};

  1. endif // MAINWINDOW_H
* mainwindow.cpp
  1. include "mainwindow.h"
  1. include <QtCore/QCoreApplication>
  1. include <QGraphicsView>
  2. include <QVBoxLayout>

MainWindow::MainWindow(QWidget parent)

: QMainWindow(parent), m_pBusyIndicator(NULL), m_pTimeLine(NULL)

{

QLayout pLayout = new QVBoxLayout();

QGraphicsScene* pScene = new QGraphicsScene(this);

m_pBusyIndicator = new BusyIndicator();
pScene->addItem(dynamic_cast<QGraphicsItem*>(m_pBusyIndicator));

QGraphicsView* pView = new QGraphicsView(pScene, this);

pView->setViewport(this);
pView->setMinimumHeight(200);
pView->show();

pLayout->addWidget(pView);

setLayout(pLayout);

m_pTimeLine = new QTimeLine(1000, this);

m_pTimeLine->setLoopCount(0);
m_pTimeLine->setFrameRange(0, 36);

connect(m_pTimeLine, SIGNAL (frameChanged(int)), this, SLOT (rotateSpinner(int)));

m_pTimeLine->start();

} //——————————————————————————

MainWindow::~MainWindow() {

//Nothing to do

} //——————————————————————————

void MainWindow::rotateSpinner(int nValue) {

qreal nTransX = m_pBusyIndicator->actualOuterRadius();
m_pBusyIndicator->setTransform(QTransform().translate(nTransX, nTransX).
rotate(nValue*10).translate(–1*nTransX, –1*nTransX));

} //——————————————————————————