sexta-feira, 25 de setembro de 2009

FXMessageBox: Tratando a resposta do usuário

Neste tutorial, falei sobre as opções de botões que o FXMessageBox fornece ao usuário. Obviamente, cada mensagem requer um conjunto de botões adequado. Mas como saber qual botão foi pressionado?

Hoje, veremos como detectar qual o botão clicado pelo usuário, em resposta à mensagem exibida.


Relembrando o conjunto de botões

O protótipo das funções que exibem mensagens são da seguinte forma (message, aqui, é utilizado como um nome genérico para information, question, warning e error):
static FXuint message(FXWindow* owner, FXuint opts, const char* caption,
        const char* message, ...);

O parâmetro opts é onde se informa o conjunto de botões que vão aparecer na mensagem, através da seguinte enumeração (abstraindo os valores):
enum {
  MBOX_OK,
  MBOX_OK_CANCEL,
  MBOX_YES_NO,
  MBOX_YES_NO_CANCEL,
  MBOX_QUIT_CANCEL,
  MBOX_QUIT_SAVE_CANCEL,
  MBOX_SKIP_SKIPALL_CANCEL,
  MBOX_SAVE_CANCEL_DONTSAVE
};

Percebam, então, que os botões que podem aparecer em uma mensagem são:
  • OK
  • Cancel
  • Yes
  • No
  • Quit
  • Save
  • Don't save
  • Skip
  • Skip All
Tratando a resposta

Voltando ao protótipo da função, vemos que ela retorna um FXint. É justamente esse retorno que indica qual o botão pressionado. Esse valor também está presente em uma enumeração para facilitar (dar nomes a números mágicos é uma boa prática de programação). Essa enumeração é a seguinte:
enum {
  MBOX_CLICKED_YES      = 1,    /// The YES button was clicked
  MBOX_CLICKED_NO       = 2,    /// The NO button was clicked
  MBOX_CLICKED_OK       = 3,    /// The OK button was clicked
  MBOX_CLICKED_CANCEL   = 4,    /// The CANCEL button was clicked
  MBOX_CLICKED_QUIT     = 5,    /// The QUIT button was clicked
  MBOX_CLICKED_SAVE     = 6,    /// The SAVE button was clicked
  MBOX_CLICKED_SKIP     = 7,    /// The SKIP button was clicked
  MBOX_CLICKED_SKIPALL  = 8     /// The SKIP ALL button was clicked
};

Oito valores enumerados para nove botões?

É...

Fuçando no código fonte, descobri algo assim:
new FXButton(buttons, "&Don't Save", NULL, this, ID_CLICKED_NO, ...

Ou seja, o valor retornado quando o usuário clica em "Don't Save" é MBOX_CLICKED_NO. Faz sentido, mas eu acharia melhor se tivesse um nome específico, o valor até poderia ser o mesmo. Mas vai entender...



Exemplo

Vou dar um exemplo simples, até porque aqui não tem mistério nenhum (fora esse de cima...). Suponha que o usuário vai realizar uma operação que não pode ser desfeita. Isso deve ser informado ao usuário, caso ele volte atrás em sua decisão. Nosso tratador fica assim:
long FoxTutorialMainWindow::onCmdEraseAll(FXObject*, FXSelector sel, void*) {
  FXuint answer;

  answer = FXMessageBox::question(this, MBOX_YES_NO, "Apagar tudo",
      "Essa ação não pode ser desfeita.\nDeseja continuar?");

  switch (answer) {
    case MBOX_CLICKED_YES:
      FXMessageBox::information(this, MBOX_OK, "Dados apagados",
          "Todos os dados foram apagados.");
    break;

    case MBOX_CLICKED_NO:
      FXMessageBox::information(this, MBOX_OK, "Dados não apagados",
          "Os dados não foram apagados.");
    break;

    default: break;
  }

  return 1;
}

Resultado




Discussão

Nada de mais aqui. Se o usuário clicou em "Yes", exibe uma mensagem informando que todos os dados foram apagados. Se clicou em "No", informa que não foram apagados. Realmente sem mistérios.


Conclusão

Este tópico foi apenas um complemento de outro anterior (link). Aqui encerro oficialmente a série sobre o FXMessageBox. A partir daqui, começaremos a falar de elementos que compõem uma interface gráfica típica (menus, barras de ferramentas, barra de status etc.). Até lá!

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!

quinta-feira, 17 de setembro de 2009

Tratamento de Eventos: Uma ação para vários IDs

Quando falei sobre o mapa de mensagens (aqui), eu adicionei quatro mapeamentos. Entretanto, todos eles eram do mesmo tipo e chamavam a mesma ação; vejam:

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_INFORMATION,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_QUESTION,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_WARNING,
  FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
  FoxTutorialMainWindow::ID_ERROR,
  FoxTutorialMainWindow::onCmdMessage),

Uma situação dessas ocorre quando eu tenho várias ações muito semelhantes: em vez de definir um callback para cada ação, eu defino apenas um; a diferença entre eles é feita dentro do corpo da função.

O FOX Toolkit fornece uma macro auxiliar para facilitar essa tarefa: FXMAPFUNCS. Neste tópico, explicarei como usá-la.


Definindo uma ação para vários IDs

Diante de uma situação dessas, seria bem mais fácil se eu pudesse diminuir o número de linhas a serem escritas. A propósito, o FOX Toolkit tem um slogan que diz: "Cada linha de código não escrita é uma linha correta". Pense no caso de uma calculadora: cada botão executa uma ação bem semelhante um ao outro: adicionar o seu valor à expressão e atualizar a caixa de texto que a exibe.

Imagina se eu tivesse que escrever uma função para cada botão? Seriam 10 botões de dígitos + 5 operações (contanto com a igualdade), isso apenas para uma calculadora básica. 15 funções, 15 entradas no mapa.

Para facilitar, pode-se definir apenas uma função e ligá-las todas aos botões da calculadora. E para evitar as quinze entradas no mapa, utiliza-se a macro FXMAPFUNCS. Para não ficar maçante, e reaproveitar os códigos já escritos, vou voltar ao exemplo do tutorial sobre as mensagens exibidas ao usuário.


Declarando as mensagens

Para poder utilizar a macro FXMAPFUNCS, é necessário que os IDs das mensagens sejam declarados seqüencialmente. Isso porque a macro pega um intervalo de IDs. No nosso exemplo:

enum {
  ID_INFORMATION = FXMainWindow::ID_LAST,
  ID_QUESTION,
  ID_WARNING,
  ID_ERROR,

  ID_LAST,
};

Com isso, basta usar a macro mágica. A sintaxe dela é:

FXMAPFUNCS(tipo, ID_min, ID_max, callback)

No nosso exemplo, o mapa de mensagens fica assim:

FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {
  FXMAPFUNCS(SEL_COMMAND,
             FoxTutorialMainWindow::ID_INFORMATION,
             FoxTutorialMainWindow::ID_ERROR,
             FoxTutorialMainWindow::onCmdMessage),
};

Ou seja, com apenas uma chamada, eu defini a mesma ação para quatro mensagens diferentes.


Diferenciando as mensagens

Já que cada evento vai disparar a mesma ação, é preciso diferenciar os diferentes eventos. Todo callback no FOX Toolkit tem o mesmo formato (com os nomes típicos dos parâmetros):

long onAction(FXObject *sender, FXSelector sel, void *ptr);

Neste caso, o que mais importa é o segundo parâmetro (sempre que um parâmetro não for utilizado, pode permanecer anônimo). Um FXSelector nada mais é que uma mistura entre o tipo de mensagem e o ID da mensagem. Essa mistura é feita automaticamente pela macro FXSEL ao se acrescentar uma entrada no mapa. Apenas adiantando uma informação, essa macro também é utilizada ao se delegar ações; falarei sobre isso no futuro.

Já que o FOX junta essas duas informações, ele pode separá-las: FXSELID recupera o ID da mensagem, e FXSELTYPE recupera o tipo da mensagem. Sendo assim, fica fácil saber quem enviou a mensagem, sem precisar recorrer a ponteiros (como normalmente acontece com uma certa linguagem que dizem não ter ponteiros). Um teste switch resolve o caso:

