Iniciando o desenvolvimento com Qt

Bem vindo ao mundo Qt — o framework multiplataforma. Neste guia introdutório você irá aprender o básico de Qt implementado um bloco de notas simples. Após a leitura deste guia você deverá estar preparado para aprofundar-se no ambiente e na documentação da API, além de saber localizar toda a documentação que você precisa para as aplicações que estiver desenvolvendo.

Olá Notepad

Neste primeiro exemplo criaremos e mostraremos simplesmente uma caixa de texto em uma janela do desktop. É a aplicação gráfica mais simples possível.

  1. #include <QApplication>
  2. #include <QTextEdit>
  3.  
  4. int main(int argv, char **args)
  5. {
  6.     QApplication app(argv, args);
  7.  
  8.     QTextEdit textEdit;
  9.     textEdit.show();
  10.     return app.exec();
  11. }

Vamos caminhar pelo código linha por linha. Nas primeiras duas linhas nós incluimos os headers QApplication [doc.qt.nokia.com] e QTextEdit [doc.qt.nokia.com], que são as duas classes que usaremos neste exemplo. Todas as classes possuem um aquivo de header com seu nome.

A linha 6 cria um objeto QApplication [doc.qt.nokia.com]. Este objeto gerencia os recursos da aplicação e é necessário para rodar qualquer programa Qt que possua uma interface gráfica. O uso de argv e argc também é necessário porque o framework Qt aceita alguns argumentos da linha de comando.

Linha 8 cria um objeto QTextEdit [doc.qt.nokia.com]. Uma caixa de texto é um elemento visual em uma interface gráfica. Em Qt, estes elementos são chamados de widgets. Exemplo de outros widgets são as barras de rolagem (scroll bars), labels, e radio buttons. Um widget também pode ser um recipiente para outros widgets; Uma janela de diálogo, ou a janela principal, por exemplo.

A linha 9 mostra a caixa de texto dentro do seu próprio frame. Como widgets funcionam também como recipientes, é possível mostar um widget simples na sua própria janela. Widgets não são visíveis por padrão, portanto o método show() [doc.trolltech.com] deve ser chamado para fazer o widget visível.

A linha 11 faz com que a QApplication entre em seu loop de eventos. Quando uma aplicação Qt está rodando, os eventos são gerados e enviados para os widgets da aplicação. Exemplo de eventos são os clicks do mouse e entradas de teclado. Quando você digita algo dentro de uma caixa de texto, esta recebe os eventos de teclado e responde desenhando o texto digitado.

Para rodar a aplicação: Abra um terminal e entre no diretório onde está seu .cpp. Para compilar a aplicação execute os seguintes passos:

  1. qmake -project
  2. qmake
  3. make

Isso deverá gerar um executável no diretório part1 (note que no windows você deve usar nmake ao invés de make. O executável também será gerado em part1/debug ou part1/release). qmake é a ferramenta Qt para construção do ambiente de compilação da sua aplicação, e depende de um arquivo de configuração (.pro) para funcionar. Este arquivo pode ser gerado com o argumento -project. Dado esse arquivo, qmake produz um Makefile que será utilizado para compilar o programa pra você. Futuramente veremos como construir nossos próprios arquivos .pro.

Aprenda mais

SobreOnde
Widgets e geometria de janelasJanelas e Diálogos [doc.qt.nokia.com]
Eventos e manipulação de eventosO Sistema de eventos [doc.qt.nokia.com]

Adicionando um botão de saída

Em uma aplicação real você normalmente precisaria de mais de um único widget. Então introduziremos um QPushButton abaixo da caixa de edição de texto. O botão irá fechar o bloco de notas quando pressionado (ex.: quando houver um click com o mouse).

Editor de texto com botão para fechar

Vamos analizar o código linha a linha:

  1. #include <QtGui>
  2.  
  3. int main(int argv, char **args)
  4. {
  5.     QApplication app(argv, args);
  6.  
  7.     QTextEdit textEdit;
  8.     QPushButton quitButton("Sair");
  9.  
  10.     QObject::connect(&quitButton, SIGNAL(clicked()), qApp, SLOT(quit()));
  11.  
  12.     QVBoxLayout layout;
  13.     layout.addWidget(&textEdit);
  14.     layout.addWidget(&quitButton);
  15.  
  16.     QWidget window;
  17.     window.setLayout(&layout);
  18.  
  19.     window.show();
  20.  
  21.     return app.exec();
  22. }

