Começando um programa

Menu Principal de um programa em Clipper. Vou mostrar como podemos começar a programar. Esse exemplo pode ser compilado com o xHarbour ou Harbour.

Para compilar o menu.prg (disponível para download no fim da página) com o Harbour use: 

hbmk2 menu c:\hb32\lib\win\mingw\libgtwin.a -lxhb.hbc -lhbxpp.hbc

A libgtwin.a é responsável pela acentuação gráfica da língua portuguesa na tela com o compilador Harbour.

Para compilar com o xHarbour, use:

hbmake menu /ex

O hbmake do xHarbour já compila tudo certinho.

Certifique-se de estar usando o Conjunto de caracteres > Europa Ocidental > OEM850. Abra o Notepad++, clique no menu Formatar e faça esta seleção. Esse é o conjunto de caracteres da nossa Língua Portuguesa.

Vamos configurar o sistema para trabalhar corretamente com arquivos de índice acentuados em nosso idioma Português; vamos utilizar os arquivos de índice DBFCDX; vamos configurar a época para 1980 e ficar imune ao Bug do Milênio; a data para o sistema britânico que é igual ao nosso dia, mês e ano; confirmar cada campo com <Enter>; posicionar mensagens na linha 24; não mostrar arquivos deletados etc. Para mais detalhes sobre cada função ou comando consulte o help online do Harbour em nosso site.

Configurando o sistema

REQUEST HB_LANG_PT        // CONFIGURAÇÃO DA LINGUAGEM PORTUGUÊS
REQUEST HB_CODEPAGE_PT850 // PARA INDEXAR CAMPOS ACENTUADOS

Function Main() // TODO PROGRAMA EM xHARBOUR DEVE TER UM FUNCTION MAIN() INDICANDO O PROGRAMA PRINCIPAL
#include "inkey.ch"
*** SETs
SET SCOREBOARD OFF
SET DATE BRITISH      // PADRÃO DE DATA BRITÂNICO DIA/MÊS/ANO
SET MESSAGE TO 24     // POSICIONA AS MENSAGENS NA LINHA 24 
SET DELETED ON        // não mostra arquivos deletados
SET EPOCH TO 1980     // Y2K COMPLIANT (FIX DO BUG DO MILÊNIO)
SETMODE(25,80)        // TELA PADRÃO
SET OPTIMIZE ON
SETMOUSE(.t.)
SET(39,159)           // MOUSE NOS GETs
SET TIME FORMAT TO "hh:mm"
SET CONFIRM ON        // CONFIRMA CAMPOS C/ 
SET HARDCOMMIT OFF    // É MELHOR DAR UM DBCOMMIT() NO FINAL DE CADA TAREFA
SET WRAP ON
 
// CONFIGURAÇÃO DA LINGUAGEM PORTUGUÊS
HB_LangSelect("PT")
HB_SetCodePage("PT850") // CODEPAGE

*** DEFINIR TIPO DE BANCO DE DADOS: DBFCDX Nativo ***
// ANNOUNCE RDDSYS
REQUEST DBFCDX
REQUEST DBFFPT
ANNOUNCE FPTCDX
RDDSETDEFAULT("DBFCDX")
RDDREGISTER( "DBFCDX", 1 ) // RDT_FULL
SET AUTOPEN OFF
 
CRIADBF()  // CHAMA ROTINA DE CRIAÇÃO DE BANCO DE DADOS
MENU()     // CHAMA ROTINA DO MENU PRINCIPAL

Montando a tela do seu programa

Primeira coisa que você tem que saber é que a tela padrão do Clipper é a mesma do MS-DOS que comporta 80 colunas e 25 linhas. As coordenadas vão de 0 a 79 e 0 a 24 respectivamente.

Vamos montar uma linha de título; corpo e linha de status.

Para desenhar quadrados a gente usa o comando @...to...double com as coordenadas linha inicial, coluna inicial até linha final, coluna final.

Para exibir alguma coisa na tela usamos o comando @...SAY.

