Você está na página 1de 58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Tudo em ADVPl
Compartilhando experincias de anlise, programao
e desenvolvimento.

Imagens no SGDB via DBAccess


(https://siga0984.wordpress.com/2015/03/09/im
agens-no-sgdb-via-dbaccess/)
09/03/201509/03/2015
Orientao a Objetos

Siga0984

ADVPL, Classes, DBAcces, Exemplos, Imagens,

Introduo
Recebi um e-mail, ou mensagem, ou post (no lembro agora) com uma sugesto interessante, para
este 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 (Bitmap
Image File), JPEG (Joint Photographic Experts Group), PNG (Portable Network Graphics), entre
outros. 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, usando
cdigos ASCII de 0 a 255 (contedo binrio), que so interpretados por uma aplicao capaz de
mostrar seu contedo em uma interface. Cada tipo de imagem possui algumas caractersticas, como
resoluo, 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 Microsoft1/58

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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

Acesso pelo DBAccess


Como de conhecimento de todos que trabalham com o ERP Microsiga, todo o accesso a Banco de
Dados relacional no Protheus feito atravs do DBAccess, um gateway de acesso para bancos
relacionais, que tambm capaz de emular o acesso ISAM, ainda usado por boa parte do cdigo
legado 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 nos
utilizarmos destes tipos de campo, devemos criar uma tabela no SGDB usando o tipo de campo
M (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 acesso
atravs 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 possvel
armazenar no SGDB uma imagem maior que isso. E, como o acesso ao contedo do campo feito
pelo DBAccess, no possvel fazer uma Query que recupere diretamente o contedo de um campo
BLOB, CLOB ou IMAGE.
Para acessar o contedo de um campo M Memo criado em uma tabela, devemos abrir a tabela no
AdvPL usando DbUseArea() ou ChkFile(), para uma tabela de dados do ERP , posicionar no
registro 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 contedo
deste campo e coloc-lo na varivel de memria.
Adicionalmente, o campo M Memo originalmente no AdvPL foi projetado para suportar apenas
64 KB de dados, e somente conseguimos aumentar esse limite para 1MB habilitando a configurao
TOPMEMOMEGA=1 na configurao do environment desejado no arquivo de configurao do
TOTVS Application Server (appserver.ini) Vide TDN, no link
http://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 de
dados do ERP, deve-se tomar cuidado quando a leitura destes campos for realizada por muitos
processos simultaneamente, isto pode gerar um gargalo na camada de rede entre as aplicaes
TOTVS Application Server, DBACcess e o SGDB.
https://siga0984.wordpress.com/

2/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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

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 do
AdvPL, para armazenar a imagem. Podemos encapsular isso em uma classe vamos cham-la
ApDbImage() e implementar os mtodos de leitura e gravao, manuteno e status, alm de
dois mtodos adicionais para ler imagens de um arquivo no disco para a memria, e gravar a
imagem da memria para o disco.
A classe APDBIMAGE() foi implementada com este propsito, mas ela tm ainda alguns detalhes
adicionais, ela guarda um HASH MD5 gerado a partir da imagem original, um campo separado
para 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 os
formatos 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 na
interface, ela pode ser gravada em disco (ou exportada), no mesmo formato que foi aberto o
programa no realiza converses , e tambm pode ser inserida no DBimage com um nome
identificador qualquer, ou usada para alterar uma imagem j existente na tabela de imagens.
O fonte da classe APDBImage() pode ser baixado no link
https://github.com/siga0984/Blog/blob/master/ApDBImage.prw
(https://github.com/siga0984/Blog/blob/master/ApDBImage.prw) , e o fonte de exemplo que usa a
classe 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 de
testes da classe ApDbImage().
E, para os curiosos e vidos por cdigo, segue o fonte da Classe APDBIMAGE logo abaixo:
#include "protheus.ch"

https://siga0984.wordpress.com/

3/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

/* --------------------------------------------------Classe ApDBImage
Autor Jlio Wittwer
Data 27/02/2015
Verso 1.150308
Descrio 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+DBAcc
ess
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()

https://siga0984.wordpress.com/

4/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// Metodos de acesso de imagens no disco


METHOD LoadFrom( cFile, cImgBuffer )
METHOD SaveTo( cFile, cImgBuffer )
ENDCLASS
/* --------------------------------------------------------Construtor da classe de Imagens no SGDB
Apenas inicializa propriedades
-------------------------------------------------------- */
METHOD New() CLASS APDBIMAGE
::bOpened := .F.
::cError := ''
Return self
/* --------------------------------------------------------Abre a tabela de imagens no SGDB
Conecta no DBAccess caso nao haja conexo
--------------------------------------------------------- */
METHOD Open( ) CLASS APDBIMAGE
Local nDBHnd := -1
Local 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.
Endif
Endif
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})
https://siga0984.wordpress.com/

5/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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 compartilhado
USE ("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 memoria
recebe o nome da imgem, retorna por referencia o tipo
da imagem e seu conteudo
-------------------------------------------------------- */
METHOD ReadStr( cImgId , /* @ */cImgType, /* @ */ cImgBuffer )
CLASS APDBIMAGE
::cError := ''

https://siga0984.wordpress.com/

6/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 memria
cImgBuffer := Decode64(ZDBIMAGE->ZDB_MEMO)
cImgType := ZDBIMAGE->ZDB_TYPE
Return .T.
/* --------------------------------------------------------Insere uma imagem na tabela de imagens do SGDB
Recebe o ID da imagem, o tipo e o buffer
-------------------------------------------------------- */
METHOD Insert( cImgId , cImgType, cImgBuffer ) CLASS APDBIMAGE
Local 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)
https://siga0984.wordpress.com/

7/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 imagens
Recebe 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

https://siga0984.wordpress.com/

8/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// Se a imagem existe, atualiza


IF !ZDBIMAGE->(DbrLock(recno()))
::cError := 'Image Id ['+cImgId+'] update lock failed.'
Return .F.
Endif
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())
Return .T.
/* --------------------------------------------------------Deleta fisicamente uma imagem da Tabela de Imagens
-------------------------------------------------------- */
METHOD Delete( cImgId , lHard ) CLASS APDBIMAGE
Local 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 deleo
nRecNo := ZDBIMAGE->(recno())
// Mesmo que a deleo seja fisica, eu garanto
// o lock do registro na camada do dbaccess
If !ZDBIMAGE->(DbrLock(nRecNo))
::cError := 'Image Id ['+cImgId+'] delete lock failed.'
Return .F.
Endif

https://siga0984.wordpress.com/

9/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// Deleta fisicamente no SGBD


