sexta-feira, 18 de setembro de 2009

Tratamento de Eventos: Sem eventos a tratar

Pode parecer contraditório (e talvez realmente seja) o título deste tópico, mas não vejo onde mais encaixá-lo senão na categoria de Tratamento de Eventos.

Acontece que às vezes criamos uma janela que não precisa tratar nenhum evento em especial, portanto não necessita do mapa de mensagens. É sobre isso que falarei neste tópico.

Quando a janela não trata eventos

Imagine que você precisa de uma janela apenas para exibir algumas informações. Nenhuma entrada do usuário é necessária, nenhuma ação do usuário é executada. Essa janela não precisa tratar evento nenhum, então não precisa de um mapa de mensagens.

Ainda assim, precisa chamar a macro FXIMPLEMENT.


Uma pausa para algumas explicações

Depois de tantos tópicos escritos, só agora senti a necessidade de explicar para que serve esse par de macros: FXDECLARE/FXIMPLEMENT. Confesso que eu simplesmente utilizava, mas sem saber qual o real significado delas. Escrevendo este tópico, tive a curiosidade de investigá-las.

A macro FXDECLARE, como o próprio nome sugere, é colocada dentro da declaração da classe. Após expandida, ela toma a seguinte forma (coloquei espaços para melhorar a legibilidade ):
public:
  struct FXMapEntry {
    FX::FXSelector keylo;
    FX::FXSelector keyhi;
    long (classname::* func)(FX::FXObject*,FX::FXSelector,void*);
  };
  
  static const FX::FXMetaClass metaClass;

  static FX::FXObject* manufacture();

  virtual long handle(FX::FXObject* sender,FX::FXSelector sel,void* ptr);

  virtual const FX::FXMetaClass* getMetaClass() const {
    return &metaClass;
  }

  friend FX::FXStream& operator <<(FX::FXStream& store,const classname* obj) {
    return store.saveObject((FX::FXObjectPtr)(obj));
  }

  friend FX::FXStream& operator >>(FX::FXStream& store,classname*& obj) {
    return store.loadObject((FX::FXObjectPtr&)(obj));
  }

private:

Explicando:
  • Ela cria uma struct interna, chamada FXMapEntry, que armazena as entradas do mapa de mensagens.
  • Declara um objeto estático do tipo FXMetaClass, que armazena informações que descrevem um objeto FOX.
  • manufacture() nada mais é que um método fábrica que retorna um novo objeto; por isso o construtor padrão vazio privado é obrigatório.
  • handle() é o método utilizado para executar as ações propriamente ditas e também utilizado para delegar ações a outros objetos.
  • Os demais são irrelevantes no momento.

Pois bem, a outra macro, FXIMPLEMENT, é o complementar de FXDECLARE. É ela quem vai implementar os métodos declarados por FXDECLARE. Por isso ela é obrigatória, mesmo na ausência do mapa de mensagens. Após expandida, o método handle() é implementado da seguinte maneira:
long classname::handle(FX::FXObject* sender, FX::FXSelector sel, void* ptr) {
  const FXMapEntry* me=(const FXMapEntry*)metaClass.search(sel);

  return me ? (this->* me->func)(sender,sel,ptr)
            : baseclassname::handle(sender,sel,ptr);
}

Fuçando no código-fonte, vi que o método search() faz uma busca linear pelo mapa de mensagens, até encontrar uma entrada contenha o FXSelector sel (passado como parâmetro de handle()). Vejam:
const void* FXMetaClass::search(FXSelector key) const {
  register const FXObject::FXMapEntry* lst=(const FXObject::FXMapEntry*)assoc;
  register FXuint n=nassocs;

  while(n--) {
    if (lst->keylo <= key && key <= lst->keyhi)
      return lst;

    lst = (const FXObject::FXMapEntry*) (((const FXchar*)lst)+assocsz);
  }

  return NULL;
}

Ou seja, se encontrar a entrada, executa a função associada; senão, delega para a superclasse. Isto segue o padrão de projeto "Cadeia de Responsabilidade" ("Chain of Responsibility"), em que um objeto vai delegando a ação a seus superiores (digamos assim), até que alguém o trate. Neste caso, o topo da hierarquia é FXObject, cujo método handle() chama onDefault(), que apenas retorna 0.

Voltando...

Depois de toda essa explicação, podemos voltar ao nosso assunto principal. Suponhamos que temos uma janela que não trata nenhum evento. Neste caso, existem duas opções: declarar um mapa vazio, ou simplesmente não declarar mapa nenhum. A primeira opção já foi discutida em um dos primeiros tutoriais, mas vou reproduzi-la aqui:
FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {
};

FXIMPLEMENT(FoxTutorialMainWindow, FXMainWindow,
    FoxTutorialMainWindowMap, ARRAYNUMBER(FoxTutorialMainWindowMap))

Neste caso, a macro ARRAYNUMBER calcula o tamanho do vetor, que é 0 (cuidado: alguns compiladores reclamam). Na hora da busca pela mensagem, o laço while é ignorado e a função retorna NULL, disparando o processo já discutido.

A outra maneira é não declarar mapa nenhum. A chamada a FXIMPLEMENT fica assim:
FXIMPLEMENT(FoxTutorialMainWindow, FXMainWindow, NULL, 0)


Conclusão

Acho que este tópico encerra a discussão sobre o mapa de mensagens. Se eu me lembrar de alguma outra coisa nesse sentido, postarei aqui.

Entretanto, ainda há muito o que falar com relação ao tratamento de mensagens. Essa série será bem longa! Até a próxima!

Nenhum comentário: