Cap7

316 views

Published on

0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
316
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
10
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Cap7

  1. 1. Processamento de Eventos ● Reimplementando Manipuladores de Eventos ● Instalando Filtros de Eventos ● Manter Respondendo durante Processamento Intensivo Eventos são gerados pelo gerenciador de janelas ou pelo próprio Qt em resposta a várias ocorrências. Quando o usuário pressiona uma tecla ou o botão do mouse, um evento de tecla (KeyPress) ou um evento do mouse (MouseDown) é gerado. Quando uma janela é exibida pela primeira vez, um evento paint é gerado para dizer a janela recém visível que ela precisa ser desenhada. A maioria dos eventos são gerados em resposta às ações de usuários, mas alguns, como os eventos de timer, são gerados de forma independente pelo sistema. Quando se programa com Qt, raramente é preciso pensar sobre os eventos, porque os widgets do Qt emitem sinais quando algo de significativo acontece. Eventos tornam­se úteis quando escrevemos nossos próprios widgets personalizados ou quando queremos modificar o comportamento dos widgets já existentes do Qt. Os eventos não devem ser confundidos com sinais. Como regra, os sinais são úteis ao utilizar um widget, enquanto que os eventos são úteis na implementação de um widget. Por exemplo, quando estamos usando QPushButton, estamos mais interessados no sinal clicked() do que nos eventos do mouse em baixo nível ou nos eventos do teclado que causaram o sinal emitido. Mas se estamos implementando uma classe como QPushButton, precisamos escrever um código para lidar com os eventos do mouse e do teclado. Assim, devemos emitir o sinal clicked(), quando necessário. Reimplementando Manipuladores de Eventos No Qt, um evento é uma instância de uma subclasse QEvent. Qt trata mais de uma centena de tipos de eventos, cada uma identificada por um valor de enumeração. Por exemplo, QEvent::type() retorna QEvent::MouseButtonPress para eventos de pressionamento do botão do mouse. Muitos tipos de eventos exigem mais informações do que pode ser armazenado em um simples objeto QEvent; por exemplo: eventos de pressionamento do mouse necessitam armazenar a informação de qual botão do mouse foi pressionado, bem como onde o ponteiro do mouse estava posicionado quando o evento ocorreu. Esta informação adicional é armazenado em uma subclasse derivada da QEvent denominada QMouseEvent. Os eventos são comunicados aos objetos através da função event(), herdada de QObject. A implementação de event() em QWidget encaminha os tipos mais comuns de eventos para  os manipuladores específicos de eventos, como mousePressEvent(), keyPressEvent() e paintEvent(). Já vimos vários manipuladores de eventos ao implementar MainWindow, IconEditor e Plotter em
  2. 2. capítulos anteriores. Muitos outros tipos de eventos são listados na documentação de referência de QEvent. Também é possível criar tipos de eventos personalizados e despachar eventos nós mesmos. Aqui, vamos analisar dois tipos de eventos comuns que merecem mais explicações: os eventos de tecla e eventos de timer. Os eventos de tecla são manipulados por reimplementação dos eventos keyPressEvent() e keyReleaseEvent(). O widget Plotter reimplementa keyPressEvent(). Normalmente, precisamos apenas reimplementar keyPressEvent(), uma vez que o código da tecla liberada normalmente é modificado por Ctrl, Shift e Alt, e estes podem ser checados no evento keyPressEvent() usando QKeyEvent()::modifiers. Por exemplo, se estivéssemos implementando um widget CodeEditor, seu evento keyPressEvent() implementado para distinguir entre o pressionamento da tecla  Home ou o  pressionamento das teclas Ctrl + Home ficaria assim: void CodeEditor::keyPressEvent(QKeyEvent *event) {     switch (event­>key()) {     case Qt::Key_Home:         if (event­>modifiers() & Qt::ControlModifier) {             goToBeginningOfDocument();         } else {             goToBeginningOfLine();         }         break;     case Qt::Key_End:         ...     default:         QWidget::keyPressEvent(event);     } } As teclas Tab e Backtab (Shift+Tab) são casos especiais. Elas são tratadas por QWidget::event() antes que o evento keyPressEvent() seja chamado. Essas teclas passam o foco do widget atual para o próximo widget ou para o widget anterior da cadeia. Este comportamento é normalmente o que desejamos para a tecla Tab, mas para um widget CodeEditor podemos preferir que a tecla Tab faça recuar uma linha. A reimplementação de event() ficaria assim: bool CodeEditor::event(QEvent *event) {     if (event­>type() == QEvent::KeyPress) {         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);         if (keyEvent­>key() == Qt::Key_Tab) {             insertAtCurrentPosition('t');             return true;         }     }     return QWidget::event(event); } Se o evento foi um pressionamento de tecla, fazemos um cast do objeto QEvent para o QKeyEvent e verificamos qual tecla foi pressionada. Se a tecla for Tab, fazemos algum processamento e retornamos true para informar ao Qt que nós tratamos o evento. Se retornarmos false, o Qt irá tratar o evento, propagando para o widget pai. Uma abordagem de alto nível para tratamento de teclas é usar um QAction. Por exemplo, se goToBeginningOfLine() e goToBeginningOfDocument() são slots públicos do widget CodeEditor e o CodeEditor é usado como o elemento central em uma classe MainWindow, podemos adicionar
  3. 3. os atalhos de teclado com o seguinte código: MainWindow::MainWindow() {     editor = new CodeEditor;     setCentralWidget(editor);     goToBeginningOfLineAction =             new QAction(tr("Go to Beginning of Line"), this);     goToBeginningOfLineAction­>setShortcut(tr("Home"));     connect(goToBeginningOfLineAction, SIGNAL(activated()),             editor, SLOT(goToBeginningOfLine()));     goToBeginningOfDocumentAction =             new QAction(tr("Go to Beginning of Document"), this);     goToBeginningOfDocumentAction­>setShortcut(tr("Ctrl+Home"));     connect(goToBeginningOfDocumentAction, SIGNAL(activated()),             editor, SLOT(goToBeginningOfDocument()));     ... } Isto torna fácil adicionar comandos ao menu ou a barra de ferramentas, como foi visto no Capítulo 3. Se os comandos não aparecem na interface do usuário, poderíamos substituir o objeto QAction pelo objeto QShortcut, que é usado internamente pelo QAction para suporte a teclas de atalho. Por padrão, atalhos de tecla configurados usando QAction ou QShortcut em um widget, são habilitados sempre que a janela que contém o widget está ativa. Isso pode ser mudado usando QAction::setShortcutContext() ou QShortcut::setContext(). Outro tipo comum de evento é o evento timer. Enquanto a maioria dos outros eventos ocorrem através de uma ação do usuário, o evento timer ocorre em um intervalo regular de tempo, permitindo que o aplicativo faça algum processamento periódico. Os eventos de timer podem ser usados para implementar um cursor piscando e outras animações, ou simplesmente para atualizar a tela. Para demonstrar eventos de timer, vamos implementar um widget Ticker mostrado na Figura 7.1. Este widget mostra um banner de texto que rola um pixel para a esquerda a cada 30 milissegundos. Se o widget é maior do que o texto, o texto é repetido tantas vezes quanto necessário para preencher toda a largura do widget. Figura 7.1. O widget Ticker Este é o arquivo de cabeçalho: #ifndef TICKER_H #define TICKER_H #include <QWidget> class Ticker : public QWidget {     Q_OBJECT     Q_PROPERTY(QString text READ text WRITE setText)
  4. 4. public:     Ticker(QWidget *parent = 0);     void setText(const QString &newText);     QString text() const { return myText; }     QSize sizeHint() const; protected:     void paintEvent(QPaintEvent *event);     void timerEvent(QTimerEvent *event);     void showEvent(QShowEvent *event);     void hideEvent(QHideEvent *event); private:     QString myText;     int offset;     int myTimerId; }; #endif Nós reimplementamos quatro manipuladores de eventos para o Ticker, sendo que três deles ainda não vimos antes, que são: timerEvent(), showEvent(), e hideEvent(). Agora vamos analisar a implementação: #include <QtGui> #include "ticker.h" Ticker::Ticker(QWidget *parent)     : QWidget(parent) {     offset = 0;     myTimerId = 0; } O construtor inicializa a variável offset com zero. A coordenada x em que o texto é desenhado é derivada do valor de offset. Os IDs do timer são sempre diferentes de zero, por isso usamos zero para indicar que nenhum timer foi iniciado. void Ticker::setText(const QString &newText) {     myText = newText;     update();     updateGeometry(); } A função setText() define o texto a ser exibido. Ele chama a função update() para solicitar o redesenho do widget e updateGeometry() para notificar algum gerenciador de layout responsável pelo widget Ticker, sobre a sugestão da mudança de tamanho. QSize Ticker::sizeHint() const {     return fontMetrics().size(0, text()); }
  5. 5. A função sizeHint() retorna o espaço necessário para o texto como tamanho ideal do widget. QWidget::fontMetrics() retorna um objeto QFontMetrics que pode ser consultado para obter informações relativas à fonte do widget. Neste caso, solicitamos o tamanho necessário para um determinado texto. (O primeiro parâmetro para QFontMetrics::size() é um flag não necessário para uma string simples, então passamos apenas zero). void Ticker::paintEvent(QPaintEvent * /* event */) {     QPainter painter(this);     int textWidth = fontMetrics().width(text());     if (textWidth < 1)         return;     int x = ­offset;     while (x < width()) {         painter.drawText(x, 0, textWidth, height(),                          Qt::AlignLeft | Qt::AlignVCenter, text());         x += textWidth;     } } A função paintEvent() desenha o texto usando QPainter::drawText(). Ele usa fontMetrics() para verificar o espaço horizontal que o texto precisa e em seguira desenha o texto quantas vezes forem necessárias para preencher toda a largura do widget levando em conta o offset. void Ticker::showEvent(QShowEvent * /* event */) {     myTimerId = startTimer(30); } A função showEvent() inicia o timer. A chamada para QObject::startTimer() retorna um número ID que pode ser usado mais tarde para identificar o timer. QObject suporta vários timers independentes, cada um com o seu respectivo intervalo de tempo. Após a chamada de startTimer(), o Qt irá gerar um evento de timer a cada 30 milissegundos aproximadamente. A precisão depende do sistema operacional usado. Poderíamos ter chamado startTimer() logo no construtor de Ticker, mas poupamos alguns recursos fazendo o Qt gerar os eventos de timer somente quando o widget está realmente visível. void Ticker::timerEvent(QTimerEvent *event) {     if (event­>timerId() == myTimerId) {         ++offset;         if (offset >= fontMetrics().width(text()))             offset = 0;         scroll(­1, 0);     } else {         QWidget::timerEvent(event);     } } O sistema chama a função timerEvent() periodicamente. Isto incrementa a variável offset em 1 para simular o movimento, envolvendo a largura do texto. Em seguida ele rola o conteúdo do widget 1 pixel para a esquerda, usando QWidget::scroll(). Poderíamos ter chamado update() em vez de scroll() que já seria suficiente, porém scroll() é mais eficiente porque ele simplesmente
  6. 6. move os pixels existentes na tela e gera um evento paint somente para a área recém­exibida do widget (uma faixa de 1­pixel­wide neste caso). Se o evento do timer não é referente ao timer que nos interessa, passamos ele para a classe base. void Ticker::hideEvent(QHideEvent * /* event */) {     killTimer(myTimerId);     myTimerId = 0; } A função hideEvent() chama QObject::killTimer() para parar o timer. Os eventos de timer são de baixo nível e se for preciso usar vários timers, pode ficar complicado manter o controle de todos os IDs de timer. Nestas situações é mais fácil criar um objeto QTimer para cada timer. QTimer emite um sinal de timeout() a cada intervalo de tempo. QTimer também fornece uma interface conveniente para single­shot timer (timer de um único intervalo), como vimos no Capítulo 6 (p. ?x?). Instalando Filtros de Eventos Uma característica muito poderosa do modelo de eventos do Qt é que uma instância de QObject pode ser definida para monitorar os eventos de outra instância de QObject antes que o último objeto ainda possa enxerga­lo. Vamos supor que temos um widget CustomerInfoDialog composto de vários QLineEdits e que queremos usar a tecla de Space para mover o foco para o próximo QLineEdit. Este comportamento não­padrão pode ser apropriado para uma aplicação interna cujos usuários são treinados para seu uso. Uma solução simples é herdar QLineEdit e reimplementar keyPressEvent() para chamar a função focusNextChild() desta forma: void MyLineEdit::keyPressEvent(QKeyEvent *event) { if (event­>key() == Qt::Key_Space) { focusNextChild(); } else { QLineEdit::keyPressEvent(event); } } Esta abordagem tem uma desvantagem. Se usarmos vários tipos diferentes de widgets (por exemplo, QComboBoxes e QSpinBoxes), teremos que tornar a fazer subclasses deles para que apresentem o mesmo comportamento. Uma solução melhor seria fazer CustomerInfoDialog monitorar os eventos de seu widgets filhos e implementar o comportamento desejado no código de monitoramento. Isso pode ser conseguido usando filtros de evento. Criar um filtro de evento envolve duas etapas: 1. Registrar o objeto de monitoramento com o objeto alvo chamando installEventFilter() no destino. 2. Manipular os eventos do objeto alvo na função eventFilter() do monitor. Um bom lugar para registrar o objeto de monitoramento é no Construtor:
  7. 7. CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent) { ...     firstNameEdit­>installEventFilter(this);     lastNameEdit­>installEventFilter(this);     cityEdit­>installEventFilter(this);     phoneNumberEdit­>installEventFilter(this); } Uma vez que o evento de filtro é registrado, os eventos enviados para os widgets firstNameEdit, lastNameEdit, cityEdit, e phoneNumberEdit são primeiro enviados para a função eventFilter() de CustomerInfoDialog antes de serem enviados para seu destino alvo. Esta é a função eventFilter() que recebe os eventos: bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event) { if (target == firstNameEdit || target == lastNameEdit || target == cityEdit || target == phoneNumberEdit) { if (event­>type() == QEvent::KeyPress) {             QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent­>key() == Qt::Key_Space) {                 focusNextChild();                 return true; } } } return QDialog::eventFilter(target, event); } Primeiro verificamos se o widget alvo é um dos QLineEdits. Se o evento foi uma tecla pressionada, fazemos um cast de QKeyEvent e verificamos qual tecla foi pressionada. Se a tecla pressionada foi Space, chamamos focusNextChild() para passar o foco ao próximo widget da cadeia de foco e retornamos true para avisar ao Qt que tratamos o evento. Se tivéssemos retornado false, Qt iria enviar o evento para seu alvo, resultando em um espaço espúrio sendo inserido no QLineEdit. Se o widget alvo não é um QLineEdit, ou se o evento não é um pressionamento da tecla de espaço, podemos passar o controle para implementação da classe base de eventFilter(). O widget alvo também poderia ser algum widget que a classe base QDialog está monitorando. (Em Qt 4. 3, este não é o caso de QDialog. No entanto, outras classes de widget do Qt, tais como QScrollArea, monitoram algumas das suas widgets filhas por diversos motivos.) Qt oferece cinco níveis em que eventos podem ser processados e filtrados: 1. Podemos reimplementar um manipulador de eventos específicos. Reimplementação de manipuladores de eventos, tais como mousePressEvent(), keyPressEvent(), e paintEvent() é de longe a forma mais comum de processar eventos. Nós já vimos vários exemplos disso. 2. Podemos reimplementar QObject::event().
  8. 8. Ao reimplementar a função event(), nós podemos processar os eventos antes que eles atinjam os manipuladores de eventos específicos. Esta abordagem é necessária para substituir o comportamento padrão da tecla Tab como foi visto anteriormente (p. ?x?). Esta abordagem também é necessária para manipular tipos raros de eventos, os quais não existem manipuladores específicos de eventos (por exemplo, QEvent::HoverEnter). Quando reimplementamos event(), temos que chamar a função event() da classe base para tratar os casos que não tratamos explicitamente. 3. Podemos instalar um filtro de eventos em um único QObject. Depois que o objeto foi registrado usando installEventFilter(), todos os eventos para o objeto alvo são primeiro enviados para a função de monitoramento de objetos, eventFilter(). Se vários eventos de filtro são instalados no mesmo objeto, os filtros são ativados por sua vez do mais recentemente instalado voltando para o primeiro instalado. 4. Podemos instalar um filtro de eventos no objeto QApplication. Depois que um filtro de evento foi registrado para o qApp (o único objeto QApplication), todos os eventos de cada objeto do aplicativo são enviados para a função eventFilter() antes de serem enviados para qualquer outro filtro de eventos. Essa abordagem é mais útil para depuração. Ela também pode ser usada para manipular eventos de mouse enviados para widgets desabilitados, que QApplication normalmente descarta. 5. Podemos herdar QApplication e reimplementar notify(). O Qt chama QApplication::notify() para enviar um evento. Reimplementar esta função é a única maneira de obter os eventos antes que qualquer filtro de eventos os receba. Os filtros de eventos são normalmente mais úteis, porque podem existir vários filtros de eventos simultâneos, enquanto que somente uma função notify(). Muitos tipos de eventos, incluindo os de mouse e os de teclado podem ser propagados. Se o evento não for tratado no caminho para seu objeto alvo ou pelo próprio objeto alvo, todo o processamento do evento é repetido, mas desta vez com o objeto pai sendo o novo objeto alvo. Isso continua se repetindo passando de filho para pai até que o evento seja tratado ou que o objeto top­level seja alcançado. A Figura 7.2 mostra como um evento de pressionamento de tecla é propagado de filho para pai em uma caixa de diálogo. Quando um usuário pressiona uma tecla, o evento primeiro é enviado para o widget com o foco, neste caso o QCheckBox inferior direito (1). Se o QCheckBox não tratar o evento, o Qt vai enviá­lo  para QGroupBox (2), e finalmente para o objeto QDialog (3). Figura 7.2. Propagação de evento em uma caixa de diálogo
  9. 9. Manter Respondendo durante Processamento Intensivo Quando chamamos Qapplication::exec(), iniciamos o event loop do Qt. O Qt emite alguns eventos na inicialização para mostrar e desenhar os widgets. Depois disso o event loop é executado, checando constantemente se ocorreram eventos e despachando esses eventos para os Qobjects do aplicativo. Enquanto um evento está sendo processado, eventos adicionais podem ser gerados e anexados a fila de eventos do Qt. Se gastamos muito tempo processando um evento específico, a interface do usuário ficará sem responder. Por exemplo, todos os eventos gerados pelo sistema de janelas enquanto o aplicativo está salvando um arquivo em disco não serão processados até que o arquivo seja salvo. Durante a gravação, o aplicativo não responderá às solicitações do sistema janelas para se redesenhar. Uma solução é usar múltiplos threads: um thread para a interface do usuário e outro para o salvamento do arquivo (ou qualquer outra operação demorada). Desta forma a interface do usuário continuará respondendo enquanto o arquivo estiver sendo salvo. Veremos como fazer isso no Capítulo 14. Uma solução mais simples é fazer chamadas frequentes para a função QApplication::processEvents() no código de gravação do arquivo. Esta função fiz para o Qt processar todos os eventos pendentes e depois retornar o controle para seu chamador. Na verdade Qapplication::exec() é um pouco mais do que um loop while em torno de uma chamada de função processEvents(). Aqui está um exemplo de como podemos manter a interface do usuário respondendo usando processEvents(), baseado no código de gravação do arquivo usado em Spreadsheet (p. ?x?): bool Spreadsheet::writeFile(const QString &fileName) {     QFile file(fileName);     ...     QApplication::setOverrideCursor(Qt::WaitCursor);     for (int row = 0; row < RowCount; ++row) {         for (int column = 0; column < ColumnCount; ++column) {             QString str = formula(row, column);             if (!str.isEmpty())                 out << quint16(row) << quint16(column) << str;         }         qApp­>processEvents();     }     QApplication::restoreOverrideCursor();     return true; } Um perigo desta abordagem é que o usuário pode fechar a janela principal enquanto o arquivo ainda está sendo gravado, ou clicar em File|Save uma segunda vez, resultando em um comportamento indefinido. A solução mais fácil para este problema é substituir qApp­>processEvents(); por qApp­>processEvents(QEventLoop::ExcludeUserInputEvents);
  10. 10. dizendo assim para o Qt ignorar os eventos do mouse e do teclado. Muitas vezes queremos mostrar um QprogressDialog enquanto é executada uma operação demorada. O QprogressDialog tem uma barra de progresso que mantém o usuário informado sobre o progresso da operação. QprogressDialog também fornece um botão Cancel que permite ao usuário cancelar a operação. Aqui está um código para salvar um arquivo do Spreadsheet usando esta abordagem. Veja o Código: bool Spreadsheet::writeFile(const QString &fileName) {     QFile file(fileName);     ...     QProgressDialog progress(this);     progress.setLabelText(tr("Saving %1").arg(fileName));     progress.setRange(0, RowCount);     progress.setModal(true);     for (int row = 0; row < RowCount; ++row) {         progress.setValue(row);         qApp­>processEvents();         if (progress.wasCanceled()) {             file.remove();             return false;         }         for (int column = 0; column < ColumnCount; ++column) {             QString str = formula(row, column);             if (!str.isEmpty())                 out << quint16(row) << quint16(column) << str;         }     }     return true; } Criamos um QprogressDialog com NumRows indicando o número total de etapas. Então para cada linha, chamamos setValue() para atualizar a barra de prograsso. QprogressDialog calcula automaticamante a porcentagem, dividindo o valor atual do prograsso pelo número total de etapas. Chamamos Qapplication::processEvents() para processar algum evento rapaint, cliques e pressionamento de teclas (por exemplo, permitir que o usuário clique em Cancel). Se o usuário clicar em Cancel, podemos cancelar o salvamento e remover o arquivo. Não chamamos show() em QprogressDialog porque diálogos de progresso fazem isso por sí mesmos. Se a operação acaba rapidamente, provavelmente pelo arquivo a salvar ser muito pequeno, ou porque a máquina é muito rápida, QProgressDialog irá detectar isto e não será mostrado totalmente. Além disso, para multithreading e usando QProgressDialog, há uma maneira completamente diferente de lidar com operações de longa duração: em vez de executar o processamento quando o usuário solicita, nós podemos adiar o tratamento até que o aplicativo fique ocioso. Isso pode funcionar se o processo puder ser interrompido e retomado com segurança, pois não podemos prever por quanto tempo o aplicativo ficará ocioso. No Qt, esta abordagem pode ser implementada usando um timer de 0­milissegundos. Esses timers esgotam o tempo sempre que não existem eventos pendentes. Aqui está um exemplo de implementação de timerEvent() que mostra uma abordagem de processamento ocioso:
  11. 11. void Spreadsheet::timerEvent(QTimerEvent *event) {     if (event­>timerId() == myTimerId) {         while (step < MaxStep && !qApp­>hasPendingEvents()) {             performStep(step);             ++step;         }     } else {         QTableWidget::timerEvent(event);     } } Se hasPendingEvents() retorna true, paramos o processamento e damos o controle de volta para o Qt. O processamento será retomado quando o Qt tiver tratado todos os seus eventos pendentes.

×