long FoxTutorialMainWindow::onCmdMessage(
        FXObject*, FXSelector sel, void*) {
  switch (FXSELID(sel)) {
    case ID_INFORMATION:
      FXMessageBox::information(this, MBOX_OK, "Informação",
              "Operação finalizada");
    break;

    case ID_QUESTION:
      FXMessageBox::question(this, MBOX_YES_NO, "Sair",
              "Deseja realmente sair do programa?");
    break;

    case ID_WARNING:
      FXMessageBox::warning(this, MBOX_OK, "Aviso",
              "Valor não especificado.\nAtribuindo padrão 1.");
    break;

    case ID_ERROR:
      FXMessageBox::error(this, MBOX_OK, "Erro",
              "%s: Arquivo não encontrado", filename.text());
    break;

    default: break;
  }

  return 1;
}

Percebam que como eu não uso nem o primeiro nem o último parâmetro, deixei eles anônimos. Dar um nome a eles pode gerar um warning enjoado por parte do compilador.


Conclusão

Neste exemplo, utilizei FXSELID para recuperar o ID da mensagem dentro do FXSelector. Mas eu falei também de FXSELTYPE, para recuperar o tipo. Isso me dá a chance de definir a mesma ação para diferentes tipos e diferenciá-los dentro da função.

Não tenho certeza se dá para fazer algo semelhante no mapa; existem duas macros, FXMAPTYPE e FXMAPTYPES, mas nunca as utilizei. Parece que elas definem um mapeamento de um tipo de mensagem (ou intervalo de tipos, no caso da segunda) para todos os IDs possíveis, chamando a mesma ação. Resumindo: melhor não usar.

Com relação ao mapa de mensagens, acho que só falta falar de quando uma janela não trata nenhum evento. Mas se eu lembrar de mais alguma outra coisa, eu posto aqui. Até a próxima.

terça-feira, 15 de setembro de 2009

Tratamento de Eventos: Tipos de mensagem

No tópico anterior, falei especificamente sobre o mapa de mensagens. Neste tópico, falarei sobre alguns tipos de mensagem que podem ser disparados.

Talvez esse tópico seja um pouco maçante, porque existem diversos tipos. Vou ser breve em cada um deles, pois são mais uma questão de prática que de teoria.

Outra característica deste tópico é que, apesar de no FOX Toolkit não haver nenhuma distinção de categoria de eventos (eventos de mouse, teclado, janela etc), vou dividi-los aqui dessa forma para facilitar a leitura.

Os tipos de mensagem estão definidos em fxdefs.h. Não vou copiá-lo aqui porque são 80 tipos de mensagem (sem contar o primeiro, SEL_NONE, e o último, SEL_LAST). Estão definidos dentro da enumeração FXSelType, e todos têm o prefixo SEL_. Sendo 80, é muito provável que a maioria deles não seja utilizada. Eu, pelo menos, não utilizo muitos. Portanto, vou limitar a minha explicação àqueles que eu já usei, ou que pelo menos sei para que servem.

Vou tentar também seguir a ordem em que são declarados na enumeração. Ainda, vou agrupar eventos muito semelhantes ou complementares. Comecemos então a analisá-los.


Eventos de teclado

SEL_KEYPRESS
SEL_KEYRELEASE


Em se tratando de teclado, há somente esses dois eventos a serem tratados. Uma tecla foi pressionada (SEL_KEYPRESS), uma tecla foi liberada (SEL_KEYRELEASE).

Apenas uma observação: se o programa não se importar com qual foi a tecla pressionada/liberada, não há nada extra. Mas se a tecla for importante, é necessário incluir o arquivo fxkeys.h. Nele estão as definições dos valores de todas as teclas que podem ser pressionadas.


Eventos de mouse

SEL_LEFTBUTTONPRESS
SEL_LEFTBUTTONRELEASE


Botão esquerdo do mouse pressionado/liberado.

SEL_MIDDLEBUTTONPRESS
SEL_MIDDLEBUTTONRELEASE


Botão do meio do mouse pressionado/liberado.

SEL_RIGHTBUTTONPRESS
SEL_RIGHTBUTTONRELEASE


Botão direito do mouse pressionado/liberado.

SEL_MOTION
SEL_ENTER
SEL_LEAVE


Esses três tipos de mensagem referem-se ao movimento do mouse relativo ao objeto que lança o evento. Quando um mouse entra na área do objeto, este lança um evento do tipo SEL_ENTER. Quando o mouse se move dentro de um objeto, este lança um evento do tipo SEL_MOTION. Finalmente, quando o mouse sai da área do objeto, este lança um evento do tipo SEL_LEAVE.

