CRUD em AdvPL – Parte 12

Introdução

No post anterior, colocamos mais um botão na interface para enviar e-mail. Agora, vamos colocar uma foto 3×4 para cada contato da agenda.

Novo campo na base

Inicialmente, vamos criar um novo campo na tabela da Agenda, para armazenar a imagem. No caso, vamos usar um campo do tipo “M” Memo do AdvPL, que por default aceita armazenar e recuperar conteúdos binários (bytes de valor 0 a 255, inclusive bytes que não têm representação no CP1252 — usado pelo Protheus).

// Cria o array com os campos do arquivo 
aadd(aStru,{"ID" ,"C",06,0})
aadd(aStru,{"NOME" ,"C",50,0})
aadd(aStru,{"ENDER" ,"C",50,0})
aadd(aStru,{"COMPL" ,"C",20,0})
aadd(aStru,{"BAIRR" ,"C",30,0})
aadd(aStru,{"CIDADE","C",40,0})
aadd(aStru,{"UF" ,"C",02,0})
aadd(aStru,{"CEP" ,"C",08,0})

// Novos campos inseridos em 07/10
aadd(aStru,{"FONE1" ,"C",20,0})
aadd(aStru,{"FONE2" ,"C",20,0})
aadd(aStru,{"EMAIL" ,"C",40,0})

// Novos campos inseridos em 21/10
aadd(aStru,{"IMAGE" ,"M",10,0})

Lembrando que o fonte original possui uma implementação para, caso detectada alguma diferença na estrutura da tabela no momento da abertura, a aplicação automaticamente vai executar um TC_ALTER() para inserir o novo campo.

Inserindo a imagem padrão no programa

A partir desse momento, no espaço reservado da interface onde vamos mostrar a foto do contato da agenda, caso o contato ainda não tenha foto, vamos mostrar uma imagem de 120 x 160 pixels — razão 3×4 — com fundo branco, e o texto “Photo 3×4”. Foi relativamente simples montar esta imagem e salvá-la no formato PNG, ela ocupou míseros 1090 bytes — menos que 1KB.

Para passar a usar esta imagem no programa, podemos proceder de várias formas. Uma delas é distribuir a imagem junto do programa, criar uma pasta ou diretório chamado “images” (por exemplo) a partir do RootPath, e salvar a imagem nesta pasta.

Outra forma seria acrescentar a imagem dentro do Repositório de Objetos (RPO) da aplicação como um “resource”. E, de dentro do programa, fazer a carga deste resource diretamente como uma imagem.

Existe ainda a terceira forma, que seria criar um fonte AdvPL que fosse capaz de criar esta imagem. Esta é a forma mais interessante, e que foi adotada neste exemplo:

