treinamento qt básico - aula ii

Download Treinamento Qt básico - aula II

If you can't read please download the document

Upload: marcelo-barros-de-almeida

Post on 19-May-2015

1.432 views

Category:

Technology


0 download

DESCRIPTION

Treinamento de Qt básico apresentado na semanada de tecnologia do Barão de Mauá (Ribeirão Preto/SP) usando um material provido pela Nokia com modificações.

TRANSCRIPT

.

The Qt object
model and
the signal
slot concept

Qt in Education

Semana de tecnologia do
Baro de Mau

Instrutor: Marcelo Barros de Almeida
[email protected]

In this lecture we will try to cover the theoretical aspects of the QObject class, which makes Qt what Qt is. Understanding this is key to the rest of the lectures.

The first half of the lecture will be devoted to the QObject class, meta-information and the possibilities given by these classes.

The second half of the lecture will be devoted to signals and slots which is the single most important concept of Qt.

Recommended reading before the lecture:http://doc.trolltech.com/4.6/object.html

2010 Nokia Corporation and its Subsidiary(-ies).

The enclosed Qt Educational Training Materials are provided under the Creative Commons
Attribution-Non-Commercial-Share Alike 2.5 License Agreement.

The full license text is available here: http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode.

Nokia, Qt and the Nokia and Qt logos are the registered trademarks of Nokia Corporation in Finland and other countries worldwide.

Signals and Slots

Dynamically and loosely tie together events and state changes with reactions

What makes Qt tick

One of the key factors of Qt is the signals and slots mechanism.

It is a mechanism to dynamically and loosely tie together events and changes with reactions

Dynamically = at run-timeLoosely = sender and receiver do not know each otherevents = timer event, clicks, etc. Not to be confused with actual events (QEvent).state changes = size changed, text changed, value changed, etcreactions = source code that actually does something

This is what makes a Qt application tick

Continues

Signals and Slots in Action

emit clicked();

Looking at an example from the first lecture.

The three buttons all emit the clicked signal when they are clicked by the user (or activated by other means).

They do this regardless whether something is connected to them or not, and regardless of what is connected to them.

Continues

Signals and Slots in Action

private slots:void on_addButton_clicked();void on_deleteButton_clicked();

connect(clearButton,SIGNAL(clicked()),listWidget,SLOT(clear()));

connect(addButton,SIGNAL(clicked()),this,SLOT(...));

2x

clear();

We choose to connect the buttons to the list widget's clear slot, and two custom slots in our custom code.

As said earlier, the buttons do not care what they are connected to, and the slots are equally independent of what triggers them. You can even call them programatically as ordinary functions.

Continues

Signals and Slots in Action

{...emit clicked();...}

{...emit clicked();...}

{...emit clicked();...}

{QString newText = QInputDialog::getText(this, "Enter text", "Text:");

if( !newText.isEmpty() )ui->listWidget->addItem(newText);}

{foreach (QListWidgetItem *item, ui->listWidget->selectedItems()){delete item;}}

clear();

So, the add button emits clicked, ending up in the on_addButton_clicked slot resulting in the following code being run. It simply asks for input and then adds it to the list.

The delete button emits clicked, ending up in the on_deleteButton_clicked slot, where all selected items are deleted.

The clear button emits clicked, which ends up in the clear slot of the QListWidget, which to us is a black box that does what its documentation says.

Signals and Slots vs Callbacks

A callback is a pointer to a function that is called when an event occurs, any function can be assigned to a callbackNo type-safety

Always works as a direct call

Signals and Slots are more dynamicA more generic mechanism

Easier to interconnect two existing classes

Less knowledge shared between involved classes

Signals and slots implement a pattern that is quite common. Usually, the mechanism is implemented as callback functions. It is important to recognize that signals/slots not are callbacks.

For instance, a callback is simply a pointer. There is not actual check of signature compatibility. They also always work as direct calls, making them tricky to use across threads, etc.

Signals and slots are more dynamic. For instance, the mechanism is 100% generic, so any QObject can be connected to any other QObject. The sender and receiver do not have to know of each others' implementations.

