Tutorial Qt – Capitulo 2 – Modelo de Objetos Qt

Qt é baseado em voltado modelo de objetos Qt.Essa arquitetura é o que faz Qt poderosa e fácil de usar. É tudo baseado em torno da classe QObject e da ferramenta moc. Pelas classes derivadas do objeto QObject vários beneficios são herdados. Eles são listados abaixo:

-> Fácil gerenciamento de memória
-> Sinais e conexões
-> Propriedades
-> Auto-conhecimento

Cada uma dessas caracteristicas são discutidas abaixo. Antes de continuar é importante lembrar que Qt segue o padrão C++ com algumas macros, como qualquer outra aplicação C/C++. Não existe nada diferente ou fora do padrão nela, que fica bem comprovado pelo fato do codigo ser muito portavel.
Fácil gerenciamento de memória

Quando criando uma instancia de uma classe derivada de QObject é possivel passar um ponteiro de um objet-pai para um construtor. Isso é o básico da gerenciamento de memória simplificado. Quando uma pai é deletado, todos os filhos são deletados também. Isso significa que uma classe derivada de QObject pode criar instâncias de filhos da QObject passando isso como pai sem preocupação com seus destrutores.

para melhor entendimento, segue abaixo um exemplo. Exemplo 2-1 mostra nossa classe exemplo. É derivada de QObject e relata tudo o que acontece a ela no console. Isso tornará obvio o que acontece com ela.

class VerboseObject : public QObject
{
public:
VerboseObject( QObject *parent=0, char *name=0 ) : QObject( parent, name )
{
std::cout << “Created: ” << QObject::name() << std::endl;
}

~VerboseObject()
{
std::cout << “Deleted: ” << name() << std::endl;
}

void doStuff()
{
std::cout << “Do stuff: ” << name() << std::endl;
}
};

Exemplo 2-1

Para fazer algo útil com essa classe, uma rotina ‘main’ é requerida. Ela é mostrada no exemplo 2-2. Repare que a primeira coisa que acontece é que uma instância de QApplication é criada. Isso é requerido por quase todas as aplicações Qt e é uma boa prática para evitar problemas desnecessários. O código cria uma hierarquia de memória mostrada na figura 2-1:

int main( int argc, char **argv )
{
// Create an application
QApplication a( argc, argv );

// Create instances
VerboseObject top( 0, “top” );
VerboseObject *x = new VerboseObject( &top, “x” );
VerboseObject *y = new VerboseObject( &top, “y” );
VerboseObject *z = new VerboseObject( x, “z” );

// Do stuff to stop the optimizer
top.doStuff();
x->doStuff();
y->doStuff();
z->doStuff();

return 0;
}

Exemplo 2-2

Certifique-se de entender como a arvore é traduzida no código do exemplo acima e vice versa.

A hierarquia da memória (Figura 2-1)

O exemplo abaixo mostra a execução do código ememplo. Como pode ser visto todos os objetos são deletados, pais primeiro e então os filhos. Repare que cada ramo é deletado completamente antes que o próximo comece a ser. Isso pode ser visto quando z é apagado antes de y.

$ ./mem
Created: top
Created: x
Created: y
Created: z
Do stuff: top
Do stuff: x
Do stuff: y
Do stuff: z
Deleted: top
Deleted: x
Deleted: z
Deleted: y
$

Exemplo 2-3

Se a referencia-pai é removida de x e y como mostrado no exemplo 2-4, um buraco de memória ocorre. Isso é como uma execução de teste documentada no exemplo 2-5:


VerboseObject *x = new VerboseObject( 0, “x” );
VerboseObject *y = new VerboseObject( 0, “y” );

Exemplo 2-4

$ ./mem
Created: top
Created: x
Created: y
Created: z
Do stuff: top
Do stuff: x
Do stuff: y
Do stuff: z
Deleted: top
$

Exemplo 2-5