FUNCTION MENU()
// MONTA A TELA DO SISTEMA
SETCOLOR("W+/N, N/W,,,W+/BG") // DEFINE COR DA LETRA, SELECIONADO/FOCO, NÃO SELECIONADO/SEM FOCO.
CLS // COMANDO PARA LIMPAR A TELA: CLear Screen
@ 00,00 TO 02,79 DOUBLE // NOME DO SISTEMA
@ 03,00 TO 05,79 DOUBLE // NOME DO PROGRAMA ATUAL
@ 06,00 TO 23,79 DOUBLE // ÁREA DE TRABALHO
@ 24,00 SAY SPACE(80) COLOR "W+/BG"  // LINHA DE STATUS, MENSAGENS, ALERTAS ETC.
cSISTEMA := "AGENDA TELEFÔNICA"
cPROGRAMA := "MENU PRINCIPAL"
@ 01,(80-LEN(cSISTEMA))/2  SAY cSISTEMA  // (80 COLUNAS - TAMANHO DA STRING)/2 = MEIO DA TELA
@ 04,(80-LEN(cPROGRAMA))/2 SAY cPROGRAMA 
@ 24,55 SAY "Tecle [Esc] para sair" COLOR "W+/BG"

Menu do sistema

Agora vamos montar o menu do sistema. Para dar escolhas ao usuário podemos usar o comando @..PROMPT dando as opções e armazenando a escolha com o comando MENU TO.

Atenção, o programa dará erro se você chamar uma função indefinida. Nesta página vamos criar apenas a função DBF().

// MONTA O MENU DO SISTEMA
SETCOLOR("W+/BG")             // PINTA UMA JANELA DE MENU
@ 11,19 CLEAR TO 16,37        // LIMPA ESSA ÁREA
@ 11,19 TO       16,37 DOUBLE // PÕE UMA MOLDURA NELA

nMENU := 1 // DEFINE UMA VARIÁVEL NUMÉRICA = 1 PARA ARMAZENAR A ESCOLHA DO USUÁRIO
@ 12,21 PROMPT " 1. Cadastro   " MESSAGE "Cadastra número telefônico           "
@ 13,21 PROMPT " 2. Consulta   " MESSAGE "Mostra números cadastrados na agenda "
@ 14,21 PROMPT " 3. Deleta     " MESSAGE "Deleta número cadastrado             "
@ 15,21 PROMPT " 4. Manutenção " MESSAGE "Recria arquivos de índices           "
MENU TO nMENU
DO CASE
   CASE nMENU = 1
        CADASTRO() // CHAMA SUBROTINA DE CADASTRO
   CASE nMENU = 2
        CONSULTA() // CHAMA SUBROTINA DE CONSULTA
   CASE nMENU = 3
        DELETA()   // CHAMA SUBROTINA PARA DELETAR REGISTROS
   CASE nMENU = 4
        ERASE("*"+INDEXEXT()) // APAGA TODOS OS ARQUIVOS DE ÍNDICE
        CRIADBF()             // CHAMA A FUNÇÃO DE CRIAÇÃO DE BANCO DE DADOS E ÍNDICES
   OTHERWISE // CASO NENHUMA DAS ANTERIORES
        // QUANDO O USUÁRIO TECLA [Esc] nMENU = 0
        QUIT // ENCERRA O PROGRAMA
ENDCASE
MENU() // CHAMA O MENU NOVAMENTE APÓS VOLTAR DE OUTRA SUBROTINA (FUNÇÃO)

Criando seu banco de dados

O ideal é que seu sistema seja capaz de criar seus próprios bancos de dados e arquivos de índices numa rotina específica. Arquivos de índices dão problema e precisam ser recriados eventualmente. 

Para criar um arquivo de índice você tem que escolher um campo chave-única que não se repete como o CPF ou TELEFONE ou uma ordem que você deseja apresentar o banco de dados (NOME) em ordem alfabética.

Campos de chave única são muito importantes porque servem para relacionar um banco de dados com outro, normalmente é um número de CÓDIGO, CPF ou CNPJ.

O comando IF...ENDIF é muito utilizado em todos os sistemas. Eles executam os comandos se uma condição for satisfeita.

A função AADD() adiciona informações numa variável do tipo Array (matriz). Significa: Array Add = AADD(). Insere um vetor numa matriz.

A função FILE() retorna verdadeiro se o arquivo especificado existir.

O comando USE abre um banco de dados e o comando INDEX cria um arquivo de índice. O comando CLOSE fecha o banco de dados e arquivo de índice.

