tudo em advpl _ compartilhando experiências de análise, programação e desenvolvimento

58
14/03/2015 Tudo em ADVPl | Compartilhando experiências de análise, programação e desenvolvimento. https://siga0984.wordpress.com/ 1/58 T udo em ADVPl Compartilhando experiências de análise, programação e desenvolvimento. I magens no SGDB via DBAccess (https://siga0984.wordpress.com/2015/03/09/im agens-no-sgdb-via-dbaccess/) 09/03/2015 09/03/2015 Siga0984 ADVPL , Classes , DBAcces , Exemplos , Imagens , Orientação a Objetos Introdução Recebi um e-mail, ou mensagem, ou post (não lembro agora) com uma sugestão interessante, para este tema (Imagens no SGDB) fosse abordado aqui no Blog. E, é totalmente possível de ser feito, de forma relativamente simples. Imagens, por dentro Um arquivo em disco que contém uma imagem pode ser salvo em diversos formatos: BMP (Bitmap Image File), JPEG (Joint Photographic Experts Group), PNG (Portable Network Graphics), entre outros. Cada formato consiste em uma especificação para a representação binária de uma imagem. Trocando em miúdos, um arquivo de imagem contém um determinado número de bytes, usando códigos ASCII de 0 a 255 (conteúdo binário), que são interpretados por uma aplicação capaz de mostrar seu conteúdo em uma interface. Cada tipo de imagem possui algumas características, como resolução, compressão, mas por dentro são apenas uma sequência de bytes. Imagens no SGDB

Upload: robson-cavalcanti

Post on 10-Nov-2015

178 views

Category:

Documents


5 download

DESCRIPTION

Tudo em ADVPL