SEL_CLICKED
SEL_DOUBLECLICKED
SEL_TRIPLECLICKED
SEL_MOUSEWHEEL


Clique, duplo clique, triplo clique e movimento da roda do mouse. Não lembro de ter usado nenhum dos três primeiros.

Já o tratamento do evento da roda do mouse é meio esquisito; no momento não me recordo como é feito, mas futuramente falaremos disso.


Eventos de janela

SEL_PAINT

Indica que um objeto deve ser redesenhado. Muito útil quando se tem um canvas: define-se um callback que desenha no canvas, que é executado sempre que este lança um evento do tipo SEL_PAINT.

SEL_CONFIGURE

Este evento é lançado quando a janela é redimensionada. Este aqui é utilizado, por exemplo, quando há uma janela com um canvas OpenGL que, ao ser redimensionada, precisa reajustar a viewport e as matrizes de visualização para evitar distorções na imagem (quem trabalha com OpenGL sabe do que estou falando).

SEL_UPDATE

Este evento é lançado constantemente pelo objetos. Ao capturar um evento destes, é possível alterar o estado do objeto que o lançou. Por exemplo, um botão deve permanecer inativo enquanto não houver nenhum arquivo aberto; a partir do momento em que abre-se um arquivo, este botão deve ser ativado. Isto pode ser feito capturando-se o evento do tipo SEL_UPDATE que ele lança.


Execução de comandos

SEL_COMMAND

Sempre que eu falo desse tipo de mensagem eu dou os exemplos mais básicos possíveis: um botão clicado e um menu acionado. Mas não são somente botões e menus que enviam mensagens deste tipo. Diversos componentes também o fazem, mas isso fica para tópicos mais específicos. Por enquanto, esses dois exemplos são suficientes.

SEL_TIMEOUT

Serve para chamar a ação associada depois de um determinado intervalo de tempo.

SEL_CHORE

Serve para executar a ação associada sempre que não houver nenhum outro evento para tratar.

No início, eu utilizava para redesenhar o canvas o tempo inteiro. Em aplicações simples, isso não proporcionava perda de desempenho perceptível. Quando eu fiz um programa maior, de reconstrução de subestações elétricas, isso deixava lentíssimo. Foi só eu deixar isso de lado e atualizar o canvas somente quando necessário e o programa ficou uma bala. Portanto, evitem usar o chore dessa maneira, a menos que vocês queiram ter certeza que o processador está trabalhando (possivelmente à toa...).

Curiosidade: "chore" significa "tarefa doméstica".


Conclusão

Este tópico foi apenas uma introdução aos tipos de mensagem, mostrando apenas um pouco da teoria. Bem melhor será quando forem vistos na prática. Portanto, escreverei tópicos específicos para explicar com mais detalhes e mostrar exemplos do tratamento de cada tipo aqui apresentado. Até lá.

segunda-feira, 14 de setembro de 2009

Tratamento de Eventos: Mapa de mensagens

O mapa de mensagens é, digamos assim, o cerne do tratamento de eventos no FOX Toolkit. É ele quem determina que callback deve ser chamado em resposta a que tipo de mensagem, enviada por um determinado objeto.

Neste tópico, explicarei com mais detalhes como funciona o mapa. Vamos lá.

Definição do mapa

O mapa de mensagens é definido através da macro FXDEFMAP. Essa macro recebe um parâmetro, que é o nome da classe. Com esta macro, declara-se um vetor onde são armazenados os mapeamentos de mensagens; essa macro expandida nada mais é que uma declaração de uma variável normal. Seguindo o exemplo dos nossos tutoriais, temos o seguinte:
FXDEFMAP(FoxTutorialMainWindow) FoxTutorialMainWindowMap[] = {

};

Essa macro (que termina com o parêntese fechado) é expandida na seguinte declaração:
static const FoxTutorialMainWindowMap::FXMapEntry
        FoxTutorialMainWindowMap[] = {

};

Sendo que FXMapEntry é uma struct interna criada automaticamente pela macro FXDECLARE, chamada ao declararmos a classe FoxTutorialMainWindow (lembram?). Ou seja, estamos declarando um vetor de entradas do mapa de mensagem.

