Interface Visual do AdvPL – Parte 02

Introdução

No post anterior, vimos alguns exemplos básicos de interface. Neste post, vamos ver uma tela usando vários componentes juntos, explorando mais possibilidades de uso. A diagramação da tela de exemplo praticamente não existe, os componentes não estão alinhados para uma apresentação visual perfeita, o objetivo do exemplo é puramente didático.

O exemplo

O fonte foi criado usando os comandos de interface, que por baixo vão chamar os construtores dos objetos visuais e seus métodos. Cada componente utilizado merece uma atenção especial, portanto neste post vamos avaliar o exemplo como um todo, e ver superficialmente o que cada componente utilizado permite fazer. Como o fonte deste exemplo ficou um pouco maior que os demais, ele estará disponível no link “APPInt02“, em formato “.doc”. O Blog não permite que eu suba arquivos fonte ou compactados, porém o fonte está inteiro dentro do arquivo, basta copiá-lo e colá-lo na tela do IDE/TDS para compilar e executá-lo. Ao ser compilado e executado diretamente pelo SmartClient, através da função U_APPINT02, o resultado de tela esperado pode ser visto abaixo:

APPINT02

Componentes utilizados

O container principal de interface usado neste exemplo foi uma tDialog(), ela será o pai de todos os demais componentes, e então o código cria todos os componentes “filhos” desta caixa de diálogo, e alguns containers para a disposição de mais elementos visuais.

@ … GROUP

O comando @ GROUP permite criar um componente de interface da classe tGroup(), com a finalidade de desenhar um quadrado ou retângulo na interface, opcionalmente com um título, para indicar visualmente que os componentes desenhados dentro da área do componente estão de alguma forma relacionados. Vale lembrar que o objeto tGroup() não é um container. Isto significa que, para colocar outro componente na área interna de um tGroup(), o componente visual deve ter o mesmo objeto pai do tGroup(), e usar o sistema de coordenadas do objeto pai. No nosso exemplo, dentro da área ocupada pelo tGroup foram colocados vários objetos de edição de dados de interface, todos a partir do oDlg (tDialog).

@ … SAY

O comando @ SAY permite criar um componente de interface da classe tSay(), com a finalidade de mostrar um texto na caixa de diálogo. Através dos parâmetros, informamos a coordenada esquerda superior onde o componente será desenhado, e o tamanho (comprimento,altura) reservados para a mensagem ser mostrada. Caso a mensagem a ser mostrada ultrapasse o comprimento do componente, haverá uma quebra de linha automática no texto da mensagem.

@ … GET

O comando @ GET permite criar um componente de interface da classe tGet(), com a finalidade de permitir a entrada e edição de dados pela interface. Podem ser editados campos do tipo C (Caractere), N (Numéricos) e D (Data). Para os campos caractere e numéricos, podemos especificar uma máscara (Picture), onde podemos especificar o formato que a informação será mostrada na tela e aplicar conversões automaticamente. Por exemplo, ao informarmos a picture “@!”, o componente automaticamente vai transformar cada caractere alfabético digitado para caixa alta (letra maiúscula), mesmo que você digite uma letra minúscula. Já a máscara numérica “999999999” vai permitir apenas a entrada de um número, com no máximo 9 dígitos, exclusivamente numéricos. Qualquer caractere não numérico digitado será ignorado.

@ … GET MULTILINE

Uma variação do @ GET foi implementada, usando a classe TMultiGet(), para permitir a edição de um conteúdo string em modo de múltiplas linhas, útil quando precisamos editar uma descrição de texto livre. Esta opção de edição não permite formatação, apenas quebra de linha usando a tecla [Enter], e tabulação usando [Control]+[I].

@ … CHECKBOX

O comando @ CHECKBOX permite criar um componente de interface da classe tCheckBox(), com a finalidade de editar um valor booleano, através de uma caixa de seleção do tipo “Check”, onde a variável associada ao componente receberá o valor .T. (verdadeiro) caso a caixa seja marcada, e .F. caso ela seja desmarcada.

@ .. COMBOBOX

O comando @ COMBOBOX permite criar um componente de interface da classe tComboBox(), com a finalidade de permitir escolher um item de um array de strings. O componente sempre mostra a escolha atual, e caso você clique na seta para baixo mostrada na frente da opção escolhida, é mostrado abaixo do componente a lista de opções disponíveis para a escolha. Ao escolher uma opção, a variável amarrada ao componente recebe a string contina no elemento do array escolhido.

@ .. LISTBOX

O comando @ LISTBOX permite criar um componente de interface da classe tListBox(), com a finalidade de permitir escolher um item de um array de strings. O componente sempre mostra em modo destacado a escolha atual, porém todas as opções possíveis são mostradas na área da tela parametrizada para o componente. Ao escolher uma opção, a variável amarrada ao componente recebe a string contida no elemento do array escolhido.

@ … FOLDER

O comando @ FOLDER permite criar um componente de interface da classe tFolder(), com a finalidade de criar um ou mais pastas dentro da área usada pelo componente, onde cada pasta possui um objeto interno para mostrar outros componentes de tela. Cada container de um folder é criado internamente, usando um objeto da classe tFolderPage, disponibilizado na propriedade aDialogs do objeto tFolder(). O primeiro elemento corresponde ao primeiro folder, o segundo elemento ao segundo folder, e assim sucessivamente. No exemplo proposto, foram colocados apenas um objeto tSay() dentro de cada Folder, para demonstrar sua funcionalidade. Lembrando que, como cada folder ué um container de elementos, as coordenadas dos componentes dentro de um folder partem do ponto esquerdo superior 0,0 de dentro do folder.