STATIC Function RAW3x4()
Local cRaw := ''
cRaw += HEx2Bin('89504E470D0A1A0A0000000D4948445200000078000000A00802000000486B3FC700000001735247')
cRaw += HEx2Bin('4200AECE1CE90000000467414D410000B18F0BFC61050000000970485973000012740000127401DE')
cRaw += HEx2Bin('661F78000003D749444154785EEDDC6D5AEA30140061F7D3F5B01FD7C37AD88F3760494E3E8A14CB')
cRaw += HEx2Bin('542E33BF34C4206F43451FEAC79721090D253494D0504243090D253494D0504243090D253494D050')
cRaw += HEx2Bin('4243090D253494D0504243090D253494D0504243090D253494D0504243090D253494D0504243090D')
cRaw += HEx2Bin('253494D0504243090D253494D0504243090D253494D0504243090D253494D0504243ED097D3A1E3E')
cRaw += HEx2Bin('BE9B3E4FF3D87FDB33A0B35F6C9AA6C3E7B1F23C7D4ED71B857EA0E2372A983E0DBA2C7C38CE43BB')
cRaw += HEx2Bin('8743A7AE8F5EE8DF15A0F3E33C1DA3FE3C2CF4EF1A41A7C2F0EC5A43A71F8D53FEBC3D9DCF9DD201')
cRaw += HEx2Bin('2BB352DF27FE3075F8F321558BFFBCCEE671D0C1A0874ECD1F96DADD980EC47C4BD794E72E4187E7')
cRaw += HEx2Bin('CC5DEB6CDEDFD8D1E302CE8F73AF77F5D38EBE779DCDC3CED1611B5D01EB079DF50B55910E7E69D7')
cRaw += HEx2Bin('5D4787ABA6CAC22DDBBA75B6ECB9D0C38A7E99181F5D19CEA3DD5927375C63117AE53A5B4643C747')
cRaw += HEx2Bin('31103DD70F2F4CBC14EE6D70046BE8B5EB6C19053DF8C570E971F7C36523F60623A03256CF5FBBCE')
cRaw += HEx2Bin('96713F0CFB7AD14BFDF0F2337EBC48195B82BE6F9D2D7B09E8B062AB30BCB332587F036BD7D9B297')
cRaw += HEx2Bin('808E4BA6C1F1AB85705FD57770993CFF2EB2729D2D7B0DE8F8AC1F57EFD07EF6F5F675EB6CD8AB40')
cRaw += HEx2Bin('9F89F2B26DFD2F749D67596AD53ADBF554E8F4EC9CC786A5676C9E18F7D1C270FA82157FA2389F0E')
cRaw += HEx2Bin('F2CC342D4EDAE14F1DCF80B6514243090D253494D0504243090D253494D0504243090D253494D050')
cRaw += HEx2Bin('4243090D253494D0504243090D253494D0504243090D253494D0504243090D253494D0504243ED07')
cRaw += HEx2Bin('7D7EEBECA1BA6276703DD123D56F806EDFFABB573B41DF7AE3FDEFDE0DDEAEFCDED0E1A280518F53')
cRaw += HEx2Bin('F70750E874A2086FB2AFE91F940E970AE40B9DDFFCD43128525FA1CB068D97979499D501A98683B9')
cRaw += HEx2Bin('D07501BAE08C06C7868DBED0C3D22B900CDAECD3B2A9E7F13C10A7759B5CE85CD8ACA1E6FAA954BD')
cRaw += HEx2Bin('A9F36711B04CC9A342E746D0E357D26153E7E2761EA10A9D1BEFE84B1D4E4B1D270CF7B8D00B9DAA')
cRaw += HEx2Bin('CBB17B9E463AECE71BC72BB43FF71F81BE14352B991EB3DC2CF4FA16A00365F8D348DED4ED6965D8')
cRaw += HEx2Bin('9B429FE99AABAFD3CB8978EE08300532D946F49B76F9ABF627FE6E37E89B957370993A8B850D1C5F')
cRaw += HEx2Bin('79B4097DE9E6D33DD084239255EFDBD442CFCDFFC921EEECFE7F3994E311B9EE9216FA5D131A4A68')
cRaw += HEx2Bin('28A1A18486121A4A6828A1A18486121A4A6828A1A18486121A4A6828A1A18486121A4A6828A1A184')
cRaw += HEx2Bin('86121A4A6828A1A18486121A4A6828A1A18486121A4A6828A1A18486121A4A6828A1A18486121A4A')
cRaw += HEx2Bin('6828A1A18486121A4A6828A1A18486121A4A6828A1A18486121AE9EBEB1FDCCEA468FA802AA60000')
cRaw += HEx2Bin('000049454E44AE426082')

Return cRaw

STATIC function HEx2Bin(cHex)
Local cBin := ''
For nI := 1 to len(cHex) STEP 2
  cBin += chr(__HEXTODEC(substr(cHex,nI,2)))
Next
Return cBin

Usando um WebSite que gera o código Hexadecimal dos bytes de um arquivo, eu gerei um “Dump” em hexadecimal da imagem no formato PNG, e criei um fonte AdvPL capaz de remontar a imagem a partir destas informações, convertendo os valores hexadecimais em pares, para chegar ao valor do Byte original, e somando estes bytes em uma variável do tipo caractere no AdvPL.

A função que faz a conversão é a Hex2Bin(), que usa internamente a função __HexToDec() do binário, que converte um valor em String hexadecimal para numérico (decimal).

Acrescentando a imagem na tela

Vamos aproveitar a área abaixo do botão “Sair” para colocar a foto do contato. Para isso, acrescentamos as seguintes linhas no código, logo abaixo da criação do botão “Sair” — lembrando de declarar a variável oBmpFoto como “Local” no fonte da STATIC Function doInit(oDlg)

@ 65,05 BUTTON oBtn5 PROMPT "Sair" SIZE 60,15 ;
  ACTION oDlg:End() OF oPanelMenu PIXEL

@ 90,05 BITMAP oBmpFoto OF oPanelMenu PIXEL 

oBmpFoto:nWidth := 120
oBmpFoto:nHeight := 160
oBmpFoto:lStretch := .T.

Agora, vamos passar a variável oBmpFoto como último parâmetro para todas as chamadas da STATIC Function ManAgenda()., inclusive na declaração desta função, recebendo o objeto no parâmetro nomeado oBmpFoto. E, por fim, acrescentar o botão para alterar a imagem 3×4 do contato atual, logo abaixo do botão “G-Mail”:

@ 125,05 BUTTON oBtnImg PROMPT "Foto 3x4" SIZE 60,15 ; 
  WHEN ( nMode == 4 ) ; // Editar foto disponivel apenas durante a consulta
  ACTION ChgImage(oDlg,aBtns,aGets,oBmpFoto) OF oPanelNav PIXEL
