Página 1 de 3

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:12
por JoséQuintas
Difícil escolher um título pra isto.
No final, achei melhor um título chamando a atenção.
Vou mostrar como acabei facilitando as coisas no meu aplicativo usando classe.
O lado bom é ficar tudo igual, assim facilita qualquer conversão posterior.

O cadastro até que é simples, o "poder" é ele transformado em classe.

Tela simple:
cadastro1.png
Pra não complicar o browse, aqui usei só o dbEdit() original.
cadastro2.png
Uso isso desde o cadastro mais simples, até a digitação de pedidos.
A tela de pedidos é um pouco mais "recheada" e com mais opções, mas tudo bem.
Como já comentei aqui, só uso botões da GTWVG. Mas isso não é importante para o assunto do tópico, e não é sobre isso que vou escrever.
cadastro3.png

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:39
por JoséQuintas
Vou começar mostrando um fonte quase tradicional, sem usar nada de classe.
Postar uma parte de cada vez, pra explicar como funciona.
Também porque devo ter usado algumas coisas não muito comuns para alguns, ou só existentes no Harbour - pouca coisa.
Depois esse fonte vai ser transformado em classe, e é aí que fica poderoso.

Começar de trás pra frente, já mostrando a diferença de classe e função.
Isso vai ser usado somente no final, mas tudo bem.

Código: Selecionar todos

PRIVATE cOpc := " "
FUNCTION Execute()
   // fonte da função
   RETURN NIL

Considerando que vamos chamar a classe de CadastroClass, então isso vai ficar assim:

Código: Selecionar todos

CREATE CLASS CadastroClass
   VAR cOpc INIT " "
   METHOD Execute()
   END CLASS

METHOD Execute() CLASS CadastroClass
   // fonte da função
   RETURN NIL
- Declarada a classe CadastroClass usando CREATE CLASS
- A antiga variável private, declarada usando VAR cOpc INIT " "
- A função Execute(), declarada usando METHOD. Primeiro como resumo na criação da classe, depois com o código fonte, acrescentado "CLASS CadastroClass" pra indicar que pertence à classe que criamos.

A título de curiosidade, é normal nos componentes Windows falarmos de propriedades e métodos.
São essas duas coisas aí: variável e função.

Dentro da classe, pra indicar a função ou variável dentro dela, indicamos com "::", ou "SELF:".
Exemplo: pra acessar cOpc dentro de Execute(), teríamos que escrever ::cOpc, ou SELF:cOpc
SELF = ela mesma

Já se chamássemos isso de um programa usando a classe seria diferente, porque a classe costuma ser uma variável.

Código: Selecionar todos

oClasse := CadastroClass():New()
? oClasse:cOpc
A classe CadastroClass inteira está dentro da variável oClasse, todas as variáveis e funções dela, dentro de uma única variável.
Então indicamos na linha seguinte que é a variável cOpc, que está dentro da classe em oClasse. oClasse:cOpc
E se fosse a função Execute(), seria oClasse:Execute().

Se escrever só Execute(), isso se refere a uma função comum chamada Execute(), e não à função Execute() que agora está dentro de oClasse.

Ok. Só lembrando que tudo isso vamos usar no final.

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:48
por JoséQuintas
Dividi o fonte em várias funções, mostrar uma de cada vez.

Código: Selecionar todos

#include "inkey.ch"
#include "hbgtinfo.ch"

REQUEST DBFCDX
REQUEST HB_CODEPAGE_PTISO
MEMVAR cOpc

PROCEDURE Main
   PRIVATE cOpc := " "
   RddSetDefault( "DBFCDX" )
   Set( _SET_CODEPAGE, "PTISO" )
   hb_gtInfo( HB_GTI_COMPATBUFFER, .F. )
   SetMode( 25, 80 )
   SetColor( "W/B,N/W,,,G+/B" )
   CLS
   @ 0, 0 Say Padc( "EXEMPLO DE CADASTRO", MaxCol() + 1 )
   @ 1, 0 TO 1, MaxCol() COLOR "GR+/N"
   @ MaxRow() - 1, 0 TO MaxRow() - 1, MaxCol() COLOR "GR+/N"
   SET KEY K_F9 TO Pesquisa
   Execute()
   CLS
   RETURN