@ … MSPANEL

O comando @ MSPANEL permite criar um componente de interface da classe tPanel(), com a finalidade de servir de container para outros componentes de iterface. O Painel pode ter um efeito de destaque em suas bordas ( RAISED / LOWERED ), e outras propriedades de alinhamento que serão vistas com maior profundidade posteriormente. No fonte de exemplo, dentro do painel foi criado apenas um objeto tSay() para demonstração.

@ … RADIO

O comando @ RADIO utiliza internamente a classe TRadMenu(), e têm um funcionamento parecido com o componente LISTBOX, para múltipla escolha a partir de uma lista de possibilidades pré-definida. Porém, visualmente, a opção escolhida é marcada com um “Bullet” à esquerda do texto. Ao marcarmos uma nova opção, a opção anteriormente marcada é imediatamente desmarcada. E, a variável associada ao componente recebe um valor numérico, correspondente a opção marcada.

@ … SCROLLBOX

O comando @ SCROLLBOX permite criar um componente de interface da classe tScrollBox(), com a finalidade de desenhar um container com barra de rolagem horizontal ou vertical, para comportar outros componentes de interface. No nosso exemplo, reservamos uma área do diálogo para este componente, e desenhamos dentro dele dois tSay(), um no início da área util, visível na construção do componente, e um segundo tSay() em uma coordenada dentro do componente, onde ele somente será visivel caso seja utilizada a barra de rolagem do componente.

@ … METER

O comando @ METER cria uma barra horizontal de progresso, utilizando um objeto da classe tMeter(). Este objeto permite que seja setado um valor total de passos, correspondendo a 100 % da barra preenchida, e permite que durante a execução da aplicação, conforme o programa seja executado, o valor da posição atual na barra de progresso seja setado.

DEFINE BUTONBAR

Para uma caixa de diálogo, podemos criar uma barra de botões, alinhada no rodapé, no topo, ou às margens da caixa de diálogo. A barra de botões é uma instância da classe TBar(), que deve apenas conter botões com imagens, instâncias da classe TBtnBmp().

DEFINE BUTTON … BUTTONBAR

Na barra de botões criada pelo comando DEFINE BUTTONBAR, criamos botões dentro da barra usando este comando. Cada botão deve usar uma imagem, que pode estar compilada no repositório de objetos ou estar em um arquivo no disco. No exemplo proposto, foram usados algumas imagens disponíveis no repositório de objetos do ERP Microsiga versão 11.

@ … BTNBMP

Este comando cria um botão muito similar ao botão de uma barra de botões, para ser colocado livremente na interface, fora da barra de botões. Internamente, este componente usa a classe tBtnBmp2()

DEFINE SBUTTON

Existem 23 tipos de botões com imagem pré-definidos no AdvPL, ainda utilizados em fontes customizados de versões anteriores ao Protheus. Cada botão possui um identificador de tipo, onde para cada tipo uma imagem diferente é associada ao botão.

DEFINE TIMER

A classe tTimer() permite o disparo automático de um CodeBlock no servidor, a partir de uma caixa de diálogo ou janela, quando o controle de execução estiver no SmartClient,

Outros componentes

Ainda existem diversos componentes gráficos, desde a criação de gráficos até plotagem livre de interface (desenhar a interface), menus, árvores e afins. A utilização da Interface no AdvPL é um tema bem extenso, ainda estou estudando como desmembrá-lo nos próximos posts deste tema.

Todas as classes visuais do AdvPL estão documentadas na TDN, utilizadas na forma “direta”, isto é, através das chamadas explícitas de seus construtores, contendo os métodos e propriedades disponíveis para uso, esta documentação está disponível no link <>

Aspectos gerais do exemplo

Cada componente de entrada de dados normalmente está associado a uma variável local, que é atualizada pelo componente no momento que um novo valor é informado, e ocorre a perda de foco do componente, para foco em um próximo componente ativo na interface. Durante a digitação de um GET de uma variável caractere, numérica ou data, o valor da variável não é atualizado enquanto o valor está sendo digitado. Normalmente usamos a tecla para dar foco ao próximo componente ativo da interface, ou a tecla . E, para dar foco no componente anterior, usamos a combinação de teclas +. Podemos também trocar o foco com o mouse, simplesmente clicando em outro componente ativo para este ganhar foco.

Como uma caixa de diálogo, por default aceita a tecla como instrução de fechamento, esta caracteristica foi desligada, setando um valor .F. na propriedade lEscClose do objeto tDialog() para Falso (.F.). Ainda no exemplo proposto, é utilizado um objeto tTimer() para disparar um CodeBlock no servidor a cada 1 segundo, para incremento e atualização de uma barra de progresso (tMeter), onde foram definidos 1000 passos, e a cada atualização do timer, a posição atual do indicador de progresso é incrementado em uma unidade. Existe uma optimização interna no método de setagem de nova posição, que somente realiza o disparo de uma atualização de posição para a interface caso a nova posição na barra de progresso caso ela seja pelo menos uns 5% maior da última posição enviada ao SmartClient.

Também foi especificado um CodeBlock, para ser executado no momento do fechamento do diálogo. Caso você pressione + ou clique no “X” para fechar a caixa de diálogo, o CodeBlock é executado, e caso o retorno deste seja .T., o diálogo é fechado, caso contrário ele permanece aberto. Isto também vale caso você crie um botão com a chamada do método End() do objeto tDialog().