nErr := 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 DBAccess
ZDBIMAGE->(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 APDBIMAGE
Descrio 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 APDBIMAGE
Local cOldAlias := Alias()
Local cQuery
Local nCountAll := 0
Local nSizeAll := 0
::cError := ''
aStat := {}
If !::bOpened
::cError := "APDBIMAGE:Status() Error: Instance not opened."
Return .F.
Endif
https://siga0984.wordpress.com/

10/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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()
Enddo
USE
// Acrescenta total de imagens
aadd(aStat , {"TOTAL_COUNT_ALL",nCountAll})
// Levanta o total de bytes usados por tipo de imagem
cQuery := "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()
Enddo
USE
// 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 memoria
Nao requer que a instancia esteja inicializada / Aberta
--------------------------------------------------------- */
METHOD LoadFrom( cFile, /* @ */ cImgBuffer ) CLASS APDBIMAGE
Local nH, nSize, nRead
::cError := ''
If !file(cFile)
::cError := "APDBIMAGE:LoadFrom() Error: File ["+cFile+"]not
found."
Return .F.
Endif
https://siga0984.wordpress.com/

11/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 <= 0
::cError := "APDBIMAGE:LoadFrom() File Size Error : Empty File"
fClose(nH)
Return .F.
Endif
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 memoria
cImgBuffer := 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
memoria
Nao requer que a instancia esteja inicializada / Aberta
--------------------------------------------------------- */
METHOD SaveTo( cFile, cImgBuffer ) CLASS APDBIMAGE
Local nH, nSize , nSaved
::cError := ''
https://siga0984.wordpress.com/

12/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 um
assistente para, por exemplo, importar uma pasta cheia de imagens para o banco de dados, dando o
nome das imagens automaticamente baseado no nome do arquivo original, e o fato dela gerar o
MD5 Hash a partir do buffer binrio original pode permitir uma busca mais rpida por imagens
idnticas repetidas dentro do banco, fazendo apenas uma Query para mostrar quais os ImageIDs
que possuem o mesmo HASH !!!
Pessoal, novamente agradeo a audincia, espero que gostem do Post. J tenho alguma coisa no
forno 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
https://siga0984.wordpress.com/

13/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 um
programinha interessante um clone do famoso jogo Tetris. Sim, aquele que os blocos caem e voc
precisa 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 as
explicaes de como ele funciona por dentro do que bolar a lgica e interface do game. Ao ser
executado, o programa j abre direto uma caixa de interface com uma pea sorteada em queda
progressiva em intervalos de 1 segundo, mostrando a prxima pea a ser usada, atualizando um
score lateral e um contador de tempo de jogo. Voc pode usar as letras ASDW ou JKLI para mover a
pea em queda para a esquerda, para baixo, para a direita e rotacion-la, respectivamente, e a barra
de espaos para dropar a pea at a linha inferior que ela pode alcanar na tela, sendo possvel neste
ponto ainda mover a pea para a direita ou esquerda, ou mesmo rotacion-la caso exista espao
hbil para tais operaes.Ainda pode ser usada a letra P para colocar e retirar o jogo de modo
Pause. 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

https://siga0984.wordpress.com/

14/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Na verso inicial, o jogo no foi montado usando orientao a objeto. Ele ainda ser passado a
limpo, com classes e propriedades, mas j funciona muito bem com o paradigma estruturado. Em
poucas 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 de
cada linha corresponde a um quadradinho colorido. A tela do jogo desenhada na interface do
Advpl em um Grid de 20 x 10 imagens, onde o jogo lida com a aplicao de peas e movimento de
peas dentro desse array, e a camada de interface apenas atualiza essa matriz na interface trocando
os 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 cada
linha uma string com um valor numrico, onde 0 significa um espao no preenchido, e cada
valor maior que zero representa uma imagem 1010 de uma cor diferente na respectiva posio do
grid de bitmaps da interface. Como todo o jogo baseado em array de strings, cada pea e sua
respectiva 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 do
array que representa a interface, e no caso de colocar a pea, a funo somente deve conseguir
realizar esta operao caso as posies dos quadrados usados pela pea estejam vazias no Grid. A
animao consiste apenas em remover a pea em jogo da posio atual do grid no array, inseri-la
em uma nova posio, e repintar o grid de interface. Esta pintura realizada simplesmente setando
novamente o nome do resource (bitmap) usado naquela posio, caso ele esteja diferente do recurso
indicado no array.
Cada STATIC FUNCTION do cdigo tem um propsito distinto, e para economizar com a
passagem de parmetros, como este fonte executado sem recurso, as variveis contendo o estado
do 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 uma
tecla de atalho ( prefixada com & ). Neste caso especifico, quando o foco da interface est em um
componente que no permite edio de contedo, o uso das teclas de atalho no precisa ser
concomitante 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. claro
que no um fonte arroz com feijo, esse prato tm uns temperos um pouco mais puxados, e
como ele no foi escrito usando orientao a objeto, ele requer um pouco mais de ateno para ser
assimilado na ntegra. Posteriormente eu vou fazer a segunda verso desse clone, com todas as
funcionalidades do primeiro, porm usando Orientao a Objetos do AdvPL, fazendo isso vai ficar
muito 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 GitHub
https://github.com/siga0984/Tetris (https://github.com/siga0984/Tetris) , bem como um patch
gerado para o Protheus 11 ( RPO TOP , Portugus ) com apenas o fonte Tetris.PRW e as imagens
do projeto. Para gerar a aplicao, basta ter um Protheus 10 ou superior, criar um projeto em

https://siga0984.wordpress.com/

15/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 funo
U_TETRIS
E, pra voc que est se coando de curiosidade, o fonte com todos os comentrios e afins, ficou com
pouco mais de 700 linhas. Segue abaixo o fonte AdvPL do clone do Tetris.
#include "protheus.ch"
/* ========================================================
Funo U_TETRIS
Autor Jlio Wittwer
Data 03/11/2014
Verso 1.150226
Descriao Rplica do jogo Tetris, feito em AdvPL
Para jogar, utilize as letras :
A ou J = Move esquerda
D ou L = Move Direita
S ou K = Para baixo
W ou I = Rotaciona sentido horario
Barra de Espao = Dropa a pea
Pendencias
Fazer um High Score
Cores das peas
O = Yellow
I = light Blue
L = Orange
Z = Red
S = Green
J = Blue
T = Purple
======================================================== */

https://siga0984.wordpress.com/

16/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

STATIC _aPieces := LoadPieces() // Array de peas do jogo


STATIC _aBlockRes := {
"BLACK","YELOW2","LIGHTBLUE2","ORANGE2","RED2","GREEN2","BLUE2","PU
RPLE2" }
STATIC _nGameClock // Tempo de jogo
STATIC _nNextPiece // Proxima pea a ser usada
STATIC _GlbStatus := 0 // 0 = Running 1 = PAuse 2 == Game Over
STATIC _aBMPGrid := array(20,10) // Array de bitmaps de interface
do jogo
STATIC _aBMPNext := array(4,5) // Array de botmaps da proxima pea
STATIC _aNext := {} // Array com a definio e posio da proxima
pea
STATIC _aDropping := {} // Array com a definio e posio da pea
em jogo
STATIC _nScore := 0 // pontuao da partida
STATIC _oScore // label para mostrar o score e time e mensagens
STATIC _aMainGrid := {} // Array de strings com os blocos da
interface representados em memoria
STATIC _oTimer // Objeto timer de interface para a queda automtica
da pea em jogo
// =======================================================
USER Function Tetris()
Local nC , nL
Local oDlg
Local oBackGround , oBackNext
Local oFont , oLabel , oMsg
// Fonte default usada na caixa de dilogo
// e respectivos componentes filhos
oFont := 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

https://siga0984.wordpress.com/

17/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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
Next
Next
// 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
Next
Next
// 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

https://siga0984.wordpress.com/

18/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

@ 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 iniciada
oDlg:bInit := {|| Start() }
ACTIVATE DIALOG oDlg CENTER
Return
/* -----------------------------------------------------------Funo Start() Inicia o jogo
------------------------------------------------------------ */
STATIC Function Start()
Local aDraw

https://siga0984.wordpress.com/

19/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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 Grid
InitGrid()
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 memoria
Em memoria, o Grid possui 14 colunas e 22 linhas
Na tela, so mostradas apenas 20 linhas e 10 colunas
As 2 colunas da esquerda e direita, e as duas linhas a mais
sao usadas apenas na memoria, para auxiliar no processo
de 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

https://siga0984.wordpress.com/

20/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

//
// 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 pea
Local nPos := aOnePiece[2] // Posio ( para rotacionar )
Local nRow := aOnePiece[3] // Linha atual no Grid
Local nCol := aOnePiece[4] // Coluna atual no Grid
Local nL , nC
Local 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 grid
For 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

https://siga0984.wordpress.com/

21/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Release 20150222 : Optimizao na camada de comunicao, apenas


setar
o 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
Next
Next
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
Next
Next
Return
/* ---------------------------------------------------------------Carga do array de peas do jogo
Array multi-dimensional, contendo para cada
linha a string que identifica a pea, e um ou mais
arrays de 4 strings, onde cada 4 elementos
representam uma matriz binaria de caracteres 4x4
para desenhar cada pea
Exemplo - Pea "O"
https://siga0984.wordpress.com/

22/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 posio
aadd(aLPieces,{'O', { '0000','0110','0110','0000'}})
// Pea "I" , em p e deitada
aadd(aLPieces,{'I', { '0000','1111','0000','0000'},;
{ '0010','0010','0010','0010'}})
// Pea "S", em p e deitada
aadd(aLPieces,{'S', { '0000','0011','0110','0000'},;
{ '0010','0011','0001','0000'}})
// Pea "Z", em p e deitada
aadd(aLPieces,{'Z', { '0000','0110','0011','0000'},;
{ '0001','0011','0010','0000'}})
// Pea "L" , nas 4 posies possiveis
aadd(aLPieces,{'L', { '0000','0111','0100','0000'},;
{ '0010','0010','0011','0000'},;
{ '0001','0111','0000','0000'},;
{ '0110','0010','0010','0000'}})
// Pea "J" , nas 4 posies possiveis
aadd(aLPieces,{'J', { '0000','0111','0001','0000'},;
{ '0011','0010','0010','0000'},;
{ '0100','0111','0000','0000'},;
{ '0010','0010','0110','0000'}})
// Pea "T" , nas 4 posies possiveis
aadd(aLPieces,{'T', { '0000','0111','0010','0000'},;
{ '0010','0011','0010','0000'},;
{ '0010','0111','0000','0000'},;
{ '0010','0110','0010','0000'}})
Return aLPieces
/* ---------------------------------------------------------Funo MoveDown()

https://siga0984.wordpress.com/

23/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Movimenta a pea em jogo uma posio para baixo.


Caso a pea tenha batido em algum obstculo no movimento
para baixo, a mesma fica e incorporada ao grid, e uma nova
pea colocada em jogo. Caso no seja possivel colocar uma
nova pea, a pilha de peas bateu na tampa -- Game Over
---------------------------------------------------------- */
STATIC Function MoveDown(lDrop)
Local aOldPiece
If _GlbStatus != 0
Return
Endif
// Clona a pea em queda na posio atual
aOldPiece := 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]++

https://siga0984.wordpress.com/

24/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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

https://siga0984.wordpress.com/

25/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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

https://siga0984.wordpress.com/

26/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 letras
de movimentao de peas, e realiza a movimentao caso
haja espao para tal.
---------------------------------------------------------- */
STATIC Function DoAction(cAct)
Local aOldPiece
// conout("Action = ["+cAct+"]")
If _GlbStatus != 0
Return
Endif
// Clona a pea em queda
aOldPiece := 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'
https://siga0984.wordpress.com/

27/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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.)
https://siga0984.wordpress.com/