A linha 1 inclui QtGui [doc.qt.nokia.com], que contém todas as classes de elementos gráficos Qt.

A Linha 10 usa o mecanismo de sinais e slots (signals & slots) para fechar a aplicação quando o botão Sair for pressionado. Um slot é um método que pode ser invocado em tempo de execução usando seu nome (como uma string literal). Um sinal é uma função que, quando chamada, irá invocar os slots registrados nela ela; Nós chamamos isso de “conectar o slot no sinal e emitir o sinal”.

quit() [doc.qt.nokia.com] é o slot de QApplication que finaliza a aplicação.clicked() [doc.qt.nokia.com] é o sinal emitido por QPushButton [doc.qt.nokia.com] quando este for pressionado. O método estático QObject::connect() [doc.qt.nokia.com] é responsável por conectar o slot ao sinal. SIGNAL e SLOT são duas macros que retornam as assinaturas dos sinais e slots, respectivamente. Nós também precisamos passar os ponteiros para os objetos que devem receber e enviar o sinal.

A linha 12 cria um QVBoxLayout [doc.qt.nokia.com]. Como mencionado, widgets podem conter outros widgets. É possível definir os limites (posição e tamanho) dos widgets filhos diretamente, mas é mais fácil usar um layout. Um layout gerencia os limites dos widgets filhos. QVBoxLayout [doc.qt.nokia.com], por exemplo, posiciona os filhos verticalmente.

As linhas 13 e 14 adicionam a caixa de texto e o botão ao layout. Na linha 17 definimos o layout em um widget.

Aprenda Mais

SobreOnde
Sinais e slotsSlots e Sinais [doc.qt.nokia.com]
LayoutsGerenciamento de Layout [doc.qt.nokia.com], widgets e Layouts [doc.qt.nokia.com], exemplos de layouts [doc.qt.nokia.com]
Os widgets do QtGaleria de Widgets [doc.qt.nokia.com], exemplos [doc.qt.nokia.com]

Herdando de QWidget

Quando o usuário desejar sair da aplicação, pode ser necessário mostrar um diálogo perguntando se ele realmente quer sair. Neste exemplo, nós especializamos QWidget [doc.qt.nokia.com] e adicionamos um slot que foi conectado ao botão Sair.

Herdando de QWidget

O código:

  1. class Notepad : public QWidget
  2. {
  3.     Q_OBJECT
  4.  
  5. public:
  6.     Notepad();
  7.  
  8. private slots:
  9.     void quit();
  10.  
  11. private:
  12.     QTextEdit *textEdit;
  13.     QPushButton *quitButton;
  14. };

A macro Q_OBJECT deve ser colocada no início da definição da classe, e essa ser uma especialização de QObject (QWidget herda de QObject). O QObject adiciona algumas habilidades a uma classe C++ padrão. Notavelmente, o nome da classe e dos slots podem ser requisitados em tempo de execução. Também é possível consultar um tipo de parâmetro do slot e invocá-lo.

A linha 9 declara o slot quit(). Isso é fácil usando a macro slots. O slot quit() agora pode ser conectado aos sinais que possuem uma assinatura equivalente (no caso, qualquer sinal que não receba parâmetros)

Ao invés de iniciarmos a interface gráfica e conectarmos o slot dentro da função main(), agora usamos o contrutor do Notepad.

  1. Notepad::Notepad()
  2. {
  3.     textEdit = new QTextEdit;
  4.     quitButton = new QPushButton(tr("Quit"));
  5.  
  6.     connect(quitButton, SIGNAL(clicked()), this, SLOT(quit()));
  7.  
  8.     QVBoxLayout *layout = new QVBoxLayout;
  9.     layout->addWidget(textEdit);
  10.     layout->addWidget(quitButton);
  11.  
  12.     setLayout(layout);
  13.  
  14.     setWindowTitle(tr("Notepad"));
  15. }