Em várias ações dos botões de interface, foram utilizadas as funçoes MsgInfo(), para mostrar uma caixa de diálogo com uma mensagem de texto, e a função GetClassName(), informando uma variável contendo um objeto da Interface. A função GetClassName() retorna uma string com o nome da classe pai deste objeto, e este retorno é mostrado na tela pela MsgInfo().

A troca de foco dos componentes quando navegamos por TAB ou SHIFT+TAB obedece a ordem de criação dos componentes. Logo, ao diagramar duas colunas de componentes de entrada de dados, de acordo com a ordem de criação você pode fazer o foco ir primeiro da esquerda para a direita, e então de cima para baixo, ou o contrário — primeiro o foco pega todos os componentes do lado esquerdo, de cima para baixo, para então cobrir os componentes da direita, de cima para baixo.

A interface no ERP Microsiga

Por baixo do Framework AdvPL do ERP Microsiga, são usados todos os componentes básicos da linguagem AdvPL deste exemplo, e os outros componentes ainda não abordados. Vários componentes de interface disponibilizados pelo FrameWork AdvPL do ERP Microsiga são heranças dos componentes básicos, e outros são componentes de mais alto nível, a partir de agrupamentos destes componentes. O MVC, por exemplo, permite o desenvolvimento de uma aplicação em AdvPL de um modo mais assistido, sem que você tenha que se preocupar com coordenadas de tela, as funções e classes por trás da implementação cuidam destes detalhes.

Normalmente usamos objetos tDialog() para montar interfaces complementares no ERP Microsiga, disparadas por pontos de entrada. Basicamente, um ponto de chamada é uma chamada de funções de usuário com nomes específicos em pontos determinados das rotinas do sistema padrão, para inferir modificações ou complementos ao seu comportamento. Quando queremos criar uma aplicação customizada no ERP, para ser chamada direto pelo SmartClient, e não como uma opção de Menu do ERP, utilizamos uma tWindow(), que permite controles adicionais.

Algumas boas práticas

Uma boa prática para a diagramação da interface, é agrupar os componentes que pertencem a uma determinada funcionalidade dentro de painéis, para alinharmos os componentes dentro do painel. Assim, caso seja necessário mover um grupo de componentes de lugar, como por exemplo em uma tela com uma barra vertical de botões auxiliares e um grupo de componentes de entrada de dados, caso seja necessário mudar a barra de botões do lado esquerdo para o direito da tela, apenas trocamos as coordenadas dos painéis, caso contrário teríamos que mexer no alinhamento de todos os componentes.

Combinando componentes

Como existem vários componentes que servem como container para outros componentes, caso você por exemplo precise que dentro de um dos seus folders caiba mais informações do que seu tamanho permite exibir, você pode criar um SCROLLBOX sem borda do tamanho do seu folder, e criar os componentes a partir do SCROLLBOX.

Conclusão

Longo é o caminho do desenvolvedor, cheio de possibilidades, mas proporcionalmente aprazível é ver a satisfação de seus clientes com um trabalho bem feito, duradouro, e construído de forma a atender a necessidade, projetado desde o alicerce para ser acessível, escalável e flexível.

Temos muitos posts pela frente, e é uma grande satisfação tê-los como leitores do “Tudo em AdvPL” ! Até o próximo post, pessoal 😉

Anúncios

Interface Visual do AdvPL – Parte 01

Introdução

A linguagem AdvPL permite a criação de programas com interface visual, com campos de entrada de dados, botões, labels, scroll (barra de rolagem), painéis e outros componentes visuais. Quando iniciamos a execução de uma aplicação através do aplicativo Smartclient, ele estabelece uma conexão TCP com o TOTVS Application Server, que faz a carga do programa solicitado do repositório de objetos do ambiente, e executa a aplicação AdvPL no servidor. Porém, todas as instruções de interface são enviadas ao SmartClient.

Introdução a interface visual

Primeiro, precisamos criar um componente principal de interface, digamos assim, o “pai de todos” os demais componentes. Normalmente criamos um objeto tWindow() ou tDialog() para isso. Os demais componentes visuais são criados, informando qual é o objeto do componente pai de cada um. No momento da criação de cada componente na memória do Application Server, durante a execução do programa AdvPL, é enviada uma mensagem do Application Server para o Smartclient, para a criação do respectivo componente na interface. Quando o programa AdvPL termina de criar os componentes, ele torna o onjeto pai dos componentes ativo, e isto transfere o controle de execução da aplicação para o SmartClient.

Assim, quando o componente pai é ativado, o SmartClient vai desenhar a tela e todos os componentes nela posicionados, e a execução do programa AdvPL permanece dentro do método de ativação do componente visual, aguardando que o usuário ou operador da aplicação executa alguma ação na interface, como por exemplo preencher um campo ou pressionar um botão de ação na interface.

Exemplo 01

Em outro tópico anterior, acredito que seja na orientação a objetos com herança de classe visual AdvPL, foi colocado um exemplo bem simples de uma caixa de díalogo, onde um dos botões da interface era criado a partir de uma classe compilada no repositório de objetos que herdava uma classe básica do objeto tButton da linguagem Advpl. O primeiro programa de “Hello World” de interfave será criado dentro desse molde, com uma mensagem estática na tela (tSay), e um botão para finalizar o objeto de interface.

#include 'protheus.ch'
User Function APPINT01()
Local oDlg
Local oBtn1, oSay1
 