aadd(aBtns,oBtnImg) // [15] Foto 3x4

Lembram-se da função SetNavBtn(), que habilitavam ou desabilitavam os botões de navegação do lado direito da tela, fazendo um SetEnable() diretamente no painel, habilitando ou desabilitando todos os botões? Bem, como vamos poder ter alguns botões com controle de habilitação independente, a função foi alterada para atuar apenas do botão 7 ao 14:

STATIC Function SetNavBtn(aBtns,lEnable)
Local nI
For nI := 7 to 13
  aBtns[nI]:SetEnable(lEnable)
Next
Return

Disparando a atualização da imagem

Como os dados sobre o contato mostrado na tela é carregado pela função ReadRecord(), vamos inserir manualmente após cada chamada da função ReadRecord() a chamada da função ShowImg(), responsável por atualizar a foto na tela.

 // Atualiza na tela o conteudo do registro atual 
ReadRecord(aGets)

// Mostra a imagem do contato 
ShowImg(oBmpFoto)

E, finalmente, vamos a função que faz a mágica, a função ShowImg():

STATIC Function ShowImg(oBmpFoto)
Local cTmpPath
Local nH
Local cRAWImage 
Local cId

// Lê o campo memo com o conteudo da imagem 
// e o ID do contato da agenda 
cId       := AGENDA->ID
cRAWImage := AGENDA->IMAGE

If empty(cRawImage)
  // Contato sem imagem, cria um cache em disco da imagem padrão
  cId := 'photo_3x4'
  cTmpPath := "\temp\tmp_"
  cTmpPath += cID
  cTmpPath += ".img"
  if !file(cTmpPath)
    nH := fCreate(cTmpPath)
    // grava no disco o conteúdo binário da imagem 
    fWrite(nH,RAW3x4()) 
    fclose(nH)
  Endif
Else
  // Contato com imagem, cria um cache da imagem usando o ID do contato
  cTmpPath := "\temp\tmp_"
  cTmpPath += cID
  cTmpPath += ".img"
  if !file(cTmpPath)
    nH := fCreate(cTmpPath)
    fWrite(nH,cRawImage)
    fclose(nH)
  Endif
Endif
oBmpFoto:Load(,cTmpPath)
Return 

A função é relativamente simples, e ainda está sujeita a melhorias. Ela depende apenas da criação da pasta “\temp\” a partir do RootPath do ambiente, pois ela será usada exatamente para fins temporários. Por hora o componente tBitmap aceita realizar a carga de uma imagem do disco, ou de um RESOURCE compilado no RPO. Como o conteúdo binário da imagem foi gravado no banco de dados, ao ser recuperado precisamos criar um arquivo em disco para ser carregado.

Neste caso, lemos a imagem do Banco de Dados, e caso o arquivo em disco ainda não exista , ele é criado na hora usando o conteúdo da imagem, e o nome do arquivo usa o código identificador do contato, para não precisar ficar criando e apagando o mesmo arquivo várias vezes, também servindo de “Cache” para as fotos armazenadas nesta tabela.

Atribuindo uma imagem ao contato

Quase que eu esqueço do principal, a função ChgImage(), para permitir atribuir ou remover uma imagem a um contato. Vejamos:

STATIC Function ChgImage(oDlg,aBtns,aGets)
Local cTitle := 'Escolha uma imagem'
Local oDlgImg
Local cFile := space(40)
Local lOk := .F.
Local aFInfo

DEFINE DIALOG oDlgImg TITLE (cTitle) ;
  FROM 0,0 TO 120,415 PIXEL;
  FONT oDlg:oFont ;
  OF oDlg ; 
  COLOR CLR_BLACK, CLR_HBLUE

@ 05,05 GET oGet1 VAR cFile SIZE CALCSIZEGET(40),12 OF oDlgImg PIXEL

@ 25,05 BUTTON oBtn1 PROMPT "Buscar" SIZE 60,15 ;
  ACTION (BuscaFile(@cFile)) OF oDlgImg PIXEL

@ 25,85 BUTTON oBtn2 PROMPT "Ok" SIZE 60,15 ;
  WHEN empty(cFile) .or. File(alltrim(cFile))  ; 
  ACTION ( lOk := .T. , oDlgImg:End() ) OF oDlgImg PIXEL

ACTIVATE DIALOG oDlgImg CENTER