O buraco de memória mostrado acima não causa nenhum aviso na atual situação, como a aplicação termina só depois dele acontecer, mas em outras situações isso poderia ser uma causa potencial para instabilidade no sistema.

Sinais e conexões

Os sinais e conexões são o que fazem os diferentes componentes Qt serem reusaveis como eles são. Eles fornecem um mecanismos no qual é possivel dispor de interfaces que podem ser livremente interconectadas. Por exemplo um item de menu, botão, barra de ferramentas e qualquer outro item pode dispor sinais correspondentes a “ativado”, “clicado” ou qualquer outro evento apropriado. Por conectar cada sinal a uma conexão de qualquer outro item, o evento automaticamente chama-se de conexão.

Um sinal pode também incluir valores, que tornam posivel conectar um deslocador, uma caixa giratória, uma maçaneta ou item que gere um valor para qualquer item que aceite valores, por exemplo outro deslocador, maçaneta ou caixa giratória, ou algo completamente diferente como uma tela LCD.

A principal vantagem dos sinais e conexões é que quem chama não tem que saber nada sobre quem recebe o sinal e vice versa. Isso torna possivel interagir muitos componentes facilmente sem que o desenhista de componentes tenha que pensar sobre a configuração usada. Isso é verdadeiramente acoplamento fraco.

Função ou Conexão?
Para complicar as coisas um pouco, o ambiente de desenvolvimento embutido com o Qt 3.1.x e posteriores algumas vezes refere-se a conexões como funções. Como elas comportam-se da mesma maneira em muitos casos isso não é um problema, masao longo desse texto o termo “conexão” será usado.

Para ser possivel usar os sinais e conexões em cada classe eles são declarados em um arquivo de cabeçalho. A implementação é melhor localizada em um arquivo cpp separado. O arquivo de cabeçalho é então passado através de uma ferramenta do Qt conhecida como moc. O moc produz um cpp contendo o código que faz os sinais e conexões funcionarem (e mais). A Figura 2-2 ilustra esse esquema. Repare a nomenclatura usada (o prefixo moc_ ) na figura:

O esquema do moc

Figura 2-2

Esse estágio adicional da compilação pode parecer complicar o processo de contrução, mais existe outra ferramenta Qt, qmake. Ela torna tão simples quanto qmake -project && qmake && make construir qualquer aplicação Qt. isso será descrito em detalhes mais adiante nesse tutorial.

O que sinais e conexões são de verdade?
Como mencionado antes, a aplicação Qt é 100% C++, então o que sinais e conexões são de verdade? uma parte são palavras chavea atuais que são simplesmente trocadas adequadamente pelo pre-processador C++. As conexões são implementadas como qualquer metodo da classe enquanto os sinais são implementados pelo moc. Cada objeto então carrega uma lista de suas ligações (em que conexões são ativadas por seu sinal) e suas conexões (em que são usadas para contruir a tabela de ligações no metodo conector). As declarações dessas tabelas são ocultas em um Q_OBJECT macro. Naturalmente existe mais que isso, mas tudo pode ser visto olhando um arquivo cpp gerado pelo moc.

Uma demonstração básica do acoplamento fraco que os sinais e conexões fornecem são demonstrados no exemplo abaixo. Primeiro, a classe recebedora é mostrada no exemplo 2-6. Repare que nada impede que uma classe seja recebedora e enviadora, isto é, uma única classe pode ter sinais e conexões.

class Receiver : public QObject
{
Q_OBJECT

public:
Receiver( QObject *parent=0, char *name=0 ) : QObject( parent, name )
{
}

public slots:
void get( int x )
{
std::cout << “Received: ” << x << std::endl;
}
};

Exemplo 2-6

Essa classe inicia com o Q_OBJECT macro que é necessário se qualuqer outra característica além de gerenciamento de memória simplificado é usado. Essa macro inclui algumas declarações chaves e serve como um criador para o moc que indica que a classe deve ser analisada. Tmabém repare que uma nova seção chamada “public slots” foi adicionada na sintaxe.