Signature compatibility checks provide safety, but also flexibility, as they allow you to ignore arguments.

All in all, signals and slots are easier, safer and more flexible.

(for the advanced: compare to functors, which are clumsier from an implementation point and less flexible when it comes to skipping arguments, etc).

Continues

What is a slot?

A slot is defined in one of the slots sections

A slot can return values, but not through connections

Any number of signals can be connected to a slot

It is implemented as an ordinary method

It can be called as an ordinary method

public slots:void aPublicSlot();protected slots:void aProtectedSlot();private slots:void aPrivateSlot();

connect(src, SIGNAL(sig()), dest, SLOT(slt()));

A slot is an ordinary function, just that it can be connected to signals. They do not have to be connected, you can call a slot like any other function, and you implement it as usual.

Slots are declared in one of the sections public, protected and private slots. These access restrictions work as intended when calling the function, but a private or protected slot can be connected to any other signal, so they can be triggered from outside the class.

Slots can return values, but connections cannot carry return arguments.

Any number of signals can be connected to a single slot. This means that a single slot can serve several sources of events think keyboard shortcut, button, etc.

Continues

What is a signal?

A signal is defined in the signals section

A signal always returns void

A signal must not be implementedThe moc provides an implementation

A signal can be connected to any number of slots

Usually results in a direct call, but can be passed as events between threads, or even over sockets (using 3rd party classes)

The slots are activated in arbitrary order

A signal is emitted using the emit keyword

signals:void aSignal();

emit aSignal();

Signals re defined in the signals section. This section can be considered protected, as a signal can only be emitted from within a class or its decendants.

Signals always return void, and must not be implemented. Instead, moc provides function bodies that trigger the actual slot-activation-code.

A signal can be connected to any number of slots, so a single event can trigger multiple reactions.