DEFINE DIALOG oDlg TITLE "Exemplo" FROM 0,0 TO 150,300 COLOR CLR_BLACK,CLR_WHITE PIXEL
@ 25,05 SAY oSay1 PROMPT "Apenas uma mensagem" SIZE 60,12 OF oDlg PIXEL 
 
@ 50,05 BUTTON oBtn1 PROMPT 'Sair' ACTION ( oDlg:End() ) SIZE 40, 013 OF oDlg PIXEL
ACTIVATE DIALOG oDlg CENTER
Return

O resultado visual do programa acima, quando executado diretamente pelo SmartClient, deve ser este aqui:

appint01

Sistema de coordenadas

Vamos reparar em alguns pontos importantes do fonte acima. Primeiro, o componente principal da interface (pai de todos) é um objeto de diálogo, que eu queria que tivesse uma área útil interna de 300 pontos de comprimento por 150 de largura. Para isso, o diálogo foi criado através do comando DEFINE DIALOG, onde eu especifiquei as coordenadas 0,0 como ponto inicial (linha superior, coluna esquerda da tela), e as coordenadas finais 150,300 (linha inferior, coluna direita da tela). Para que no momento da ativação, este componente fosse centralizado na tela, eu usei o parâmetro “CENTER” na ativação do diálogo, feita pelo comando ACTIVATE DIALOG.

A criação do diálogo na prática chamou o construtor do objeto tDialog(), armazenando o objeto do componente criado na variável oDlg. E, a ativação do diálogo chamou o método Activate() do objeto tDialog(). E, como eu especifiquei um objeto de 300 x 150 pontos ( 300 pontos de comprimento por 150 de altura), o componente após montado vai ocupar um espaço maior que este, pois estes valores são interpretados como sendo a área interna útil em pontos (ou PIXELs) do componente.

Dentro deste tDialog(), eu coloquei um componente tSay(), para mostrar um texto, nas coordenadas 20,05 (linha,coluna), com um tamanho 60,12 (comprimento,altura). E, um pouco mais para baixo, nas coordenadas 50,05 (linha,coluna) eu coloquei um botão com tamanho 40,13 (comprimento,altura) com a ação de encerramento da interface principal — chamada do método End() do objeto tDialog().

Existem duas particularidades importantes sobre o sistema de coordenadas de componentes da interface. Primeiro, as coordenadas de um componente de interface s]ao sempre relativas às coordenadas 0,0 ( linha 0, coluna 0 ) da área útil do componente pai. Portanto, não importa onde o objeto tDialog() seja desenhado na tela, o objeto tSay() vai estar na coordenada 5,5 a partir da coordenada 0,0 de dentro da tDialog().

A segunda particularidade sobre o sistema de coordenadas de componentes é: As coordenadas informadas consideram o dobro da área útil em PIXELS do componente. Reparem que o componente tSay() foi criado na coordenada 25,05 (teoricamente 25 pixels pra baixo do ponto 0,0 de dentro da tDialog()), e o componente tButton() foi criado a partir da coordenada 50,05 (também teoricamente 50 pixels para baixo do ponto 0,0) da tDialog(). Se reparamos que a área interna do objeto tDialog() possui 150 pixels de altura, e o programa de testes cria componentes até no máximo a linha 50, o programa de exemplo deveria ocupar perto de 1/3 da altura do tDialog().

Porém, ao verificarmos o resultado em tela do programa, quase não sobrou nada na parte de baixo da tDialog(). Isso aconteceu devido as coordenadas de tela dos componentes, internamente, são multiplicadas por 2, tanto linha como coluna. Logo, quando informamos que o componente tSay() deve ser colocado na coordenada 20,05 , na verdade ele será desenhado a partir da coordenada 40,10 (40 pixels pra baixo, 10 pixels para a direita).

Cada componente de interface têm a sua coordenada de início no canto superior esquerdo. Logo, o botão desenhado a partir da coordenada 20,05 e com tamanho de 60,12 vai ser desenhado dentro da tDialog a partir da coordenada 40,10 , e vai terminar na coordenada 52,70 — basta somar as coordenadas de inicio “reais” com o comprimento e altura informados ao objeto.

Se eu não me engano, este comportamento do sistema de coordenadas foi mantido na linguagem AdvPL devido a código legado, que utilizava uma API que permitia utilizar os componentes de interface das primeiras versões do Windows.

Formas de escrever o fonte

Existem duas formas de endereçar os objetos da interface visual no AdvPL. Isto pode ser feito diretamente através dos construtores e métodos dos objetos de interface, documentados na TDN a partir do link (), ou através de comandos diretos — como foi feito no exemplo acima.

A utilização de comandos deixa o fonte mais compreensível, pois os objetos de interface possuem muitos parâmetros opcionais, e quando utilizamos a nomenclatura por comandos, nós somente endereçamos o que realmente é necessário ser informado no objeto. A leitura do código fica muito mais fácil. Porém, os comandos de interface ainda não foram oficialmente documentados.

Exemplo 02

O fonte do primeiro exemplo, escrito para utilizar diretamente os construtores e métodos dos componentes, ficaria da seguinte forma:

User Function APPINT02() 
Local oDlg
Local oBtn1, oSay1
 
