domingo, 14 de julho de 2013

Iniciantes em PowerBuilder

Todo iniciante a desenvolver com o PowerBuilder enfrenta dificuldades para encontrar tutoriais e apostilas sobre a ferramenta. A quantidade de materiais disponíveis na web é bastante escassa, principalmente em português.

Devido a isto, resolvi criar este post dando orientações para os iniciantes em tal desenvolvimento ou para aqueles que simplesmente querem conhecer melhor a linguagem.

Versão atual do PowerBuilder


As versões atuais do PowerBuilder sempre ficam disponíveis para download no site da Sybase, no modo de avaliação (trial).
  • Página da Sybase para apresentação do PowerBuilder: clique aqui.
  • Página do SAP/Sybase para download da versão 12.5 (trial) do PowerBuilder: clique aqui.

Obs: a versão 12.5 é a mais atual até o momento que elaborei este post.


Versões antigas


Caso você esteja interessado em versões antigas da ferramenta, seguem alguns links:
Obs: em breve pretendo disponibilizar mais versões antigas.

Ferramentas e bibliotecas auxiliares


Banco de dados
Como a grande maioria das aplicações escritas em PowerBuilder armazenam informações no banco de dados, disponibilizei alguns links de softwares de banco de dados recomendados:
  • Página para download do MySQL (É necessário criar conta na Oracle): clique aqui.
  • Página da Microsoft para download do SQL Server: clique aqui.

Framework
O principal framework do PowerBuilder é a PFC (PowerBuilder Foundation Class). Este conjunto de bibliotecas permite aos desenvolvedores construir poderosas aplicações com pouco esforço. Se você for construir aplicações comerciais, ela é indispensável.


Tutorial do PowerBuilder


Aqui, no próprio PowerBuilder Blog, disponibilizo um tutorial bem simples e intuitivo para iniciantes. O tutorial apresenta conceitos básicos da ferramenta e ensina ao leitor a construir uma aplicação para controle de bibliotecas. Para visualizar o tutorial: clique aqui.

Espero que as informações que apresentei neste post sejam úteis. Para outras dúvidas, ofereço uma área para contato aqui no PowerBuilder Blog.

quarta-feira, 20 de março de 2013

Tratamento de exceções

Atualmente, qualquer linguagem de programação que se preze oferece recursos para o tratamento de exceções. Nosso bom e velho PowerBuilder também oferece tais recursos (apesar de não ser da melhor forma...).

Vamos entender um pouco melhor como as exceções funcionam. Quando ocorre um erro em tempo de execução em uma aplicação PowerBuilder, é disparado um evento da aplicação chamado SystemError(). Você pode visualizar este evento abrindo um objeto "application" (figura abaixo).

Objeto "application".

Se optarmos por utilizar o SystemError() para realizar alguns tratamentos de falhas, com certeza enfrentaremos dificuldades, já que esse evento é disparado fora do local onde o código está sendo executado. Contudo, você pode querer capturar e tratar alguns erros no próprio código de uma interface ou classe. Veja o exemplo de código abaixo. Imagine que estamos tentando capturar, através de uma datawindow, o nickname de um usuário que acabou de logar em um sistema.

String ls_user

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

Como programador PowerBuilder, você deve saber que, caso esta datawindow não possua tal linha ou coluna solicitadas, a aplicação irá disparar a clássica mensagem "Error: Invalid DataWindow row/column specified at line...". Para realizar o tratamento deste erro no próprio método, precisaremos conhecer o que a linguagem oferece. Veja abaixo detalhes de cada palavra reservada:

TRY
   <codificação que pode gerar uma exceção>
CATCH
   <codificação que permite capturar e tratar uma exceção>
FINALLY
   <codificação que permite encerrar o bloco de código. Ideal para fechamento de conexões, destruição de objetos, etc. >
END TRY


