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.

Tratamento de Eventos: Convenções de nomenclatura

No tópico anterior, mostrei como capturar um evento e utilizar um tratador definido pelo usuário.

Neste tópico falarei sobre as convenções de nomenclatura do callbacks.

São três convenções que ajudam na identificação imediata dos callbacks, bem como de sua funcionalidade:
  1. Sempre começam com on.
  2. Em seguida, vem um identificador do tipo de mensagem. Este será discutido mais adiante.
  3. Por último, um nome descritivo opcional. Caso exista, deve deixar claro que ação ele executará.

O identificador do tipo de mensagem deve condizer, claro, com o tipo de mensagem que irá tratar. Existem três tipos que são normalmente abreviados:
  • SEL_COMMAND: abreviado para onCmd...
  • SEL_UPDATE: abreviado para onUpd...
  • SEL_CHANGED: abreviado para onChg...

O terceiro é bem raro no FOX, só encontrei seis ocorrências de callbacks desse tipo (não que isso signifique que o usuário não possa definir seus próprios).

Normalmente, para esses três tipos de mensagem é necessário informar um nome extra, já que por si sós não são descritivos o suficientes (terceiro item das convenções).

Para os demais tipos de mensagem, normalmente utiliza-se o nome completo. Exemplos:
  • SEL_LEFTBUTTONPRESS: onLeftButtonPress
  • SEL_PAINT: onPaint
  • SEL_KEYPRESS: onKeyPress

Esses já dizem claramente o que vão tratar, por isso normalmente recebem o nome completo. E, ao contrário daqueles três, normalmente não é necessário mais de um tratador por janela, ou seja, não precisam de um nome extra para diferenciá-los.

Voltando, então, ao exemplo do tópico anterior, nós temos uma mensagem chamada ID_HELLO. Este é um evento disparado por um botão, ou seja, do tipo SEL_COMMAND. Um nome bastante adequado para o callback que trata essa mensagem, portanto, é onCmdHello.

Com isso, eu já sei que onCmdHello é o callback (on) que vai tratar um comando (Cmd), referente à mensagem ID_HELLO (Hello).


Conclusão

Este tópico foi apenas para chamar atenção a esse pequeno detalhe, mas que eu considero bastante importante, afinal, é importante ter boa legibilidade no código.

Posteriormente, falarei mais especificamente sobre o mapa de mensagens e os tipos de mensagem fornecidos pelo FOX Toolkit.

Um abraço e até lá.

quinta-feira, 7 de maio de 2009

Tratamento de Eventos: Enviando e tratando

No primeiro tópico sobre tratamento de eventos (aqui), eu criei um botão que apenas fechava o aplicativo, ou seja, o botão enviava uma mensagem pré-definida.

Neste tópico, mostrarei como definir suas próprias mensagens a serem enviadas, assim como tratar os eventos a elas relacionados.

Pois bem, ponhamos a mão na massa.


Declaração das mensagens

O primeiro passo é declarar as mensagens que serão enviadas. Isto é feito na declaração da classe através de uma enumeração, que deve estar na interface pública. Assim:
30 public:
31 enum {
32 ID_HELLO = FXMainWindow::ID_LAST,
33
34 ID_LAST,
35 };

Aqui, estou chamando minha mensagem de ID_HELLO, seguindo a convenção de prefixar as mensagens com ID_. É importante também que o nome seja bem descritivo.

Em se tratando de uma enumeração, por padrão os itens são numerados seqüencialmente começando em 0. Porém essa é uma característica indesejável nesse caso, pois isso proporcionaria conflito de mensagens. Para garantir que cada mensagem tenha um valor diferente, todos os widgets FOX declaram a "mensagem" ID_LAST, cujo único propósito é garantir a seqüência de mensagens e evitar colisão. Então, deve-se atribuir à primeira mensagem o valor da última mensagem da superclasse direta, neste caso FXMainWindow.