It is fully possible to connect signals and slots across threads. Third party libraries such as Qxt (http://doc.libqxt.org/0.5.0/classQxtRPCPeer.html).

Inside a signal emitting class, you use the emit keyword to emit a signal.

Continues

Making the connection

QObject::connect( src, SIGNAL( signature ), dest, SLOT( signature ) );

( ... )

clicked()toggled(bool)setText(QString)textChanged(QString)rangeChanged(int,int)

setTitle(QString text)setValue(42)

A signature consists of the function name
and argument types. No variable names,
nor values are allowed.

Custom types reduces reusability.

QObject*setItem(ItemClass)

You can make signals to slots connections between any two QObjects.

Qt verifies that the signatures of the signal and slot match. The signature consists of the name of the signal or slot followed by the argument types.

There must be no values nor variable names in the signature.

It is also recommended to stick to using standard types, e.g. the ItemClass custom type reduces the reusability and should thus be avoided.

Continues

Making the connection

Qt can ignore arguments, but not create values from nothing

SignalsrangeChanged(int,int)rangeChanged(int,int)rangeChanged(int,int)

valueChanged(int)valueChanged(int)valueChanged(int)

textChanged(QString)

clicked()clicked()

SlotssetRange(int,int)setValue(int)updateDialog()

setRange(int,int)setValue(int)updateDialog()

setValue(int)

setValue(int)updateDialog()

When matching signatures, Qt is very forgiving. The basic rule is that Qt cannot create or convert values, but apart from that anything is allowed (i.e. skipping arguments).

The examples on the slide demonstrate this. The errors are (from the top):missing the last int (cannot create)

QString does not match int (cannot convert)

missing the only int (cannot create)

Continues

Automatic Connections

When using Designer it is convenient to have automatic connections between the interface and your code

Triggered by calling QMetaObject::connectSlotsByName

Think about reuse when namingCompare on_widget_signal to updatePageMargins

on_ object name _ signal name ( signal parameters )

on_addButton_clicked();

on_deleteButton_clicked();

on_listWidget_currentItemChanged(QListWidgetItem*,QListWidgetItem*)

updatePageMargins
can be connected to
a number of signals
or called directly.When making connections from Designer to your own source, Qt uses the automatic connection mechanism.

It lets signals automatically connect to slots with the corresponding name (structure and examples on the slide).

The automatic connections are made when connectSlotsByName is called. That is done at the end of the setupUi function generated by Designer.

When using this mechanism, think about reusability. Sometimes handwriting a couple of connect statements can greatly improve readability of the code.

Continues

Synchronizing Values

Connect both ways

An infinite loop must be stopped no signal is emitted unless an actual change takes place

connect(dial1, SIGNAL(valueChanged(int)), dial2, SLOT(setValue(int)));

connect(dial2, SIGNAL(valueChanged(int)), dial1, SLOT(setValue(int)));

void QDial::setValue(int v){if(v==m_value)return;...

This is the responsibility of all code that can emit signals do not forget it in your own classes

A common scenario for signals and slots is to synchronize a pair of widgets.

This is implemented by interconnecting two objects. (Refer to the example). If the value of dial1 changes, it emits valueChanged, which changes the value of dial2, that emits valueChanged, which changes the value of dial1, that emits...

To avoid an infinite loop (which results in endless recursion, which will make the stack grow until you run out of memory and then crash miserably) the setValue function ignores attempts to set the current value.

Custom signals and slots

class AngleObject : public QObject{Q_OBJECTQ_PROPERTY(qreal angle READ angle WRITE setAngle NOTIFY angleChanged)

public:AngleObject(qreal angle, QObject *parent = 0);qreal angle() const;

public slots:void setAngle(qreal);

signals:void angleChanged(qreal);

private:qreal m_angle;};

Add a notify signal here.

Setters make natural slots.

Signals matchthe settersAdding custom signals and slots to a class is really quite easy.

You can add slots for numerous reasons. For instance, for each and every possible user action (file open, save, close, copy, cut, paste, help, about). Setter functions also make natural slots.

Adding slots is just a matter of putting your functions in the right section of the declaration.

Adding signals is just as easy, simply declare them in the signals section.

If you have properties, it is convention to inform Qt of signals using the Q_PROPERTY macro.

Continues

Setter implementation details

void AngleObject::setAngle(qreal angle){if(m_angle == angle)return;

m_angle = angle;emit angleChanged(m_angle);}

Protection againstinfinite loops.Do not forget this!

Update the internal state,then emit the signal.Signals are protected so you can emit them from derived classes.Implementing slots is just as implementing common methods. However, you must not forget the infinite loop protection.

Emitting signals is as easy as calling emit signalName(arguments).

When emitting signals, make sure to update the internal state first, so that your object is updated before it is queried.

The QObject

QObject is the base class of almost all Qt classes and all widgets

It contains many of the mechanisms that make up Qtevents

signals and slots

properties

memory management

The QObject class is essential to Qt

It is the base class of most active classes in Qt, including all widgets (the image shows only a subset of the classes derived from QObject, it also shows that those subclasses are themselves inherited by other classes)

The QObject implements many of the features that makes Qt what it is, such as:

signalsslotspropertiessimplified memory management

The QObject

QObject is the base class to most Qt classes. Examples of exceptions are:Classes that need to be lightweight such as graphical primitives

Data containers (QString, QList, QChar, etc)

Classes that needs to be copyable, as QObjects cannot be copied

The QObject is the base class of many of Qt's classes, but there are some exceptions.

Graphics view items are not QObjects, as they have been designed to be lean (there can be tens of thousands of them, so every byte is important).

QString, QList, QChar, all represent values, and cannot be QObjects because of that (continues)

The QObject

They can have a name (QObject::objectName)

They are placed in a hierarchy of QObject instances

They can have connections to other QObject instances

Example: does it make sense to copy a widget at run-time?

QObject instances are individuals!

So, why cannot QChar be a QObject. QObjects are individuals!

This means that you cannot write obj1 = obj2, copy is not a valid operation.

Why?

QObjects have names, for instance addButton, deleteButton, etc (from the first lecture's demo).How do you copy this? You don't want two addButtons?

QObjects are structured in hierarchies. How do you copy that context? Do you want them at the same level? Leaf level? Root level?

QObjects can be interconnected (add calls a slot)How do you copy this? Do you want to duplicate connections?

Meta data

Qt implements introspection in C++

Every QObject has a meta object

The meta object knows aboutclass name (QObject::className)

inheritance (QObject::inherits)

properties

signals and slots

general information (QObject::classInfo)

QObjects carry meta-data, that is data about the object itself.

This makes it possible to add introspection to Qt, for instance to ask a class which methods it has

Every QObject has a meta object which can be retreived using the metaObject method.

The meta object knows about the class, its name, base class, properties, methods, slots and signals.

Continues

Meta data

The meta data is gathered at compile time by the meta object compiler, moc.

sources*.cppexecutablesobject files*.oheaders*.hOrdinary C++ Build Process

includes

compiles

links

While providing C++ developers with meta data, Qt is still based 100% on C++. There is no other language involved.

Instead, moc, the meta object compiler, parses the C++ code and generates even more C++ code.

The figure shows an ordinary C++ build process, headers are included, sources compiled, object files linked and the end result is executable code. (Even libraries are, more or less, executable code, so this holds true in all cases.)

Continues

Meta data

The meta data is gathered at compile time by the meta object compiler, moc.

The moc harvests data from your headers.

sources*.cppexecutablesobject files*.oheaders*.hgeneratedmoc_*.cppQt C++ Build Process

includes

compiles

links

compiles

mocs

So, Qt adds a new step to the build process. Moc parses class declarations and generates C++ code that implements the specific meta object.

Notice that it is possible to have the moc work on a source file directly, and then include the result into the same source file, but that is only useful in the special case of a QObject derived class only used from within one single file.

All this is handled by QtCreator, so you do not need to worry about it. There are solutions taking care of this step for all other build environments as well (command line builds, Visual Studio, Eclipse, Xcode, etc)

Continues

Meta data

What does moc look for?

class MyClass : public QObject{Q_OBJECTQ_CLASSINFO("author", "John Doe")

public:MyClass(const Foo &foo, QObject *parent=0);

Foo foo() const;

public slots:void setFoo( const Foo &foo );

signals:void fooChanged( Foo );

private:Foo m_foo;};

Qt keywordsGeneral infoabout the class

The Q_OBJECTmacro, usually first

Make sure that you inherit QObject first (could be indirect)

What does moc look for in your class declarations?

First of all, you need to inherit QObject directly or indirectly. If you are inheriting multiple classes, your QObject (decendant) must appear first. You cannot inherit from QObject twice (directly or indirectly).

You then need to put the Q_OBJECT macro in your class declaration, in the private section. By convention, and historical limitations, this is usually placed first.

You can then add class info, and more, through special macros. For instance, in this case the key author is given the value John Doe.

Qt also adds some special keywords (just macros, from the compiler's point of view) which will discuss later on.

Introspection

The classes know about themselves at run-time

Great for implementing
scripting and dynamic
language bindings

if (object->inherits("QAbstractItemView")){QAbstractItemView *view = static_cast(widget);view->...

enum CapitalsEnum { Oslo, Helsinki, Stockholm, Copenhagen };

int index = object->metaObject()->indexOfEnumerator("CapitalsEnum");object->metaObject()->enumerator(index)->key(object->capital());

Enables dynamic casting without RTTI

Example:It is possible to convertenumeration values to strings for easier reading and storing

The meta object knowsabout the detailsHere, the slides will walk through the different features that the QObject class and meta data enable.

First example, the inherits keyword lets you check if a class inherits, i.e. is-a, class. A class inherits itself, so it can be used as a check when deciding how to cast. This shows that meta data knows of the class hierarchy.

Another example is that the meta data contains information about your enums (have you passed the enum through the Q_ENUM macro). That way, you can convert enum values from and to text easily. This shows that the meta data has detailed knowledge about each class.

All this information makes it quite easy to integrate Qt with scripting languages and other dynamic environments (PyQt, Ruby, JavaScript, etc)

Properties

QObject have properties with getter and setter methods

Naming policy: color, setColor

For booleans: isEnabled, setEnabled

class QLabel : public QFrame{Q_OBJECTQ_PROPERTY(QString text READ text WRITE setText)public:QString text() const;public slots:void setText(const QString &);};

Setter, returns void,takes value as only argument

Getter, const, returns value,takes no argumentsOne commonly used feature is the Qt property system. You have probably used it without noticing if you have looked at the Designer part of QtCreator. There you have a list of properties for each class easily available.

Properties are implemented through getter and setter methods, so the actual value is stored as a private member.

Convention is that:Getters are named after the property (no get-prefix) or with the is-prefix if a boolean.Setters are named with a set-prefix.

The pair of functions is then made into a Qt property using the Q_PROPERTY macro. (arguments: type, name, READ, getter, WRITE, setter)

Continues

Properties

Why setter methods?Possible to validate settings

Possible to react to changes

void setMin( int newMin ){if( newMin > m_max ){qWarning("Ignoring setMin(%d) as min > max.", newMin);return;}...

void setMin( int newMin ){...

m_min = newMin;updateMinimum();}

Why do you want to use setters instead of public variables?

Setters makes it possible to validate settings before you store them. Asserting all input is a good habit and improves code quality.

Setters also make it possible to react to changes. For instance, if someone alters the text property of a label, the label makes sure that it is repainted as needed.

Continues

Properties

Why getter method?Indirect properties

QSize size() const{return m_size;}

int width() const{return m_size.width();}

Getter methods are also useful, even though not as useful as setters. (reading public variables is somewhat ok)

What they do is that they separate the storage of the data from the reading. The read is indirect.

Example: size contains both width and height. Width contains the width of the size.

Example: a set of flags can be stored in a 32-bit word instead of in separate booleans.

Continues

Properties

Q_PROPERTY(type nameREAD getFunction[WRITE setFunction][RESET resetFunction][NOTIFY notifySignal][DESIGNABLE bool][SCRIPTABLE bool][STORED bool][USER bool][CONSTANT][FINAL])

Properties as specified using the Q_PROPERTY macro. What can we read from it?

All properties must have a type, a name and be readable.

WRITE: They can be written to (optional)RESET: They can be reset (optional), this means to write no value to itNOTIFY: A notify signal is sent when the property is changed (more on signals later). An example would be, textChanged, in the previous class (text/setText)DESIGNABLE/SCRIPTABLE: Properties can be made available in designer and through scripts.STORED: Most properties are stored, meaning that they are a part of the object's state. However, some properties take their values from other properties (e.g. width is a part of size) and are not stored.USER: A user property is the property modified by the user, e.g. isChecked of a QCheckBox)CONSTANT: Constant properties do not change for an instance of a class. (i.e. it can change between instances)FINAL: Final properties are not overridden. This is not enforced, but relied upon for performance optimizations.

Continues

Using properties

Direct access

Through the meta info and property system

Discover properties at run-time

QString text = label->text();label->setText("Hello World!");

QString text = object->property("text").toString();object->setProperty("text", "Hello World");

int QMetaObject::propertyCount();QMetaProperty QMetaObject::property(i);

QMetaProperty::name/isConstant/isDesignable/read/write/...

What does a property look like when using a class?

You can use it directly, simply call the getter and setter.

You can use the property/setProperty calls. They work with QVariants (a type that contains all types), so you have to cast it to strings using toString, etc.

You can also ask the meta object about properties. Whether they can be read/written, their name, etc. This can then be used when calling property/setProperty.

Continues

Dynamic properties

Lets you add properties to objects at run-time

Can be used to tag objects, etc

bool ret = object->setProperty(name, value);

QObject::dynamicPropertyNames() const

true if the property has been defined using Q_PROPERTY

false if it is dynamically added

returns a list of thedynamic propertiesUsing the property/setProperty approach, you can also create dynamic properties.

This makes it possible to tag objects. For instance, to add a required boolean property to certain QLineEdits. The line editors do not need to know about this, but they can still keep track of it as a dynamic property. When validating the form, you can check this property and see if they are empty or not.

Continues

Creating custom properties

class AngleObject : public QObject{Q_OBJECTQ_PROPERTY(qreal angle READ angle WRITE setAngle)

public:AngleObject(qreal angle, QObject *parent = 0);

qreal angle() const;

void setAngle(qreal);

private:qreal m_angle;};

Macro describing the property

Initial value

Getter

Setter

Private stateWhen adding custom properties, it is good to follow the standard template. This makes it more intuitive to use your code with Qt code.

The parts that you need are a getter and a setter. In addition, you need to store the state somewhere (private state).

Optionally, you can allow the initialization of common properties (i.e. text of a label, but not window title of it as it isn't a top level widget most of the time).

When all parts are there, you can use the Q_PROPERTY macro. From left to right: type, name, getter, setter.

Continues

Creating custom properties

AngleObject::AngleObject(qreal angle, QObject *parent) :QObject(parent), m_angle(angle){}

qreal AngleObject::angle() const{return m_angle;}

void AngleObject::setAngle(qreal angle){m_angle = angle;doSomething();}

Initial value

Getter simply returns the value. Here you can calculate complex values.

Update internal state, then react to the change.When implementing the custom properties, you simply implement the functions as always.

Ensure that the constructor calls QObject with the parent pointer.

In the getter, you could have returned a calculated value. For instance, with width, as a part of the size (shown earlier).

In the setter, first update the state, then react to the change. This way, you can use the getter throughout the class. This makes it easier to make changes later on.

Continues

Memory Management

QObject can have parent and children

When a parent object is deleted, it deletes its children

QObject *parent = new QObject();QObject *child1 = new QObject(parent);QObject *child2 = new QObject(parent);QObject *child1_1 = new QObject(child1);QObject *child1_2 = new QObject(child1);

delete parent;

parentchild1child2child1_1child1_2

parent deletes child1 and child2child1 deletes child1_1 and child1_2

QObjects can be used in a way that takes care of all the dirty parts of memory management. It becomes almost as easy as working with a garbage collector, but you are still in full control.

The trick is that all QObjects have a parent, or an owner. When the parent is deleted, it deletes all its children.

This reduces the number of objects that you need to keep track of to one, in the ideal case.

Continues

Memory Management

This is used when implementing visual hierarchies.

QDialog *parent = new QDialog();QGroupBox *box = new QGroupBox(parent);QPushButton *button = new QPushButton(parent);QRadioButton *option1 = new QRadioButton(box);QRadioButton *option2 = new QRadioButton(box);

delete parent;

parent deletes box and buttonbox deletes option1 and option2The very same parent-child relationship is used to represent visual hierarchies.

Refer to the tree structure, the box contains the radio buttons (option1/2). The parent contains the box and the button. Compare to the previous slide.

Continues

Usage Patterns

Use the this-pointer as top level parent

Allocate parent on the stack

void Widget::showDialog(){Dialog dialog;

if (dialog.exec() == QDialog::Accepted){...}}

Dialog::Dialog(QWidget *parent) : QDialog(parent){QGroupBox *box = QGroupBox(this);QPushButton *button = QPushButton(this);QRadioButton *option1 = QRadioButton(box);QRadioButton *option2 = QRadioButton(box);...

dialog is deleted whenthe scope ends

So, how does this make memory management easy. I still need to keep track of an object and make sure that I delete it?

No, not if you use the stack cleverly.

First of all, the example from the previous slide would probably have been implemented in the parent's constructor, i.e. this is the top-level parent.

Second, when using the dialog, you allocate it on the stack. This means that the dialog, along with all its children, will be deleted when the scope ends.

Continues

Heap

When using new and delete, memory is allocated on the heap.

Heap memory must be explicitly freed using delete to avoid memory leaks.

Objects allocated on the heap can live for as long as they are needed.

new

delete

Construction

Destruction

These slides intend to jog the students' memory, not explain the stack vs heap decision in full.

The heap is used when you allocate memory dynamically. In C++, that means new/delete. In C you have used malloc and free.

Heap memory must be explicitly freed, i.e. you must call delete on everything that you allocate.

If you do not do so, you will leak memory. This will, eventually, lead to memory shortage and a crash.

Dynamically allocated objects live until you delete them, so you have full control of when something is constructed or destructed.

Continues

Stack

Local variables are allocated on the stack.

Stack variables are automatically destructed when they go out of scope.

Objects allocated on the stack are always destructed when they go out of scope.

int a

}

Construction

Destruction

The stack is used for automatic memory allocations (as opposed to dynamic memory).

The stack grows and shrinks when you make function calls. It is used for local variables, function arguments and return addresses.

Objects allocated on the stack are destructed when they go out of scope.

The scope ends with a }, or return, or for single-line scopes, at the end of the line.

Continues

Stack and Heap

To get automatic memory management, only the parent needs to be allocated on the stack.

MyMainWindow

QApplicationint main(int argc, char **argv){QApplication a(argc, argv);MyMainWindow w;w.show();return a.exec();}

MyMainWindow::MyMainWindow(...{new QLabel(this);new ...}

To get almost automatic memory management using Qt, the trick is to allocate the outermost parents on the stack, and the rest on the heap.

For instance, the main function scope will be valid for as long as the application is running, so we allocate the application and window on the stack.

The window, in turn, creates a bunch of child widgets in its constructor. To avoid destruction when the scope of the constructor ends, they are allocated dynamically. But, they are given the window as their parent object and are thus also destructed when the main function scope ends.

Constructor Etiquette

Almost all QObjects take a parent object with a default value of 0 (null)

The parent of QWidgets are other QWidgets

Classes have a tendency to provide many constructors for convenience (including one taking only parent)

The parent is usually the first argument with a default value

QLabel(const QString &text, QWidget *parent=0, Qt::WindowFlags f=0);

QObject(QObject *parent=0);

QPushButton(QWidget *parent=0);QPushButton(const QString &text, QWidget *parent=0);QPushButton(const QIcon &icon, const QString &text, QWidget *parent=0);

As QObjects all have parents, most QObject-related constructors take a parent pointer.

Exceptions: QCoreApplication (QApplication) are QObjects w/o parents

The parent usually appears as the left-most argument with a default value. However, as there can be multiple constructors, this can be compensated.

It is common to use the explicit keyword to avoid unwanted automatic conversion of types.

Example: QPushButton, the parent ends up last in all c'tor versions as there are no other default arguments

Example: QLabel, the window flags and parent have default values. This means that the parent is the first of them, but after the text as the text property has no default value.

Continues

Constructor Etiquette

When creating your own QObjects, considerAlways allowing parent be 0 (null)

Having one constructor only accepting parent

parent is the first argument with a default value

Provide several constructors to avoid having to pass 0 (null) and invalid (e.g. QString()) values as arguments

As these are recommended guidelines and making exceptions is always an option. However, following them, makes your code more Qt-esque.

Allow parent to be null (take that into consideration in your code).

Have one constructor taking only parent (lets you use your widgets from Designer). This forces you to handle the case where all properties are set to default values or not set at all.

Placing parent in the right place helps reuse.

Multiple constructors avoids the need to pass dummy settings to unused properties.

Pratice

Temperature Converter

Uses the TempConverter class to convert between Celsius and Fahrenheit

Emits signals when temperature changes

Let's look at a real example that adds slightly more complexity to the situation.

We will use two dial LCD pairs and interconnect them using our custom TempConverter class that converts between Celsius and Fahrenheit.

It does not only convert, it monitors and emits signals when changes take place.

Continues

Temperature Converter

The dialog window contains the following objectsA TempConverter instance

Two QGroupBox widgets, each containingA QDial widget

A QLCDNumber widget

The dialog contains a TempConverter object and the user interface.

The user interface consists of two halves one for Celsius and one for Fahrenheit. Each consisting of a QGroupBox.

The group boxes each contains a QDial and a QLCDNumber.

Continues

Temperature Converter

class TempConverter : public QObject{Q_OBJECT

public:TempConverter(int tempCelsius, QObject *parent = 0);

int tempCelsius() const;int tempFahrenheit() const;

public slots:void setTempCelsius(int);void setTempFahrenheit(int);

signals:void tempCelsiusChanged(int);void tempFahrenheitChanged(int);

private:int m_tempCelsius;};

Q_OBJECT macro first

QObject as parent

parent pointer

Read and write methods

Emitted on changesof the temperature

Internal representationin integer Celsius.The TempConverter class declaration.

QObject, parent and Q_OBJECT macro are needed before we can add signals and slots.

The setters are slots.

There are signals for changes in either temperature.

To avoid infinite loops we must have a current temperature. In this example we have decided to keep it in celsius.

As we use integers throughout, this will not be very accurate, from a temperature conversion point of view.

Continues

Temperature Converter

void TempConverter::setTempCelsius(int tempCelsius){if(m_tempCelsius == tempCelsius)return;

m_tempCelsius = tempCelsius;

emit tempCelsiusChanged(m_tempCelsius);emit tempFahrenheitChanged(tempFahrenheit());}

void TempConverter::setTempFahrenheit(int tempFahrenheit){int tempCelsius = (5.0/9.0)*(tempFahrenheit-32);setTempCelsius(tempCelsius);}

The setTempCelsius slot:

The setTempFahrenheit slot:

Test for change tobreak recursion

Update object state

Emit signal(s)reflecting changesConvert and pass onas Celsius is the internalrepresentationLooking at the slot implementations, the set temp Celsius slot contains the recursion lock, as the current temperature is kept in Celsius. It then updates the internal state and emits both signals.

Notice that the argument of the Fahrenheit signal is retrieved from the getter function, which does the actual conversion C to F.

The set Fahrenheit slot converts the temperature (F to C) and then uses the set Celsius method.

Continues

Temperature Converter

The dials are interconnected through the TempConverter

The LCD displays are driven directly from the dials

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

The window making up the application then contains four widgets (dial + LCD for Celsius and Fahrenheit) and a TempConverter object.

The connections between the dials are set up to go through the temperature converter object, while the LCDs are directly driven by the dials.

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

The next slides will show how a signal propagates through the system.

Everything starts with a user event the celsiusDial is moved.

This results in it emitting the valueChanged signal

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

The valueChanged signal is connected to display of the celsiusLcd and setTempCelsius of the temperature converter.

The display call simply changes the value shown in the LCD, while the setTempCelsius call changes the value of the temperature converter.

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

Changing the temperature of the temp converter results in two signals being emitted: tempCelsiusChanged and tempFahrenheitChanged

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

The tempCelsiusChanged signal is connected to the celsiusDial's setValue slot. This slot detects that the value of the dial isn't changed thus halting there.

The tempFahrenheitChanged signal connects to setValue of the fahrenheitDial, causing the dial's value to change

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

As the dial's value changes, it emits the valueChanged signal with the new value

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

This causes the fahrenheitLcd to be updated (through the display slot)

The setTempFahrenheit slot is also called. This slot detects that the temperature isn't changed and stops there.

Continues

Temperature Converter

The user moves the celsiusDial

TempConverter

setTempCelsiussetTempFahrenheit

tempCelsiusChangedtempFahrenheitChanged

valueChanged setTempCelsius

valueChanged setTempFahrenheit

tempCelsiusChanged setValue

tempFahrenheitChanged setValue

valueChanged display

connect(celsiusDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempCelsius(int)));connect(celsiusDial, SIGNAL(valueChanged(int)), celsiusLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempCelsiusChanged(int)), celsiusDial, SLOT(setValue(int)));

connect(fahrenheitDial, SIGNAL(valueChanged(int)), tempConverter, SLOT(setTempFahrenheit(int)));connect(fahrenheitDial, SIGNAL(valueChanged(int)), fahrenheitLcd, SLOT(display(int)));connect(tempConverter, SIGNAL(tempFahrenheitChanged(int)), fahrenheitDial, SLOT(setValue(int)));

At this point, all signals have propagated through the system and the TempConverter objects, and all the widgets are again in sync.

Notice how the slots take responsibility to stop infinite propagation through the system.

Also notice the importance of picking a scale to use for the current temperature. A rounding error causing a mismatch between the Celsius and Fahrenheit values could have caused the system to swing forth and back indefinitely.