IF lOk
  cFile := alltrim(cFile)
  aFInfo := Directory(cFile)
  If len(aFInfo) > 0 
    If aFInfo[1][2] > ( 128 * 1024) // Até 128 KB
      MsgStop("Arquivo muito grande ("+str(aFInfo[1][2]/1024,8,2)+" KB)","Imagem maior que 128 KB")
      return 
    Endif
  Else
    MsgStop('Arquivo não encontrado',cFile)
    return 
  Endif
  // Chegou ate aqui, atualiza o campo memo 
  cMemoImg := AGENDA->IMAGE
  If !empty(cMemoImg)
    lOk := MsgYesNo("Este contato já tem uma foto. Deseja substituí-la ?")
  Endif
  If lOk
    // Lê a imagem do disco 
    cMemoImg := ReadFile(cFile)
    If empty(cMemoImg)
      Return
    Endif
    DBSelectArea("AGENDA")
    If DbrLock(recno())
      AGENDA->IMAGE := cMemoImg
      DBRUnlock() 
      MsgInfo("Imagem atualizada.")
    Else
      // Nao conseguiu bloqueio do registro
      // Mostra a mensagem e permanece no modo de alteração
      MsgStop("Registro não pode ser alterado, está sendo usado por outro usuário")
    Endif
  Endif
Endif
Return

A imagem escolhida não pode ter mais que 128 KB — é uma foto 3×4 de 120 x 160 pontos, e deve ser pequena para permitir recuperação e desenho de interface rápidos.

E, por final a função auxiliar ReadFile() que lê um arquivo binário do disco e retorna seu conteúdo em uma String AdvPL a seguir:

STATIC Function ReadFile(cFile)
Local cBuffer := ''
Local nH , nTam
nH := Fopen(cFile)
IF nH != -1
  nTam := fSeek(nH,0,2)
  fSeek(nH,0)
  cBuffer := space(nTam)
  fRead(nH,@cBuffer,nTam)
  fClose(nH)
Else
  MsgStop("Falha na abertura do arquivo ["+cFile+"]","FERROR "+cValToChar(Ferror()))
Endif
Return cBuffer

Pronto

Com tudo isso setado compile os fontes execute, teste,etc. Lembre-se de criar a pasta temp a partir do RootPAth do ambiente. Após entrar na Agenda e acessar a consulta, será mostrado o primeiro contato, com a imagem padrão.

Foto 1

Agora, clicamos no botão “Foto 3×4”, e será apresentada a caixa de diálogo abaixo:

Foto 2

No campo acima, você pode informar manualmente o caminho completo seguido do nome do arquivo de imagem a ser carregado — formatos BMP, PNG, JPG, TIFF — ou usar o botão “Buscar”

foto 3

O Botão de busca abre a interface acima, para escolhermos um arquivo de imagem a partir do RootPath do servidor. Após selecionar o arquivo desejado, clique no botão de confirmação — esse da esquerda com um  “v” vezinho verde. A caixa de diálogo de busca de arquivos será fechada, e o campo do formulário anterior será preenchido com o caminho e nome do arquivo escolhido.

foto 4

Agora, ao clicarmos no botão OK, a imagem será carregada e salva no campo memo chamado “IMAGE” do contato atualmente na tela. Em caso de sucesso, será mostrada a imagem abaixo:

Crud - Foto 6

Ao fechar esta caixa de diálogo, a nova foto atribuída ao contato é mostrada na tela.

foto 5.png

Ao usar os botões de navegação, cada contato posicionado mostrará a foto correspondente.

Conclusão

Não foi fácil chegar ao fim desta implementação, durante os testes vários comportamentos estranhos e ajustes foram necessários. Por exemplo, o componente BITMAP possui uma otimização, para evitar carregar o mesmo arquivo duas vezes. Quando eu resolvi trocar a foto, o arquivo temporário no disco precisava ser apagado e recriado, mas o fato dele usar o mesmo nome, fazia com que a foto não fosse recarregada. Contornei este comportamento simplesmente carregando a foto padrão antes de recarregar a nova foto após a alteração, além de criar o arquivo com a imagem default apenas uma vez na entrada da agenda, e criar uma função para apagar o arquivo do cache em disco ao inserir ou alterar uma foto. Nos próximos POSTS, vamos incrementar mais um pouco este programa !!!!

*** Não entre em pânico, entre no GITHUB e pegue a versão final deste fonte ***

Desejo a todos novamente TERABYTES DE SUCESSO 😀

Referências

 

Anúncios

6 comentários sobre “CRUD em AdvPL – Parte 12

  1. Júlio, absolutamente tudo o que há na web sobre AdvPL e conceitos não se compara a qualidade dos seus post. Muito obrigado por compartilhar, parabéns pela dedicação e nobreza. Abraços, RLEG.

    Curtido por 1 pessoa

  2. inclui no ExistDir e o MakeDir, só o DirRemove que ainda preciso testar para ver se é necessário, pois pensando nos post anteriores a utilização do disco deve ser analisada conforme necessidade certo.

    Curtido por 1 pessoa

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair /  Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair /  Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair /  Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair /  Alterar )

Conectando a %s