Eu costumo definir também a minha própria ID_LAST, apesar de nunca ter derivado uma classe minha. Se for garantido que sua classe não será derivada, não é necessário.


Declaração dos callbacks

Além das mensagens, é preciso definir as funções que irão tratar as mensagens. Quando se trata de interação com o usuário, essas funções são chamadas callbacks, exatamente porque elas não têm um momento certo para serem chamadas, mas são executadas em resposta às ações do usuário. Em outro tópico discutirei um tema relevante com relação a essa questão dos callbacks.

Eles devem estar na interface pública da classe. Como C++ permite definir o escopo dos componentes da classe diversas vezes, eu gosto de "agrupar" a declaração das mensagens e dos callbacks em uma "seção pública" separada, conforme pode-se observar no código.

No FOX, todos os callbacks têm um protótipo igual:
long callback(FXObject*, FXSelector, void*);

ou seja, sempre retornam um long e recebem três parâmetros: FXObject*, FXSelector, void*.

Além disso, existe uma convenção de nomenclatura, que será tratada em outro tópico.

Neste exemplo, definimos o seguinte callback:
37   long onCmdHello(FXObject*, FXSelector, void*);

Aqui terminamos as declarações necessárias. Agora partimos para a implementação.


Mapa de mensagens

O mapa de mensagens é definido com auxílio da macro FXDEFMAP, já apresentada aqui. Porém, estava vazio, pois a janela não tratava nenhum evento. Agora vamos colocar um mapeamento nele.

Cada mapeamento é definido pela macro FXMAPFUNC, recebe três parâmetros: tipo da mensagem, ID da mensagem e um ponteiro para o callback. No nosso exemplo, temos:
13   FXMAPFUNC(SEL_COMMAND,
FoxTutorialMainWindow::ID_HELLO,
FoxTutorialMainWindow::onCmdHello),

Obs.: Por limitações de espaço, quebrei a linha em três, mas normalmente deixo a declaração inteira em uma linha só.

Isso quer dizer que sempre que a nossa janela receber uma mensagem do tipo SEL_COMMAND (um botão clicado, um menu selecionado etc.), associado à mensagem ID_HELLO, ela deve executar o callback onCmdHello.

A linha contendo a chamada à macro FXIMPLEMENT permanece intacta, pois a macro auxiliar ARRAYNUMBER garante a atualização automática do tamanho do mapa de mensagens.


Conectando os objetos

Em seguida, eu preciso dizer quem vai enviar essa mensagem. Para isso, eu criei um botão:
26   new FXButton(contents, "&Hello FOX!", NULL, this, ID_HELLO,
27 BUTTON_NORMAL|LAYOUT_FILL_X);

Quando o usuário clica no botão, ele lança um evento do tipo SEL_COMMAND, associado à sua mensagem, que neste caso é ID_HELLO. Esse evento é enviado para o alvo do botão, que neste caso é a própria janela (this).

Em seguida, o alvo (nossa janela) consulta seu mapa de mensagens e descobre que um evento SEL_COMMAND, associado à mensagem ID_HELLO, resulta em uma chamada a onCmdHello.


Implementando o callback

Naturalmente, o callback deve ser implementado. Neste exemplo, será exibida uma mensagem de saudação:
48 long FoxTutorialMainWindow::onCmdHello(FXObject*, FXSelector, void*) {
49 FXMessageBox::information(this, MBOX_OK, "Olá", "Olá, FOX Toolkit");
50
51 return 1;
52 }

Note o valor de retorno: 1. Callbacks, no FOX Toolkit, retornam basicamente 1 para informar que o evento foi tratado, ou 0, indicando que não foi tratado.

Essa convenção é utilizada para, por exemplo, tratar a atualização da interface, especialmente em eventos do mouse: se o evento foi tratado, agenda uma atualização da interface; senão, nada mais acontece.

Na prática, o valor de retorno é quase sempre 1 (pelo menos, eu nunca retornei 0, mas já andei fuçando os fontes e isso acontece, portanto é bom saber).