Como podemos ver na definição da classe, usamos ponteiros para os nossos QObjects [doc.qt.nokia.com] (textEdit e quitButton). Como regra: sempre devemos alocar os QObjects [doc.qt.nokia.com] na heap e nunca copiá-los.

Usamos a função tr() [doc.qt.nokia.com] em torno de nossas strings visíveis ao usuário. Esta função é necessária quando queremos distribuir nossa aplicação em mais de um idioma (por exemplo: inglês e alemão). Não vamos entrar em detalhes aqui, mas você pode seguir o link Qt Linguist na tabela Aprenda Mais.

Aprenda Mais

SobreOnde
tr() e i18nManual do Qt Linguist [doc.qt.nokia.com], Escrevendo código fonte para tradução [doc.qt.nokia.com], Example Olá tr() [doc.qt.nokia.com], Internationalização com Qt [doc.qt.nokia.com]
QObjects [doc.qt.nokia.com] e o modelo do Qt Object (Isto é essencial para entender Qt)Modelo de Objeto [doc.qt.nokia.com]
qmake e o systema de construção do QtManual qmake [doc.qt.nokia.com]

Criando um arquivo .pro

Para este exemplo escreveremos nosso próprio arquivo .pro ao invés de usarmos a opção -project do qmake.

  1. HEADERS =  notepad.h
  2. SOURCES =  notepad.cpp \
  3. main.cpp

Para compilar o programa siga os seguintes passos:

  1. qmake
  2. make

Usando o QMainWindow

Várias aplicações serão beneficiadas ao usar a QMainWindow [doc.qt.nokia.com], que possui seu próprio layout no qual pode-se adicionar uma barra de menu, dock widgets, barra de ferramentas e barra de status. QMainWindow [doc.qt.nokia.com] possui também uma área central que pode ser ocupada por qualquer tipo de widget. No nosso caso, colocaremos lá nossa caixa de edição de texto.

Usando QMainWindow

Vamos à nova definição da classe Notepad.

  1. #include <QtGui>
  2.  
  3. class Notepad : public QMainWindow
  4. {
  5.     Q_OBJECT
  6.  
  7. public:
  8.     Notepad();
  9.  
  10. private slots:
  11.     void open();
  12.     void save();
  13.     void quit();
  14.  
  15. private:
  16.     QTextEdit *textEdit;
  17.  
  18.     QAction *openAction;
  19.     QAction *saveAction;
  20.     QAction *exitAction;
  21.  
  22.     QMenu *fileMenu;
  23. };

Incluímos mais dois slots que podem salvar e abrir um documento. Iremos implementá-los na próxima sessão.

Em uma janela principal (main window) o mesmo slot costuma ser invocado por vários widgets. Exemplos disso são os ítens de menu e os botões em uma barra de ferramentas. Para facilitar, Qt provê o QAction, que pode ser passado para vários widgets e ser conectado aos slots. Por exemplo, QMenu e QToolBar podem criar ítens de menu através dos mesmos QActions. Iremos ver como isso funciona mais a frente.