Os exemplos 2-7 e 2-8 contém as implementações de classes enviadoras. A primeira classe SenderA, implementa o sinal enviador que é emitido do membro doEmit. A segunda classe SenderB emite o sinal que é emitido do membro doStuff. Repare que essas classes nada tem em comum exceto a herança de QObject.

class SenderA : public QObject
{
Q_OBJECT

public:
SenderA( QObject *parent=0, char *name=0 ) : QObject( parent, name )
{
}

void doSend()
{
emit( send( 7 ) );
}

signals:
void send( int );
};

Exemplo 2-7

class SenderB : public QObject
{
Q_OBJECT

public:
SenderB( QObject *parent=0, char *name=0 ) : QObject( parent, name )
{
}

void doStuff()
{
emit( transmit( 5 ) );
}

signals:
void transmit( int );
};

Exemplo 2-8

Para demonstrar como o código moc é misturado com o código escrito por programadores humanos, esse exemplo não usa o classico método do qmake mas ao invés disso inclui o código explicitamente. Para chamar o moc, o comando abaixo é usado:

$QTDIR/bin/moc sisl.cpp -o moc_sisl.h.

A primeira lina do exemplo 2-9 inclui o arquivo resultante, moc_sisl.h. O código exemplo também contém o código criado por dierentes intancias de objetos e liga os diferentes sinais e conexões, Essa peça de código é o úncio local ciente tanto da classe recebedora quanto da enviadora. A classe por se só conhece somente a assinatura, além de conhecer a interface do sinal que emite ou recebe.

#include “moc_sisl.h”

int main( int argc, char **argv )
{
QApplication a( argc, argv );

Receiver r;
SenderA sa;
SenderB sb;

QObject::connect( &sa, SIGNAL(send(int)), &r, SLOT(get(int)) );
QObject::connect( &sb, SIGNAL(transmit(int)), &r, SLOT(get(int)) );

sa.doSend();
sb.doStuff();

return 0;
}

Exemplo 2-9

Finalmente, o exemplo 2-10 mostra o resultado de uma execução de teste. Os sinais são emitidos e o recebedor mostra os valores.

$ ./sisl
Received: 7
Received: 5
$

Exemplo 2-10

Valores na ligação?
Uma concepção errada comum é que é possivel definir valores a serem enviados junto com um sinal quando fazendo a ligação, por exemplo, connect( &a, SIGNAL(signal(5)), &b, SLOT(slot(int)) );. Isso não é possivel.Apenas as assinaturas dos sinais são usadas na chamada da ligação. Quando emite-se o sinal um parametro pode ser fornecido, e apenas então. A versão correta do coódigo anterior poderia ser emitindo o sinal

Propriedades

Objetos Qt podem ter propriedades. Elas são simplesmente um valor com um tipo e, pelo menso, uma função de leitura, mas possivelmente uma função de escrita também. Elas são, por exemplo, usadas pelo desenhista para mostrar as propriedades de todos os widgets. A documentação oficial das propriedades pode ser encontrada aqui

As propriedades não são apenas o grande caminho para organizar o código e definir qual função afeta qual propiedade. Elas podem também ser usadas como uma forma primitiva de reflexão. Qualquer ponteiro para um QObject pode acessar propriedades de um object para o qual aponta. Até se é uma classe derivada, mas complexa.

O código no exemplo 2-11 demonstra como uma classe com propriedades é declarado. O código exibido pertence ao arquivo propobject.h. A macro Q_PROPERTY combinada com a macro Q_ENUMS faz o truque.

#ifndef PROPOBJECT_H
#define PROPOBJECT_H

#include
#include

class PropObject : public QObject
{
Q_OBJECT

Q_PROPERTY( TestProperty testProperty READ testProperty WRITE setTestProperty )
Q_ENUMS( TestProperty )

Q_PROPERTY( QString anotherProperty READ anotherProperty )

public:
PropObject( QObject *parent=0, char *name=0 );

enum TestProperty { InitialValue, AnotherValue };

void setTestProperty( TestProperty p );
TestProperty testProperty() const;

QString anotherProperty() const { return QString( “I’m read-only!” ); }

private:
TestProperty m_testProperty;
};