FUNCTION CRIADBF() // CRIA SUBROTINA CHAMADA DBF()
// CRIA BANCO DE DADOS E ÍNDICES, CASO NÃO EXISTAM
IF .NOT. FILE("AGENDA.DBF") // SE NÃO EXISTIR O ARQUIVO "AGENDA.DBF"...
   aDBF = {} // CRIA UMA VARIÁVEL DO TIPO ARRAY (MATRIZ) PARA ARMAZENAR A ESTRUTURA DO BANCO DE DADOS
   AADD(aDBF, {"NOME", "C", 30, 0}) // NOME, TIPO "C" CARACTER, TAMANHO 30, 0 CASAS DECIMAIS.
   AADD(aDBF, {"FONE", "N", 11, 0}) // FONE, TIPO "N" NUMÉRICO, TAMANHO 11, 0 CASAS DECIMAIS.  
   AADD(aDBF, {"CPF",  "N", 11, 0})  
   DBCREATE("AGENDA.DBF", aDBF)     // FUNÇÃO QUE CRIA O BANCO DE DADOS COM BASE NUMA VARIÁVEL ARRAY (MATRIZ)
ENDIF
IF .NOT. FILE( "AGENDA"+INDEXEXT() )  // SE NÃO EXISTIR O ARQUIVO DE ÍNDICE DA AGENDA...
   USE AGENDA ALIAS FO EXCLUSIVE NEW  // ABRE O BANCO DE DADOS AGENDA EM MODO EXCLUSIVO PARA INDEXAR
   INDEX ON NOME TAG NOME TO AGENDA   // CRIA O ARQUIVO DE ÍNDICE INDEXANDO PELO NOME
   INDEX ON FONE TAG FONE TO AGENDA   // CRIA O ARQUIVO DE ÍNDICE INDEXANDO PELO TELEFONE
   INDEX ON CPF  TAG CPF  TO AGENDA   // CRIA O ARQUIVO DE ÍNDICE INDEXANDO PELO CPF
   CLOSE
ENDIF
RETURN

Para poder compilar sem erros precisa definir todas as funções que o sistema chama. Continue a leitura nas próximas páginas.

FUNCTION CADASTRO()
// SUBROTINA A SER IMPLEMENTADA
RETURN

FUNCTION CONSULTA()
// SUBROTINA A SER IMPLEMENTADA
RETURN

FUNCTION DELETA()
// SUBROTINA A SER IMPLEMENTADA
RETURN

Resultado:

Menu Principal

Aprofunde-se mais, restrinja o acesso a usuários cadastrados antes de acessar o menu do sistema.

Abaixo está o código do nosso pequeno sistema de agenda completo num PRG só para download.

Cadastrando dados no Clipper, Harbour, xHarbour.

Vamos dar continuidade ao sistema apresentando a rotina de cadastro da nossa agenda.

Começamos abrindo o banco de dados em modo compartilhado com o parâmetro SHARED para usar em modo rede multi-usuário.

Depois criamos um loop de cadastro. Nele pegamos as informações do usuário com o comando @...SAY...GET; inserimos um registro em branco no arquivo com o DBAPPEND() e gravamos as variáveis do programa que o usuário digitou nos campos do banco de dados e liberamos o registro para uso normal na rede com o DBUNLOCK(). Fechamos o banco de dados para encerrar e voltar ao menu.

FUNCTION CADASTRO()
USE AGENDA ALIAS AG SHARED NEW // ABRE O BANCO DE DADOS EM MODO COMPARTILHADO
SET INDEX TO AGENDA            // ABRE O ARQUIVO DE ÍNDICE  
SET ORDER TO TAG CPF           // COLOCA ORDENADO POR CPF

SETCOLOR("W+/N")
@ 04,01 SAY PADC("CADASTRO DA AGENDA", 77, " ") // CENTRALIZA DENTRO DE 77 CARACTERES