Com isso, temos um botão que, ao ser clicado, exibe uma mensagem ao usuário.


Resultado

Confira no vídeo a seguir como ficou a nossa janela:



---
Código-fonte para este tutorial.
Baixar vídeo.

terça-feira, 5 de maio de 2009

Botões: Texto e ícone

Neste tópico, falarei sobre adicionar um ícone ao botão, e mostrarei as posições relativas entre ícone e texto.

Carregando o ícone

Para carregar o ícone, basta proceder conforme explicado aqui.

Na declaração da classe:
30   FXIcon *searchIcon;

Em loadIcons():
82   searchIcon = iconSource.loadScaledIconFile("icons/search.gif", 32);

Lembrando que o ícone deve ser destruído manualmente, em destroyIcons():
89   delete searchIcon;

Com isso, basta passar o ícone como terceiro argumento do construtor do botão:
new FXButton(p, "text", searchIcon, ...


Posições relativas entre ícone e texto

A relação de posição entre ícone e menu é definida em FXLabel.h. FXButton é derivado direto de FXLabel. Isso significa que todas as opções deste valem para aquele também. Significa também que toda a discussão a seguir é válida para quando adicionarmos legendas (labels) na nossa interface.

Existem cinco maneiras de posicionar um ícone em relação ao texto, e cada uma delas possui dois sinônimos. As opções são:
enum {
ICON_UNDER_TEXT = 0,
ICON_AFTER_TEXT = 0x00080000,
ICON_BEFORE_TEXT = 0x00100000,
ICON_ABOVE_TEXT = 0x00200000,
ICON_BELOW_TEXT = 0x00400000,
TEXT_OVER_ICON = ICON_UNDER_TEXT,
TEXT_AFTER_ICON = ICON_BEFORE_TEXT,
TEXT_BEFORE_ICON = ICON_AFTER_TEXT,
TEXT_ABOVE_ICON = ICON_BELOW_TEXT,
TEXT_BELOW_ICON = ICON_ABOVE_TEXT
};

Obs.: existem também opções de posicionamento de texto e ícone em relação ao widget, usando opções de justificação, mas isso fica para outro tópico.


ICON_UNDER_TEXT
TEXT_OVER_ICON

Seu valor é 0, o que indica que esta é a opção "padrão". No fim deste tópico, falarei a respeito dessa questão.

Esta opção faz com que o ícone apareça por trás do texto. Exemplo:
25   new FXButton(matrix, "ICON_UNDER_TEXT", searchIcon, NULL, 0,
26 FRAME_RAISED|FRAME_THICK|ICON_UNDER_TEXT);
27
28 new FXButton(matrix, "TEXT_OVER_ICON", searchIcon, NULL, 0,
29 FRAME_RAISED|FRAME_THICK|TEXT_OVER_ICON);



ICON_AFTER_TEXT
TEXT_BEFORE_ICON

Desenha o ícone à direita do texto. Exemplo:
32   new FXButton(matrix, "ICON_AFTER_TEXT", searchIcon, NULL, 0,
33 FRAME_RAISED|FRAME_THICK|ICON_AFTER_TEXT);
34
35 new FXButton(matrix, "TEXT_BEFORE_ICON", searchIcon, NULL, 0,
36 FRAME_RAISED|FRAME_THICK|TEXT_BEFORE_ICON);


ICON_BEFORE_TEXT
TEXT_AFTER_ICON

Desenha o ícone à esquerda do texto. Exemplo:
39   new FXButton(matrix, "ICON_BEFORE_TEXT", searchIcon, NULL, 0,
40 FRAME_RAISED|FRAME_THICK|ICON_BEFORE_TEXT);
41
42 new FXButton(matrix, "TEXT_AFTER_ICON", searchIcon, NULL, 0,
43 FRAME_RAISED|FRAME_THICK|TEXT_AFTER_ICON);


ICON_ABOVE_TEXT
TEXT_BELOW_ICON

Desenha o ícone acima do texto. Exemplo:
46   new FXButton(matrix, "ICON_ABOVE_TEXT", searchIcon, NULL, 0,
47 FRAME_RAISED|FRAME_THICK|ICON_ABOVE_TEXT);
48
49 new FXButton(matrix, "TEXT_BELOW_ICON", searchIcon, NULL, 0,
50 FRAME_RAISED|FRAME_THICK|TEXT_BELOW_ICON);


ICON_BELOW_TEXT
TEXT_ABOVE_ICON

Desenha o ícone sob o texto. Exemplo:
53   new FXButton(matrix, "ICON_BELOW_TEXT", searchIcon, NULL, 0,
54 FRAME_RAISED|FRAME_THICK|ICON_BELOW_TEXT);
55
56 new FXButton(matrix, "TEXT_ABOVE_ICON", searchIcon, NULL, 0,
57 FRAME_RAISED|FRAME_THICK|TEXT_ABOVE_ICON);


Voltando à questão do valor "padrão"

ICON_UNDER_TEXT tem valor 0, o que o torna, teoricamente, a opção padrão.

Para entender essa questão, note que as demais opções podem ser divididas entre posicionamento vertical (ICON_ABOVE_TEXT e ICON_BELOW_TEXT) e posicionamento horizontal (ICON_BEFORE_TEXT e ICON_AFTER_TEXT).

Isso significa que eu posso combinar opções de posicionameto horizontal e vertical. Porém, se eu não especificar um ou outro, a opção padrão será utilizada. Neste caso, é melhor entender a opção padrão não como o nome diz (ícone debaixo (atrás?) do texto), mas que o ícone ficará centralizado naquele eixo que não foi especificado.

Voltando aos exemplos, perceba que ICON_AFTER_TEXT e ICON_BEFORE_TEXT deixam o ícone centralizado na vertical. Da mesma forma, ICON_ABOVE_TEXT e ICON_BELOW_TEXT deixam o ícone centralizado na horizontal. Ainda, a opção ICON_UNDER_TEXT deixa o ícone centralizado em ambos os eixos.

Então por que "padrão", entre aspas? Tem algum detalhe?
Sim. Este padrão é considerado neste caso, em que se omite o posicionamento em um dos eixos. Acontece que muitos widgets FOX têm uma opção com o sufixo _NORMAL, que reúnem as opções mais comuns, e são passadas como argumento padrão para o construtor.

Assim, para o FXButton, temos BUTTON_NORMAL, definido junto com outras opções:
BUTTON_NORMAL    = (FRAME_RAISED|FRAME_THICK |
JUSTIFY_NORMAL
|ICON_BEFORE_TEXT)

Na declaração do construtor:
FXButton(FXComposite* p, ..., FXuint opts=BUTTON_NORMAL, ...

Ou seja, para um botão, o padrão é exibir o ícone à esquerda do texto. Como não foi especificado o posicionamento na vertical, fica centralizado.

Espero que essa diferença tenha ficado clara. A distinção está na questão de existir uma opção padrão (que leva o valor 0 exatamente para que não seja necessário informá-la explicitamente) e existir um argumento padrão para o construtor de um widget, que reúne as opções mais comuns, que não necessariamente são as opções padrões.

E como fica se eu combinar posicionamento vertical e horizontal?

Veja:


Deixo como exercício.

Até a próxima.

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

segunda-feira, 4 de maio de 2009

Layout Managers: FXHorizontalFrame

Neste tópico, falarei sobre o gerenciador de layout FXHorizontalFrame. Aproveito também para mostrar algumas dicas de layout.


Descrição
O gerenciador de layout HorizontalFrame é utilizado para posicionar automaticamente as janelas filhas horizontalmente, da esquerda para a direita, ou da direita para a esquerda, dependendo das dicas de layout da janela.

Obs.: "janela" aqui refere-se a qualquer derivado de FXWindow.


Exemplo

Neste exemplo, temos um HorizontalFrame e dentro dele seis botões:
22   FXHorizontalFrame *frame = new FXHorizontalFrame(this,
23 LAYOUT_FILL_X|PACK_UNIFORM_WIDTH|PACK_UNIFORM_HEIGHT|FRAME_RAISED);

Aqui, é criado o frame. Ele se expandirá por todo o eixo X da janela (LAYOUT_FILL_X) e todos os seus filhos terão a mesma dimensão (PACK_UNIFORM_WIDTH | PACK_UNIFORM_HEIGHT). Também possui uma borda "levantada", que eu coloquei aqui apenas para destacar.

Em seguida, são criados os botões:
25   new FXButton(frame, "Botão 1");
26 new FXButton(frame, "Botão 2");
27 new FXButton(frame, "Botão com texto grande");
28 new FXButton(frame, "Botão em\nduas linhas");
29
30 new FXButton(frame, "Centro", NULL, NULL, 0,
31 BUTTON_NORMAL|LAYOUT_CENTER_X);
32
33 new FXButton(frame, "Botão à direita", NULL, NULL, 0,
34 BUTTON_NORMAL|LAYOUT_RIGHT);

Os dois primeiros não têm nada de peculiar. O terceiro possui o texto mais longo de todos e o quarto tem o texto quebrado em duas linhas. Isso vai influir no layout dos botões, conforme veremos mais adiante.

O quinto botão é posicionado no centro (LAYOUT_CENTER_X). Isso não fará que ele fique no centro da janela, mas no centro da área restante. O BUTTON_NORMAL serve para preservar as características originais de borda e texto do botão. O sexto botão fica alinhado à direita (LAYOUT_RIGHT).

Não há muito o que discutir além disso, só mostrar o resultado.


Resultado


Percebe-se que os botões estão todos juntos. Isso se deve ao fato de eu não ter especificado nenhum tamanho para a janela principal (na verdade, não especifiquei tamanho de ninguém...). Assim, ela assume o tamanho mínimo para que caibam todos os seus filhos. E assim cada filho dela também o fez.

Por isso o "Botão com texto grande" ficou mais longo e o "Botão em duas linhas" ficou com altura maior. Como o frame foi instruído a deixar todo mundo nas mesmas dimensões, todo mundo recebeu a largura do terceiro botão e a altura do quarto.

Aliás, esse é o principal motivo pelo qual eu não especifiquei um tamanho para a janela principal, como vinha fazendo nos tutoriais anteriores (nos quais eu criava uma janela 200x200). Especificar um tamanho corre o risco de não caber todo mundo, ou de ficar grande demais. Na verdade, é bem difícil acertar. É muito melhor deixar o próprio FOX tratar esses detalhes.

E quanto ao posicionamento dos dois últimos botões? Um centralizado e o outro à direita?

Pois bem, esse é o tamanho mínimo, que coube todo mundo, então fica todo mundo juntinho. Se eu aumentar a janela, eles assumem a posição correspondente:


Desta vez fica claro: o botão "Centro" ficou no meio do espaço disponível, e o "Botão à direita" ficou no canto onde realmente deveria estar.


Conclusão

Este tópico mostrou um dos gerenciadores de layout mais simples que o FOX Toolkit fornece. Mostrou também como agem algumas dicas de layout, bem como as dicas de empacotamento discutidas aqui e aqui.

Com o tempo, ficará bem mais claro o poder que os gerenciadores de layout oferecem.

Só como presente de despedida desse tópico, uma pergunta que não quer calar:
Se eu não definisse aquelas opções de empacotamento?

Apenas veja:


Eu poderia ainda instruir os botões a ficarem centralizados no eixo Y (LAYOUT_CENTER_Y), mas isso não ajudaria muito em termos estéticos. Vou deixar isso a cargo da curiosidade do leitor.

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