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.
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
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"
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)
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:
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.
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.
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:
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 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:
O nome, telefone e CPF acima são fictícios.