Vamos agora tratar o nosso código:

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o  usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Entenda que, desta forma, ao ser disparada a falha no getItemString(), automaticamente a próxima instrução a ser executada será o CATCH, independente se existir mais codificações ao longo do TRY. 

Vamos complementar o código fonte do TRY com mais algumas instruções. Nelas você verá que também é possível lançar exceções manualmente. Desta forma, podemos centralizar o tratamento de outras situações:

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

   IF ISNULL(ls_user) OR Trim(ls_user) = "" THEN
  Exception loException
      loException = CREATE Exception
      loException.setMessage("O nickname do usuário está nulo ou vazio")
      THROW (loException)
  END IF

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Métodos que disparam exceções

Para elaboramos códigos com qualidade e organização, é importante conhecermos as boas práticas de programação relacionadas. Quando conhecemos os recursos para tratamento de exceções, já podemos eliminar uma (má) prática que ainda é bastante utilizada. Quantas vezes você codificou um método cujo valor de retorno era um tipo int e sua codificação retornava -1 para indicar falha? Com o tratamento de exceções isto não é mais necessário!

Vamos adaptar o código que vimos anteriormente para que esteja contido em uma função chamada of_getNickname(). O retorno desta função será do tipo "string", ou seja, o nickname do usuário. Se ocorrer algum problema na recuperação desse nickname, vamos evitar retornar uma string vazia ou nula para indicar a falha. Iremos lançar uma nova exceção a fim de indicar ao código de origem (o que invocou a função) que houve uma ação inesperada.

// Função: of_getnickname() Retorno: string

String ls_user

TRY

ls_user = dw_test.getItemString(1, "NICKNAME_USER")

   IF ISNULL(ls_user) OR Trim(ls_user) = "" THEN
  Exception loException
      loException = CREATE Exception
      loException.setMessage("O nickname do usuário está nulo ou vazio")
      THROW (loException)
  END IF

CATCH (Throwable aoException)
THROW (aoException)
END TRY

RETURN ls_user

Se você já inseriu o código acima para fazer algum teste, perceberá que o PowerBuilder acusou um erro. O motivo disto é porque devemos informar que nossa função of_getnickname() está pronta para tratar exceções. Para definir isto, você precisará preencher o campo "Throws", na especificação da função, com o valor "exception". Veja na imagem abaixo.



Pronto. Agora basta que função de origem faça um tratamento semelhante ao que você vê a seguir.



TRY

 String ls_user = of_getNickname()

CATCH (Throwable aoException)
Messagebox ("Falha", "Não foi possível identificar o usuário: " + aoException.getMessage())
CLOSE(THIS)
END TRY


Caso queira tratar suas exceções mais especificamente, listo abaixo algumas classes de exceção:
  • Throwable - Tratamento mais genérico, englobando todos os erros;
  • Exception - Exceções criadas pelo usuário e herdadas do Throwable;
  • RuntimeError - Erros de Runtime do PowerBuilder e herdado do Throwable;
  • NullObjectError - Referência nula para objetos e herdado do RuntimeError;
  • DivideByZeroError - Divisão por zero e herdado do RuntimeError;
  • DWRuntimeError - Erros em comandos da Datawindow e herdado do RuntimeError;
  • OLERuntimeError - Erros em comandos de Objetos OLE e herdado do RuntimeError;
  • CORBASystemException - Erros em comando de objetos CORBA e herdado do RuntimeError.

Espero que tenha dado uma noção de como construir aplicações com uma estrutura que utiliza tratamento de exceções. Apesar de toda a sintaxe utilizada nos exemplos ter sido em PowerScript, não descarto o aprendizado também para desenvolvedores C#, Java, Visual Basic, etc.


quarta-feira, 9 de janeiro de 2013

Autenticação Windows em uma aplicação PowerBuilder

Quando falamos em autenticar usuários de uma aplicação, estamos tratando de aspectos de segurança de dados. Existem diversas formas de se realizar autenticações, porém é necessário ter cautela na escolha de qual modo é mais adequado ao seu sistema.