TRANSCRIPT

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 1/58

    Tudo em ADVPl

    Compartilhando experincias de anlise, programaoe desenvolvimento.

    Imagens no SGDB via DBAccess(https://siga0984.wordpress.com/2015/03/09/imagens-no-sgdb-via-dbaccess/)

    09/03/201509/03/2015 Siga0984 ADVPL, Classes, DBAcces, Exemplos, Imagens,Orientao a Objetos

    Introduo

    Recebi um e-mail, ou mensagem, ou post (no lembro agora) com uma sugesto interessante, paraeste tema (Imagens no SGDB) fosse abordado aqui no Blog. E, totalmente possvel de ser feito, de

    forma relativamente simples.

    Imagens, por dentro

    Um arquivo em disco que contm uma imagem pode ser salvo em diversos formatos: BMP (BitmapImage File), JPEG (Joint Photographic Experts Group), PNG (Portable Network Graphics), entreoutros. Cada formato consiste em uma especificao para a representao binria de uma imagem.Trocando em midos, um arquivo de imagem contm um determinado nmero de bytes, usandocdigos ASCII de 0 a 255 (contedo binrio), que so interpretados por uma aplicao capaz demostrar seu contedo em uma interface. Cada tipo de imagem possui algumas caractersticas, comoresoluo, compresso, mas por dentro so apenas uma sequncia de bytes.

    Imagens no SGDB

    Existem bancos de dados que possuem um tipo de campo prprio para imagens, como o Microsoft

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 2/58

    Existem bancos de dados que possuem um tipo de campo prprio para imagens, como o MicrosoftSQL Server (campo image), mas a grosso modo praticamente todos os bancos de dados comerciaispossuem um tipo de campo conhecido por BLOB (Binary Large OBject), capaz de suportarcontedo binrio.

    Acesso pelo DBAccess

    Como de conhecimento de todos que trabalham com o ERP Microsiga, todo o accesso a Banco deDados relacional no Protheus feito atravs do DBAccess, um gateway de acesso para bancosrelacionais, que tambm capaz de emular o acesso ISAM, ainda usado por boa parte do cdigolegado do ERP.

    O DBAccess no permite acesso direto a campos IMAGE, BLOB ou CLOB, mas internamente ele se

    utiliza destes campos para emular o campo do tipo M memo do AdvPL. Logo, para nosutilizarmos destes tipos de campo, devemos criar uma tabela no SGDB usando o tipo de campoM (Memo) do AdvPL.

    Ateno, no ERP existe o conceito de campo Memo virtual, criado no dicionrio de dados do ERP(SX3), que na prtica utiliza um arquivo auxiliar (SYP) na Base de Dados principal, com acessoatravs de uma API Advpl, ao qual esse exemplo no se aplica. O campo Memo que ser criado um Memo real no SGDB.

    Caractersticas e Limites

    O AdvPL possui um limite de 1MB de tamanho mximo de String, logo ainda no possvelarmazenar no SGDB uma imagem maior que isso. E, como o acesso ao contedo do campo feitopelo DBAccess, no possvel fazer uma Query que recupere diretamente o contedo de um campoBLOB, CLOB ou IMAGE.

    Para acessar o contedo de um campo M Memo criado em uma tabela, devemos abrir a tabela noAdvPL usando DbUseArea() ou ChkFile(), para uma tabela de dados do ERP , posicionar noregistro desejado e ler o valor do campo do registro atual atravs da expresso cVARIAVEL :=ALIAS->CAMPOMEMO, e o DBAccess ir fazer uma requisio exclusiva para trazer o contedodeste campo e coloc-lo na varivel de memria.

    Adicionalmente, o campo M Memo originalmente no AdvPL foi projetado para suportar apenas64 KB de dados, e somente conseguimos aumentar esse limite para 1MB habilitando a configuraoTOPMEMOMEGA=1 na configurao do environment desejado no arquivo de configurao doTOTVS Application Server (appserver.ini) Vide TDN, no linkhttp://tdn.totvs.com/pages/viewpage.action?pageId=6065746(http://tdn.totvs.com/pages/viewpage.action?pageId=6065746) )

    Como as imagens gravadas costumam ser bem maiores que os registros gravados em tabelas dedados do ERP, deve-se tomar cuidado quando a leitura destes campos for realizada por muitosprocessos simultaneamente, isto pode gerar um gargalo na camada de rede entre as aplicaesTOTVS Application Server, DBACcess e o SGDB.

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 3/58

    E, existem alguns bancos de dados homologados que por default no se utilizam de campos BLOBou similares para armazenar os dados de campo M Memo. Para ter certeza que a implementaovai funcionar em todos os bancos homologados, podemos limitar o tamanho da imagem em 745KB, e converter o buffer binrio da imgem para BASE64, onde so usadas strings de texto normalpara a representao dos dados, e fazer as converses em memria para Ler e Gravar o bufferbinrio.

    Mos obra

    Basicamente, armazenar uma imagem no SGDB requer no mnimo 2 campos na tabela de imagens:Um campo caractere, identificador nico da imagem, indexado, e um campo M Memo doAdvPL, para armazenar a imagem. Podemos encapsular isso em uma classe vamos cham-laApDbImage() e implementar os mtodos de leitura e gravao, manuteno e status, alm dedois mtodos adicionais para ler imagens de um arquivo no disco para a memria, e gravar aimagem da memria para o disco.

    A classe APDBIMAGE() foi implementada com este propsito, mas ela tm ainda alguns detalhesadicionais, ela guarda um HASH MD5 gerado a partir da imagem original, um campo separadopara o tipo da imagem, e permite as operaes bsicas de incluso, leitura, alterao e excluso.

    Exemplo de uso e fontes

    O programa de exemplo funciona como um manager simples de imagens, permitindo abrir osformatos suportados de imagens do disco, para serem mostrados na tela, ou do prprio repositrio,ou tambm da tabela de imagens do Banco de Dados. Uma vez visualizada uma imagem nainterface, ela pode ser gravada em disco (ou exportada), no mesmo formato que foi aberto oprograma no realiza converses , e tambm pode ser inserida no DBimage com um nomeidentificador qualquer, ou usada para alterar uma imagem j existente na tabela de imagens.

    O fonte da classe APDBImage() pode ser baixado no linkhttps://github.com/siga0984/Blog/blob/master/ApDBImage.prw(https://github.com/siga0984/Blog/blob/master/ApDBImage.prw) , e o fonte de exemplo que usa aclasse est no link https://github.com/siga0984/Blog/blob/master/TSTDBIMG.prw(https://github.com/siga0984/Blog/blob/master/TSTDBIMG.prw) , ambos no GitHub do Blog (https://github.com/siga0984/blog (https://github.com/siga0984/blog) ). Basta baixar os fontes,compil-los com o IDE ou o TDS, e executar a funo U_TSTDBIMG para acionar o programa detestes da classe ApDbImage().

    E, para os curiosos e vidos por cdigo, segue o fonte da Classe APDBIMAGE logo abaixo:

    #include "protheus.ch"

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 4/58

    /* ---------------------------------------------------Classe ApDBImageAutor Jlio WittwerData 27/02/2015 Verso 1.150308Descrio Classe para encapsular leitura e gravao de

    imagens em tabela do SGDB atravs do DBACCESS

    Observao

    Como apenas o banco MSSQL aceita contedo binrio ( ASCII 0 a 255 )para campos MEMO, e os bancos ORACLE e DB2 ( quando usado BLOB ), para servir para todos os bancos, a imagem gravada no banco usando Encode64 -- para converter contedo binrio em Texto codificado em Base64, a maior imagem nao pode ter mais de 745000 bytes

    Referncias

    http://tdn.totvs.com/display/tec/Acesso+ao+banco+de+dados+via+DBAccess

    http://tdn.totvs.com/pages/viewpage.action?pageId=6063692

    http://tdn.totvs.com/display/tec/Encode64

    http://tdn.totvs.com/display/tec/Decode64

    --------------------------------------------------- */

    #define MAX_IMAGE_SIZE 745000

    CLASS APDBIMAGE

    // Propriedades DATA bOpened DATA cError

    // Mtodos METHOD New() METHOD Open() METHOD Close() METHOD ReadStr( cImgId , /* @ */ cImgType , /* @ */ cImgBuffer ) METHOD Insert( cImgId , cImgType , /* @ */ cImgBuffer ) METHOD Update( cImgId , cImgType , /* @ */ cImgBuffer ) METHOD Delete( cImgId ) METHOD Status()

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 5/58

    // Metodos de acesso de imagens no disco METHOD LoadFrom( cFile, cImgBuffer ) METHOD SaveTo( cFile, cImgBuffer ) ENDCLASS

    /* ---------------------------------------------------------Construtor da classe de Imagens no SGDBApenas inicializa propriedades-------------------------------------------------------- */METHOD New() CLASS APDBIMAGE::bOpened := .F.::cError := ''Return self

    /* ---------------------------------------------------------Abre a tabela de imagens no SGDBConecta no DBAccess caso nao haja conexo--------------------------------------------------------- */

    METHOD Open( ) CLASS APDBIMAGELocal nDBHnd := -1Local aStru := {}Local cOldAlias := Alias()

    ::cError := ''

    IF ::bOpened // Ja estava aberto, retorna direto Return .T.Endif

    If !TcIsConnected() // Se no tem conexo com o DBAccess, cria uma agora // Utiliza as configuraes default do appserver.ini nDBHnd := tcLink() If nDBHnd < 0 ::cError := "TcLink() error "+cValToChar(nDbHnd) Return .F. EndifEndif

    If !TCCanOpen("ZDBIMAGE") // Cria array com a estrutura da tabela aAdd(aStru,{"ZDB_IMGID" ,"C",40,0}) aAdd(aStru,{"ZDB_TYPE" ,"C",3,0}) // BMP JPG PNG aAdd(aStru,{"ZDB_HASH" ,"C",32,0}) aAdd(aStru,{"ZDB_SIZE" ,"N",8,0}) aAdd(aStru,{"ZDB_MEMO" ,"M",10,0})

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 6/58

    // Cria a tabela direto no SGDB DBCreate("ZDBIMAGE",aStru,"TOPCONN") // Abre em modo exclusivo para criar o ndice de ID USE ("ZDBIMAGE") ALIAS ZDBIMAGE EXCLUSIVE NEW VIA "TOPCONN" If NetErr() ::cError := "Failed to open [ZDBIMAGE] on EXCLUSIVE Mode" Return Endif // Cria o ndice por ID da imagem INDEX ON ZDB_IMGID TO ("ZDBIMAGE1") // Fecha a tabela USE Endif // Abre em modo compartilhadoUSE ("ZDBIMAGE") ALIAS ZDBIMAGE SHARED NEW VIA "TOPCONN"If NetErr() ::cError := "Failed to open [ZDBIMAGE] on SHARED Mode" Return .F.Endif

    DbSetIndex("ZDBIMAGE1")DbSetOrder(1)

    ::bOpened := .T.

    If !Empty(cOldAlias) .and. Select(cOldAlias) > 0 DbSelectArea(cOldAlias)Endif

    Return ::bOpened

    /* ---------------------------------------------------------Le uma imagem do banco para a memoriarecebe o nome da imgem, retorna por referencia o tipoda imagem e seu conteudo -------------------------------------------------------- */METHOD ReadStr( cImgId , /* @ */cImgType, /* @ */ cImgBuffer ) CLASS APDBIMAGE

    ::cError := ''

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 7/58

    If !::bOpened ::cError := "APDBIMAGE:ReadStr() Error: Instance not opened." Return .F.Endif

    If empty(cImgId) ::cError := "APDBIMAGE:ReadStr() Error: ImageId not specified." Return .F. Endif

    cImgId := Lower(cImgId)

    If !ZDBIMAGE->(DbSeek(cImgId)) ::cError := "APDBIMAGE:ReadStr() ImageId ["+cImgId+"] not found." Return .F.Endif

    // Caso a imagem com o ID informado seja encontrada// Carrega o buffer da imagem para a varivel de memriacImgBuffer := Decode64(ZDBIMAGE->ZDB_MEMO)cImgType := ZDBIMAGE->ZDB_TYPE

    Return .T.

    /* ---------------------------------------------------------Insere uma imagem na tabela de imagens do SGDBRecebe o ID da imagem, o tipo e o buffer -------------------------------------------------------- */METHOD Insert( cImgId , cImgType, cImgBuffer ) CLASS APDBIMAGELocal bOk := .F.

    ::cError := ''

    If !::bOpened ::cError := "APDBIMAGE:Insert() Error: Instance not opened." Return .F. Endif

    If empty(cImgId) ::cError := "APDBIMAGE:Insert() Error: ImageId not specified." Return .F. Endif

    If empty(cImgType) ::cError := "APDBIMAGE:Insert() Error: ImageType not specified." Return .F. Endif

    cImgId := Lower(cImgId)cImgType := Lower(cImgType)

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 8/58

    If !ZDBIMAGE->(DbSeek(cImgId)) // Se a imagem no existe, insere ZDBIMAGE->(DBAppend(.T.)) ZDBIMAGE->ZDB_IMGID := cImgId ZDBIMAGE->ZDB_TYPE := cImgType ZDBIMAGE->ZDB_SIZE := len(cImgBuffer) ZDBIMAGE->ZDB_HASH := Md5(cImgBuffer,2) // Hash String Hexadecimal ZDBIMAGE->ZDB_MEMO := Encode64(cImgBuffer) ZDBIMAGE->(DBRUnlock()) bOk := .T.else ::cError := 'Image Id ['+cImgId+'] already exists.'Endif

    Return bOk

    /* ---------------------------------------------------------Atualiza uma imagem ja existente no banco de imagensRecebe ID, tipo e buffer-------------------------------------------------------- */METHOD Update( cImgId , cImgType, cImgBuffer ) CLASS APDBIMAGE

    ::cError := ''

    If !::bOpened ::cError := "APDBIMAGE:Update() Error: Instance not opened." Return .F. Endif

    If empty(cImgId) ::cError := "APDBIMAGE:Update() Error: ImageId not specified." Return .F. Endif

    If empty(cImgType) ::cError := "APDBIMAGE:Update() Error: ImageType not specified." Return .F. Endif

    cImgId := Lower(cImgId)cImgType := Lower(cImgType) If !ZDBIMAGE->(DbSeek(cImgId)) ::cError := 'Image Id ['+cImgId+'] not found.' Return .F.Endif

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 9/58

    // Se a imagem existe, atualizaIF !ZDBIMAGE->(DbrLock(recno())) ::cError := 'Image Id ['+cImgId+'] update lock failed.' Return .F.Endif

    ZDBIMAGE->ZDB_TYPE := cImgTypeZDBIMAGE->ZDB_SIZE := len(cImgBuffer)ZDBIMAGE->ZDB_HASH := MD5(cImgBuffer,2) // Hash String HexadecimalZDBIMAGE->ZDB_MEMO := Encode64(cImgBuffer)

    ZDBIMAGE->(DBRUnlock())

    Return .T.

    /* ---------------------------------------------------------Deleta fisicamente uma imagem da Tabela de Imagens-------------------------------------------------------- */

    METHOD Delete( cImgId , lHard ) CLASS APDBIMAGELocal nRecNo

    ::cError := ''

    If !::bOpened ::cError := "APDBIMAGE:Delete() Error: Instance not opened." Return .F. Endif

    If empty(cImgId) ::cError := "APDBIMAGE:Delete() Error: ImageId not specified." Return .F. Endif

    If !ZDBIMAGE->(DbSeek(cImgId)) ::cError := 'Image Id ['+cImgId+'] not found.' Return .F.Endif

    // Se a imagem existe, marca o registro para deleonRecNo := ZDBIMAGE->(recno())

    // Mesmo que a deleo seja fisica, eu garanto // o lock do registro na camada do dbaccessIf !ZDBIMAGE->(DbrLock(nRecNo)) ::cError := 'Image Id ['+cImgId+'] delete lock failed.' Return .F.Endif

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 10/58

    // Deleta fisicamente no SGBDnErr := TcSqlExec("DELETE FROM ZDBIMAGE WHERE R_E_C_N_O_ = " + cValToChar(nRecNo) )If nErr < 0 ::cError := 'Image Id ['+cImgId+'] delete error: '+TcSqlError()Endif

    // Solto o lock do registro no DBAccessZDBIMAGE->(DBRUnlock())

    Return .T.

    /* ---------------------------------------------------------Fecha a tabela de imagens-------------------------------------------------------- */

    METHOD Close() CLASS APDBIMAGE

    If Select('ZDBIMAGE') > 0 ZDBIMAGE->(DbCloseArea())Endif

    ::cError := '' ::bOpened := .F.

    Return .T.

    /* ---------------------------------------------------------Metodo Status()Classe APDBIMAGEDescrio Monta array por referencia contendo as informaes da base de imagens: Quantidade de registros total, tamanho estimado total das imagens, quantidade de registros marcados para deleo e tamanho estimado de imagens marcadas para deleao -------------------------------------------------------- */

    METHOD Status( /* @ */ aStat ) CLASS APDBIMAGELocal cOldAlias := Alias()Local cQuery Local nCountAll := 0Local nSizeAll := 0

    ::cError := '' aStat := {}

    If !::bOpened ::cError := "APDBIMAGE:Status() Error: Instance not opened." Return .F. Endif

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 11/58

    // Conta quantas imagens tem na tabela, por tipo cQuery := "SELECT ZDB_TYPE, count(*) AS TOTAL"+; " FROM ZDBIMAGE GROUP BY ZDB_TYPE ORDER BY ZDB_TYPE" USE (TcGenQry(,,cQuery)) ALIAS QRY EXCLUSIVE NEW VIA "TOPCONN"While !eof() aadd(aStat , {"TOTAL_COUNT_"+QRY->ZDB_TYPE,QRY->TOTAL}) nCountAll += QRY->TOTAL DbSkip()EnddoUSE

    // Acrescenta total de imagensaadd(aStat , {"TOTAL_COUNT_ALL",nCountAll}) // Levanta o total de bytes usados por tipo de imagemcQuery := "SELECT ZDB_TYPE, SUM(ZDB_SIZE) AS TOTAL"+; " FROM ZDBIMAGE GROUP BY ZDB_TYPE ORDER BY ZDB_TYPE" USE (TcGenQry(,,cQuery)) ALIAS QRY EXCLUSIVE NEW VIA "TOPCONN"While !eof() aadd(aStat , {"TOTAL_SIZE_"+QRY->ZDB_TYPE,QRY->TOTAL}) nSizeAll += QRY->TOTAL DbSkip()EnddoUSE

    // Acrescenta total de bytes usados aadd(aStat , {"TOTAL_SIZE_ALL",nSizeAll})

    If !Empty(cOldAlias) DbSelectArea(cOldAlias)Endif

    Return .T.

    /* ---------------------------------------------------------Ler um arquivo de imagem do disco para a memoriaNao requer que a instancia esteja inicializada / Aberta--------------------------------------------------------- */

    METHOD LoadFrom( cFile, /* @ */ cImgBuffer ) CLASS APDBIMAGELocal nH, nSize, nRead::cError := ''

    If !file(cFile) ::cError := "APDBIMAGE:LoadFrom() Error: File ["+cFile+"]not found." Return .F. Endif

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 12/58

    nH := Fopen(cFile,0)

    If nH == -1 ::cError := "APDBIMAGE:LoadFrom() File Open Error ( FERROR "+cValToChar( Ferror() )+")" Return .F. Endif

    nSize := fSeek(nH,0,2)fSeek(nH,0)

    If nSize MAX_IMAGE_SIZE ::cError := "APDBIMAGE:LoadFrom() File TOO BIG ("+ cValToChar(nSize) +" bytes)" fClose(nH) Return .F. Endif

    // Aloca buffer para ler o arquivo do disco // e le o arquivo para a memoriacImgBuffer := space(nSize)nRead := fRead(nH,@cImgBuffer,nSize)

    // e fecha o arquivo no disco fClose(nH)

    If nRead < nSize cImgBuffer := '' ::cError := "APDBIMAGE:LoadFrom() Read Error ( FERROR "+cValToChar( Ferror() )+")" Return .F. Endif

    Return .T.

    /* ---------------------------------------------------------Gravar um arquivo de imagem no disco a partir de uma imagem na memoriaNao requer que a instancia esteja inicializada / Aberta--------------------------------------------------------- */

    METHOD SaveTo( cFile, cImgBuffer ) CLASS APDBIMAGELocal nH, nSize , nSaved ::cError := ''

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 13/58

    If file(cFile) ::cError := "APDBIMAGE:SaveTo() Error: File ["+cFile+"] alreay exists." Return .F. Endif

    // Cria o arquivo no disco nH := fCreate(cFile)

    If nH == -1 ::cError := "APDBIMAGE:SaveTo() File Create Error ( FERROR "+cValToChar( Ferror() )+")" Return .F. Endif // Calcula tamanho do buffer de memoria// e grava ele no arquivo nSize := len(cImgBuffer)nSaved := fWrite(nH,cImgBuffer)

    // Fecha o arquivo fClose(nH)

    If nSaved < nSize ::cError := "APDBIMAGE:SaveTo() Write Error ( FERROR "+cValToChar( Ferror() )+")" Return .F. Endif

    Return .T.

    Concluso

    Esta classe s um esboo, com alguns parafusos a mais ela pode ser usada para construir umassistente para, por exemplo, importar uma pasta cheia de imagens para o banco de dados, dando onome das imagens automaticamente baseado no nome do arquivo original, e o fato dela gerar oMD5 Hash a partir do buffer binrio original pode permitir uma busca mais rpida por imagensidnticas repetidas dentro do banco, fazendo apenas uma Query para mostrar quais os ImageIDsque possuem o mesmo HASH !!!

    Pessoal, novamente agradeo a audincia, espero que gostem do Post. J tenho alguma coisa noforno para os prximos posts, mas continuo aceitando sugestes !! At o prximo post, pessoal

    2 Comentrios

    Jogos em AdvPL Tetris(https://siga0984.wordpress.com/2015/02/27/jo

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 14/58

    gos-em-advpl-tetris/)

    27/02/2015 Siga0984 ADVPL, Exemplos, Games, Interface, Programao

    Introduo

    Como eu havia dito, no primeiro post deste blog, em AdvPL muita coisa pode ser feita, at um ERP.E, para dar nfase em outras caractersticas do AdvPL, resolvi fazer algumas revises e publicar umprograminha interessante um clone do famoso jogo Tetris. Sim, aquele que os blocos caem e vocprecisa alinh-los para eliminar linhas da tela

    (https://siga0984.files.wordpress.com/2015/02/tetris-v3.png)

    O Algoritmo

    No foi to difcil fazer o jogo funcionar, foi mais trabalhoso o refactoring para publicao e asexplicaes de como ele funciona por dentro do que bolar a lgica e interface do game. Ao serexecutado, o programa j abre direto uma caixa de interface com uma pea sorteada em quedaprogressiva em intervalos de 1 segundo, mostrando a prxima pea a ser usada, atualizando umscore lateral e um contador de tempo de jogo. Voc pode usar as letras ASDW ou JKLI para mover apea em queda para a esquerda, para baixo, para a direita e rotacion-la, respectivamente, e a barrade espaos para dropar a pea at a linha inferior que ela pode alcanar na tela, sendo possvel nesteponto ainda mover a pea para a direita ou esquerda, ou mesmo rotacion-la caso exista espaohbil para tais operaes.Ainda pode ser usada a letra P para colocar e retirar o jogo de modoPause. E, para sair do jogo instantaneamente, pressione a tecla [ESC].

    Na verso inicial, o jogo no foi montado usando orientao a objeto. Ele ainda ser passado a

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 15/58

    Na verso inicial, o jogo no foi montado usando orientao a objeto. Ele ainda ser passado alimpo, com classes e propriedades, mas j funciona muito bem com o paradigma estruturado. Empoucas linhas, o painel de jogo possui uma representao em memria usando array de strings,onde cada elemento do array representa uma linha da tela, e a sequncia de nmeros dentro decada linha corresponde a um quadradinho colorido. A tela do jogo desenhada na interface doAdvpl em um Grid de 20 x 10 imagens, onde o jogo lida com a aplicao de peas e movimento depeas dentro desse array, e a camada de interface apenas atualiza essa matriz na interface trocandoos resources das imagens. Como cada resource possui uma cor diferente, com uma matriz de 200

    quadradinhos na tela e oito imagens, possvel montar a interface.

    O miolo do jogo consiste em trabalhar com um array de strings, com 20 linhas, contendo em cadalinha uma string com um valor numrico, onde 0 significa um espao no preenchido, e cadavalor maior que zero representa uma imagem 1010 de uma cor diferente na respectiva posio dogrid de bitmaps da interface. Como todo o jogo baseado em array de strings, cada pea e suarespectiva representao de blocos feita em um array multi-dimensional de peas, onde cada pea representada por um grid de string 44 com 0 e 1.

    As funes de trabalho com as matrizes devem ser capazes de remover ou colocar uma pea doarray que representa a interface, e no caso de colocar a pea, a funo somente deve conseguirrealizar esta operao caso as posies dos quadrados usados pela pea estejam vazias no Grid. Aanimao consiste apenas em remover a pea em jogo da posio atual do grid no array, inseri-laem uma nova posio, e repintar o grid de interface. Esta pintura realizada simplesmente setandonovamente o nome do resource (bitmap) usado naquela posio, caso ele esteja diferente do recursoindicado no array.

    Cada STATIC FUNCTION do cdigo tem um propsito distinto, e para economizar com apassagem de parmetros, como este fonte executado sem recurso, as variveis contendo o estadodo jogo tambm so STATIC. A interface para obter as teclas pressionadas foi feita com botes,criados fora da rea visvel da tela, onde cada letra usada indicada no prompt do boto como umatecla de atalho ( prefixada com & ). Neste caso especifico, quando o foco da interface est em umcomponente que no permite edio de contedo, o uso das teclas de atalho no precisa serconcomitante com a tecla [ALT], o que torna essa idia de input de interface vivel no AdvPL.

    Dentro do fonte AdvPL as partes do cdigo e suas funcionalidades esto bem documentadas. claroque no um fonte arroz com feijo, esse prato tm uns temperos um pouco mais puxados, ecomo ele no foi escrito usando orientao a objeto, ele requer um pouco mais de ateno para serassimilado na ntegra. Posteriormente eu vou fazer a segunda verso desse clone, com todas asfuncionalidades do primeiro, porm usando Orientao a Objetos do AdvPL, fazendo isso vai ficarmuito visvel o nvel de clareza da aplicao quando utilizamos a Orientao a Objetos.

    Fontes e Patch

    O fonte deste aplicativo e os bitmaps (resources) necessrios esto no GitHubhttps://github.com/siga0984/Tetris (https://github.com/siga0984/Tetris) , bem como um patchgerado para o Protheus 11 ( RPO TOP , Portugus ) com apenas o fonte Tetris.PRW e as imagensdo projeto. Para gerar a aplicao, basta ter um Protheus 10 ou superior, criar um projeto em

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 16/58

    branco, baixar o PRW e as imagens do GitHub, acrescentar o fonte no projeto, e acrescentar todas

    as imagens como resources. Para execut-lo, basta chamar o SmartClient, informando a funoU_TETRIS

    E, pra voc que est se coando de curiosidade, o fonte com todos os comentrios e afins, ficou compouco mais de 700 linhas. Segue abaixo o fonte AdvPL do clone do Tetris.

    #include "protheus.ch"

    /* ========================================================Funo U_TETRISAutor Jlio WittwerData 03/11/2014Verso 1.150226Descriao Rplica do jogo Tetris, feito em AdvPL

    Para jogar, utilize as letras :

    A ou J = Move esquerdaD ou L = Move DireitaS ou K = Para baixoW ou I = Rotaciona sentido horarioBarra de Espao = Dropa a pea

    Pendencias

    Fazer um High Score

    Cores das peas

    O = YellowI = light BlueL = OrangeZ = RedS = GreenJ = BlueT = Purple

    ======================================================== */

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 17/58

    STATIC _aPieces := LoadPieces() // Array de peas do jogo STATIC _aBlockRes := { "BLACK","YELOW2","LIGHTBLUE2","ORANGE2","RED2","GREEN2","BLUE2","PURPLE2" }STATIC _nGameClock // Tempo de jogo STATIC _nNextPiece // Proxima pea a ser usadaSTATIC _GlbStatus := 0 // 0 = Running 1 = PAuse 2 == Game OverSTATIC _aBMPGrid := array(20,10) // Array de bitmaps de interface do jogo STATIC _aBMPNext := array(4,5) // Array de botmaps da proxima peaSTATIC _aNext := {} // Array com a definio e posio da proxima peaSTATIC _aDropping := {} // Array com a definio e posio da pea em jogoSTATIC _nScore := 0 // pontuao da partidaSTATIC _oScore // label para mostrar o score e time e mensagensSTATIC _aMainGrid := {} // Array de strings com os blocos da interface representados em memoriaSTATIC _oTimer // Objeto timer de interface para a queda automtica da pea em jogo // =======================================================

    USER Function Tetris()Local nC , nLLocal oDlgLocal oBackGround , oBackNextLocal oFont , oLabel , oMsg

    // Fonte default usada na caixa de dilogo // e respectivos componentes filhosoFont := TFont():New('Courier new',,-16,.T.,.T.)

    DEFINE DIALOG oDlg TITLE "Tetris AdvPL" FROM 10,10 TO 450,365 ; FONT oFont COLOR CLR_WHITE,CLR_BLACK PIXEL

    // Cria um fundo cinza, "esticando" um bitmap@ 8, 8 BITMAP oBackGround RESOURCE "GRAY" ;SIZE 104,204 Of oDlg ADJUST NOBORDER PIXEL

    // Desenha na tela um grid de 20x10 com Bitmaps// para ser utilizado para desenhar a tela do jogo

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 18/58

    For nL := 1 to 20 For nC := 1 to 10 @ nL*10, nC*10 BITMAP oBmp RESOURCE "BLACK2" ; SIZE 10,10 Of oDlg ADJUST NOBORDER PIXEL _aBMPGrid[nL][nC] := oBmp NextNext // Monta um Grid 4x4 para mostrar a proxima pea// ( Grid deslocado 110 pixels para a direita )

    @ 8, 118 BITMAP oBackNext RESOURCE "GRAY" ; SIZE 54,44 Of oDlg ADJUST NOBORDER PIXEL

    For nL := 1 to 4 For nC := 1 to 5 @ nL*10, (nC*10)+110 BITMAP oBmp RESOURCE "BLACK" ; SIZE 10,10 Of oDlg ADJUST NOBORDER PIXEL _aBMPNext[nL][nC] := oBmp NextNext

    // Label fixo, ttulo do Score.@ 80,120 SAY oLabel PROMPT "[Score]" SIZE 60,10 OF oDlg PIXEL // Label para Mostrar score, timers e mensagens do jogo@ 90,120 SAY _oScore PROMPT " " SIZE 60,120 OF oDlg PIXEL // Define um timer, para fazer a pea em jogo// descer uma posio a cada um segundo// ( Nao pode ser menor, o menor tempo 1 segundo )_oTimer := TTimer():New(1000, ; {|| MoveDown(.f.) , PaintScore() }, oDlg )

    // Botes com atalho de teclado// para as teclas usadas no jogo// colocados fora da area visivel da caixa de dialogo

    @ 480,10 BUTTON oDummyBtn PROMPT '&A' ; ACTION ( DoAction('A')); SIZE 1, 1 OF oDlg PIXEL

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 19/58

    @ 480,20 BUTTON oDummyBtn PROMPT '&S' ; ACTION ( DoAction('S') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&D' ; ACTION ( DoAction('D') ) ; SIZE 1, 1 OF oDlg PIXEL @ 480,20 BUTTON oDummyBtn PROMPT '&W' ; ACTION ( DoAction('W') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&J' ; ACTION ( DoAction('J') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&K' ; ACTION ( DoAction('K') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&L' ; ACTION ( DoAction('L') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&I' ; ACTION ( DoAction('I') ) ; SIZE 1, 1 OF oDlg PIXEL @ 480,20 BUTTON oDummyBtn PROMPT '& ' ; // Espao = Dropa ACTION ( DoAction(' ') ) ; SIZE 1, 1 OF oDlg PIXEL

    @ 480,20 BUTTON oDummyBtn PROMPT '&P' ; // Pause ACTION ( DoPause() ) ; SIZE 1, 1 OF oDlg PIXEL

    // Na inicializao do Dialogo uma partida iniciadaoDlg:bInit := {|| Start() }

    ACTIVATE DIALOG oDlg CENTER

    Return

    /* ------------------------------------------------------------Funo Start() Inicia o jogo------------------------------------------------------------ */

    STATIC Function Start()Local aDraw

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 20/58

    // Inicializa o grid de imagens do jogo na memria// Sorteia a pea em jogo// Define a pea em queda e a sua posio inicial// [ Peca, direcao, linha, coluna ]// e Desenha a pea em jogo no Grid// e Atualiza a interface com o GridInitGrid()nPiece := randomize(1,len(_aPieces)+1)_aDropping := {nPiece,1,1,6}SetGridPiece(_aDropping,_aMainGrid)PaintMainGrid()

    // Sorteia a proxima pea e desenha // ela no grid reservado para ela InitNext()_nNextPiece := randomize(1,len(_aPieces)+1)aDraw := {_nNextPiece,1,1,1}SetGridPiece(aDraw,_aNext)PaintNext()

    // Inicia o timer de queda automtica da pea em jogo_oTimer:Activate()

    // Marca timer do inicio de jogo _nGameClock := seconds()

    Return

    /* ----------------------------------------------------------Inicializa o Grid na memoriaEm memoria, o Grid possui 14 colunas e 22 linhasNa tela, so mostradas apenas 20 linhas e 10 colunasAs 2 colunas da esquerda e direita, e as duas linhas a maissao usadas apenas na memoria, para auxiliar no processode validao de movimentao das peas.---------------------------------------------------------- */

    STATIC Function InitGrid()_aMainGrid := array(20,"11000000000011")aadd(_aMainGrid,"11111111111111")aadd(_aMainGrid,"11111111111111")return

    STATIC Function InitNext()_aNext := array(4,"00000")return

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 21/58

    //// Aplica a pea no Grid.// Retorna .T. se foi possivel aplicar a pea na posicao atual// Caso a pea no possa ser aplicada devido a haver// sobreposio, a funo retorna .F. e o grid no atualizado//

    STATIC Function SetGridPiece(aOnePiece,aGrid)Local nPiece := aOnePiece[1] // Numero da peaLocal nPos := aOnePiece[2] // Posio ( para rotacionar ) Local nRow := aOnePiece[3] // Linha atual no GridLocal nCol := aOnePiece[4] // Coluna atual no GridLocal nL , nCLocal aTecos := {}Local cTeco, cPeca , cPieceStr

    cPieceStr := str(nPiece,1)

    For nL := nRow to nRow+3 cTeco := substr(aGrid[nL],nCol,4) cPeca := _aPieces[nPiece][1+nPos][nL-nRow+1] For nC := 1 to 4 If Substr(cPeca,nC,1) == '1' If substr(cTeco,nC,1) != '0' // Vai haver sobreposio, // Nao d para desenhar a pea Return .F. Endif cTeco := Stuff(cTeco,nC,1,cPieceStr) Endif Next // Array temporario com a pea j colocada aadd(aTecos,cTeco)Next

    // Aplica o array temporario no array do gridFor nL := nRow to nRow+3 aGrid[nL] := stuff(_aMainGrid[nL],nCol,4,aTecos[nL-nRow+1])Next

    Return .T.

    /* ----------------------------------------------------------

    Funo PaintMainGrid()Pinta o Grid do jogo da memria para a Interface

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 22/58

    Release 20150222 : Optimizao na camada de comunicao, apenas setaro nome do resource / bitmap caso o resource seja diferente do atual.---------------------------------------------------------- */

    STATIC Function PaintMainGrid()Local nL, nc , cLine, nPeca

    for nL := 1 to 20 cLine := _aMainGrid[nL] For nC := 1 to 10 nPeca := val(substr(cLine,nC+2,1)) If _aBMPGrid[nL][nC]:cResName != _aBlockRes[nPeca+1] // Somente manda atualizar o bitmap se houve // mudana na cor / resource desta posio _aBMPGrid[nL][nC]:SetBmp(_aBlockRes[nPeca+1]) endif NextNext

    Return

    // Pinta na interface a prxima pea // a ser usada no jogo STATIC Function PaintNext()Local nL, nC, cLine , nPeca

    For nL := 1 to 4 cLine := _aNext[nL] For nC := 1 to 5 nPeca := val(substr(cLine,nC,1)) If _aBMPNext[nL][nC]:cResName != _aBlockRes[nPeca+1] _aBMPNext[nL][nC]:SetBmp(_aBlockRes[nPeca+1]) endif NextNext

    Return

    /* -----------------------------------------------------------------Carga do array de peas do jogo Array multi-dimensional, contendo para cada linha a string que identifica a pea, e um ou maisarrays de 4 strings, onde cada 4 elementos representam uma matriz binaria de caracteres 4x4 para desenhar cada pea

    Exemplo - Pea "O"

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 23/58

    aLPieces[1][1] C "O"aLPieces[1][2][1] "0000" aLPieces[1][2][2] "0110" aLPieces[1][2][3] "0110" aLPieces[1][2][4] "0000"

    ----------------------------------------------------------------- */

    STATIC Function LoadPieces()Local aLPieces := {}

    // Pea "O" , uma posioaadd(aLPieces,{'O', { '0000','0110','0110','0000'}})

    // Pea "I" , em p e deitadaaadd(aLPieces,{'I', { '0000','1111','0000','0000'},; { '0010','0010','0010','0010'}})

    // Pea "S", em p e deitadaaadd(aLPieces,{'S', { '0000','0011','0110','0000'},; { '0010','0011','0001','0000'}})

    // Pea "Z", em p e deitadaaadd(aLPieces,{'Z', { '0000','0110','0011','0000'},; { '0001','0011','0010','0000'}})

    // Pea "L" , nas 4 posies possiveisaadd(aLPieces,{'L', { '0000','0111','0100','0000'},; { '0010','0010','0011','0000'},; { '0001','0111','0000','0000'},; { '0110','0010','0010','0000'}})

    // Pea "J" , nas 4 posies possiveisaadd(aLPieces,{'J', { '0000','0111','0001','0000'},; { '0011','0010','0010','0000'},; { '0100','0111','0000','0000'},; { '0010','0010','0110','0000'}})

    // Pea "T" , nas 4 posies possiveisaadd(aLPieces,{'T', { '0000','0111','0010','0000'},; { '0010','0011','0010','0000'},; { '0010','0111','0000','0000'},; { '0010','0110','0010','0000'}})

    Return aLPieces

    /* ----------------------------------------------------------Funo MoveDown()

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 24/58

    Movimenta a pea em jogo uma posio para baixo.Caso a pea tenha batido em algum obstculo no movimentopara baixo, a mesma fica e incorporada ao grid, e uma novapea colocada em jogo. Caso no seja possivel colocar umanova pea, a pilha de peas bateu na tampa -- Game Over

    ---------------------------------------------------------- */

    STATIC Function MoveDown(lDrop)Local aOldPiece If _GlbStatus != 0 ReturnEndif

    // Clona a pea em queda na posio atualaOldPiece := aClone(_aDropping)

    If lDrop // Dropa a pea at bater embaixo // O Drop incrementa o score em 1 ponto // para cada linha percorrida. Quando maior a quantidade // de linhas vazias, maior o score acumulado com o Drop // Guarda a pea na posio atual aOldPiece := aClone(_aDropping) // Remove a pea do Grid atual DelPiece(_aDropping,_aMainGrid) // Desce uma linha pra baixo _aDropping[3]++ While SetGridPiece(_aDropping,_aMainGrid) // Encaixou, remove e tenta de novo DelPiece(_aDropping,_aMainGrid) // Guarda a pea na posio atual aOldPiece := aClone(_aDropping) // Desce a pea mais uma linha pra baixo _aDropping[3]++

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 25/58

    // Incrementa o Score _nScore++ Enddo // Nao deu mais pra pintar, "bateu" // Volta a pea anterior, pinta o grid e retorna // isto permite ainda movimentos laterais // caso tenha espao. _aDropping := aClone(aOldPiece) SetGridPiece(_aDropping,_aMainGrid) PaintMainGrid() Else // Move a pea apenas uma linha pra baixo // Primeiro remove a pea do Grid atual DelPiece(_aDropping,_aMainGrid) // Agora move a pea apenas uma linha pra baixo _aDropping[3]++ // Recoloca a pea no Grid If SetGridPiece(_aDropping,_aMainGrid) // Se deu pra encaixar, beleza // pinta o novo grid e retorna PaintMainGrid() Return Endif // Opa ... Esbarrou em alguma coisa // Volta a pea pro lugar anterior // e recoloca a pea no Grid _aDropping := aClone(aOldPiece) SetGridPiece(_aDropping,_aMainGrid)

    // Incrementa o score em 4 pontos // Nao importa a pea ou como ela foi encaixada _nScore += 4

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 26/58

    // Agora verifica se da pra limpar alguma linha ChkMainLines() // Pega a proxima pea nPiece := _nNextPiece _aDropping := {nPiece,1,1,6} // Peca, direcao, linha, coluna

    If !SetGridPiece(_aDropping,_aMainGrid) // Acabou, a pea nova nao entra (cabe) no Grid // Desativa o Timer e mostra "game over" // e fecha o programa

    _GlbStatus := 2 // GAme Over

    // volta os ultimos 4 pontos ... _nScore -= 4

    // Cacula o tempo de operao do jogo _nGameClock := round(seconds()-_nGameClock,0) If _nGameClock < 0 // Ficou negativo, passou da meia noite _nGameClock += 86400 Endif

    // Desliga o timer de queda de pea em jogo _oTimer:Deactivate() Endif // Se a peca tem onde entrar, beleza // -- Repinta o Grid -- PaintMainGrid()

    // Sorteia a proxima pea // e mostra ela no Grid lateral

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 27/58

    If _GlbStatus != 2 // Mas apenas faz isso caso nao esteja em game over InitNext() _nNextPiece := randomize(1,len(_aPieces)+1) SetGridPiece( {_nNextPiece,1,1,1} , _aNext) PaintNext() Else // Caso esteja em game over, apenas limpa a proxima pea InitNext() PaintNext() Endif Endif

    Return

    /* ----------------------------------------------------------Recebe uma ao da interface, atravs de uma das letrasde movimentao de peas, e realiza a movimentao casohaja espao para tal.---------------------------------------------------------- */STATIC Function DoAction(cAct)Local aOldPiece

    // conout("Action = ["+cAct+"]")

    If _GlbStatus != 0 ReturnEndif

    // Clona a pea em quedaaOldPiece := aClone(_aDropping)

    if cAct $ 'AJ'

    // Movimento para a Esquerda (uma coluna a menos) // Remove a pea do grid DelPiece(_aDropping,_aMainGrid) _aDropping[4]-- If !SetGridPiece(_aDropping,_aMainGrid) // Se nao foi feliz, pinta a pea de volta _aDropping := aClone(aOldPiece) SetGridPiece(_aDropping,_aMainGrid) Endif // Repinta o Grid PaintMainGrid() Elseif cAct $ 'DL'

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 28/58

    // Movimento para a Direita ( uma coluna a mais ) // Remove a pea do grid DelPiece(_aDropping,_aMainGrid) _aDropping[4]++' If !SetGridPiece(_aDropping,_aMainGrid) // Se nao foi feliz, pinta a pea de volta _aDropping := aClone(aOldPiece) SetGridPiece(_aDropping,_aMainGrid) Endif // Repinta o Grid PaintMainGrid() Elseif cAct $ 'WI' // Movimento para cima ( Rotaciona sentido horario ) // Remove a pea do Grid DelPiece(_aDropping,_aMainGrid) // Rotaciona _aDropping[2]-- If _aDropping[2] < 1 _aDropping[2] := len(_aPieces[_aDropping[1]])-1 Endif If !SetGridPiece(_aDropping,_aMainGrid) // Se nao consegue colocar a pea no Grid // Nao possivel rotacionar. Pinta a pea de volta _aDropping := aClone(aOldPiece) SetGridPiece(_aDropping,_aMainGrid) Endif // E Repinta o Grid PaintMainGrid() ElseIF cAct $ 'SK' // Desce a pea para baixo uma linha intencionalmente MoveDown(.F.) // se o movimento foi intencional, ganha + 1 ponto _nScore++ ElseIF cAct == ' ' // Dropa a pea - empurra para baixo at a ltima linha // antes de baer a pea no fundo do Grid MoveDown(.T.)

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 29/58

    Endif

    // Antes de retornar, repinta o scorePaintScore()

    Return .T.

    Static function DoPause()

    If _GlbStatus == 0 // Pausa _GlbStatus := 1 _oTimer:Deactivate()Else // Sai da pausa _GlbStatus := 0 _oTimer:Activate()Endif

    // Antes de retornar, repinta o scorePaintScore()

    Return

    /* -----------------------------------------------------------------------Remove uma pea do Grid atual----------------------------------------------------------------------- */STATIC Function DelPiece(aPiece,aGrid)

    Local nPiece := aPiece[1]Local nPos := aPiece[2]Local nRow := aPiece[3]Local nCol := aPiece[4]Local nL, nCLocal cTeco, cPeca

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 30/58

    // Como a matriz da pea 4x4, trabalha em linhas e colunas// Separa do grid atual apenas a rea que a pea est ocupando// e desliga os pontos preenchidos da pea no Grid.For nL := nRow to nRow+3 cTeco := substr(aGrid[nL],nCol,4) cPeca := _aPieces[nPiece][1+nPos][nL-nRow+1] For nC := 1 to 4 If Substr(cPeca,nC,1)=='1' cTeco := Stuff(cTeco,nC,1,'0') Endif Next aGrid[nL] := stuff(_aMainGrid[nL],nCol,4,cTeco)Next

    Return

    /* -----------------------------------------------------------------------Verifica se alguma linha esta completa e pode ser eliminada----------------------------------------------------------------------- */STATIC Function ChkMainLines()Local nErased := 0

    For nL := 20 to 2 step -1 // Sempre varre de baixo para cima // Pega uma linha, e remove os espaos vazios cTeco := substr(_aMainGrid[nL],3) cNewTeco := strtran(cTeco,'0','') If len(cNewTeco) == len(cTeco) // Se o tamanho da linha se manteve, no houve // nenhuma reduo, logo, no h espaos vazios // Elimina esta linha e acrescenta uma nova linha // em branco no topo do Grid adel(_aMainGrid,nL) ains(_aMainGrid,1) _aMainGrid[1] := "11000000000011" nL++ nErased++ Endif Next

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 31/58

    // Pontuao por linhas eliminadas // Quanto mais linhas ao mesmo tempo, mais pontosIf nErased == 4 _nScore += 100ElseIf nErased == 3 _nScore += 50ElseIf nErased == 2 _nScore += 25ElseIf nErased == 1 _nScore += 10Endif

    Return

    /* ------------------------------------------------------Seta o score do jogo na telaCaso o jogo tenha terminado, acrescenta a mensagem de "GAME OVER"------------------------------------------------------*/STATIC Function PaintScore()

    If _GlbStatus == 0

    // JOgo em andamento, apenas atualiza score e timer _oScore:SetText(str(_nScore,7)+CRLF+CRLF+; '[Time]'+CRLF+str(seconds()-_nGameClock,7,0)+' s.')

    ElseIf _GlbStatus == 1

    // Pausa, acresenta a mensagem de "GAME OVER" _oScore:SetText(str(_nScore,7)+CRLF+CRLF+; '[Time]'+CRLF+str(seconds()-_nGameClock,7,0)+' s.'+CRLF+CRLF+; "*********"+CRLF+; "* PAUSE *"+CRLF+; "*********")

    ElseIf _GlbStatus == 2

    // Terminou, acresenta a mensagem de "GAME OVER" _oScore:SetText(str(_nScore,7)+CRLF+CRLF+; '[Time]'+CRLF+str(_nGameClock,7,0)+' s.'+CRLF+CRLF+; "********"+CRLF+; "* GAME *"+CRLF+; "********"+CRLF+; "* OVER *"+CRLF+; "********")

    Endif

    Return

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 32/58

    Concluso

    Para se tirar o melhor de cada ferramenta, devemos conhec-la, e quanto mais profundamente aconhecemos, melhores so os resultados que podemos obter dela. Espero que isso estimulem vocs aquererem saber mais, e colocar em prtica o seu conhecimento. E, aprender tambm pode serdivertido !!! Espero que todos gostem !! At o prximo post, pessoal

    Referncias

    TDN Totvs Development Network. TOTVS, 2015. Disponvel emhttp://tdn.totvs.com/display/tec/AdvPL (http://tdn.totvs.com/display/tec/AdvPL) . Acesso em: 26fev. 2015.

    TETRIS. In: WIKIPDIA, a enciclopdia livre. Flrida: Wikimedia Foundation, 2015. Disponvel em:[http://pt.wikipedia.org/w/index.php?title=Tetris&oldid=41347620(http://pt.wikipedia.org/w/index.php?title=Tetris&oldid=41347620)]. Acesso em: 23 fev. 2015.

    Tetris. (2015, February 16). In Wikipedia, The Free Encyclopedia. Retrieved 04:38, February 23,2015, from http://en.wikipedia.org/w/index.php?title=Tetris&oldid=647443725(http://en.wikipedia.org/w/index.php?title=Tetris&oldid=647443725)

    11 Comentrios

    Fontes do Blog no GitHub(https://siga0984.wordpress.com/2015/02/22/fontes-do-blog-no-github/)

    22/02/2015 Siga0984 ADVPL, ExemplosPara facilitar o processo de utilizao dos exemplos postados no blog, criei um repositrio pblico noGitHub. O repositrio pode ser acessado atravs do link https://github.com/siga0984/Blog(https://github.com/siga0984/Blog)

    Nele, esto todos os fontes utilizados nos exemplos postados no blog, e no cabealho de cada fonteexiste um link que aponta para qual post o fonte est relacionado. Ainda estou ajustando acodificao dos fontes, para a visualizao direta no navegador o encoding (codificao decaracteres) dos fontes deve ser UTF-8, porm o AdvPL trata as strings acentuadas dentro doscdigos como CP1252, interferindo no resultado final esperado. Ainda esta noite os fontes seroreconvertidos para CP1252 e atualizados.

    Para compilar os fontes, basta baix-los para o seu equipamento, e usando o IDE ou o TDS, criar

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 33/58

    Para compilar os fontes, basta baix-los para o seu equipamento, e usando o IDE ou o TDS, criarum projeto e acrescentar todos os cdigos dentro do projeto e compil-los. Alguns fontes tiveram osseus nomes ou o nome de suas funes trocados dentro do cdigo, para haver uma padronizaoda nomenclatura, e podem estar diferentes dos nomes publicados nos posts, para viabilizar acompilao de todos dentro de um projeto nico.

    Espero que todos faam bom proveito dos cdigos de exemplo, e que estes de alguma forma sejamteis para aprofundar o conhecimento de vocs no AdvPL. Caso algum tenha alguma dvida sobreos fontes, pode postar uma pergunta no prprio post onde o fonte foi utilizado.

    Por enquanto s, mas para a prxima semana estou preparando mais alguns cdigosinteressantes! At o prximo post, pessoal E obrigado pela audincia

    7 Comentrios

    Escalabilidade e performance Fila com job(https://siga0984.wordpress.com/2015/02/17/escalabilidade-e-performance-fila-com-job/)

    17/02/201517/02/2015 Siga0984 ADVPL, Avanado, Exemplos, Performance

    Introduo

    Em um tpico anterior, sobre escalabilidade e performance, foi abordado o tema das filas, e a suaimportncia na busca por desempenho em processos. Hoje, vamos ver mais de perto o que alinguagem AdvPL nos oferece para criarmos um ou mais processos dedicados sem interface visual,os Jobs, e com poderamos us-los para processar uma fila de requisies de processamento.

    Processamento assncrono

    Pelo pouco que vimos nos posts anteriores sobre AdvPL e SmartClient, vimos que a interface visualnativa do AdvPL possui a arquitetura client-server, e que trabalha de modo sncrono, com conexopersistente. E, voltando um pouco nas tcnicas para obter performance e escalabilidade, que quandofor possvel (e vamos entender essa palavra no contexto de onde for cabvel) utilize processosassncronos.

    O exemplo que serve como uma luva neste paradigma a criao de uma fila, e um processoseparado para o processamento das requisies colocadas nesta fila, onde o programa cliente da fila,ao colocar a solicitao de processamento na fila, no precisa esperar uma resposta imediata doprocesso para dar um retorno para a camada de interface.

    Acredito que eu j tenha mencionado isso no tpico a respeito de filas, por exemplo, para o envio de

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 34/58

    Acredito que eu j tenha mencionado isso no tpico a respeito de filas, por exemplo, para o envio dee-mails de notificao ou processos secundrios.

    Jobs

    Antes de nos afundarmos nas filas, vamos ver um pouco sobre os Jobs no AdvPL. Entendemos comoJob o processo de executar um programa desamarrado da camada de interface. O AdvPL nosfornece algumas formas de fazer isso.

    Funo StartJob()

    Permite, a partir de um programa AdvPL em execuo, iniciar um novo contexto de execuo deAdvpl para a execuo de uma funo em um determinado ambiente do Application Server, semrelao nenhuma com a camada de interface.

    Configurao JOBS da seo ONSTART

    O arquivo de configurao do Application Server permite que seja configurados um ou mais jobs,executados no momento da subida ou inicializao do servio do Application Server. Os jobs noexplicitamente configurados com um tipo especial so interpretados como jobs de execuo direta,onde inclusive podemos especificar quantas execues sero disparadas ao mesmo tempo (paralelo)de um determinado job.

    Partindo para a prtica

    No exemplo de hoje, vamos usar a configurao ONSTART, para iniciar um job dedicado a removeritens de uma fila global, e depois algumas funes interessantes para dar mais resilincia noprocesso. A fila de requisies possui um formato desenhado para ser um container independente derequisies, para armazenar um valor AdvPL qualquer (exceto CodeBlock e Objeto). No nossoexemplo, vamos apenas simular um processamento, que por baixo vai manter o job deprocessamento de fila em SLEEP() por 5 segundos. A fila vai ser mantida em memria, eindependente de quantas instncias de SmartClient forem usadas para abrir o programa que colocarequisies na fila, um Job ser mantido no ar para o processamento dos elementos da fila.

    O exemplo abaixo utiliza uma classe de fila global, com escopo por environment e por instncia deApplication Server. Para fazer o Download do arquivo que contm a classe AdvPL de fila global deexemplo, clique no link a seguir: APGlbQueue(https://siga0984.files.wordpress.com/2015/02/apglbqueue.docx)

    #include 'protheus.ch'

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 35/58

    /* ===================================================================Funo U_TSTQueueAutor Jlio WittwerData 08/02/2015Desrio Teste de fila global com processamento das requisies em JOB

    Configurao de onstart para o Job de processamento

    [ONSTART]Jobs=JOBFILA01

    [JOBFILA01]Environment=NOMEDOSEUAMBIENTEmain=U_FILAPROCnParms=1Parm1=FILA01=================================================================== */

    User Function TSTQueue()

    Local oDlgLocal oButton1Local oSay1Local oSay2Local oQueueLocal nStatusLocal cMsg1 := space(40)Local cMsg2 := space(40)Local cMsg3 := space(40) // Cria o objeto da fila global // Informa o nome da fila global e // a quantidade mxima de elementos

    oQueue := APGlbQueue():New( "FILA01", 10 )

    DEFINE DIALOG oDlg TITLE "Cliente da Fila" FROM 0,0 TO 160,300 PIXEL

    // Botao para acrescentar na fila uma requisicao de processamento

    @ 10,10 BUTTON oButton1 PROMPT "&Inserir requisio" ; ACTION ( InsertReq(oQueue,oSay1,oSay2) ) ; SIZE 080, 013 of oDlg PIXEL

    @ 30,10 SAY oSay1 VAR cMsg1 SIZE 080, 013 of oDlg PIXEL@ 40,10 SAY oSay2 VAR cMsg2 SIZE 080, 013 of oDlg PIXEL

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 36/58

    ACTIVATE DIALOG oDlg CENTER ; VALID ( MsgYesNo("Deseja encerrar a aplicao ?") )

    Return

    /* ----------------------------------------------------------------Insero de requisio na fila, disparada pelo boto de interfaceAtualiza valor na tela com o total de requisicoes enviadas ---------------------------------------------------------------- */

    STATIC Function InsertReq(oQueue,oSay1,oSay2)Local nValue Local nStatus

    // Requisicao fixa, 5 segundos ( 5000 ms ) nValue := 5000nStatus := oQueue:Enqueue( nValue )

    If nStatus < 0 // Falha ao inserir na fila MsgStop(oQueue:GetErrorStr(),"Falha ao acrescentar elemento na fila")Else // Inseriu com sucesso, atualiza informaoes na tela oSay1:SetText("Requisies inseridas ... "+cValToChar(oQueue:nEnqCnt)) oSay2:SetText("Itens na fila ........... "+cValToChar(nStatus))endif

    Return

    /* --------------------------------------------------------------JOB dedicado para retirar e processar elementos colocados na filaDeve ser iniciado na subida do servidor, no [ONSTART] como umjob, informando como parametro o ID da Fila Global------------------------------------------------------------- */

    USER Function FILAPROC( cQueueId )

    Local nStatusLocal oQueueLocal xRequest := NIL

    // Coloca observao do processo para Protheus MonitorPtInternal(1,"Job de Processamento - Fila "+cQueueId)

    If empty(cQueueId) // Se esta funcao foi chamada sem o ID da Fila ... UserException("Missing QueueId configuration for U_FILAPROC")Endif

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 37/58

    conout("["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] Iniciado.")

    // Cria a instncia para manuteno da filaoQueue := APGlbQueue():New( cQueueId )

    While !KillApp() // Loop de processamento permanece em execuo // desde que esta thread nao tenha recebido // uma notificao de finalizao // Tenta remover o primeiro item da fila nStatus := oQueue:Dequeue( @xRequest ) If ( nStatus >= 0 ) conout("["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] Tamanho da fila = "+cValToChar(nStatus)) // Pegou o primeiro item da fila // e retornou o numero de itens pendentes // neste momento na fila // Informa no console que vai fazer um "Sleep" conout( "["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] Processando ... " ) // Aqui eu poderia chamar qqer coisa // neste exemplo ser feito um sleep mesmo Sleep(xRequest) conout( "["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] Fim de processamento " )

    Else // Nao pegou nenhum item ... // Falha de lock ou Fila vazia conout("["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] "+oQueue:GetErrorStr())

    Endif Enddo

    conout("["+dtos(date())+" "+time()+"][JOB] Thread ["+cValToChar(ThreadId())+"] Finalizado.")

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 38/58

    Return

    Funcionamento

    Todas as explicaes especficas sobre o que cada parte do cdigo fazem esto dentro do prpriocdigo. Aqui vamos avaliar a execuo da aplicao como um todo. Aps compilar o fonte client deexemplo, e compilar o fonte que gerencia a fila global em memoria, e configurar o JOB para serexecutado na subida do Totvs Application Server, o programa U_FILAPROC ser colocado emexecuo via Job, sem interface, e ficar em loop fazendo Dequeue() da lista.

    Ao iniciarmos a aplicao U_TSTQUEUE atravs do SmartClient, cada acionamento do botoInserir requisio acrescenta na fila de requisies global uma requisio de processamento, quecontm um nmero de milissegundos que a funo de processamento deve permanecer em Sleep().Podemos subir mais de um programa client ao mesmo tempo. O programa usa uma classe deexemplo, APGlbQueue, que realiza as operaes de empilhamento e desempilhamento, queinternamente garante a semaforizao da operao de enqueue() e dequeue() (acrescentar e removerrequisio da fila, respectivamente). A fila utilizada do tipo FIFO (First In, First Out), isto , oprimeiro elemento acrescentado ser o primeiro a sair da fila (e ser processado).

    Cada acionamento da aplicao Client atualiza informaes na tela, mostrando quantas requisiesforam acrescentadas pela interface atual e quantas requisies ainda esto pendentes na fila derequisies. Podemos acompanhar o processamento da fila atravs das mensagens mostradas no logde console ( console.log) do Application Server. Caso a fila receba uma insero, mas o numero deelementos na fila j est no mximo (no nosso caso, 10 elementos), a operao de Enqueue() retornaum erro, indicando que a fila est cheia.

    Como cada requisio mantm o Job de processamento (Dequeue()) ocupado por 5 segundos, bastaaguardar um pouco para a fila ficar menor, e a operao de acrescentar um novo item ser possvel.Mesmo que todos os programas de interface sejam encerrados, o job de processamento est em umlooping separado e independente da interface, e continua em execuo.

    A classe de fila global criada para este exemplo utiliza um container de armazenamento global dememria do Appplication Server, onde basicamente usamos duas funes ( Set / Get ) paraarmazenar contedo nomeado na memria da instncia atual de um TOTVS Application Server, erecuper-lo dentro de qualquer job ou aplicao em execuo no mesmo Appplication Server.

    Consideraes sobre Balanceamento de Carga

    Caso este recurso seja colocado por exemplo, em um ambiente com balanceamento de carga noSmartClient, o servidor Master poder redirecionar a conexo para qualquer um dos serviosconfigurados para balanceamento. Logo, neste caso cada servio slave precisaria ter na seoOnStart uma chamada para colocar o job de Dequeue() em execuo, seno o client vai acrescentarelementos na fila, que nunca sero processados, at ele recusar novos elementos caso a fila atinja 10itens, ou ainda apenas um servio seja configurado para fazer o processamento da fila, com um oumais instncias do job, e a aplicao client da fila utilize um RPC por exemplo, para conectar-se como servio que processa a fila, para acrescentar na memria deste servio as novas requisies.

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 39/58

    Consideraes sobre a persisncia da fila

    Uma vez que a fila, no nosso exemplo, est sendo persistida em memria, se o servio doApplication Server for finalizado, o processamento pode ser encerrado antes de todas as requisiesda fila terem sido processadas, ento este exemplo no pode ser usado para etapas importantes deprocesso, que exijam por exemplo que a requisio desta fila no possa se perder em caso de umafalha grave ou finalizao anormal do servio. Caso seja necessria uma persistncia segura, ocorreto a fazer criar uma classe que persista a informao da fila em um banco de dados, para quequando o processo seja recolocado no ar, as requisies pendentes sejam processadas. E, removeruma requisio da fila no quer dizer que o processamento foi feito com sucesso, mas sim quealgum removeu para processar. Podem ser necessrios mais mecanismos para garantir que oprocesso tenha chego ao final com sucesso. Vamos abordar mais destas questes em um exemploum pouco mais rebuscado do que este, em um prximo post.

    Concluso

    Este post demorou quase 2 semanas para sair, afinal eu queria publicar algo realmente diferente, erelativamente simples, para aplicar conceitos como o das filas, paralelismo e jobs, e este exemplo tevepelo menos duas refatoraes at estar no ponto de ser servido !!! At o prximo post, pessoal

    6 Comentrios

    Interface visual do Advpl SmartClient(https://siga0984.wordpress.com/2015/02/02/interface-visual-do-advpl-smartclient/)

    02/02/2015 Siga0984 ADVPL, Exemplos, Interface, Programao

    Introduo

    Nos posts anteriores, vimos um pouco do que o SmartClient capaz de fazer, mas h muito mais aser visto. O que no vimos ainda so alguns detalhes, particularidades e caractersticas doSmartClient. Agora que j vimos um pouco do que ele faz, vamos ver mais algumas coisas, e umpouco de como isto feito por dentro.

    Conexo TCP

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 40/58

    Entre o SmartClient e o Application Server, existe uma conexo TCP estabelecida, que parte doSmartClient. No arquivo de configurao do SmartClient, chamado smartclient.ini, podemos criarsees nomeadas para especificar IP e Porta para a conexo com um Application Server. Na telainicial do SmartClient, podemos especificar o programa a ser executado, qual a conexo nomeadadeve ser utilizada, e qual o nome do ambiente (Environment) no Application Server deve atenderesta requisio de processamento.

    Time-Out de Comunicao

    Quando ativamos uma interface, como por exemplo uma janela ou caixa de dilogo, o APPlicationServer permanece dentro do mtodo Activate(), aguardando por interaes de interface. Nestasituao, o controle de execuo da aplicao est na interface. Mesmo que o operador no faanada na interface, apenas fique olhando para a tela, a cada 60 segundos o SmartClient envia umpulso na conexo com o Application Server, apenas para informar que ele (SmartClient) estvivo.

    Existe um mecanismo de time-out de inatividade de comunicao TCP no Application Server, ativoquando o controle de execuo est na interface. Caso o Application Server no receba nada (nemmesmo um pulso) em 3 minutos, ele (AppServer) assume que algo horrivel aconteceu com oSmartClient, ou com a conexo de rede entre ele e o Application Server. Neste caso, o APPlicationServer encerra a conexo, mostrando uma mensagem no log de console do Application ServerMsgManager TimeOut waiting for data, e encerra a aplicao em execuo, fecha as conexesem uso com gateways de dados ( DBAccess, c-Tree), solta locks de tabelas, e todos os demaisrecursos consumidos e alocados para aquele programa. Afinal, se a interface no est mais l, nofaz sentido manter o contexto da aplicao aberto segurando recursos.

    Time-Out por Inatividade de Interface

    Existe uma configurao diferenciada para o ambiente, chamada de INACTIVETIMEOUT, quesignifica um tempo de inatividade de interao do usurio na interface. Quando o servidor estaguardando por interao do usurio, se este fica apenas olhando para a tela, o Application Serverrecebe a cada um minuto um pulso, mas este pulso no conta como interao do usurio. Devehaver o disparo de algum evento de componente de interface para que este contador de inatividaderetorne ao zero novamente, como por exemplo uma troca de foco entre componentes. Caso estaconfigurao esteja habilitada, e o operador ficar mais do que o tempo configurado sem interagircom a interface, o SmartClient deste operador encerrado com uma mensagem de Time-Outatingido por inatividade.

    Balanceamento de Carga

    O TOTVS Application Server permite, de forma nativa, configurar um servio para balanceamentode conexes de SmartClient entre vrios servios do Application Server, na mesma mquina e/ou emoutras mquinas. Todo o procedimento de configurao desta funcionalidade est documentada na

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 41/58

    TDN, vide link http://tdn.totvs.com/pages/viewpage.action?pageId=6064861(http://tdn.totvs.com/pages/viewpage.action?pageId=6064861)

    Uma caracterstica interessante do balanceamento de carga do TOTVS Application Server, que oservio de balanceamento no atua como um gateway ou proxy reverso. O balanceador mantmuma conexo persistente de monitoramento com cada servio slave configurado nele, e no momentoque o balanceador recebe uma conexo do SmartClient, ele verifica entre os servios disponvels,quem est com o menor nmero de processos em execuo, e retorna ao SmartClient umainformao de redirecionamento, para que o SmartClient conecte diretamente no servio slave queele escolheu.

    Por isso, se o servio do balanceador for interrompido, isto no interfere nas conexes jestabelecidas e em execuo nos servios slave. E, tambm por esta razo, todos os IPs e portas detodos os servios slaves devem estar visveis para que qualquer SmartClient que conectar com oBalance, seja capaz de conectar com qualquer um dos slaves utilizazdos para balanceamento.

    Outro ponto interessante que o servio de Balance leva em conta um flag de aceite de conexo porparte do Slave. Meso que o servio slave esteja no ar, caso voc use o TOTVS Monitor, e bloqueie asconexes naquele servio, o servio de Balance vai saber que as conexes daquele slave estobloqueadas, e no vai mais considerar aquele slave na distribuio, at que ele seja novamentedesbloqueado para aceitar novas conexes. Esta caracterstica foi muito til quando foiimplementado um mecanismo de proteo de alocao de memria no Application Server, ondecaso a memria em uso por um Slave ultrapasse 90% do limite suportado pelo Application Server,este Slave automaticamente desabilita a entrada de novas conexes, e o Balance passa adesconsider-lo. Somente quando a memria daquele slave volta ao patamar de 80% do limite, oslave automaticamente libera novas conexes, e o Balance volta a consider-lo no algoritmo dedistribuio.

    Conexo SSL

    A conexo TCP entre SmartClient e Application Server troca pacotes em formato proprietrio, queno interpretvel facilmente, porm nesta camada no aplicada nenhuma criptografia.Dependendo do tamanho do pacote, ao utilizar por exemplo um Sniffer de rede, voc podereventualmente ver strings com textos e dados de campos serem trafegados. Para oferecer umaforma de criptografia segura das informaes trafegadas entre Application Server e SmartClient,podemos configurar o Application Server para utilizar um certificado digital seoSSLCONFIGURE e configurar uma porta SSL para receber as conexes do SmartClient. Assim,desta forma, os dados trafegados entre estas aplicaes vo estar efetivamente criptografados.

    Log de comunicao

    Todas as mensagens trocadas entre o APPlication Server e o SmartClient podem ser registradas nolog de console (Console.log) do APPlication Server, utilizando a chave LOGMESSAGES=1 na seo[GENERAL] do arquivo de configurao do APPlication Server (Appserver.ini). Experimente pegarum destes exemplos de interface, e executar com este log habilitado no APPlication Server, e vocvai ver quais e quando so trocadas as mensagens entre as aplicaes. Faa isso em um ambiente

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 42/58

    separado, de testes, pois a gerao deste log ao mesmo tempo por mltiplas conexes pode onerarum ambiente de produo. Este tipo de LOG somente necessrio e utilizado quando existe anecessidade de investigar um comportamento anormal da interface ou do APPlication Serverrelacionado ao uso de algum componente de interface, habilitado em ambientes especficos dedesenvolvimento e testes.

    Verses e Plataformas de Smartclient

    At o momento, o AdvPL possui 3 verses de interface SmartClient: O SmartClient em formatoexecutvel, compilado para as plataformas Windows, Linux e MAC; o SmartClient ActiveX, quepode ser publicado em um servidor HTTP / WEB e instalado como uma extenso / Plug-In doInternet Explorer, e por ltimo o SmartClient HTML, que pode ser instanciado pela maioria dosInternet Browses de mercado sem a necessidade de instalao de nenhuma extenso ou Plug-In.Existem algumas limitaes no Smartclient ActiveX e no SmartClient HTML, por exemplo aimpossibilidade de usar as funes de baixo nvel de arquivo ( fopen, fcreate, ) no sistema dearquivos da mquina onde o SmartClient est sendo utilizado.

    Como ainda no falamos nada sobre o sistema de arquivos do AdvPL, por hora vamos apenas saberque, usando as funes de acesso a disco e arquivos do AdvPL, podemos enderear arquivos namquina onde est sendo executado o TOTVS Application Server, a partir do RootPath doambiente, e podemos tambm acessar o sistema de arquivos da mquina onde o SmartClient queiniciou a execuo de uma aplicao est rodando. Este tema tambm ser abordadoposteriormente, em tpico especfico.

    Boas prticas

    O cenrio ideal para a utilizao de interface Client-Server persistente para processamento derequisies de retorno rpido. Relatrios extensos ou processamentos que demoram mais do quealguns minutos devem ser evitados de serem iniciados atravs de uma conexo de interface, afinal seesta conexo cair por qualquer razo durante o processamento, normalmente em at 3 minutos oAPPlication Server deve perceber que a conexo com o SmartClient foi perdida, e assim que issoacontecer, ele vai encerrar a execuo da aplicao, no importa se ela tenha terminado o seuprocessamento ou no. Em ambientes intranet, onde pressupe-se uma estabilidade maior naconexo, isto pode no ser to impactante, mas em um acesso remoto ou via internet, isso pode serum pouco traumtico.

    Para situaes como essas, existem relatrios que podem ser executados no sistema ERP em Job(processo sem interface), ou colocados para serem executados em um Scheduler (agendador) doERP, onde voc pode programar a execuo de um relatrio ou recalculo que vai demorar muitashoras, e program-lo para ser executado durante a noite, o processamento no vai depender deinterface, e se tudo correr bem, estar pronto na manh seguinte.

    As boas prticas de interface vo um pouco alm do cdigo, vo desde a disposio doscomponentes em tela de forma visualmente agradvel e lgica, dada a sequncia da operao dosistema, at a coerncia e clareza das mensagens e informaes solicitadas aos usurios ouoperadores durante o uso da aplicao. Nem sempre uma mensagem que muito clara para o

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 43/58

    programador entendida pelo usurio. Normalmente colca-se uma mensagem com teor explicativoao operador do sistema, e em um segundo ponto da mensagem, informaes tcnicas teis para odepartamento de suporte e programao.

    Existem ainda outras boas prticas da programao para a interface AdvPL. Uma delas nointerromper uma operao transacionada na base de dados com uma operao de interface queaguarde uma deciso de usurio. Isso mantm a transao aberta por tempo indeterminado, emantm o bloqueio (lock) de registros na base de dados, o que pode prejudicar outros processosconcorrentes. Caso o operador tenha iniciado um processo e sado de sua estao para tomar umcaf, ele somente vai ver a mensagem quando ele voltar, e durante este tempo registros da base dedados permaneceram bloqueados, impedindo outros processos concorrentes que dependam dobloqueio nestes registros para serem executados. Normalmente a operao transacionada em basede dados realizada usando as instrues BEGIN TRANSACTION e END TRANSACTION. Vamosentrar nesse assunto em maior profundidade em posts posteriores sobre a o acesso a tabelas e dadosno AdvPL.

    Exemplo AdvPL

    E, quase no final do tpico, vamos a mais um exemplo AdvPL, de uma rotina que procura obter omximo de informaes possveis do SmartClient em execuo, que iniciou o programa atual. Seguefonte abaixo:

    #include 'protheus.ch'

    /* ======================================================================Funo U_RmtDet()Autor Jlio WittwerData 01/02/2015Descrio Monta uma caixa de dilogo com todas as informaes possveisde se obter do SmartClient que iniciou este programa.====================================================================== */

    User Function RmtDet()

    Local oDlgLocal nRmtTypeLocal cRmtTypeLocal cRmtLibLocal oFontLocal cInfo := ''

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 44/58

    // Habilita interface com data mostrada com 4 digitos no ano// e Habilita data em formato britnico ( Dia/Mes/Ano )SET CENTURY ONSET DATE BRITISH

    cRmtLib := ''nRmtType := GetRemoteType ( @cRmtLib )

    DO CASE CASE nRmtType == -1 UserException("Invalid JOB/BLIND call to U_RmtDet") CASE nRmtType == 0 cRmtType := 'SmartClient Delphi' CASE nRmtType == 1 cRmtType := 'SmartClient QT Windows' CASE nRmtType == 2 cRmtType := 'SmartClient QT Linux/Mac' CASE nRmtType == 5 cRmtType := 'SmartClient HTML' OTHERWISE cRmtType := 'Remote Type '+cValToChar(nRmtType)ENDCASE

    If !empty(cRmtLib) cRmtType += ' ('+cRmtLib+')'Endif

    // Usa uma fonte mono-espaada DEFINE FONT oFont NAME 'Courier New'

    // Cria uma caixa de dilogo com rea util de 640x480 PIXELsDEFINE DIALOG oDlg TITLE (cRmtType) FROM 0,0 TO 480,640 PIXEL

    // Informaes da Interfae remotacRmtBuild := GetBuild(.T.)cRmtIp := GetclientIP()cUsrName := LogUserName()dRmtDate := GetRmtDate()cRmtTime := GetRmtTime()aRmtInfo := GetRmtInfo()cRmtTmp := GetTempPath(.T.)lActivex := IsPlugin()lSSLConn := IsSecure()

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 45/58

    cInfo += 'SmartClient Build ....... ' + cRmtBuild + CRLFcInfo += 'SmartClient Activex ..... ' + IIF(lActivex,"SIM","NAO") + CRLF cInfo += 'SmartClient Connection .. ' + IIF(lSSLConn ,"SSL","TCP") + CRLF cInfo += 'Remote IP ............... ' + cRmtIp + CRLF cInfo += 'Remote User Name ........ ' + cUsrName + CRLF cInfo += 'Remote DateTime ......... ' + dtoc(dRmtDate)+' '+cRmtTime + CRLFcInfo += 'Remote Temp Path ........ ' + cRmtTmp + CRLFcInfo += 'Remote Computer Name .... ' + aRmtInfo[1] + CRLFcInfo += 'Remote O.S. ............. ' + aRmtInfo[2] + CRLFcInfo += 'Remote O.S. Detais ...... ' + aRmtInfo[3] + CRLFcInfo += 'Remote Memory (MB) ...... ' + aRmtInfo[4] + CRLFcInfo += 'Remote CPU Count ........ ' + aRmtInfo[5] + CRLFcInfo += 'Remote CPU MHZ .......... ' + aRmtInfo[6] + CRLFcInfo += 'Remote CPU String ....... ' + aRmtInfo[7] + CRLFcInfo += 'Remote O.S. Language .... ' + aRmtInfo[8] + CRLFcInfo += 'Remote Web Browser ...... ' + aRmtInfo[9] + CRLF

    // Coloca a string com os dados dentro de um Get Multiline@ 5,5 GET oGet1 VAR cInfo MULTILINE FONT oFont SIZE 310,230 OF oDlg PIXEL

    ACTIVATE DIALOG oDlg CENTER

    Return

    O resultado obtido com a execuo do programa deve ser algo parecido com a caixa de dilogoabaixo:

    (https://siga0984.files.wordpress.com/2015/02/appint03.png)

    Exemplo da execuo do programaU_RmtDet

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 46/58

    Concluso

    Como eu j havia dito, com AdvPL podemos fazer muitas coisas, inclusive um ERP. Eu (ainda) notenho todas as demais interfaces de SmartClient configuradas no meu ambiente, ento pode ser quealguma das funes utilizadas no exemplo acima retornem contedos em branco ou apresentemerro de execuo caso este exemplo seja executado dentro de um SmartClient ActiveX ouSmartClient HTML. De qualquer modo, eles foram concebidos para serem capazes de renderizar ainterface da mesma forma.

    Muitas vezes o arroz com feijo ser usado na maioria das rotinas e interfaces do sistema, pormalgumas rotinas podem precisar de algo um pouco mais especfico, um pouco mais rebuscado. E alinguagem capaz de prover isso. At o prximo post, pessoal

    Referncias

    http://tdn.totvs.com/display/tec/GetRemoteType (http://tdn.totvs.com/display/tec/GetRemoteType)http://tdn.totvs.com/display/tec/GetBuild (http://tdn.totvs.com/display/tec/GetBuild)http://tdn.totvs.com/display/tec/GetComputerName(http://tdn.totvs.com/display/tec/GetComputerName)http://tdn.totvs.com/display/tec/GetRmtDate (http://tdn.totvs.com/display/tec/GetRmtDate)http://tdn.totvs.com/display/tec/GetRmtTime (http://tdn.totvs.com/display/tec/GetRmtTime)http://tdn.totvs.com/display/tec/GetRmtInfo (http://tdn.totvs.com/display/tec/GetRmtInfo)http://tdn.totvs.com/display/tec/GetTempPath (http://tdn.totvs.com/display/tec/GetTempPath)http://tdn.totvs.com/display/tec/IsPlugin (http://tdn.totvs.com/display/tec/IsPlugin)http://tdn.totvs.com/display/tec/IsSecure (http://tdn.totvs.com/display/tec/IsSecure)http://tdn.totvs.com/display/tec/LogUserName (http://tdn.totvs.com/display/tec/LogUserName)

    1 comentrio

    Interface Visual do AdvPL Parte 02(https://siga0984.wordpress.com/2015/01/24/interface-visual-do-advpl-parte-02/)

    24/01/2015 Siga0984 ADVPL, Exemplos, Interface

    Introduo

    No post anterior, vimos alguns exemplos bsicos de interface. Neste post, vamos ver uma tela

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 47/58

    No post anterior, vimos alguns exemplos bsicos de interface. Neste post, vamos ver uma telausando vrios componentes juntos, explorando mais possibilidades de uso. A diagramao da telade exemplo praticamente no existe, os componentes no esto alinhados para uma apresentaovisual perfeita, o objetivo do exemplo puramente didtico.

    O exemplo

    O fonte foi criado usando os comandos de interface, que por baixo vo chamar os construtores dosobjetos visuais e seus mtodos. Cada componente utilizado merece uma ateno especial, portantoneste post vamos avaliar o exemplo como um todo, e ver superficialmente o que cada componenteutilizado permite fazer. Como o fonte deste exemplo ficou um pouco maior que os demais, ele estardisponvel no link APPInt02 (https://siga0984.files.wordpress.com/2015/01/appint02.doc), emformato .doc. O Blog no permite que eu suba arquivos fonte ou compactados, porm o fonte estinteiro dentro do arquivo, basta copi-lo e col-lo na tela do IDE/TDS para compilar e execut-lo. Aoser compilado e executado diretamente pelo SmartClient, atravs da funo U_APPINT02, oresultado de tela esperado pode ser visto abaixo:

    (https://siga0984.files.wordpress.com/2015/01/appint02.png)

    Componentes utilizados

    O container principal de interface usado neste exemplo foi uma tDialog(), ela ser o pai de todos osdemais componentes, e ento o cdigo cria todos os componentes filhos desta caixa de dilogo, ealguns containers para a disposio de mais elementos visuais.

    @ GROUP

    O comando @ GROUP permite criar um componente de interface da classe tGroup(), com afinalidade de desenhar um quadrado ou retngulo na interface, opcionalmente com um ttulo, paraindicar visualmente que os componentes desenhados dentro da rea do componente esto dealguma forma relacionados. Vale lembrar que o objeto tGroup() no um container. Isto significaque, para colocar outro componente na rea interna de um tGroup(), o componente visual deve ter o

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 48/58

    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 vrios objetos de edio 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 demostrar um texto na caixa de dilogo. Atravs dos parmetros, informamos a coordenada esquerdasuperior onde o componente ser desenhado, e o tamanho (comprimento,altura) reservados para amensagem ser mostrada. Caso a mensagem a ser mostrada ultrapasse o comprimento docomponente, haver uma quebra de linha automtica no texto da mensagem.

    @ GET

    O comando @ GET permite criar um componente de interface da classe tGet(), com a finalidade depermitir a entrada e edio de dados pela interface. Podem ser editados campos do tipo C(Caractere), N (Numricos) e D (Data). Para os campos caractere e numricos, podemos especificaruma mscara (Picture), onde podemos especificar o formato que a informao ser mostrada na telae aplicar converses automaticamente. Por exemplo, ao informarmos a picture @!, o componenteautomaticamente vai transformar cada caractere alfabtico digitado para caixa alta (letramaiscula), mesmo que voc digite uma letra minscula. J a mscara numrica 999999999 vaipermitir apenas a entrada de um nmero, com no mximo 9 dgitos, exclusivamente numricos.Qualquer caractere no numrico digitado ser ignorado.

    @ GET MULTILINE

    Uma variao do @ GET foi implementada, usando a classe TMultiGet(), para permitir a edio deum contedo string em modo de mltiplas linhas, til quando precisamos editar uma descrio detexto livre. Esta opo de edio no permite formatao, apenas quebra de linha usando a tecla[Enter], e tabulao usando [Control]+[I].

    @ CHECKBOX

    O comando @ CHECKBOX permite criar um componente de interface da classe tCheckBox(), com afinalidade de editar um valor booleano, atravs de uma caixa de seleo do tipo Check, onde avarivel 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(),

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 49/58

    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 sempremostra a escolha atual, e caso voc clique na seta para baixo mostrada na frente da opo escolhida, mostrado abaixo do componente a lista de opes disponveis para a escolha. Ao escolher umaopo, a varivel 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 afinalidade de permitir escolher um item de um array de strings. O componente sempre mostra emmodo destacado a escolha atual, porm todas as opes possveis so mostradas na rea da telaparametrizada para o componente. Ao escolher uma opo, a varivel amarrada ao componenterecebe a string contida no elemento do array escolhido.

    @ FOLDER

    O comando @ FOLDER permite criar um componente de interface da classe tFolder(), com afinalidade de criar um ou mais pastas dentro da rea usada pelo componente, onde cada pastapossui um objeto interno para mostrar outros componentes de tela. Cada container de um folder criado internamente, usando um objeto da classe tFolderPage, disponibilizado na propriedadeaDialogs do objeto tFolder(). O primeiro elemento corresponde ao primeiro folder, o segundoelemento ao segundo folder, e assim sucessivamente. No exemplo proposto, foram colocados apenasum 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 umfolder 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 afinalidade de servir de container para outros componentes de iterface. O Painel pode ter um efeito dedestaque em suas bordas ( RAISED / LOWERED ), e outras propriedades de alinhamento que serovistas com maior profundidade posteriormente. No fonte de exemplo, dentro do painel foi criadoapenas um objeto tSay() para demonstrao.

    @ RADIO

    O comando @ RADIO utiliza internamente a classe TRadMenu(), e tm um funcionamento parecidocom o componente LISTBOX, para mltipla escolha a partir de uma lista de possibilidades pr-definida. Porm, visualmente, a opo escolhida marcada com um Bullet esquerda do texto.Ao marcarmos uma nova opo, a opo anteriormente marcada imediatamente desmarcada. E, avarivel associada ao componente recebe um valor numrico, correspondente a opo marcada.

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 50/58

    @ SCROLLBOX

    O comando @ SCROLLBOX permite criar um componente de interface da classe tScrollBox(), coma finalidade de desenhar um container com barra de rolagem horizontal ou vertical, para comportaroutros componentes de interface. No nosso exemplo, reservamos uma rea do dilogo para estecomponente, e desenhamos dentro dele dois tSay(), um no incio da rea util, visvel na construodo componente, e um segundo tSay() em uma coordenada dentro do componente, onde ele somenteser visivel caso seja utilizada a barra de rolagem do componente.

    @ METER

    O comando @ METER cria uma barra horizontal de progresso, utilizando um objeto da classetMeter(). Este objeto permite que seja setado um valor total de passos, correspondendo a 100 % dabarra preenchida, e permite que durante a execuo da aplicao, conforme o programa sejaexecutado, o valor da posio atual na barra de progresso seja setado.

    DEFINE BUTONBAR

    Para uma caixa de dilogo, podemos criar uma barra de botes, alinhada no rodap, no topo, ou smargens da caixa de dilogo. A barra de botes uma instncia da classe TBar(), que deve apenasconter botes com imagens, instncias da classe TBtnBmp().

    DEFINE BUTTON BUTTONBAR

    Na barra de botes criada pelo comando DEFINE BUTTONBAR, criamos botes dentro da barrausando este comando. Cada boto deve usar uma imagem, que pode estar compilada no repositriode objetos ou estar em um arquivo no disco. No exemplo proposto, foram usados algumas imagensdisponveis no repositrio de objetos do ERP Microsiga verso 11.

    @ BTNBMP

    Este comando cria um boto muito similar ao boto de uma barra de botes, para ser colocadolivremente na interface, fora da barra de botes. Internamente, este componente usa a classetBtnBmp2()

    DEFINE SBUTTON

    Existem 23 tipos de botes com imagem pr-definidos no AdvPL, ainda utilizados em fontescustomizados de verses anteriores ao Protheus. Cada boto possui um identificador de tipo, ondepara cada tipo uma imagem diferente associada ao boto.

  • 14/03/2015 Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

    https://siga0984.wordpress.com/ 51/58

    DEFINE TIMER

    A classe tTimer() permite o disparo automtico de um CodeBlock no servidor, a partir de uma caixade dilogo ou janela, quando o controle de execuo estiver no SmartClient,

    Outros componentes

    Ainda existem diversos componentes grficos, desde a criao de grficos at plotagem livre deinterface (desenhar a interface), menus, rvores e afins. A utilizao da Interface no AdvPL umtema bem extenso, ainda estou estudando como desmembr-lo nos prximos posts deste tema.

    Todas as cl