oDlg = TDialog():New( 0, 0, 150, 300, "Exemplo" ,,,,, CLR_BLACK,CLR_WHITE ,,, .T. )
oSay1 := TSay():New( 25, 05, {|| "Apenas uma mensagem"} ,oDlg,,,,,,.T.,,, 60, 12 )
oBtn1 := TButton():New( 50, 05, "Sair", oDlg,{|| oDlg:End() }, 40, 013,,,,.T. )
oDlg:Activate( , , , .T. )
Return

Execução do código

Como eu havia mencionado anteriormente, no momento que ocorre a ativação do tDialog(), o TOTVS Application Server permanece na execução do método activate, aguardando por uma interação do usuário ou operador do aplicativo, para disparar alguma ação na interface, como por exemplo fechar a aplicação ou pressionar um botão.

No nosso exemplo, temos um botão que dispara uma ação no momento que o mesmo é acionado. Logo, ao clicarmos no botão, o SmartClient informa ao TOTVS Appplication Server que um botão foi pressionado, e o Application Server executa o bloco de código (ou CodeBlock) relacionado à ação de clique deste componente.

Esta ação pode chamar uma outra rotina, que cria e ativa uma nova caixa de dialogo, com outros componentes e botões, onde uma vez que esta caixa de diálogo esteja ativa, não é possível clicar em nenhum componente da caixa de diálogo anterior, que automaticamente deixa de receber foco com a ativação de uma nova tDialog.

Logo, qualquer comando ou função colocada dentro de um fonte, após o Activate() de uma tWindow() ou tDialog(), somente será executado quando esta janela ou diálogo for finalizado.

Capacidades da Interface

Através da linguagem AdvPL e aplicações sendo executadas através do SmartClient, podemos executar instruções de acesso a disco e arquivos (File I/O), portas e impressoras na estação onde o SmartClient está sendo executado. Podemos copiar arquivos da máquina onde está sendo executado o SmartClient para dentro de pastas a partir do rootpath (ou diretório raiz do ambiente), e vice-versa.

Restrições da Interface

Uma aplicação AdvPL não pode criar mais de uma tWindow(). Este objeto é “Highlander” (só pode haver um). A interface do ERP Microsiga conhecida por “SIGAMDI” cria apenas uma tWindow, e por baixo utiliza outros objetos para containers de seções independentes, onde cada seção ( ou “aba” ) da MDI pode ser executada em um servidor de aplicação diferente, onde existe um componente pai das abas de MDI, e cada componente filho é um container para a execução de uma aplicação em um contexto de conexão independente com o TOTVS Application Server, esses detalhes serão abortados em um outro post.

Existem componentes visuais que podem ser containers de outros componentes, para fins de alinhamento por exemplo, e existem componente não visuais ligados diretamente a interface, como também existem componentes de interface que não apresentam nada na interface ( tTimer() por exemplo). Como regra geral, um componente final de interface somente pode ter como componente pai um container. Caso isto não seja feito da forma correta, na melhor das hipóteses o componente apenas não será mostrado na interface, e na pior das hipóteses, pode ocorrer um erro de acesso inválido de memória ( Access Violation ou Segment Faut) no TOTVS Application Server, ou mesmo no próprio SmartClient.

Conclusão

Existem mais dicas e restrições, que vão aparecer no decorrer da abordagem deste assunto, e estes serão elucidadas e esclarecidas nos respectivos tópicos. Acho que para uma abordagem “inicial”, já temos bastante informações. Todos os componentes visuais do AdvPL estão documentados na TDN, existem recursos desde agrupamentos de componentes, painéis, pastas, grid, menus pop-up, drop-down, seleção múltipla, até um mini-editor de textos e um painel de desenho. Como eu disse no primeiro post desse blog, com AdvPL você pode fazer muita coisa, inclusive um ERP !!!

Nos próximos posts sobre a interface Visual do AdvPL, serão abordados exemplos usando alguns dos componentes principais e suas características, desde a utilização em programas de entrada de dados, e alguns exemplos um pouco mais rebuscados ! Vai ser divertido, eu garanto !

Até o próximo post, pessoal 😉

Funções recursivas em AdvPL

Hoje vamos abordar o conceito de funções recursivas, com foco na linguagem AdvPL, com alguns exemplos de uso, e para cada exemplo será abordada uma alternativa para não usar recursividade. Trata-se de um artigo técnico, que explora um recurso abordado em programação de nível avançado.

Definição

Uma função ou rotina é classificada como recursiva, quando esta rotina realiza uma ou mais chamadas para ela mesma, de dentro do seu próprio código.

Exemplo 1 – Fatorial

Acho que o exemplo mais simples e mais utilizado para exemplificar uma função recursiva é o algoritmo para cálculo fatorial de um número. Vamos escrever esta rotina em AdvPL:

User Function TesteFat()
msginfo(Fatorial(4) , "Resultado do Fatorial")
Return
STATIC Function Fatorial( nNum )
Local nResult
If nNum > 1
  nResult := nNum * Fatorial( nNum - 1 ) 
Else
  nResult := 1
Endif
Return nResult

Se tudo der certo, a execução da função U_TESTEFAT deve mostrar uma mensagem contendo o número 24 ( 4! = 4x3x2x1 = 24 )

Reparem no corpo da função Fatorial(), caso o número recebido como parâmetro seja maior que um, o retorno da função será o número recebido como parâmetro multiplicado pelo retorno da própria função Fatorial(), chamada com o número recebido subtraído de uma unidade. Quando a função receber um valor que não é maior que 1, ela retorna 1.