Percebam o nome do vetor de mapeamento. Aqui temos um padrão de nomenclatura: o nome do vetor é composto pelo nome da classe com o sufixo "Map".

Dentro deste vetor, colocamos os mapeamentos em si. Isto é feito com auxílio da macro FXMAPFUNC, que veremos em seguida.

Adicionando mapeamentos

A macro FXMAPFUNC recebe três parâmetros: o tipo da mensagem, o ID da mensagem e um ponteiro para o callback. Tomando como exemplo este tutorial, adicionamos os seguintes mapeamentos:
FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_INFORMATION,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_QUESTION,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_WARNING,
    FoxTutorialMainWindow::onCmdMessage),

FXMAPFUNC(SEL_COMMAND,
    FoxTutorialMainWindow::ID_ERROR,
    FoxTutorialMainWindow::onCmdMessage),

Tipo de mensagem

Informa qual o tipo de evento que foi disparado: um comando, um clique do mouse, uma tecla pressionada, uma janela redimensionada etc.

Teremos um tópico exclusivo (ou vários tópicos...) para discutir os diferentes tipos de mensagem.

ID da mensagem

De certa maneira, identifica quem enviou a mensagem. Junto com o tipo da mensagem, tem-se uma combinação suficiente para dizer que ação (callback) deve ser executada. Essa combinação deve ser única, portanto uma declaração como a seguinte não é válida:
FXMAPFUNC(SEL_COMMAND, ID_ACTION, onCmdAction1),
FXMAPFUNC(SEL_COMMAND, ID_ACTION, onCmdAction2),

Neste caso, tentamos fazer com que a mesma mensagem ative duas ações, o que na realidade não ocorre. É possível, entretanto, combinar a mesma mensagem com diferentes tipos, podendo ou não executar a mesma ação. Por exemplo, eu tenho um canvas (área de desenho) que deve executar uma determinada ação quando o mouse for clicado e outra quando for redimensionado. Como é um objeto só, ele vai ter apenas um ID, diga-se ID_CANVAS. No mapa, teremos (para efeito de exemplo, vou omitir o escopo dos identificadores; compare com os exemplos reais acima):
FXMAPFUNC(SEL_CLICKED, ID_CANVAS, onCanvasClick),
FXMAPFUNC(SEL_CONFIGURE, ID_CANVAS, onCanvasConfigure),

É possível, ainda, que um mesmo ID, com diferentes tipos de mensagem, execute a mesma ação. Digamos que nosso canvas trate igualmente o clique com o botão esquerdo e direito do mouse:
FXMAPFUNC(SEL_LEFTBUTTONPRESS, ID_CANVAS, onCanvasClick),
FXMAPFUNC(SEL_RIGHTBUTTONPRESS, ID_CANVAS, onCanvasClick),

Resumindo, apenas a combinação <tipo de mensagem, id da mensagem> deve ser única.

Ponteiro para o callback

É exatamente isso: um ponteiro para a função membro, que foi declarada na classe (e, naturalmente, deve estar devidamente implementada). Realmente não há muito o que dizer aqui.

Observações finais

Nos exemplos didáticos, eu removi o escopo dos identificadores (id e ponteiros de função) por motivos de espaço. É necessário fornecê-los pelo seguinte motivo: a declaração do mapa de mensagens, na verdade, declara um vetor global, ou seja, fora do escopo da classe. Estando fora do escopo da classe, é necessário informá-lo, qualificando os identificadores através do operador de escopo ( :: ).

Outro motivo, talvez menos comum, é que é possível que um objeto de uma classe execute uma ação de outro objeto de outra classe. Isso acontece, por exemplo, quando um botão de uma caixa de diálogo altera o estado de um componente da janela principal, ou seja, o comando deste botão dispara uma ação da janela principal.

Estes dois motivos explicam, também, porque tanto as IDs das mensagens quanto as ações devem estar localizadas na interface pública da classe.

Conclusão

Neste tópico, apresentei alguns detalhes referentes ao mapa de mensagens. É muito importante entendê-lo, já que ele informa que ações devem ser executadas em resposta às diversas ações do usuário.

Existem ainda alguns outros detalhes, mas que deixarei para outros tópicos, pois acho que este já está extenso e cobriu bem o básico. Até lá.