Há softwares que autenticam usuário através de tabelas e métodos próprios e outros autenticam através de mecanismos oferecidos pelos bancos de dados. Neste post, irei descrever como a autenticação de usuários da sua aplicação pode ser realizada via usuários do Windows. Isto significa que podem ser os próprios usuários de um domínio (DNS - Domain Name System) da sua empresa.

Antes do código fonte da autenticação propriamente dita, é interessante conhecer outras funções relacionadas. A primeira é a função que captura o nickname do usuário logado atualmente. Para utilizá-la, acesse a área de "Local External Functions" da sua janela ou objeto PowerBuilder e insira o código abaixo:

Function ulong GetUserNameW(ref string lpBuffer, ref ulong nSize) Library "advapi32.dll"

Perceba que estamos utilizando bibliotecas fornecidas pelo próprio sistema operacional.

Após isto, basta inserir o código abaixo em uma função ou evento do seu objeto para poder recuperar o nickname em questão:

String is_nome_usuario
ulong ll_size

// Captura o nome do usuário da sessão atual do windows
ll_size = 256
is_nome_usuario = Space(ll_size + 1)
GetUserNameW(ref is_nome_usuario, ref ll_size)

Abaixo, apresento mais duas funções úteis. Uma para recuperar o ID do processo atual e outra para recuperar o nome da estação (computador) utilizado:

Function ulong GetCurrentProcessId() Library "kernel32.DLL" 

