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á.

quarta-feira, 15 de julho de 2009

Liberando memória

Foi postada na lista de e-mails do FOX Toolkit uma dúvida que achei bem interessante para colocar aqui, pois é um assunto básico: liberação de memória.

A dúvida foi enviada por John Selverian, e apresento-a aqui, junto com a resposta dada pelo Jeroen van der Zijp. Ela tem alguns conceitos que ainda não apresentei aqui, mas serve como um norte para o futuro.

Já falei rapidamente sobre isso em tópicos anteriores, mas creio que postando essa dúvida aqui o assunto fique mais sólido, além de apresentar um aspecto bem interessante a esse respeito. Vamos lá.

Liberando memória
Eu tenho uma pergunta bem básica. Eu tenho várias janelas FXDialogBox. Nos arquivos de cabeçalho eu declaro ponteiros e no construtor eu aloco a memória. No método de sair de algumas dessas FXDialogBox eu deleto a memória e em algumas não (sloppy programming, eu acho). Não vejo diferença na alocação de memória ou vazamento. Estou fazendo algo errado ou a memória é automaticamente liberada pelo FOX? Também, muitas vezes meu destrutor não é chamado... isso faz sentido?

Obrigado,

John S



Resposta
O FOX tenta liberar a memória, sim. Mas não há contagem de referência ou coleta de lixo, então ele só deleta as coisas que sabe que pode deletar:

  1. Janelas pais liberam widgets filhos.
  2. A árvore de widgets inteira é liberada pelo FXApp.

Mas:

  1. Recursos compartilhados como ícones, fontes NÃO são deletados, já que várias referências podem existir (exceção: stock icons e cursores que são criados pelo FXApp).

Uma regra simples é deletar, no destrutor do diálogo, todos os ícones/fontes/etc. que você criar em seu construtor do diálogo.

No FOX 1.7, agora tem FXAutoPtr, que faz isso bem fácil:
class MyDialog : public FXDialog {
FXAutoPtr<FXIcon> buttonIcon;
};

A idéia do FXAutoPtr é comportar-se como um ponteiro normal mas agir como o único dono do objeto para o qual aponta, e então quando o FXAutoPtr é destruído então o objeto que ele aponta também é destruído.

Então declarar suas variáveis de ícones como FXAutoPtr em vez de FXIcon* vai eliminar a necessidade de escrever explicitamente comandos delete em seus destrutores.

Espero que ajude,
- Jeroen


---
Retirado da lista de e-mails Foxgui-users. Veja o original aqui.

segunda-feira, 25 de maio de 2009

FXMessageBox: Opções de botões

No tópico anterior, apresentei as mensagens que o FOX Toolkit fornece ao usuário através da classe FXMessageBox. Percebam que as mensagens de informação, aviso e erro possuíam apenas o botão OK, enquanto a pergunta possuía dois botões, Sim e Não.

Neste tópico, mostrarei as opções de botões disponíveis para as caixas de mensagem. Novamente, mostrarei apenas os trechos de código e o resultado, pois são bem auto-explicativos.

Os botões que devem aparecer na caixa de mensagem são passados como segundo parâmetro, e são definidos em termos de uma enumeração declarada em FXMessageBox.h:
enum {
MBOX_OK = 0x10000000,
MBOX_OK_CANCEL = 0x20000000,
MBOX_YES_NO = 0x30000000,
MBOX_YES_NO_CANCEL = 0x40000000,
MBOX_QUIT_CANCEL = 0x50000000,
MBOX_QUIT_SAVE_CANCEL = 0x60000000,
MBOX_SKIP_SKIPALL_CANCEL = 0x70000000,
MBOX_SAVE_CANCEL_DONTSAVE = 0x80000000
};


MBOX_OK
FXMessageBox::information(&app, MBOX_OK, "Informação",
"Operação finalizada");



MBOX_OK_CANCEL
FXMessageBox::question(&app, MBOX_OK_CANCEL, "Apagar tudo",
"Essa operação não pode ser desfeita.\nDeseja continuar?");



MBOX_YES_NO
FXMessageBox::question(&app, MBOX_YES_NO, "Sair",
"Sair do programa?");