28/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Endif
// Antes de retornar, repinta o score
PaintScore()
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 score
PaintScore()
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, nC
Local cTeco, cPeca

https://siga0984.wordpress.com/

29/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// 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

https://siga0984.wordpress.com/

30/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// Pontuao por linhas eliminadas


// Quanto mais linhas ao mesmo tempo, mais pontos
If nErased == 4
_nScore += 100
ElseIf nErased == 3
_nScore += 50
ElseIf nErased == 2
_nScore += 25
ElseIf nErased == 1
_nScore += 10
Endif
Return
/* -----------------------------------------------------Seta o score do jogo na tela
Caso 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
https://siga0984.wordpress.com/

31/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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

Referncias
TDN Totvs Development Network. TOTVS, 2015. Disponvel em
http://tdn.totvs.com/display/tec/AdvPL (http://tdn.totvs.com/display/tec/AdvPL) . Acesso em: 26
fev. 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/fo
ntes-do-blog-no-github/)
22/02/2015
Siga0984
ADVPL, Exemplos
Para facilitar o processo de utilizao dos exemplos postados no blog, criei um repositrio pblico no
GitHub. 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 fonte
existe um link que aponta para qual post o fonte est relacionado. Ainda estou ajustando a
codificao dos fontes, para a visualizao direta no navegador o encoding (codificao de
caracteres) dos fontes deve ser UTF-8, porm o AdvPL trata as strings acentuadas dentro dos
cdigos como CP1252, interferindo no resultado final esperado. Ainda esta noite os fontes sero
reconvertidos para CP1252 e atualizados.

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

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Para compilar os fontes, basta baix-los para o seu equipamento, e usando o IDE ou o TDS, criar
um projeto e acrescentar todos os cdigos dentro do projeto e compil-los. Alguns fontes tiveram os
seus nomes ou o nome de suas funes trocados dentro do cdigo, para haver uma padronizao
da nomenclatura, e podem estar diferentes dos nomes publicados nos posts, para viabilizar a
compilao de todos dentro de um projeto nico.
Espero que todos faam bom proveito dos cdigos de exemplo, e que estes de alguma forma sejam
teis para aprofundar o conhecimento de vocs no AdvPL. Caso algum tenha alguma dvida sobre
os 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 cdigos
interessantes! At o prximo post, pessoal
E obrigado pela audincia
7 Comentrios

Escalabilidade e performance Fila com job


(https://siga0984.wordpress.com/2015/02/17/es
calabilidade-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 sua
importncia na busca por desempenho em processos. Hoje, vamos ver mais de perto o que a
linguagem 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 visual
nativa do AdvPL possui a arquitetura client-server, e que trabalha de modo sncrono, com conexo
persistente. E, voltando um pouco nas tcnicas para obter performance e escalabilidade, que quando
for possvel (e vamos entender essa palavra no contexto de onde for cabvel) utilize processos
assncronos.
O exemplo que serve como uma luva neste paradigma a criao de uma fila, e um processo
separado 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 do
processo para dar um retorno para a camada de interface.

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

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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

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

Funo StartJob()
Permite, a partir de um programa AdvPL em execuo, iniciar um novo contexto de execuo de
Advpl para a execuo de uma funo em um determinado ambiente do Application Server, sem
relao 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 no
explicitamente 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 remover
itens de uma fila global, e depois algumas funes interessantes para dar mais resilincia no
processo. A fila de requisies possui um formato desenhado para ser um container independente de
requisies, para armazenar um valor AdvPL qualquer (exceto CodeBlock e Objeto). No nosso
exemplo, vamos apenas simular um processamento, que por baixo vai manter o job de
processamento de fila em SLEEP() por 5 segundos. A fila vai ser mantida em memria, e
independente de quantas instncias de SmartClient forem usadas para abrir o programa que coloca
requisies 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 de
Application Server. Para fazer o Download do arquivo que contm a classe AdvPL de fila global de
exemplo, clique no link a seguir: APGlbQueue
(https://siga0984.files.wordpress.com/2015/02/apglbqueue.docx)
#include 'protheus.ch'

https://siga0984.wordpress.com/

34/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

/*
===================================================================
Funo U_TSTQueue
Autor Jlio Wittwer
Data 08/02/2015
Desrio Teste de fila global com processamento
das requisies em JOB
Configurao de onstart para o Job de processamento
[ONSTART]
Jobs=JOBFILA01
[JOBFILA01]
Environment=NOMEDOSEUAMBIENTE
main=U_FILAPROC
nParms=1
Parm1=FILA01
===================================================================
*/
User Function TSTQueue()
Local oDlg
Local oButton1
Local oSay1
Local oSay2
Local oQueue
Local nStatus
Local 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
https://siga0984.wordpress.com/