Function ulong GetComputerNameW(ref string lpBufferref ulong nSizeLibrary "kernel32.DLL"

E aqui o código da função:

String is_nome_computador
ulong ii_process_id
ulong ll_size

// Captura o ID do processo atual da aplicação
ii_process_id = GetCurrentProcessId()

// Captura o nome da estação de trabalho
ll_size = 256
is_nome_computador = Space(ll_size + 1)
GetComputerNameW(is_nome_computador, ll_size)

Agora, segue a declaração da função de autenticação citada anteriormente:

Function boolean LogonUser(string lpszUsername, string lpszDomain, string lpszPassword, ulong dwLogonType, ulong dwLogonProvider, ref ulong phToken) Library "advapi32.dll" Alias For "LogonUserA;Ansi"

Function boolean CloseHandle(ulong hObject) Library "kernel32.dll"

E, finalmente, o código fonte para autenticação do usuário:


Boolean lb_resultado
String ls_dominio
String ls_windows_usuario
String ls_windows_senha
ULong lul_token

Constant ULong LOGON32_LOGON_NETWORK = 3
Constant ULong LOGON32_PROVIDER_DEFAULT = 0

// Captura de variáveis
ls_dominio   = "DEFINA O DOMÍNIO (SE HOUVER)"
ls_windows_usuario = "DEFINA O USUÁRIO"
ls_windows_senha = "DEFINA A SENHA"

// Autentica o usuário
lb_resultado = LogonUserA(ls_windows_usuario, ls_dominio, ls_windows_senha, &
LOGON32_LOGON_NETWORK,  LOGON32_PROVIDER_DEFAULT, lul_token )

// Verifica se autenticou com sucesso
IF NOT lb_resultado THEN
CloseHandle(lul_token)
Messagebox("Não autenticado", "O login ou a senha informados estão incorretos.",stopsign! )
RETURN TRUE
END IF

RETURN FALSE


Caso deseje conhecer a API dos comandos do sistema operacional utilizados nos exemplos acima, seguem os links:

quarta-feira, 19 de dezembro de 2012

DataWindows com Marca d'água


A utilização de imagens como marca d'água de relatórios ou documentos é uma prática normalmente associada à segurança. Sistemas precisam inserir marcas d'águas em páginas impressas, a fim de deixar visível que a impressão foi realizada sobre uma circunstância especial.

Existem textos bastante comuns utilizados como marca d'água, como por exemplo: "Rascunho", "Cancelado", "Sem validade", "Para uso interno", etc.

Mas como utilizar marca d'águas no PowerBuilder? Bem, isto é possível através da inserção de imagens nas datawindows, mas imagine o trabalho que pode dar se a quantidade de dataobjects do seu sistema seja alta.
A fim de evitar que cada datawindow seja alterada, eu criei um código que insere a marca d'água dinamicamente em uma datawindow. Veja mais abaixo.



String  ls_marca_dagua
String ls_datawindow_syntax
Long  ll_pos

// Recupera dinamicamente a sintaxe da datawindow
ls_datawindow_syntax = dw_report.DESCRIBE("Datawindow.syntax")

// Constrói a sintaxe do controle referente a imagem da marca dágua
ls_marca_dagua= 'bitmap(band=foreground filename="Imagens\Marca.gif" x="14" y="28" height="2572" width="2194" border="0"  name=p_1 visible="1" )'

// Recupera a posição do caractere em que será inserido o controle
ll_pos = Pos(ls_datawindow_syntax, "htmltable(")

// Modifica a sintaxe data datawindow, inserindo o controle
ls_datawindow_syntax = MID(ls_datawindow_syntax, 1, ll_pos -1) + ls_marca_dagua + MID(ls_datawindow_syntax, ll_pos , LEN(ls_datawindow_syntax))

// Recria a datawindow com a nova sintaxe
dw_report.CREATE( ls_datawindow_syntax)


Perceba no código acima que eu trabalhei com a imagem no formato GIF. O motivo disto foi para tentar colocar um pouco de "transparência" à minha marca d'água. Eu só precisei utilizar este formato porque desenvolvi o código fonte em PowerBuilder 10. Se você estiver utilizando a versão 11 do PowerBuilder ou uma superior, pode usar dois recursos mais eficientes para a transparência:

  • Ou utilizar uma imagem no formato PNG;
  • Ou definir o atributo transparency para o controle de imagem. Veja como ficaria:


...

// Constrói a sintaxe do controle referente a imagem da marca dágua
ls_marca_dagua= 'bitmap(band=foreground filename="Imagens\Marca.gif" x="14" y="28" height="2572" width="2194" border="0"  name=p_1 visible="1" transparency="50")'

...


A imagem com a marca utilizada por mim foi esta:


Agora, veja um relatório de exemplo com e sem marca d'água.





terça-feira, 4 de dezembro de 2012

Verificação de IPs (ping)

Certas aplicações necessitam utilizar recursos da rede ou se conectar com outros computadores. Infelizmente, em PowerBuilder, as bibliotecas oferecidas para este tipo de conexão são bastante pobres.

Neste post eu apenas dou uma dica para realizar uma simples verificação de IP, ou seja, executar o clássico comando "ping" através do PowerBuilder. Faremos isto através de instruções fornecidas pelo próprio Shell do sistema operacional.

Abaixo eu apresento um exemplo de como isto pode ser implementado.

OleObject lo_Shell
Integer  li_return
String ls_ip

ls_ip = sle_ip.text

lo_Shell = CREATE OleObject
li_return = lo_Shell.ConnectToNewObject( "WScript.Shell" )

IF li_return <> 0 THEN
Messagebox("Falha", "Falha ao tentar instanciar Shell do Sistema Operacional.")
END IF

li_return =  lo_Shell.Run("ping -n 1 -w 300 " + ls_ip, 0, TRUE)

IF li_return <> 0 THEN
Messagebox("Falha", "Host indisponível.")
END IF


sábado, 1 de dezembro de 2012

Validação de campos de e-mail em PowerBuilder

Neste post eu apresento um código bastante útil para quem está trabalhando com campos de e-mail em uma aplicação.

Por questões de segurança e padronização, ao permitir que o usuário de seu sistema insira um e-mail, você deve validá-lo quanto ao seu formato. Abaixo segue um código em PowerBuider, mas que também pode ser adaptado a outras linguagens, cujo resultado é a validação de um-mail.

string  ls_email
boolean lb_email_valido

// Código para setar o e-mail em ls_email
ls_email = of_getEmail()

lb_email_valido = FALSE

IF (NOT ISNULL(ls_email)) AND Trim(ls_email) <> "" THEN
IF match(ls_email,'^[a-zA-Z0-9][a-zA-Z\0-9\-_\.]*[^.]\@[^.][a-zA-Z\0-9\-_\.]+\.[a-zA-Z\0-9\-_\.]*[a-zA-Z\0-9]+$'THEN
lb_email_valido = TRUE
END IF
END IF




segunda-feira, 12 de novembro de 2012

Trabalhando com links

O PowerBuilder disponibiliza diferentes formas para o desenvolvedor trabalhar com links.

A forma mais comum é a através do controle StaticHyperLink. Independente de você estar desenvolvendo uma interface para desktop ou para web, o controle StaticHyperLink trabalhará do mesmo modo.

Para testá-lo, crie uma novo objeto window em um workspace de teste e adicione o controle através do menu Insert > Control > StaticHyperLink (ver imagem 1).

Imagem 1
Após adicionar o controle na sua interface, é hora de configurar as propriedades. Dentre elas, detalhei as mais relevantes:

  • Text: Texto a ser exibido ao usuário para efetuar o clique do link;
  • URL: Endereço propriamente dito a ser acessado através do link. Ex: "http://www.blogger.com";

Obs: Há um bug nos navegadores Mozilla Firefox e Opera em que, ao clicar no link com seu navegador padrão fechado, o programa não acessa o endereço definido. Uma solução para isto é adicionar um espaço em branco antes do endereço no campo URL.

É possível também definir links que redirecionem para os provedores de e-mail. Isto é feito definindo um código mais completo no campo URL, por exemplo: "<a href=”mailto:teste@gmail.com?subject=Feedback” >teste@gmail.com</a>".

Outro modo de trabalhar com links é pelo próprio código fonte. Segue abaixo um exemplo de como isto pode ser feito através da classe INet.

INet iinet_base
GetContextService("Internet", iinet_base)
iinet_base.HyperlinkToURL("http://www.blogger.com")


Por último, mostro como utilizar links através de datawindows. Porém, este modo só é possível quando você está trabalhando com uma aplicação web. Se este é o seu caso, crie uma datawindow e adicione um novo controle do tipo "Text" ou utilize uma "Column" mesmo. Acesse a aba "HTML" em suas propriedades. Lá você irá encontrar os seguintes campos:

  • Link: Endereço propriamente dito a ser acessado através do link. Perceba que é possível utilizar uma fórmula para compôr o link dinamicamente;
  • Link Target: Define como cada link será carregado na janela do navegador quando for clicado. Os valores possíveis são:
    • _blank: Abre uma nova página em uma nova janela do navegador;
    • _self: Carrega a nova página na janela atual;
    • _parent: Carrega a nova página no frame ancestral;
    • _top: Carrega a nova página na janela atual do navegador, cancelando todos os frames.

Os demais campos não são para situações tão básicas, mas você pode sentir necessidade de utilizá-los também.

Bom, espero que tenha dado uma visão geral da utilização de links em PowerBuilder. Existem muitas situações mais avançadas do que as que exemplifiquei aqui. Caso tenha dúvidas para utilizar algum recurso, é só me passar pelos comentários ou através do e-mail.

sexta-feira, 9 de novembro de 2012

PowerBuilder x TFS Source Control

Neste post eu explico como configurar o PowerBuilder para utilizar o Team Foundation Server Source Control como controlador de versão dos objetos.

De antemão, gostaria de listar aqui os benefícios e limitações desta integração. Os principais benefícios são:

  • Performance: Os check-ins, check-outs, atualização de status de objetos e outras operações são mais velozes do que outros sistemas controladores de versão;
  • Associação de check-ins com WorkItens do TFS: Para quem já utiliza o Team Foundation Server como sistema gerenciador de projetos, pode associar o check-in dos objetos à um WorkItem, proporcionando uma completa rastreabilidade do que é produzido. Isto significa que, através do WorkItem, é possível visualizar todos os objetos alterados para o tal;
  • Rastreabilidade a nível de código: O Source Control permite que o usuário abra o código fonte de um objeto registrado e exiba em qual check-in foi inserido ou alterado cada parte do código. Isto significa que o usuário pode, por exemplo, visualizar quem e porque inseriu aquele "IF" que está ocasionando um bug no sistema.
Como nem tudo são flores, o Source Control também possui seus problemas. Porém, o único que considero  relevante citar é a forma como ele define o status de cada objeto. O controle é feito através de um "flag"  atribuído a cada objeto, que define se o objeto no "workspace" do usuário está atualizado conforme última versão ou não. Isto significa que a atualização de status dos objetos não é feita através de um "diff", como em outros sistema controladores de versão, a exemplo do Microsoft Visual Source Safe. O problema disto é que se o usuário não realizar corretamente as operações, terá uma dor de cabeça com os status dos objetos. O Source Control poderá exibir que o objeto está atualizado, porém não estará.

Bem, o primeiro passo para configurar a integração é baixar e instalar o plug-in que permite que o PowerBuilder se integre ao Source Control. Este plug-in é fornecido pela própria Microsoft.
Acesse este link para baixar o plug-in compatível com suas configurações: Team Foundation Server MSSCCI Provider

Se ainda não possui o seu projeto criado no Source Control, crie-o. Para isto, abra o Team Explorer no Visual Studio e clique em adicionar (seu usuário precisará ter autorização para tal).

Após isto, abra o PowerBuilder e, em seguida, o workspace do projeto que deseja "versionar". Clique com o botão direito do mouse no workspace e acesse "Propriedades". Na aba "SourceControl", selecione "Microsoft Team Foundation Server" (ver imagem 1).

Imagem 1
Esta opção será exibida somente se você estiver instalado corretamente o plug-in mencionado anteriormente. Nos campos seguintes defina o usuário e selecione o projeto do TFS. O PowerBuilder disponibiliza algumas opções para tratar a integração:
  • Requires comments on chek-in: Obriga ao desenvolvedor a informar um comentário no momento que efetuar o check-in de um objeto. Sugiro habilitar esta opção;
  • This project requires that I sometimes work offline: Desabilita a conexão automática ao sistema controlador de versão quando você abre o workspace. Sugiro desabilitar esta opção;
  • Delete PowerBuilder-genereated object files: Para realizar algumas operações internas como a comparação de objetos ("Show differences"), o PowerBuilder precisa gerar arquivos fisicamente na pasta onde está localizada a PBL em questão. Marcando esta opção o PowerBuilder irá excluir estes arquivos gerados. Deixe esta opção habilitada;
  • Perform diff on status update: Verifica quais objetos locais estão diferentes do servidor, permitindo que o desenvolvedor identifique que já existe uma versão mais atual. Esta opção não funciona corretamente com o Source Control. Deixe esta opção desabilitada.
  • Suppress prompts to overwrite read-only files: É comum que os sistemas controladores de versão, inclusive o Source Control, gerem os arquivos versionados como "read only" na pasta de trabalho do desenvolvedor, a fim de evitar uma edição desses arquivos sem um prévio check-out. Como PowerBuilder exporta arquivos para esta pasta e encontra o arquivo como "read only",  ele irá solicitar ao usuário se deseja substituir o arquivo ou não. Selecionando esta opção, esse tipo de solicitação ao usuário não será realizado. Deixe esta opção habilitada;
  • Show warning when opening objects not checked out: Oculta mensagens exibidas ao abrir objetos que não estão em check-out. Deixe esta opção habilitada.