Neste caso, ao informarmos o valor 4, a função chama ela mesma passando o valor 3, que por sua vez chama ela mesma novamente passando o valor 2, que por sua vez chama ela mesma com o valor 1, e neste ultimo caso a função retorna 1, que será multiplicado por 2 e retornado ( 2 ) , e este retorno multiplicado por 3 e retornado ( 6 ), e este por sua vez será multiplicado por 4 e retornado ( 24 ).

A utilização de recursão normalmente está sujeito ao limite de pilha de chamada de funções da linguagem em uso. No caso do AdvPL, o limite de empilhamento — ou “stack” — é de 100 funções. Logo, uma recursão não poderá ter mais de 99 niveis.

Agora, existem formas de se evitar a recursão. Normalmente é possível “planificar” o algoritmo, para não precisar usar recursão. Por exemplo, vou escrever uma segunda versão da função Fatorial, sem o uso de recursão:

STATIC Function Fatorial2( nNumero )
Local nRetorno := nNumero
Local nI
For nI := nNumero - 1 to 1 STEP -1
  nRetorno := nRetorno * nNumero 
Next
Return nRetorno

Usando a função acima, o número recebido como parâmetro é atribuído diretamente ao resultado, então o programa entra em um loop de processamento, contando a partir do numero anterior até 1, multiplicando o retorno pelo número da variável do loop, fazendo exatamente o cálculo fatorial, sem usar recursão. Ao eliminar a recursão, além de evitar o risco de estouro na pilha de chamada de funções da linguagem, economizamos vários ciclos de CPU gastos com a manutenção da pilha de chamadas de funções.

Exemplo 2 – Varrer uma árvore

Existem outros algoritmos que normalmente utilizam recursão, como por exemplo um algoritmo para varrer uma estrutura de árvore. Por exemplo, fazer uma soma de todos os arquivos a partir de um diretório e seus sub-diretórios. Em uma função recursiva, cada diretório encontrado em um nível chama a própria função informando o subdiretório encontrado, que por sua vez chamará a função novamente caso dentro deste diretório existam outros subdiretórios. Vamos ao exemplo:

User Function TesteDir()
MsgInfo( TamDir('\') , 'Tamanho do RootPath' )
Return
STATIC Function TamDir( cPath )
Local aFiles := Directory(cPath+'*.*')
Local aSubDir := Directory(cPath+'*.*','D')
Local nI
Local nSize := 0
// Acumula o tamanho dos arquivos desta pasta
For nI := 1 to len(aFiles)
  nSize += aFiles[nI][2]
Next
// Acumula o tamanho das pastas a partir desta, caso existam
// Ignora os diretorios "." ( atual ) e ".." ( anterior ) 
For nI := 1 to len(aSubDir)
  If aSubDir[nI][1] != "." .and. aSubDir[nI][1] != ".."
    nSize += TamDir( cPath + aSubDir[nI][1] + '\' )
  Endif
Next
Return nSize

Feito da forma acima, primeiro a função contabiliza o tamanho dos arquivos da pasta informada como parâmetro, para depois contabilizar o tamanho das sub-pastas, recursivamente. Agora, vamos escrever a mesma função sem recursão:

STATIC Function TamDir2( cPath )
Local aFiles := {}
Local aSubDir := {}
Local aVerify := {}
Local nI
Local nSize := 0
// Acrescenta a pasta atual como pendencia
aadd( aVerify , cPath )
While len(aVerify) > 0
 
  // Loop de processamento, enquanto houver subdiretorios
  // pendentes a serem verificados
 
  // Pega o path da ultima pendencia
  cPath := aTail(aVerify)
 
  // remove a ultima pendencia encolhendo o array
  aSize( aVerify , len(aVerify)-1 )
 
  // Identifica os arquivos da pasta
  aFiles := Directory(cPath+'*.*')
 
  // Acumula o tamanho dos arquivos desta pasta
  For nI := 1 to len(aFiles)
    nSize += aFiles[nI][2]
  Next
 
   // Verifica os sub-diretorios a partir desta pasta
   aSubDir := Directory(cPath+'*.*','D')
 
   // Acrescenta os sub-diretorios como pendencias
   For nI := 1 to len(aSubDir)
     If aSubDir[nI][1] != "." .and. aSubDir[nI][1] != ".."
       aAdd( aVerify , cPath + aSubDir[nI][1] + '\' )
     Endif
   Next
 
Enddo
Return nSize

Sem a recursão, criamos um array de pendências, onde cada pendência é um path a ser verificado e contabilizado. A primeira pendência é o próprio diretório recebido como parâmetro. O loop principal de processamento não é finalizado enquanto houverem pendências. A cada interação, a última pendência é removida. Os arquivos contidos no path em processamento é verificado, e caso dentro dele exista qualquer sub-diretório, estes serão acrescentados como novas pendências. Assim, conseguimos varrer toda a estrutura de diretórios e sub-diretórios sem usar recursão.

Finalizando

Na verdade, ao deixarmos de usar recursividade, passamos a trabalhar com um loop de iterações. Os problemas de cálculo fatorial e varrer todos os elementos de uma árvore são naturalmente recursivos, já que é necessário ser mantido registro de estados anteriores durante sua execução. Em ambas as abordagens, devemos tomar cuidado para evitar um cenário de loop infinito, principalmente na análise de elementos de uma árvore. Caso uma estrutura em árvore contenha um nó filho que aponte para um nó pai — situação conhecida como referência circular — tanto usando recursão como iteração, caso não exista uma proteção para evitar analisar um nó da árvore já processado, a aplicação é finalizada com uma ocorrência de estouro de pilha — stack — quando usada recursão, ou acaba entrando em loop infinito – quando utilizada iteração.

Imagine uma visão de estrutura organizacional de uma empresa, onde cadastramos todos os departamentos, e depois amarrações hierárquicas dentro de uma visão. Caso erradamente seja criada uma referência circular neste cadastro, e você chame uma rotina de processamento que varre a árvore de departamentos para registrar um acumulador por departamento seguindo esta árvore, se não houver uma proteção ou validação durante a execução ou no cadastro, fatalmente você terá problemas dessa natureza.

Até o próximo post, pessoal 😉

Referências

FATORIAL. In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2014. Disponível em: <http://pt.wikipedia.org/w/index.php?title=Fatorial&oldid=38597266>. Acesso em: 14 jan. 2015.

DIRECTORY, in TDN – Totvs Development Network. Disponível em: <http://tdn.totvs.com/display/tec/Directory>. Acesso em: 14 jan. 2015.

Recursividade, in DEI – Departamento Engenharia Informática. Disponível em: <http://academy.dei.uc.pt/page/programacao/progAvancado/recursividade>. Acesso em: 14 jan. 2015.

RECURSIVIDADE (CIÊNCIA DA COMPUTAÇÃO). In: WIKIPÉDIA, a enciclopédia livre. Flórida: Wikimedia Foundation, 2014. Disponível em: <http://pt.wikipedia.org/w/index.php?title=Recursividade_(ci%C3%AAncia_da_computa%C3%A7%C3%A3o)&oldid=38973260>. Acesso em: 14 jan. 2015.

Desmistificando a análise de sistemas

Prefácio

Nada como uma linda manhã de Domingo para estar inspirado e escrever um novo artigo. O título foi a ultima coisa a ser definida. Terminado mais um ano, em 04/01/2015 completei mais um aniversário na Totvs, são 16 anos completos, com muitos desafios, sempre vistos como oportunidade de crescimento, e muitas realizações, cada uma com um sabor especial. Agora estou curtindo alguns dias de férias, recarregando as baterias para entrar no 17o ano, com mais desafios, aprendizados e compartilhamento de experiências, buscando novas realizações.

Hoje o post não têm códigos de máquina, nenhum programa. Em um post anterior, “Desmistificando a programação”, eu tentei em algumas palavras mostrar que programar um computador é mais simples do que parece a primeira vista. O post de hoje complementa o artigo inicial, e estende a sua abrangência até a análise de sistemas. Novamente, afirmo que isto não é um bicho de 7 cabeças 🙂

Introdução

No último século, nunca houve tanta evolução em comparação com qualquer outro período da história da humanidade. Não me refiro apenas a informática, computação ou tecnologia da informação, mas em todas as áreas. A conquista do espaço, os avanços da medicina, o estudo do genoma humano, erradicação de algumas doenças, globalização, a revolução industrial, muita coisa para colocar em apenas um parágrafo.

O uso da eletricidade e as pesquisas realizadas para descobrir mais formas de utilizá-la levaram a um avanço desproporcional da tecnologia da informação e a outras ferramentas e recursos utilizados em todas as áreas. Pra ser um pouco mais específico, após a descoberta de materiais semicondutores, capazes de modular corrente elétrica em baixa voltagem e com tamanho cada vez mais reduzido, foram criados os mais diversos tipos de equipamentos. E estes evoluiram assustadoramente nos ultimos 50 anos.

Computadores

Um computador que ocupava um andar de um prédio, foi reduzido até ficar do tamanho de um fogão doméstico, e foi crucial para completar com sucesso a primeira missão tripulada para o espaço, que conseguiu sair da órbita da Terra, chegar até a lua, descer no asteroide, voltar novamente ao espaço e retornar em segurança de volta à Terra. Outro dia assisti a um documentário da History Channel que abordou os detalhes dessa proeza, inclusive entrevistou algumas pessoas ainda vivas atualmente, que participaram direta ou indiretamente neste projeto.

Cada equipe envolvida tinha desafios inéditos e realmente difíceis para fazer o projeto dar certo. Desafios de concepção, testes, validações e integrações, um universo de possibilidades a ser avaliado, muitas das soluções encontradas tiveram que ser re-feitas várias vezes, até que fosse encontrada uma alternativa funcional, e torná-la viável. Mas todas elas tinham um ponto em comum: Foi estabelecido um alvo, uma meta a ser realizada. E, a partir dela, quais as etapas necessárias para que esta meta fosse atingida. Primeiro definimos o que queremos como resultado, para então trabalhar em como fazer este resultado ser atingido.

Cozinhar arroz

E esta linha de raciocínio aplica-se praticamente a toda e qualquer realização, para qualquer objetivo. Partindo de um exemplo simples, vamos determinar um objetivo: Eu quero arroz no almoço de hoje. Detalhando um pouco mais o objetivo, deve ser arroz branco comum, não parbolilizado ou integral, apenas arroz tipo 1, e é para o almoço de hoje, e necessariamente eu quero cozinhar esse arroz. Para fazer esta tarefa, eu preciso enumerar os insumos necessários para cozinhar arroz, e as etapas necessárias para uso destes insumos, para no final da execução de todas as etapas, meu resultado seja “arroz cozido”.

Logo, vou precisar pelo menos de um recipiente de preparo (panela), uma fonte de calor (fogão), acessórios de preparo (copo para medida, uma colher), e é claro, vou precisar de arroz, água e temperos. E, na lista de tarefas para preparar o arroz, eu preciso definir todas as etapas de preparo, como lavar o arroz, ferver água, misturar o arroz com os temperos, colocar para cozinhar, etc. São todos procedimentos simples, mas é necessário saber cada um deles e executá-los corretamente para que o resultado seja arroz branco soltinho e apetitoso.

E, como todo o problema simples esconde um universo de problemas menores, qualquer etapa trocada ou mal executada interfere no resultado. Se a quantidade de água não for correta, ou o arroz fica cru ou fica “empapado”, se a panela não for tirada do fogo na hora certa o arroz fica cru ou queima no fundo. E, se faltar algum insumo ou algum recurso usado para fazer o arroz apresentar problema, você nem chega perto do resultado… Por exemplo, o fogão quebrou durante o cozimento, ou acabou o gás, ou não têm arroz no armário e o mercado está fechado.

Da cozinha pro teclado

Qualquer semelhança entre fazer arroz, escovar os dentes, e projetar um sistema informatizado não é mera coincidência. Tirando o uso final do resultado (você não vai colocar o sistema em um prato e almoçá-lo), as etapas básicas são as mesmas de qualquer atividade. O que fatalmente muda é a quantidade de etapas e os tipos de insumos aplicados.

Todas estas tarefas têm em comum a necessidade de levantar o que precisa ser feito, como deve ser feito, e se todos os insumos necessários existem, estão disponíveis e em condições de uso, e podem envolver um plano de contingência para cobrir as maiores possibilidades de fracasso da tarefa. Se o arroz der errado, é bom você ter um miojo guardado no armário, para não ficar com fome.

Projetando, analisando e desenvolvendo

Na construção de um um sistema informatizado, ou apenas no desenvolvimento de uma funcionalidade de um sistema, primeiro você precisa saber exatamente qual é o resultado esperado, depois avaliar as possibilidades de obter este resultado, verificar se a possibilidade escolhida é aplicável, analisar e aplicar a possibilidade escolhida. Dado o número de varíaveis, procedimentos, fluxo da informação e componentes deste processo, a atividade de análise e desenvolvimento de sistemas pode parecer num primeiro momento muito complexo, mas cada etapa e cada necessidade pode ser dividida em fases e etapas menores, onde algumas etapas podem ser feitas ao mesmo tempo, e quando se tem mais de uma pessoa no mesmo projeto, algumas etapas podem ser paralelizadas.

Esta lógica é praticamente instintiva, nós aplicamos ela sem perecber em muitas atividades corriqueiras do nosso cotidiano. Sim, a tal da lógica de programação, é tão simples quanto isso. Para escovar os dentes, precisamos conhecer as etapas do processo, experimentar o processo, fazer alguns ajustes na forma de execução ou na sequência ou no tempo de cada etapa, após algumas tentativas, a execução do processo torna-se tão natural, que nós nem precisamos mais pensar nas etapas, elas são realizadas naturalmente. Nós não nascemos sabendo escovar os dentes, nós aprendemos isso. Em um determinado momento, nós apredemos o que é a escova, pra que serve, qual é a maneira certa de segurar a escova, o que precisamos para usá-la, e como usá-la da melhor forma. Não é nada diferente das etapas de análise e desenvolvimento, ou aprendizagem e uso de uma linguagem de programação. O que pode tornar este aprendizado naturalmente mais difícil é a quantidade de elementos envolvidos e a quantidade de propriedades destes elementos.

Conclusão

Desde o princípio da informática, um bit têm apenas dois valores lógicos: 0 (desligado) ou 1 (ligado), e a utilização de uma corrente elétrica para a representação binária em um ponto de um circuito é este ponto estar energizado (1) ou não estar energizado (0). Os conceitos de processo, sistemas operacionais, interface, memórias (temporaria e persistente) não mudou desde a sua origem. Tudo o que temos hoje é resultado da evolução deste model-base. Conhecendo bem a base deste modelo, definitivamente torma mais fácil entender o que ele é e como ele funciona.

A cada novo avanço da tecnologia da informação, a utilização de aplicações em computadores têm se tornado cada vez mais intuitiva, você não precisa aprender ou endender muito como o seu celular funciona por dentro, para ser capaz de fazer uma ligação, outirar uma foto e publicá-la no Facebook, ou para jogar nele. Então, cada vez menos pessoas sabem o que acontece por trás da tela do computador, celular ou tablet. Muitos cursos de informática ensinam como você opera algumas aplicações dentro de um sistema operacional, ou mesmo como operar um sistema operacional, mas poucos entram nos méritos de como isso acontece por dentro do equipamento.

Se você vai ser um operador da máquina ou de seus aplicativos, você não precisa efetivamente saber disso em maior profundidade, mas para programar um equipamento, é necessário aprofundar-se um pouco mais, e isto realmente abre seus horizontes e a sua forma de enxergar um sistema informatizado, e eu garanto que absorver este conheicmento faz muita diferença. Normalmente você estuda estes aspectos em maior profundidade fazendo um curso superior de tecnologia em análise e desenvolvimento de sistemas, e em maior profundidade em ciência da computação.

Quanto mais você conhece como o equipamento funciona, e quais são as melhores práticas de como utilizá-lo, as escolhas das alternativas para a solução que você busca tornam-se mais fáceis. Um desenvolvedor de sistemas deve gostar de aprender e não ter medo de desafios, pois cada novo desafio envolve novos aprendizados e extensões do seu conhecimento.

Até o próximo post, pessoal 😉