MBOX_YES_NO_CANCEL
FXMessageBox::question(&app, MBOX_YES_NO_CANCEL, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



MBOX_QUIT_CANCEL
FXMessageBox::question(&app, MBOX_QUIT_CANCEL, "Processo em execução",
"Terminar o aplicativo encerrará um processo em andamento."
"\nDeseja mesmo sair?"
);



MBOX_QUIT_SAVE_CANCEL
FXMessageBox::question(&app, MBOX_QUIT_SAVE_CANCEL, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



MBOX_SKIP_SKIPALL_CANCEL
FXMessageBox::error(&app, MBOX_SKIP_SKIPALL_CANCEL, "Entrada inválida",
"Entrada inválida.");



MBOX_SAVE_CANCEL_DONTSAVE
FXMessageBox::question(&app, MBOX_SAVE_CANCEL_DONTSAVE, "Salvar arquivo",
"O arquivo foi alterado.\nDeseja salvar antes de sair?");



Em inglês?
É... open-source: quem quiser em português, tem que ir no código-fonte, traduzir e recompilar.


No próximo tópico, mostrarei como tratar a opção selecionada pelo usuário.

Até lá.

FXMessageBox: Tipos de mensagem

Esta será uma série a respeito da classe FXMessageBox. Ela serve para, como o próprio nome diz, exibir uma mensagem ao usuário de forma fácil e rápida.

Já utilizei esta classe em um tutorial passado para exibir uma mensagem de boas-vindas ao FOX Toolkit. Aquele foi apenas um tipo de mensagem dentre quatro disponíveis nesta classe:
  • informação
  • pergunta
  • aviso
  • erro

Esses quatro tipos de mensagem são fornecidos em forma de funções estáticas de FXMessageBox, ou seja, não é preciso instanciar explicitamente um objeto para exibir uma mensagem ao usuário.

Neste tópico, apenas discutirei os parâmetros de cada função, que são gerais, e mostrarei um exemplo de cada tipo, pois realmente não há muito o que explicar. Vamos lá.


Parâmetros

Cada função estática (information, question, warning, error) recebe pelo menos quatro parâmetros:
FXWindow* owner, FXuint opts, const char* caption, const char* message, ...

Existe também uma outra versão que, em vez de se passar uma janela, passa-se o aplicativo:
FXApp* app, FXuint opts, const char* caption, const char* message, ...

Essa segunda versão é útil, por exemplo, quando se deseja exibir uma mensagem ao usuário antes da janela principal ser criada.

Os parâmetros serão explicados a seguir.


FXWindow *owner / FXApp *app

Janela/aplicativo da mensagem. Nada de muito especial aqui.


FXuint opts

As opções da mensagem. Aqui se informam quais são os botões que aparecerão na mensagem (OK, Cancelar etc.). Para não ficar muito extenso, abordarei essa questão em outro tópico.


const char* caption

Título da janela com a mensagem. Também nada de especial aqui.


const char* message, ...

Texto da mensagem. Aqui, sim, há um detalhe importante.

Percebam as reticências. Isso indica que essa função recebe um número variável de parâmetros. Neste caso, no mínimo os quatro que são explicitamente declarados.

Essa mensagem é formatada pelo FOX de forma muito semelhante à função printf(). Ou seja, se eu quiser exibir uma mensagem que contenha dados da aplicação, não é necessário montá-la manualmente, pois o FOX faz isso automaticamente. Um exemplo (didático...) será mostrado mais à frente.


Exemplos

Nesta seção, mostrarei um exemplo de cada tipo de mensagem. O código-fonte para esse tutorial está disponível no final do tópico.

Serão mostrados apenas o trecho de código usado para exibir a mensagem e uma captura de tela da mensagem.
Obs.: Apenas lembrando, pelo fato de serem funções estáticas, são chamadas diretamente da classe, sem necessidade de instanciar um objeto, através do operador de escopo (::).

Informação
FXMessageBox::information(this, MBOX_OK, "Informação",
"Operação finalizada");




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




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




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




Discussão

Em primeiro lugar, percebam que para cada tipo de mensagem há um ícone diferente; é basicamente isso que diferencia um tipo do outro, pois o resto é igual. Essas são o que chamamos de funções de conveniência, pois automatizam tarefas comuns.

Segundo, na mensagem de aviso, eu utilizei uma quebra de linha para evitar que a mensagem fique muito longa. Isso é de controle exclusivo do usuário; o texto da mensagem é exibido através de um FXLabel, que não executa quebra de linha automática.

E terceiro, na mensagem de erro, um exemplo didático da formatação da mensagem a la printf(): a mensagem exibe o conteúdo de uma variável.


Conclusão

Esta foi apenas uma introdução às mensagens que o FOX Toolkit disponibiliza para o usuário. Existem outros aspectos relacionados, que serão discutidos em outros tópicos.

Até lá, e um abraço.

---
Código-fonte deste tutorial.

quinta-feira, 14 de maio de 2009

Layout Managers: Aninhamento

Todos os gerenciadores de layout do FOX podem ser aninhados, ou seja, podem ser filhos de outros gerenciadores de layout. Essa, na verdade, é uma característica obrigatória em qualquer biblioteca de interface gráfica, pois é isso que permite compor a aparência dos programas. Sem essa capacidade, ficaria quase impossível criar uma interface sofisticada (pense em posicionar cada elemento manualmente, ajustar as bordas de cada seção e outras tarefas tediosas).

Neste tópico, apresentarei um exemplo bem simples (e didático...) desse aninhamento. Farei uma janela semelhante a uma que eu criei para a minha monografia. À esquerda da janela principal, quatro imagens; à direita, uma janela para a exibição 3D. Assim:


Vou logo avisando que vai ficar bem diferente dessa de cima. No final eu explico por que. O objetivo desse tópico é basicamente mostrar o aninhamento dos gerenciadores de layout, utilizando a bagagem que foi adquirida até aqui.

Comecemos, então.


Codificação

O primeiro elemento da janela é uma barra de menu. Aqui tudo será simulado, então utilizarei um HorizontalFrame para a barra:
23   FXHorizontalFrame *menuBar = new FXHorizontalFrame(this,
24 LAYOUT_SIDE_TOP|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);

Os "menus" são simplesmente labels:
26   /* Menu Arquivo */
27 new FXLabel(menuBar, "&Arquivo");
28
29 /* Menu Imagens */
30 new FXLabel(menuBar, "&Imagens");

Agora vêm os aninhamentos. Na verdade, desde a primeira vez que colocamos um gerenciador de layout, fizemos um aninhamento, já que a própria janela já é um gerenciador de layout, mas aqui isso fica mais explícito.

Primeiramente, um frame horizontal representando o conteúdo da janela:
32   FXHorizontalFrame *contents = new FXHorizontalFrame(this,
LAYOUT_SIDE_TOP|LAYOUT_FILL|FRAME_NORMAL);

Eu gosto de definir os filhos imediatamente, formando realmente uma árvore no código (inclusive com indentação); acho que fica mais fácil saber quem é filho de quem, em vez de definir todos os filhos e depois os filhos dos filhos.

Sendo assim, o primeiro filho de contents é um frame vertical:
33     FXVerticalFrame *frameExam = new FXVerticalFrame(contents,
LAYOUT_FILL|FRAME_NORMAL);

Dentro dele, tem dois frames horizontais (que ficarão um sobre o outro); dentro de cada um deles, tem dois objetos do tipo FXFrame (que pode ser considerado um espaço vazio), que ficarão um do lado do outro:
34       FXHorizontalFrame *ccFrame = new FXHorizontalFrame(frameExam,
LAYOUT_FILL|FRAME_NORMAL);
35 new FXFrame(ccFrame, LAYOUT_FILL|FRAME_NORMAL);
36 new FXFrame(ccFrame, LAYOUT_FILL|FRAME_NORMAL);
37
38 FXHorizontalFrame *mloFrame = new FXHorizontalFrame(frameExam,
LAYOUT_FILL|FRAME_NORMAL);
39 new FXFrame(mloFrame, LAYOUT_FILL|FRAME_NORMAL);
40 new FXFrame(mloFrame, LAYOUT_FILL|FRAME_NORMAL);

O segundo filho de contents também é um FXFrame, apenas para ocupar o espaço:
42       new FXFrame(contents, LAYOUT_FILL|FRAME_NORMAL);


Resultado

Repetindo, o resultado é totalmente diferente do que foi mostrado acima. Com essa codificação, o que se obtém é isso:


Eu mandei a borda ser desenhada "afundada" justamente para que o aninhamento seja mais visível.


Discussão

O resultado ficou bem diferente do modelo por alguns motivos básicos.

Primeiramente, no modelo eu não utilizei somente FX(Horizontal|Vertical)Frame (não resisti à regex). Existem gerenciadores de layout bem mais propícios para uma interface como aquela.

Segundo, não alterei valores de espaçamento; por isso, dá para ver que as bordas ficam bem distantes umas das outras.

Terceiro, de contents para baixo, todos estão com a opção LAYOUT_FILL. Isso faz que o tamanho final de cada filho seja proporcional ao seu tamanho padrão em relação a seus irmãos. Como do lado esquerdo tem mais filhos, este ficou bem maior que o direito.

E quarto, para o modelo, eu defino explicitamente a largura dos contêineres das imagens (as áreas escuras) de acordo com as imagens carregadas. Tanto que, ao iniciar o programa, eles estão com a largura padrão, que é 1. Vejam:



Conclusão

Este tópico mostrou como é feito o aninhamento de gerenciadores de layout no FOX Toolkit. Espero ter ficado bem claro, pois é algo extremamente útil.

Utilizei nele poucos recursos porque achei melhor me limitar ao que já foi tratado até aqui. Entretanto, tudo o que foi dito aqui vale para qualquer outro gerenciador de layout. O FOX vem inclusive com um exemplo que diz "É claro que eles podem ser aninhados arbitrariamente." Creio que essa frase mostra bem o espírito da coisa.

Só uma observação final: FXFrame, ao contrário do que possa parecer, NÃO é um gerenciador de layout. Muito menos superclasse de FX(Horizontal|Vertical)Frame (como sugere o sufixo). É ele que desenha as bordas dos controles (grossa, fina, levantada, abaixada etc.).

FXFrame pode ser utilizado apenas como um "guarda-lugar" (placeholder), como foi feito aqui. Entretanto, é mais utilizado como classe-base para controles simples; por exemplo, FXLabel é derivado direto de FXFrame (e FXButton é derivado direto de FXLabel).


Um abraço e até a próxima.


---
Código-fonte para este tutorial.

segunda-feira, 11 de maio de 2009

Layout Managers: FXVerticalFrame

Neste tópico, falarei a respeito do gerenciador de layout FXVerticalFrame. A princípio, eu achei que seria demais um tópico só para ele, por ser bastante semelhante ao FXHorizontalFrame. Pensei então em apenas tratá-lo rapidamente dentro deste tópico. Mas percebi que eram temas muito diferentes, e apenas iria deixar o tópico mais extenso.

Resolvi, então, tratá-lo em um tópico exclusivo. Aproveito, ainda, para falar de mais algumas opções de layout.

Vamos lá, então.


Exemplo

Como eu utilizei esse gerenciador em um tópico anterior, vou reaproveitá-lo aqui. São apenas dois botões.

Primeiramente, é preciso criar o gerenciador:
23   FXVerticalFrame *contents =
24 new FXVerticalFrame(this, LAYOUT_FILL|FRAME_NORMAL);

Aqui, eu crio um FXVerticalFrame que se expandirá por toda a janela em ambas as direções (LAYOUT_FILL). FRAME_NORMAL significa que ele terá uma borda grossa e rebaixada. As opções de borda serão discutidas em detalhes futuramente.

Agora eu crio os botões:
26   new FXButton(contents, "&Hello FOX!", NULL, this, ID_HELLO,
27 BUTTON_NORMAL|LAYOUT_FILL_X);
28
29 new FXButton(contents, "&Goodbye, FOX!", NULL, a, FXApp::ID_QUIT,
30 BUTTON_NORMAL|LAYOUT_FILL_X|LAYOUT_BOTTOM);

O primeiro botão será posicionado junto à borda superior (por padrão), e se expandirá pelo eixo horizontal.

Já o segundo tem apenas uma diferença em relação ao primeiro (em opções de layout): ele será colocado junto à borda inferior (LAYOUT_BOTTOM).


Resultado

Neste tópico, há um vídeo mostrando o resultado do tratamento dos eventos.

No vídeo, a janela está com o tamanho mínimo, ou seja, os dois botões aparecem juntos um do outro. Agora vejam o que acontece quando se redimensiona a janela:



Conclusão

Conforme eu falei no início deste tópico, o FXVerticalFrame é bastante semelhante ao FXHorizontalFrame. Desta forma, tudo o que foi discutido no no outro tópico vale aqui também.

Sendo assim, deixo para a curiosidade do leitor fazer testes incluindo novos botões e utilizando as opções de layout e empacotamento já discutidos.

Um abraço e até a próxima.