DO WHILE .T. // CRIA UM LOOP SUPOSTAMENTE INFINITO
   @ 07,01 CLEAR TO 22,78  // LIMPA ESSA ÁREA
   cNOME := SPACE(30) // DEFINE UMA VARIÁVEL CARACTER TAMANHO 30
   nFONE := 0         // DEFINE TELEFONE NUMÉRICO
   cCPF  := SPACE(11) // DEFINE CPF CARACTER PORQUE O PONTO DA MÁSCARA É LIDO COMO DECIMAL
   @ 09,10 SAY "CPF.:" GET cCPF PICTURE "@R 999.999.999-99" // EXIBE UMA MÁSCARA NO GET QUE RETORNA 99999999999
   @ 24,00 SAY PADL("Insira um número de CPF",54, " ") COLOR "W+/BG"      // MENSAGEM NA LINHA DE STATUS
   READ // AGUARDA A LEITURA DOS GETs

   IF LASTKEY() = K_ESC // LASTKEY() RETORNA A ÚLTIMA TECLA, SE FOI [ESC]...
      EXIT // SAI DO LOOP
   ELSEIF EMPTY(cCPF)   // SE DEIXOU EM BRANCO
          ALERT("PRECISA DIGITAR O CPF")
          LOOP          // VOLTA PRA LINHA SEGUINTE AO 'DO WHILE'
   ENDIF   

   nCPF := VAL( cCPF ) // CONVERTE cCPF CARACTER PARA nCPF NUMÉRICO COM VAL()
   IF AG->(DBSEEK(nCPF)) // SE A PESQUISA DBSEEK() NO DBF RETORNOU .T. (VERDADEIRA)
      ALERT("ESSA PESSOA JÁ FOI CADASTRADA")
      LOOP
   ELSE
      // ROTINA DE CADASTRO
      // PEDE PRO USUÁRIO DIGITAR O NOME E TELEFONE
      @ 24,00 SAY PADL("Digite um nome e telefone", 54, " ") COLOR "W+/BG" // NA COLUNA 55 TEM 'TECLE [ESC]'

      // PEGA DADOS DIGITADOS PELO USUÁRIO COM GET/READ  
      @ 10,10 SAY "NOME:" GET cNOME 
      @ 11,10 SAY "FONE:" GET nFONE PICTURE "@R (99) 99999-9999"
      READ	   
  
      IF ( ALERT("Confirma cadastro?", {"Sim", "Não"}) ) = 1  // ALERT() RETORNA A POSIÇÃO DO ARRAY ESCOLHIDO, "Sim"=1, "Não"=2, Esc=0.
         AG->(DBAPPEND()) // INSERE UM REGISTRO EM BRANCO NO ARQUIVO DBF  
	 AG->CPF  := nCPF    // ATRIBUI O CAMPO DO DBF À VARIÁVEL DO PROGRAMA
         AG->NOME := cNOME
         AG->FONE := nFONE
         AG->(DBCOMMIT()) // JOGA O CACHE PRO DISCO (GRAVA NO DBF)
         AG->(DBUNLOCK()) // DESBLOQUEIA O REGISTRO
      ENDIF
   ENDIF   
ENDDO // VOLTA PRA LINHA SEGUINTE DO 'DO WHILE'

AG->(DBCLOSEAREA()) // FECHA O BANCO DE DADOS
RETURN // RETORNA PRA FUNCÃO QUE CHAMOU CADASTRO()

Aprofunde-se mais sobre cadastrar dados.

Consulta banco de dados em tabela (grid)

Abaixo uma rotina simples de consulta de banco de dados em forma de tabela (grid).

FUNCTION CONSULTA()
USE AGENDA ALIAS AG SHARED NEW // ABRE O BANCO DE DADOS EM MODO COMPARTILHADO
SET INDEX TO AGENDA            // ABRE O ARQUIVO DE ÍNDICE  
SET ORDER TO TAG NOME          // COLOCA ORDENADO POR CPF
// Observe que você apelidou AGENDA de AG
// AG-> É o mesmo que dar um SELECT AGENDA
// ALIAS é muito útil num sistema com vários DBF abertos

SETCOLOR("W+/N")
@ 04,01 SAY PADC("CONSULTA AGENDA", 77, " ") // CENTRALIZA DENTRO DE 77 CARACTERES
@ 07,01 CLEAR TO 22,78                       // LIMPA ESSA ÁREA
@ 24,55 SAY "Tecle [Esc] para sair" COLOR "W+/BG"

aDADOS   := { "NOME", "FONE"              , "AG->(STR(CPF,11))" }
aPICTURE := {       , "@R (99) 99999-9999", "@R 999.999.999-99" }
aTITLES  := {"NOME" , "TELEFONE"          , "CPF"               } 
AG->(DBEDIT(08,02,22,77, aDADOS,,aPICTURE, aTITLES))

AG->(DBCLOSEAREA()) // Fecha AGENDA.DBF

RETURN

Resultado:

consulta dbf

Os nomes, telefones e CPFs acima são fictícios!!!