#endif

Exemplo 2-11

Repare que não existem virgulas entre os parametros na macro Q_PROPERTY. A sintaxe é primeiro o tipo da propriedade é declarada, depois segue o nome. Depois uma palavra chaves, READ ou WRITE é encontrada e seguido pela função membro correspondente.

Funções Leitura e Escrita
Não hánada especial sobre as funções de escrita ou leitura. A única restrição é que a de leitura precisa ser do tipo parametro e não ter argumentos (isto é, tem que ser do tipo void) enquanto a de escrita precisa aceitar um único argumento e ser do ipo parametro.
É prática comum delcarar funções de escrita como conexões. Isso aumenta de forma fácil a reutilização já que muitas funções de escrita são naturalmente conexões. Alguma dessas são também candidatas para emitirem sinais.

O exemplo 2-12 mostra o código de propobject.cp. Isso é a implementação da classe de propriedades do objeto.

#include “propobject.h”

PropObject::PropObject( QObject *parent, char *name ) : QObject( parent, name )
{
m_testProperty = InitialValue;
}

void PropObject::setTestProperty( TestProperty p ) { m_testProperty = p; }
PropObject::TestProperty PropObject::testProperty() const { return m_testProperty; }

Exemplo 2-12

Repare que o construtor aceita os argumentos ‘parent’ e ‘name’ de QObject. Elas são passadas pela classe base. As funções membros são somente implementações comuns de uma propriedade de escite e leitura e de uma propriedade somente de leitura.

Finalmente, o exemplo 2-13 mostra o código de de main.cpp. Esse código acessa as propriedades através da interface do QObject padrão ao invés de acessar diretamente. A execução mostrada no exemplo 2-14 mostra queno código funciona. Repare que ‘enum’ é mostrado como é tratado internamente pelo computador, isto é, como um inteiro.

#include
#include

#include “propobject.h”

int main( int argc, char **argv )
{
QApplication a( argc, argv );

QObject *o = new PropObject();

std::cout << o->property( “testProperty” ).toString() << std::endl;

o->setProperty( “testProperty”, “AnotherValue” );
std::cout << o->property( “testProperty” ).toString() << std::endl;

std::cout << o->property( “anotherProperty” ).toString() << std::endl;

return 0;
}

Exemplo 2-13

$ ./prop
0
1
I’m read-only!
$

Exemplo 2-14

Auto conhecimento

Cada objeto Qt tem um meta-objeto. Esse objeto é representado por uma instancia da classe QMetaObject. Ë usada para fornecer informações sobre a classe atual. O meta objeto pode ser acessado através da funcao-membro QObject::metaObject(). O meta-objeto fornece algumas funções úteis listadas abaixo.

* QMetaObject::className()

Fornece o nome da classe, por exemplo, PropObject no exemplo da seção anterior.

* QMetaObject::superClass()

Fornece p meta-objeto da super classe, ou 0 (null) se não houver nenhuma.

* QMetaObject::propertyNames() and QMetaObject::property()

Fornece os nomes das propriedades e os meta dados de cada propriedades como uma QMetaProperty.

* QMetaObject::slotNames()

Fornece os nomes das conexões da classe. Se o parametro opcional, super, é setado como true as conexões da super classe são incluidos também.

* QMetaObject::signalNames()

Fornece os nomes dos sinais da classe. Um parametro opcional, super, está disponivel também.

As funções membro listadas acima são somente dicas. Existem mais meta informações disponiveis. Olhe a documentação oficial para detalhes.

Sumário

O código fonte com os exemplos desse capitulo podem ser baixados aqui -> ex02.tar

Traduzido de http://www.digitalfanatics.org/projects/qt_tutorial/chapter02.html