Programa principal, de diferente:
- Mudança da tabela ASCII pra PTBR (REQUEST HB_CODEPAGE_PTISO, e Set( _SET_CODEPAGE, "PTISO" )
- Mudança do SAVE SCREEN/RESTORE SCREEN tradicional, senão os caracteres ficam bagunçados ( hb_gtInfo( HB_GTI_COMPATBUFFER, .F. )

O restante é normal do Clipper, incluindo:
- Uma tecla destinada a pesquisa em cadastros ( SET KEY K_F9 TO Pesquisa)

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:54
por JoséQuintas
Aqui parece um esqueleto de cadastro.
Tudo usando funções.

Código: Selecionar todos

STATIC FUNCTION Execute()
   IF .NOT. AbreArquivos()
      RETURN NIL
   ENDIF
   DO WHILE .T.
      TelaDados( .F. )
      MenuOpcoes()
      DO CASE
      CASE LastKey() == K_ESC
         EXIT
      CASE cOpc == "P" // Primeiro
         Primeiro()
         LOOP
      CASE cOpc == "U" // Ultimo
         Ultimo()
         LOOP
      CASE cOpc == "-" // Anterior
         Anterior()
         LOOP
      CASE cOpc == "+" // seguinte
         Seguinte()
         LOOP
      ENDCASE
      IF .NOT. cOpc $ "IAEC"
         LOOP
      ENDIF
      IF .NOT. Especifico()
         LOOP
      ENDIF
      TelaDados()
      IF cOpc == "C"
         LOOP
      ENDIF
      DO CASE
      CASE cOpc == "I" // inclui
         IF PodeAlterar()
            TelaDados( .T. )
         ENDIF
      CASE cOpc == "A" // Altera
         IF PodeAlterar()
            TelaDados( .T. )
         ENDIF
      CASE cOpc == "E" // Exclui
         IF PodeAlterar()
            Exclui()
         ENDIF
      ENDCASE
   ENDDO
   CLOSE DATABASES
   RETURN NIL
Os nomes são auto-explicativos. Talvez explicar estes:
TelaDados() ou TelaDados(.F.) é a tela sem digitação.
TelaDados(.T.) é a tela com digitação.

Como exemplo, ao escolher seguinte, basta posicionar e voltar ao início, porque já tem uma chamada a TelaDados() pra atualizar a informação da tela.

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:56
por JoséQuintas
AbreArquivos() pra.... abrir arquivos.

Código: Selecionar todos

STATIC FUNCTION AbreArquivos()
   LOCAL oStru
   IF .NOT. File( "test.dbf" )
      oStru := { { "CDCODIGO", "N", 6, 0 }, { "CDNOME", "C", 30, 0 } }
      dbCreate( "test", oStru )
   ENDIF
   IF .NOT. File( "test.cdx" )
      USE Test
      INDEX ON test->cdCodigo TAG "CODIGO"
      USE
   ENDIF
   USE test
   SET INDEX TO test
   RETURN .T.

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 19:59
por JoséQuintas
O menu com as opções.
Aqui parece complicado, mas é porque está verificando cada tecla, e aceitando INSERT, DELETE, PAGEUP, PAGEDOWN, e também o teclado numérico mesmo que o NUMLOCK esteja ativado, letra maiúscula e minúscula, então tem que testar bastante coisa.
Conforme a tecla, define uma opção diferente.
Para o restante do programa não interessa, sempre vai retornar a letra certa em cOpc
Tudo bem, cada um pode melhorar isso depois do jeito que quiser.

Código: Selecionar todos

STATIC FUNCTION MenuOpcoes()
   LOCAL nKey
   Mensagem( "Consulta, Inclui, Altera, Exclui, Prim, Ult, +Seg, -Ant, <ESC>Sair" )
   nKey := Inkey(0)
   Mensagem()
   DO CASE
   CASE nKey == Asc( "C" ) .OR. nKey == Asc( "c" )
      cOpc := "C"
   CASE nKey == Asc( "I" ) .OR. nKey == Asc( "i" ) .OR. nKey == K_INS .OR. nKey == Asc( "0" )
      cOpc := "I"
   CASE nKey == Asc( "A" ) .OR. nKey == Asc( "a" )
      cOpc := "A"
   CASE nKey == Asc( "E" ) .OR. nKey == Asc( "e" ) .OR. nKey == K_DEL .OR. nKey == Asc( "." ) .OR. nKey == Asc( "," )
      cOpc := "E"
   CASE nKey == Asc( "P" ) .OR. nKey == Asc( "p" ) .OR. nKey == K_HOME .OR. nKey == Asc( "7" )
      cOpc := "P"
   CASE nKey == Asc( "U" ) .OR. nKey == Asc( "u" ) .OR. nKey == K_END .OR. nKey == Asc( "1" )
      cOpc := "U"
   CASE nKey == Asc( "+" ) .OR. nKey == K_PGDN .OR. nKey == Asc( "3" )
      cOpc := "+"
   CASE nKey == Asc( "-" ) .OR. nKey == K_PGUP .OR. nKey == Asc( "9" )
      cOpc := "-"
   OTHERWISE
      cOpc := " "
   ENDCASE
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:00
por JoséQuintas
Opção primeiro, só precisa posicionar, então é só isso mesmo.
Parece exagero, mas é bom fazer assim pra quando virar classe.

Código: Selecionar todos

STATIC FUNCTION Primeiro()
   GOTO TOP
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:01
por JoséQuintas
Opção último, idem.

Código: Selecionar todos

STATIC FUNCTION Ultimo()
   GOTO BOTTOM
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:03
por JoséQuintas
Opção anterior, já acrescentei algo mais.
Se não existir anterior ( Bof() ), então mostra mensagem.

Código: Selecionar todos

STATIC FUNCTION Anterior()
   SKIP -1
   IF Bof()
      Mensagem( "Não tem registro anterior ao atual. Tecle algo pra prosseguir" )
      Inkey(0)
      Mensagem()
      GOTO TOP
   ENDIF
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:04
por JoséQuintas
Opção seguinte, mesma coisa, mas checando Eof()

Código: Selecionar todos

STATIC FUNCTION Seguinte()
   SKIP
   IF Eof()
      Mensagem( "Não tem registro posterior ao atual. Tecle algo pra prosseguir" )
      Inkey(0)
      Mensagem()
      GOTO BOTTOM
   ENDIF
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:05
por JoséQuintas
Opção exclui. Geralmente é confirmar e excluir.
Mas pode acontecer de estar posicionado em Eof(), ou o arquivo estar vazio, então só uma checagem a mais.

Código: Selecionar todos

STATIC FUNCTION Exclui()
   LOCAL cResp := " ", GetList := {}
   IF Eof()
      Mensagem( "Não tem registro posicionado pra poder excluir. Tecle algo pra prosseguir" )
      Inkey(0)
      RETURN NIL
   ENDIF
   Mensagem( "Exclui?" )
   @ Row(), Col() + 2 GET CResp PICTURE "!A"
   READ
   Mensagem()
   IF cResp == "S" .AND. LastKey() != K_ESC
      DELETE
   ENDIF
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:09
por JoséQuintas
Na TelaDados() uso um pequeno truque: GET pra fazer o serviço do SAY.
Vantagem: A mesma tela atende pra inclusão, alteração, exclusão, consulta, etc.

Nota:
Pra anular o GET, uso CLEAR GETS
E salva no final, se for pra salvar.

Código: Selecionar todos

STATIC FUNCTION TelaDados( lDigita )
   LOCAL GetList := {}
   LOCAL mcdCodigo := test->cdCodigo
   LOCAL mcdNome   := test->cdNome
   lDigita := iif( lDigita == NIL, .F., lDigita )
   @ 2, 0 SAY "Codigo....:" GET mcdCodigo PICTURE "@K 999999" WHEN .F.
   @ 4, 0 SAY "Nome......:" GET mcdNome   PICTURE "@!"
   IF lDigita
      Mensagem( "Digite informações, <ESC> Sai" )
      READ
      Mensagem()
      IF LastKey() != K_ESC
         REPLACE test->cdNome WITH mcdNome
      ENDIF
   ELSE
      CLEAR GETS
   ENDIF
   RETURN NIL

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:11
por JoséQuintas
Se for pra escolher um código, precisa pedir o código, esta rotina é pra isso.
Pra facilitar depois, criei em separado a função CodigoInvalido(), pra ver se pode continuar dependendo do código digitado.
Também coloquei a inclusão aqui mesmo, pra facilitar. (APPEND)

Código: Selecionar todos

STATIC FUNCTION Especifico()
   LOCAL GetList := {}
   LOCAL mcdCodigo := test->cdCodigo
   Mensagem( "Digite código, <ESC> Sai" )
   @ 2, 0 SAY "Codigo....:" GET mcdCodigo PICTURE "@K 999999" VALID mcdCodigo > 0
   READ
   Mensagem()
   IF LastKey() == K_ESC
      RETURN .F.
   ENDIF
   SEEK mcdCodigo
   IF CodigoInvalido()
      RETURN .F.
   ENDIF
   IF cOpc == "I"
      APPEND BLANK
      REPLACE test->cdCodigo WITH mcdCodigo
   ENDIF
   RETURN .T.

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:12
por JoséQuintas
A função de código inválido.
Vai ser inválido se tentar incluir um código que já existe, ou alterar um que não existe.

Código: Selecionar todos

STATIC FUNCTION CodigoInvalido()
   LOCAL lInvalido := .F.
   IF cOpc == "I" .AND. .NOT. Eof()
      Mensagem( "Código já cadastrado. Tecle algo para continuar" )
      Inkey(0)
      Mensagem()
      lInvalido := .T.
   ENDIF
   IF cOpc != "I" .AND. Eof()
      Mensagem( "Código não cadastrado. Tecle algo para continuar" )
      Inkey(0)
      Mensagem()
      lInvalido := .T.
   ENDIF
   RETURN lInvalido

Cadastro poderoso usando classe

Enviado: 24 Mai 2015 20:14
por JoséQuintas
A de mensagem, bem comum.
Serve pra mostrar ou limpar a mensagem, se posicionando sempre na última linha.

Código: Selecionar todos

FUNCTION Mensagem( cTexto )
   cTexto := iif( cTexto == NIL, "", cTexto )
   @ MaxRow(), 0 CLEAR TO MaxRow(), MaxCol()
   @ MaxRow(), 0 SAY Pad( cTexto, MaxCol() + 1 )
   RETURN NIL