Aprofunde-se mais em tabelas de banco de dados aprendendo o TBrowse().

Aprenda a deletar um registro

Aprenda a deletar um registro do banco de dados em nossa rotina comentada abaixo. Consultamos se existe o CPF no banco de dados e se verdadeiro mostra os dados do registro na tela e pede confirmação do usuário para apagar.

FUNCTION DELETA()
	USE AGENDA ALIAS AG SHARED NEW // ABRE O BANCO DE DADOS EM MODO COMPARTILHADO
	SET INDEX TO AGENDA            // ABRE O ARQUIVO DE ÍNDICE  
	AG->(ORDSETFOCUS("CPF"))       // COLOCA ORDENADO POR CPF
	
	SETCOLOR("W+/N")
	@ 04,01 SAY PADC("APAGAR REGISTRO DA AGENDA", 77, " ") // CENTRALIZA DENTRO DE 77 CARACTERES
	
	DO WHILE .T. // CRIA UM LOOP SUPOSTAMENTE INFINITO
		@ 07,01 CLEAR TO 22,78  // LIMPA ESSA ÁREA
		
		cCPF  := SPACE(11) // DEFINE CPF CARACTER PORQUE O PONTO DA MÁSCARA É LIDO COMO DECIMAL
		@ 09,10 SAY "CPF.:" GET cCPF PICTURE "@R 999.999.999-99" // EXIBE UMA MÁSCARA NO GET QUE RETORNA 99999999999
		@ 24,00 SAY PADL("Insira um número de CPF para pesquisar",54, " ") COLOR "W+/BG"      // MENSAGEM NA LINHA DE STATUS
		READ // AGUARDA A LEITURA DOS GETs
	
		IF LASTKEY() = K_ESC	   
		   EXIT // SAI DO LOOP
		ELSEIF EMPTY(cCPF) // SE DEIXOU EM BRANCO
			   ALERT("PRECISA DIGITAR O CPF")
			   LOOP        // VOLTA PRA LINHA SEGUINTE AO 'DO WHILE'
		ENDIF   
	
		nCPF := VAL( cCPF ) // CONVERTE cCPF CARACTER PARA nCPF NUM�RICO COM VAL()
		IF !AG->(DBSEEK(nCPF)) // SE A PESQUISA DBSEEK() É NÃO VERDADEIRA, OU SEJA, FALSA, NÃO ENCONTROU NADA.
		   ALERT("ESSA PESSOA NÃO ESTÁ CADASTRADA")
		   LOOP
		ELSE
		   // REGISTRO FOI ENCONTRADO, MOSTRA DADOS NA TELA E PEDE CONFIRMAÇÃO PARA APAGAR
		   cSN := " " // DEFINE A VARIÁVEL cSN COMO STRING VAZIA DE 1 CARACTER
		   @ 10,10 SAY "NOME: " + AG->NOME 
		   @ 11,10 SAY "FONE: " + TRANSFORM(AG->FONE, "@R (99) 99999-9999") // ESSA FUNÇÃO TRABALHA IGUAL AO PICTURE DO @...GET
		   @ 24,00 SAY SPACE(79) COLOR "W+/BG" // LIMPA A LINHA DE STATUS
		   @ 24,00 SAY "Confirma deletar o registro? (S/N):" COLOR "W+/BG" GET cSN COLOR "GR+/N" PICTURE "@!" VALID cSN $ "SN" 
		   READ
		   
		   // MAIORES DETALHES SOBRE O @...GET ACIMA
		   // PICTURE "@!": Significa que tudo o que for digitado será MAIÚSCULO
		   // VALID cSN $ "SN": Valida o campo GET se cSN está contido na string "SN", só sai se o usuário pressionar "S" ou "N".
		   		   
		   IF cSN = "S"        // SE DIGITOU "S"
			  AG->(RLOCK())    // BLOQUEIA O REGISTRO
			  AG->(DBDELETE()) // MARCA O REGISTRO PARA DELEÇÃO
			  AG->(DBUNLOCK()) // DESBLOQUEIA O REGISTRO
		   ENDIF
		ENDIF   
	ENDDO // VOLTA PRA LINHA SEGUINTE DO 'DO WHILE'
	
	AG->(DBCLOSEAREA()) // FECHA O BANCO DE DADOS
RETURN

Resultado:

aprenda deletar registro dbf

O nome, telefone e CPF acima são fictícios.