35/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

ACTIVATE DIALOG oDlg CENTER ;


VALID ( MsgYesNo("Deseja encerrar a aplicao ?") )
Return
/* ---------------------------------------------------------------Insero de requisio na fila, disparada pelo boto de interface
Atualiza 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 := 5000
nStatus := 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 fila
Deve ser iniciado na subida do servidor, no [ONSTART] como um
job, informando como parametro o ID da Fila Global
------------------------------------------------------------- */
USER Function FILAPROC( cQueueId )
Local nStatus
Local oQueue
Local xRequest := NIL
// Coloca observao do processo para Protheus Monitor
PtInternal(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
https://siga0984.wordpress.com/

36/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

conout("["+dtos(date())+" "+time()+"][JOB] Thread


["+cValToChar(ThreadId())+"] Iniciado.")
// Cria a instncia para manuteno da fila
oQueue := 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.")
https://siga0984.wordpress.com/

37/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Return

Funcionamento
Todas as explicaes especficas sobre o que cada parte do cdigo fazem esto dentro do prprio
cdigo. Aqui vamos avaliar a execuo da aplicao como um todo. Aps compilar o fonte client de
exemplo, e compilar o fonte que gerencia a fila global em memoria, e configurar o JOB para ser
executado na subida do Totvs Application Server, o programa U_FILAPROC ser colocado em
execuo via Job, sem interface, e ficar em loop fazendo Dequeue() da lista.
Ao iniciarmos a aplicao U_TSTQUEUE atravs do SmartClient, cada acionamento do boto
Inserir requisio acrescenta na fila de requisies global uma requisio de processamento, que
contm 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 de
exemplo, APGlbQueue, que realiza as operaes de empilhamento e desempilhamento, que
internamente garante a semaforizao da operao de enqueue() e dequeue() (acrescentar e remover
requisio da fila, respectivamente). A fila utilizada do tipo FIFO (First In, First Out), isto , o
primeiro elemento acrescentado ser o primeiro a sair da fila (e ser processado).
Cada acionamento da aplicao Client atualiza informaes na tela, mostrando quantas requisies
foram acrescentadas pela interface atual e quantas requisies ainda esto pendentes na fila de
requisies. Podemos acompanhar o processamento da fila atravs das mensagens mostradas no log
de console ( console.log) do Application Server. Caso a fila receba uma insero, mas o numero de
elementos na fila j est no mximo (no nosso caso, 10 elementos), a operao de Enqueue() retorna
um erro, indicando que a fila est cheia.
Como cada requisio mantm o Job de processamento (Dequeue()) ocupado por 5 segundos, basta
aguardar 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 um
looping separado e independente da interface, e continua em execuo.
A classe de fila global criada para este exemplo utiliza um container de armazenamento global de
memria do Appplication Server, onde basicamente usamos duas funes ( Set / Get ) para
armazenar contedo nomeado na memria da instncia atual de um TOTVS Application Server, e
recuper-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 no
SmartClient, o servidor Master poder redirecionar a conexo para qualquer um dos servios
configurados para balanceamento. Logo, neste caso cada servio slave precisaria ter na seo
OnStart uma chamada para colocar o job de Dequeue() em execuo, seno o client vai acrescentar
elementos na fila, que nunca sero processados, at ele recusar novos elementos caso a fila atinja 10
itens, ou ainda apenas um servio seja configurado para fazer o processamento da fila, com um ou
mais instncias do job, e a aplicao client da fila utilize um RPC por exemplo, para conectar-se com
o servio que processa a fila, para acrescentar na memria deste servio as novas requisies.
https://siga0984.wordpress.com/

38/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Consideraes sobre a persisncia da fila


Uma vez que a fila, no nosso exemplo, est sendo persistida em memria, se o servio do
Application Server for finalizado, o processamento pode ser encerrado antes de todas as requisies
da fila terem sido processadas, ento este exemplo no pode ser usado para etapas importantes de
processo, que exijam por exemplo que a requisio desta fila no possa se perder em caso de uma
falha grave ou finalizao anormal do servio. Caso seja necessria uma persistncia segura, o
correto a fazer criar uma classe que persista a informao da fila em um banco de dados, para que
quando o processo seja recolocado no ar, as requisies pendentes sejam processadas. E, remover
uma requisio da fila no quer dizer que o processamento foi feito com sucesso, mas sim que
algum removeu para processar. Podem ser necessrios mais mecanismos para garantir que o
processo tenha chego ao final com sucesso. Vamos abordar mais destas questes em um exemplo
um 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, e
relativamente simples, para aplicar conceitos como o das filas, paralelismo e jobs, e este exemplo teve
pelo 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/int
erface-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 a
ser visto. O que no vimos ainda so alguns detalhes, particularidades e caractersticas do
SmartClient. Agora que j vimos um pouco do que ele faz, vamos ver mais algumas coisas, e um
pouco de como isto feito por dentro.

Conexo TCP
https://siga0984.wordpress.com/

39/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Entre o SmartClient e o Application Server, existe uma conexo TCP estabelecida, que parte do
SmartClient. No arquivo de configurao do SmartClient, chamado smartclient.ini, podemos criar
sees nomeadas para especificar IP e Porta para a conexo com um Application Server. Na tela
inicial do SmartClient, podemos especificar o programa a ser executado, qual a conexo nomeada
deve ser utilizada, e qual o nome do ambiente (Environment) no Application Server deve atender
esta requisio de processamento.

Time-Out de Comunicao
Quando ativamos uma interface, como por exemplo uma janela ou caixa de dilogo, o APPlication
Server permanece dentro do mtodo Activate(), aguardando por interaes de interface. Nesta
situao, o controle de execuo da aplicao est na interface. Mesmo que o operador no faa
nada na interface, apenas fique olhando para a tela, a cada 60 segundos o SmartClient envia um
pulso na conexo com o Application Server, apenas para informar que ele (SmartClient) est
vivo.
Existe um mecanismo de time-out de inatividade de comunicao TCP no Application Server, ativo
quando o controle de execuo est na interface. Caso o Application Server no receba nada (nem
mesmo um pulso) em 3 minutos, ele (AppServer) assume que algo horrivel aconteceu com o
SmartClient, ou com a conexo de rede entre ele e o Application Server. Neste caso, o APPlication
Server encerra a conexo, mostrando uma mensagem no log de console do Application Server
MsgManager TimeOut waiting for data, e encerra a aplicao em execuo, fecha as conexes
em uso com gateways de dados ( DBAccess, c-Tree), solta locks de tabelas, e todos os demais
recursos consumidos e alocados para aquele programa. Afinal, se a interface no est mais l, no
faz 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, que
significa um tempo de inatividade de interao do usurio na interface. Quando o servidor est
aguardando por interao do usurio, se este fica apenas olhando para a tela, o Application Server
recebe a cada um minuto um pulso, mas este pulso no conta como interao do usurio. Deve
haver o disparo de algum evento de componente de interface para que este contador de inatividade
retorne ao zero novamente, como por exemplo uma troca de foco entre componentes. Caso esta
configurao esteja habilitada, e o operador ficar mais do que o tempo configurado sem interagir
com a interface, o SmartClient deste operador encerrado com uma mensagem de Time-Out
atingido por inatividade.

Balanceamento de Carga
O TOTVS Application Server permite, de forma nativa, configurar um servio para balanceamento
de conexes de SmartClient entre vrios servios do Application Server, na mesma mquina e/ou em
outras mquinas. Todo o procedimento de configurao desta funcionalidade est documentada na
https://siga0984.wordpress.com/

40/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 o
servio de balanceamento no atua como um gateway ou proxy reverso. O balanceador mantm
uma conexo persistente de monitoramento com cada servio slave configurado nele, e no momento
que 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 uma
informao de redirecionamento, para que o SmartClient conecte diretamente no servio slave que
ele escolheu.
Por isso, se o servio do balanceador for interrompido, isto no interfere nas conexes j
estabelecidas e em execuo nos servios slave. E, tambm por esta razo, todos os IPs e portas de
todos os servios slaves devem estar visveis para que qualquer SmartClient que conectar com o
Balance, 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 por
parte do Slave. Meso que o servio slave esteja no ar, caso voc use o TOTVS Monitor, e bloqueie as
conexes naquele servio, o servio de Balance vai saber que as conexes daquele slave esto
bloqueadas, e no vai mais considerar aquele slave na distribuio, at que ele seja novamente
desbloqueado para aceitar novas conexes. Esta caracterstica foi muito til quando foi
implementado um mecanismo de proteo de alocao de memria no Application Server, onde
caso 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 a
desconsider-lo. Somente quando a memria daquele slave volta ao patamar de 80% do limite, o
slave automaticamente libera novas conexes, e o Balance volta a consider-lo no algoritmo de
distribuio.

Conexo SSL
A conexo TCP entre SmartClient e Application Server troca pacotes em formato proprietrio, que
no interpretvel facilmente, porm nesta camada no aplicada nenhuma criptografia.
Dependendo do tamanho do pacote, ao utilizar por exemplo um Sniffer de rede, voc poder
eventualmente ver strings com textos e dados de campos serem trafegados. Para oferecer uma
forma de criptografia segura das informaes trafegadas entre Application Server e SmartClient,
podemos configurar o Application Server para utilizar um certificado digital seo
SSLCONFIGURE 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 no
log 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 pegar
um destes exemplos de interface, e executar com este log habilitado no APPlication Server, e voc
vai ver quais e quando so trocadas as mensagens entre as aplicaes. Faa isso em um ambiente
https://siga0984.wordpress.com/

41/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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

Verses e Plataformas de Smartclient


At o momento, o AdvPL possui 3 verses de interface SmartClient: O SmartClient em formato
executvel, compilado para as plataformas Windows, Linux e MAC; o SmartClient ActiveX, que
pode ser publicado em um servidor HTTP / WEB e instalado como uma extenso / Plug-In do
Internet Explorer, e por ltimo o SmartClient HTML, que pode ser instanciado pela maioria dos
Internet 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 a
impossibilidade de usar as funes de baixo nvel de arquivo ( fopen, fcreate, ) no sistema de
arquivos da mquina onde o SmartClient est sendo utilizado.
Como ainda no falamos nada sobre o sistema de arquivos do AdvPL, por hora vamos apenas saber
que, usando as funes de acesso a disco e arquivos do AdvPL, podemos enderear arquivos na
mquina onde est sendo executado o TOTVS Application Server, a partir do RootPath do
ambiente, e podemos tambm acessar o sistema de arquivos da mquina onde o SmartClient que
iniciou a execuo de uma aplicao est rodando. Este tema tambm ser abordado
posteriormente, em tpico especfico.

Boas prticas
O cenrio ideal para a utilizao de interface Client-Server persistente para processamento de
requisies de retorno rpido. Relatrios extensos ou processamentos que demoram mais do que
alguns minutos devem ser evitados de serem iniciados atravs de uma conexo de interface, afinal se
esta conexo cair por qualquer razo durante o processamento, normalmente em at 3 minutos o
APPlication Server deve perceber que a conexo com o SmartClient foi perdida, e assim que isso
acontecer, ele vai encerrar a execuo da aplicao, no importa se ela tenha terminado o seu
processamento ou no. Em ambientes intranet, onde pressupe-se uma estabilidade maior na
conexo, isto pode no ser to impactante, mas em um acesso remoto ou via internet, isso pode ser
um 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) do
ERP, onde voc pode programar a execuo de um relatrio ou recalculo que vai demorar muitas
horas, e program-lo para ser executado durante a noite, o processamento no vai depender de
interface, 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 dos
componentes em tela de forma visualmente agradvel e lgica, dada a sequncia da operao do
sistema, at a coerncia e clareza das mensagens e informaes solicitadas aos usurios ou
operadores durante o uso da aplicao. Nem sempre uma mensagem que muito clara para o
https://siga0984.wordpress.com/

42/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

programador entendida pelo usurio. Normalmente colca-se uma mensagem com teor explicativo
ao operador do sistema, e em um segundo ponto da mensagem, informaes tcnicas teis para o
departamento de suporte e programao.
Existem ainda outras boas prticas da programao para a interface AdvPL. Uma delas no
interromper uma operao transacionada na base de dados com uma operao de interface que
aguarde uma deciso de usurio. Isso mantm a transao aberta por tempo indeterminado, e
mantm o bloqueio (lock) de registros na base de dados, o que pode prejudicar outros processos
concorrentes. Caso o operador tenha iniciado um processo e sado de sua estao para tomar um
caf, ele somente vai ver a mensagem quando ele voltar, e durante este tempo registros da base de
dados permaneceram bloqueados, impedindo outros processos concorrentes que dependam do
bloqueio nestes registros para serem executados. Normalmente a operao transacionada em base
de dados realizada usando as instrues BEGIN TRANSACTION e END TRANSACTION. Vamos
entrar nesse assunto em maior profundidade em posts posteriores sobre a o acesso a tabelas e dados
no AdvPL.

Exemplo AdvPL
E, quase no final do tpico, vamos a mais um exemplo AdvPL, de uma rotina que procura obter o
mximo de informaes possveis do SmartClient em execuo, que iniciou o programa atual. Segue
fonte abaixo:
#include 'protheus.ch'
/*
===================================================================
===
Funo U_RmtDet()
Autor Jlio Wittwer
Data 01/02/2015
Descrio Monta uma caixa de dilogo com todas as informaes
possveis
de se obter do SmartClient que iniciou este programa.
===================================================================
=== */
User Function RmtDet()
Local oDlg
Local nRmtType
Local cRmtType
Local cRmtLib
Local oFont
Local cInfo := ''

https://siga0984.wordpress.com/

43/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

// Habilita interface com data mostrada com 4 digitos no ano


// e Habilita data em formato britnico ( Dia/Mes/Ano )
SET CENTURY ON
SET 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 PIXELs
DEFINE DIALOG oDlg TITLE (cRmtType) FROM 0,0 TO 480,640 PIXEL
// Informaes da Interfae remota
cRmtBuild := GetBuild(.T.)
cRmtIp := GetclientIP()
cUsrName := LogUserName()
dRmtDate := GetRmtDate()
cRmtTime := GetRmtTime()
aRmtInfo := GetRmtInfo()
cRmtTmp := GetTempPath(.T.)
lActivex := IsPlugin()
lSSLConn := IsSecure()

https://siga0984.wordpress.com/

44/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

cInfo += 'SmartClient Build ....... ' + cRmtBuild + CRLF


cInfo += '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
+ CRLF
cInfo += 'Remote Temp Path ........ ' + cRmtTmp + CRLF
cInfo += 'Remote Computer Name .... ' + aRmtInfo[1] + CRLF
cInfo += 'Remote O.S. ............. ' + aRmtInfo[2] + CRLF
cInfo += 'Remote O.S. Detais ...... ' + aRmtInfo[3] + CRLF
cInfo += 'Remote Memory (MB) ...... ' + aRmtInfo[4] + CRLF
cInfo += 'Remote CPU Count ........ ' + aRmtInfo[5] + CRLF
cInfo += 'Remote CPU MHZ .......... ' + aRmtInfo[6] + CRLF
cInfo += 'Remote CPU String ....... ' + aRmtInfo[7] + CRLF
cInfo += 'Remote O.S. Language .... ' + aRmtInfo[8] + CRLF
cInfo += '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 dilogo
abaixo:

(https://siga0984.files.wordpress.com/2015/02/appint03.png)
Exemplo da execuo do programa
U_RmtDet

https://siga0984.wordpress.com/

45/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Concluso
Como eu j havia dito, com AdvPL podemos fazer muitas coisas, inclusive um ERP. Eu (ainda) no
tenho todas as demais interfaces de SmartClient configuradas no meu ambiente, ento pode ser que
alguma das funes utilizadas no exemplo acima retornem contedos em branco ou apresentem
erro de execuo caso este exemplo seja executado dentro de um SmartClient ActiveX ou
SmartClient HTML. De qualquer modo, eles foram concebidos para serem capazes de renderizar a
interface da mesma forma.
Muitas vezes o arroz com feijo ser usado na maioria das rotinas e interfaces do sistema, porm
algumas rotinas podem precisar de algo um pouco mais especfico, um pouco mais rebuscado. E a
linguagem 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/int
erface-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

https://siga0984.wordpress.com/

46/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

No post anterior, vimos alguns exemplos bsicos de interface. Neste post, vamos ver uma tela
usando vrios componentes juntos, explorando mais possibilidades de uso. A diagramao da tela
de exemplo praticamente no existe, os componentes no esto alinhados para uma apresentao
visual 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 dos
objetos visuais e seus mtodos. Cada componente utilizado merece uma ateno 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
disponvel no link APPInt02 (https://siga0984.files.wordpress.com/2015/01/appint02.doc), em
formato .doc. O Blog no permite que eu suba arquivos fonte ou compactados, porm 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, atravs da funo U_APPINT02, o
resultado 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 os
demais componentes, e ento o cdigo cria todos os componentes filhos desta caixa de dilogo, e
alguns containers para a disposio 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 retngulo na interface, opcionalmente com um ttulo, para
indicar visualmente que os componentes desenhados dentro da rea do componente esto de
alguma forma relacionados. Vale lembrar que o objeto tGroup() no um container. Isto significa
que, para colocar outro componente na rea interna de um tGroup(), o componente visual deve ter o
https://siga0984.wordpress.com/

47/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 de
mostrar um texto na caixa de dilogo. Atravs dos parmetros, 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 automtica 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 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 especificar
uma mscara (Picture), onde podemos especificar o formato que a informao ser mostrada na tela
e aplicar converses automaticamente. Por exemplo, ao informarmos a picture @!, o componente
automaticamente vai transformar cada caractere alfabtico digitado para caixa alta (letra
maiscula), mesmo que voc digite uma letra minscula. J a mscara numrica 999999999 vai
permitir 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 de
um contedo string em modo de mltiplas linhas, til quando precisamos editar uma descrio de
texto 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 a
finalidade de editar um valor booleano, atravs de uma caixa de seleo do tipo Check, onde a
varivel 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(),

https://siga0984.wordpress.com/

48/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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 opo escolhida,
mostrado abaixo do componente a lista de opes disponveis para a escolha. Ao escolher uma
opo, 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 a
finalidade de permitir escolher um item de um array de strings. O componente sempre mostra em
modo destacado a escolha atual, porm todas as opes possveis so mostradas na rea da tela
parametrizada para o componente. Ao escolher uma opo, a varivel 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 sero
vistas com maior profundidade posteriormente. No fonte de exemplo, dentro do painel foi criado
apenas um objeto tSay() para demonstrao.
@ RADIO
O comando @ RADIO utiliza internamente a classe TRadMenu(), e tm um funcionamento parecido
com o componente LISTBOX, para mltipla escolha a partir de uma lista de possibilidades prdefinida. Porm, visualmente, a opo escolhida marcada com um Bullet esquerda do texto.
Ao marcarmos uma nova opo, a opo anteriormente marcada imediatamente desmarcada. E, a
varivel associada ao componente recebe um valor numrico, correspondente a opo marcada.

https://siga0984.wordpress.com/

49/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

@ 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 dilogo para este
componente, e desenhamos dentro dele dois tSay(), um no incio da rea util, visvel na construo
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 execuo da aplicao, conforme o programa seja
executado, 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 s
margens da caixa de dilogo. A barra de botes uma instncia da classe TBar(), que deve apenas
conter botes com imagens, instncias da classe TBtnBmp().

DEFINE BUTTON BUTTONBAR


Na barra de botes criada pelo comando DEFINE BUTTONBAR, criamos botes dentro da barra
usando este comando. Cada boto deve usar uma imagem, que pode estar compilada no repositrio
de objetos ou estar em um arquivo no disco. No exemplo proposto, foram usados algumas imagens
disponveis 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 colocado
livremente na interface, fora da barra de botes. Internamente, este componente usa a classe
tBtnBmp2()

DEFINE SBUTTON
Existem 23 tipos de botes com imagem pr-definidos no AdvPL, ainda utilizados em fontes
customizados de verses anteriores ao Protheus. Cada boto possui um identificador de tipo, onde
para cada tipo uma imagem diferente associada ao boto.
https://siga0984.wordpress.com/

50/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

DEFINE TIMER
A classe tTimer() permite o disparo automtico de um CodeBlock no servidor, a partir de uma caixa
de 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 de
interface (desenhar a interface), menus, rvores e afins. A utilizao da Interface no AdvPL um
tema bem extenso, ainda estou estudando como desmembr-lo nos prximos posts deste tema.
Todas as classes visuais do AdvPL esto documentadas na TDN, utilizadas na forma direta, isto ,
atravs das chamadas explcitas de seus construtores, contendo os mtodos e propriedades
disponveis para uso, esta documentao est disponvel no link <>

Aspectos gerais do exemplo


Cada componente de entrada de dados normalmente est associado a uma varivel local, que
atualizada pelo componente no momento que um novo valor informado, e ocorre a perda de foco
do componente, para foco em um prximo componente ativo na interface. Durante a digitao de
um GET de uma varivel caractere, numrica ou data, o valor da varivel no atualizado
enquanto o valor est sendo digitado. Normalmente usamos a tecla para dar foco ao prximo
componente ativo da interface, ou a tecla . E, para dar foco no componente anterior, usamos a
combinao de teclas +. Podemos tambm trocar o foco com o mouse, simplesmente clicando em
outro componente ativo para este ganhar foco.
Como uma caixa de dilogo, por default aceita a tecla como instruo 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 atualizao de uma barra de progresso (tMeter),
onde foram definidos 1000 passos, e a cada atualizao do timer, a posio atual do indicador de
progresso incrementado em uma unidade. Existe uma optimizao interna no mtodo de setagem
de nova posio, que somente realiza o disparo de uma atualizao de posio para a interface caso
a nova posio na barra de progresso caso ela seja pelo menos uns 5% maior da ltima posio
enviada ao SmartClient.
Tambm foi especificado um CodeBlock, para ser executado no momento do fechamento do
dilogo. Caso voc pressione + ou clique no X para fechar a caixa de dilogo, o CodeBlock
executado, e caso o retorno deste seja .T., o dilogo fechado, caso contrrio ele permanece aberto.
Isto tambm vale caso voc crie um boto com a chamada do mtodo End() do objeto tDialog().
Em vrias aes dos botes de interface, foram utilizadas as funoes MsgInfo(), para mostrar uma
caixa de dilogo com uma mensagem de texto, e a funo GetClassName(), informando uma
varivel contendo um objeto da Interface. A funo GetClassName() retorna uma string com o
https://siga0984.wordpress.com/

51/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

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
criao dos componentes. Logo, ao diagramar duas colunas de componentes de entrada de dados,
de acordo com a ordem de criao voc pode fazer o foco ir primeiro da esquerda para a direita, e
ento de cima para baixo, ou o contrrio primeiro o foco pega todos os componentes do lado
esquerdo, de cima para baixo, para ento cobrir os componentes da direita, de cima para baixo.

A interface no ERP Microsiga


Por baixo do Framework AdvPL do ERP Microsiga, so usados todos os componentes bsicos da
linguagem AdvPL deste exemplo, e os outros componentes ainda no abordados. Vrios
componentes de interface disponibilizados pelo FrameWork AdvPL do ERP Microsiga so heranas
dos componentes bsicos, e outros so componentes de mais alto nvel, a partir de agrupamentos
destes componentes. O MVC, por exemplo, permite o desenvolvimento de uma aplicao em AdvPL
de um modo mais assistido, sem que voc tenha que se preocupar com coordenadas de tela, as
funes e classes por trs da implementao 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 funes
de usurio com nomes especficos em pontos determinados das rotinas do sistema padro, para
inferir modificaes ou complementos ao seu comportamento. Quando queremos criar uma
aplicao customizada no ERP, para ser chamada direto pelo SmartClient, e no como uma opo
de Menu do ERP, utilizamos uma tWindow(), que permite controles adicionais.

Algumas boas prticas


Uma boa prtica para a diagramao da interface, agrupar os componentes que pertencem a uma
determinada funcionalidade dentro de painis, para alinharmos os componentes dentro do painel.
Assim, caso seja necessrio mover um grupo de componentes de lugar, como por exemplo em uma
tela com uma barra vertical de botes auxiliares e um grupo de componentes de entrada de dados,
caso seja necessrio mudar a barra de botes do lado esquerdo para o direito da tela, apenas
trocamos as coordenadas dos painis, caso contrrio teramos que mexer no alinhamento de todos os
componentes.

Combinando componentes
Como existem vrios componentes que servem como container para outros componentes, caso voc
por exemplo precise que dentro de um dos seus folders caiba mais informaes 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.

https://siga0984.wordpress.com/

52/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Concluso
Longo o caminho do desenvolvedor, cheio de possibilidades, mas proporcionalmente aprazvel
ver a satisfao de seus clientes com um trabalho bem feito, duradouro, e construdo de forma a
atender a necessidade, projetado desde o alicerce para ser acessvel, escalvel e flexvel.
Temos muitos posts pela frente, e uma grande satisfao t-los como leitores do Tudo em AdvPL
! At o prximo post, pessoal
6 Comentrios

Interface Visual do AdvPL Parte 01


(https://siga0984.wordpress.com/2015/01/21/int
erface-visual-do-advpl-parte-01/)
21/01/201521/01/2015

Siga0984

ADVPL, Exemplos, Interface

Introduo
A linguagem AdvPL permite a criao de programas com interface visual, com campos de entrada
de dados, botes, labels, scroll (barra de rolagem), painis e outros componentes visuais. Quando
iniciamos a execuo de uma aplicao atravs do aplicativo Smartclient, ele estabelece uma
conexo TCP com o TOTVS Application Server, que faz a carga do programa solicitado do
repositrio de objetos do ambiente, e executa a aplicao AdvPL no servidor. Porm, todas as
instrues de interface so enviadas ao SmartClient.

Introduo 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 so criados, informando qual o objeto do componente pai de cada
um. No momento da criao de cada componente na memria do Application Server, durante a
execuo do programa AdvPL, enviada uma mensagem do Application Server para o Smartclient,
para a criao 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 execuo
da aplicao para o SmartClient.

Assim, quando o componente pai ativado, o SmartClient vai desenhar a tela e todos os

https://siga0984.wordpress.com/

53/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Assim, quando o componente pai ativado, o SmartClient vai desenhar a tela e todos os
componentes nela posicionados, e a execuo do programa AdvPL permanece dentro do mtodo de
ativao do componente visual, aguardando que o usurio ou operador da aplicao executa
alguma ao na interface, como por exemplo preencher um campo ou pressionar um boto de ao
na interface.

Exemplo 01
Em outro tpico anterior, acredito que seja na orientao a objetos com herana de classe visual
AdvPL, foi colocado um exemplo bem simples de uma caixa de dalogo, onde um dos botes da
interface era criado a partir de uma classe compilada no repositrio de objetos que herdava uma
classe bsica do objeto tButton da linguagem Advpl. O primeiro programa de Hello World de
interfave ser criado dentro desse molde, com uma mensagem esttica na tela (tSay), e um boto
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:

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

54/58

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Sistema de coordenadas
Vamos reparar em alguns pontos importantes do fonte acima. Primeiro, o componente principal da
interface (pai de todos) um objeto de dilogo, que eu queria que tivesse uma rea til interna de
300 pontos de comprimento por 150 de largura. Para isso, o dilogo foi criado atravs 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 ativao, este componente fosse centralizado na tela, eu usei o parmetro
CENTER na ativao do dilogo, feita pelo comando ACTIVATE DIALOG.
A criao do dilogo na prtica chamou o construtor do objeto tDialog(), armazenando o objeto do
componente criado na varivel oDlg. E, a ativao do dilogo chamou o mtodo 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 aps montado vai ocupar um espao maior que este, pois estes valores
so 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 boto com tamanho 40,13
(comprimento,altura) com a ao de encerramento da interface principal chamada do mtodo
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, no 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 (tambm
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 mximo
a linha 50, o programa de exemplo deveria ocupar perto de 1/3 da altura do tDialog().
Porm, ao verificarmos o resultado em tela do programa, quase no sobrou nada na parte de baixo
da tDialog(). Isso aconteceu devido as coordenadas de tela dos componentes, internamente, so
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 tm a sua coordenada de incio no canto superior esquerdo. Logo, o
boto 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 no me engano, este comportamento do sistema de coordenadas foi mantido na linguagem 55/58

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Se eu no me engano, este comportamento do sistema de coordenadas foi mantido na linguagem


AdvPL devido a cdigo legado, que utilizava uma API que permitia utilizar os componentes de
interface das primeiras verses do Windows.

Formas de escrever o fonte


Existem duas formas de enderear os objetos da interface visual no AdvPL. Isto pode ser feito
diretamente atravs dos construtores e mtodos dos objetos de interface, documentados na TDN a
partir do link (), ou atravs de comandos diretos como foi feito no exemplo acima.
A utilizao de comandos deixa o fonte mais compreensvel, pois os objetos de interface possuem
muitos parmetros opcionais, e quando utilizamos a nomenclatura por comandos, ns somente
endereamos o que realmente necessrio ser informado no objeto. A leitura do cdigo fica muito
mais fcil. Porm, os comandos de interface ainda no foram oficialmente documentados.

Exemplo 02
O fonte do primeiro exemplo, escrito para utilizar diretamente os construtores e mtodos 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

Execuo do cdigo
Como eu havia mencionado anteriormente, no momento que ocorre a ativao do tDialog(), o
TOTVS Application Server permanece na execuo do mtodo activate, aguardando por uma
interao do usurio ou operador do aplicativo, para disparar alguma ao na interface, como por
exemplo fechar a aplicao ou pressionar um boto.
No nosso exemplo, temos um boto que dispara uma ao no momento que o mesmo acionado.56/58

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

No nosso exemplo, temos um boto que dispara uma ao no momento que o mesmo acionado.
Logo, ao clicarmos no boto, o SmartClient informa ao TOTVS Appplication Server que um boto
foi pressionado, e o Application Server executa o bloco de cdigo (ou CodeBlock) relacionado ao
de clique deste componente.
Esta ao pode chamar uma outra rotina, que cria e ativa uma nova caixa de dialogo, com outros
componentes e botes, onde uma vez que esta caixa de dilogo esteja ativa, no possvel clicar em
nenhum componente da caixa de dilogo anterior, que automaticamente deixa de receber foco com
a ativao de uma nova tDialog.
Logo, qualquer comando ou funo colocada dentro de um fonte, aps o Activate() de uma
tWindow() ou tDialog(), somente ser executado quando esta janela ou dilogo for finalizado.

Capacidades da Interface
Atravs da linguagem AdvPL e aplicaes sendo executadas atravs do SmartClient, podemos
executar instrues de acesso a disco e arquivos (File I/O), portas e impressoras na estao onde o
SmartClient est sendo executado. Podemos copiar arquivos da mquina onde est sendo executado
o SmartClient para dentro de pastas a partir do rootpath (ou diretrio raiz do ambiente), e viceversa.

Restries da Interface
Uma aplicao AdvPL no 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 sees independentes, onde cada seo ( ou aba
) da MDI pode ser executada em um servidor de aplicao diferente, onde existe um componente pai
das abas de MDI, e cada componente filho um container para a execuo de uma aplicao em um
contexto de conexo independente com o TOTVS Application Server, esses detalhes sero abortados
em um outro post.
Existem componentes visuais que podem ser containers de outros componentes, para fins de
alinhamento por exemplo, e existem componente no visuais ligados diretamente a interface, como
tambm existem componentes de interface que no 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 no seja feito da forma correta, na melhor das hipteses o componente
apenas no ser mostrado na interface, e na pior das hipteses, pode ocorrer um erro de acesso
invlido de memria ( Access Violation ou Segment Faut) no TOTVS Application Server, ou mesmo
no prprio SmartClient.

Concluso

Existem mais dicas e restries, que vo aparecer no decorrer da abordagem deste assunto, e estes57/58

https://siga0984.wordpress.com/

14/03/2015

Tudo em ADVPl | Compartilhando experincias de anlise, programao e desenvolvimento.

Existem mais dicas e restries, que vo aparecer no decorrer da abordagem deste assunto, e estes
sero elucidadas e esclarecidas nos respectivos tpicos. Acho que para uma abordagem inicial, j
temos bastante informaes. Todos os componentes visuais do AdvPL esto documentados na TDN,
existem recursos desde agrupamentos de componentes, painis, pastas, grid, menus pop-up, dropdown, seleo mltipla, 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 prximos posts sobre a interface Visual do AdvPL, sero abordados exemplos usando alguns
dos componentes principais e suas caractersticas, desde a utilizao em programas de entrada de
dados, e alguns exemplos um pouco mais rebuscados ! Vai ser divertido, eu garanto !
At o prximo post, pessoal
2 Comentrios
Tudo em ADVPl (https://siga0984.wordpress.com/)
Crie um website ou blog gratuito no WordPress.com (https://pt-br.wordpress.com/?
ref=footer_website). O tema Big Brother (https://wordpress.com/themes/big-brother/).
Seguir

Seguir Tudo em ADVPl


Crie um site com WordPress.com (https://pt-br.wordpress.com/?ref=lof)

https://siga0984.wordpress.com/

58/58