Como antes, usamos o costrutor do Notepad para iniciar a interface gráfica.

  1. Notepad::Notepad()
  2. {
  3.     saveAction = new QAction(tr("&Open"), this);
  4.     saveAction = new QAction(tr("&Save"), this);
  5.     exitAction = new QAction(tr("E&xit"), this);
  6.  
  7.     connect(openAction, SIGNAL(triggered()), this, SLOT(open()));
  8.     connect(saveAction, SIGNAL(triggered()), this, SLOT(save()));
  9.     connect(exitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
  10.  
  11.     fileMenu = menuBar()->addMenu(tr("&File"));
  12.     fileMenu->addAction(openAction);
  13.     fileMenu->addAction(saveAction);
  14.     fileMenu->addSeparator();
  15.     fileMenu->addAction(exitAction);
  16.  
  17.     textEdit = new QTextEdit;
  18.     setCentralWidget(textEdit);
  19.  
  20.     setWindowTitle(tr("Notepad"));
  21. }

QActions [doc.qt.nokia.com] são criadas com o texto que deve aparecer nos widgets em que forem adicionadas (no nosso caso, o menu). Se nós também quisermos adicioná-los à barra de ferramentas poderemos utilizar ícones [doc.qt.nokia.com] nos QActions.

Quando um ítem do menu for pressionado, o ítem irá ativar a ação e o respectivo slot será invocado.

Aprenda Mais

SobreOnde
Main windows e suas classesAplicação comMain Window [doc.qt.nokia.com], Exemplos da Main Window [doc.qt.nokia.com]
Aplicações MDIQMdiArea [doc.qt.nokia.com], Exemplo MDI [doc.qt.nokia.com]

Salvando e Abrindo

Neste exemplo iremos implementar a funcionalidade de abrir e salvar, implementando os slots open() e save() que foram adicionados no exemplo anterior.

Abrir e salvar

Vamos começar com o slot open():

  1. QString fileName = QFileDialog::getOpenFileName(this, tr("Open File"), "",
  2.                                  tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::ReadOnly)) {
  7.         QMessageBox::critical(this, tr("Error"),
  8.             tr("Could not open file"));
  9.         return;  
  10.     }
  11.     QString contents = file.readAll().constData();
  12.     textEdit->setPlainText(contents);
  13.     file.close();
  14. }

O primeiro passo é perguntar ao usuário pelo nome do arquivo que deseja abrir. Qt vem com o QFileDialog [doc.qt.nokia.com], que é um diálogo onde o usuário pode selecionar um arquivo. A imagem acima mostra o diálogo no Kubuntu. O método estático getOpenFileName() [doc.qt.nokia.com] mostra um diálogo de arquivo modal, e não retorna até que o usuário seleciona um arquivo. Ele retorna o caminho para o arquivo selecionado, ou uma string vazia caso o usuário cancele o diálogo.

Se nós tivermos um nome de arquivo então tentaremos abrí-lo uando o método open() [doc.qt.nokia.com], que retorna True se o arquivo pode ser aberto. Não iremos tratar gerenciamento de erro neste artigo, para isso você pode seguir os links da seção Aprenda Mais. Se o arquivo não pode ser aberto, usaremos uma QMessageBox [doc.qt.nokia.com] para mostar um diálogo com uma mensagem de erro (veja a descrição da classe QMessageBox [doc.qt.nokia.com] para maiores detalhes).

Ler os dados do arquivo é trivial usando o método readAll() [doc.qt.nokia.com] que retorna todos os dados do arquivo em um QByteArray [doc.qt.nokia.com]. constData() [doc.qt.nokia.com] retorna todos os dados em um array como um const char*, que pode ser usado para gerar uma QString [doc.qt.nokia.com]. O conteúdo pode ser mostrado na caixa de edição de texto. Então o método close() [doc.qt.nokia.com]é invocado para retornar o descritor de arquivo para o sistema operacional.

Agora, vejamos o método save():

  1. QString fileName = QFileDialog::getSaveFileName(this, tr("Save File"), "",
  2.     tr("Text Files (*.txt);;C++ Files (*.cpp *.h)"));
  3.  
  4. if (fileName != "") {
  5.     QFile file(fileName);
  6.     if (!file.open(QIODevice::WriteOnly)) {
  7.         // error message
  8.     } else {
  9.         QTextStream stream(&file);
  10.         stream << textEdit->toPlainText();
  11.         stream.flush();
  12.         file.close();
  13.     }
  14. }

Quando escrevemos o conteúdo da caixa de edição de texto no arquivo usamos a classe QTextStream [doc.qt.nokia.com] que recebe um objeto tipo QFile. O stream de text pode escrever QStrings diretamente no arquivo enquanto QFile [doc.qt.nokia.com] somente aceita dado puro (char *) com o método write() [doc.qt.nokia.com] do QIODevice [doc.qt.nokia.com].

Aprenda Mais

SobreOnde
Arquivos e dispositivos de entrada e saídaQFile [doc.qt.nokia.com], QIODevice [doc.qt.nokia.com]