CURSO de OOP en CLIPPER 5.01 (N 2) GRUPO EIDOS            Brief by HEST

DEL BROWSE DE DBASE AL DBEDIT DE CLIPPER 
 
Cualquier usuario de dBase  ha  utilizado  en  alguna  ocasin  el  comando 
BROWSE.  La  importancia  de  este  comando reside en la capacidad de poder 
consultar  en forma de tabla la informacin contenida en una base de datos. 
Al  visualizar  conjuntamente  un cierto nmero de registros, podemos sacar 
conclusiones  y  analizar  los  datos  almacenados  de una manera mucho ms 
cmoda y efectiva que si tuvisemos que hacerlo registro a registro.
 
La innovacin que Clipper Summer '87 aport en este sentido fue la creacin 
de  la  funcin  DbEdit().  Esta  ofrece  mucha ms potencia que el comando 
BROWSE  de dBase. Con DbEdit() es posible definir el nmero de columnas que 
queramos visualizar, as como el contenido de cada una de ellas. Adems, no 
tenemos  porqu  limitarnos  a visualizar el contenido de un campo. Podemos 
definir expresiones, las  cuales  pueden  incluir  el  contenido  de  otros 
campos,  variables de memoria o sencillamente algunos clculos que queramos 
realizar. Hay  que  reconocer  que  funciones  como  sta,  o  las  famosas 
AChoice() y MemoEdit(), fueron verdaderas innovaciones tecnolgicas  en  su 
da, que nos permitieron  realizar  programas  con  unas  prestaciones  muy 
superiores a lo que se conoca por entonces.
 
Otro aspecto en el que Clipper Summer '87 result claramente innovador, fue 
la curiosa forma  en  que  se  nos  permita  modificar  el  comportamiento 
estndar  de  estas  tres  funciones:  DbEdit(),  AChoice()  y  MemoEdit(). 
Podamos  definir  una UDF (User Defined Function = Funcin Definida por el 
Usuario)  a la que Clipper acceda para consultar qu hacer en cada posible 
situacin  en la que nos encontrsemos. Esto nos facilitaba el poder elegir 
entre  un comportamiento totalmente estndar o un comportamiento adecuado a 
las necesidades particulares del programa que estuvisemos desarrollando.
 
A pesar del enorme aumento de potencia que  estas  funciones  nos  ofrecan 
respecto  al  dBase,  la  limitacin  que  los  programadores en Summer '87 
encontrbamos  al  cabo  de algn tiempo de utilizarlas, era que no siempre 
podamos hacer con ellas todo lo  que  necesitbamos.  Al  llamar  a  estas 
funciones,  les  cedemos a ellas el control de la ejecucin del programa, y 
aunque  a  travs  de nuestras UDFs de control conseguimos un alto nivel de 
personalizacin,  tarde  o  temprano  echamos en falta el no disponer de un 
control absoluto de los procesos que estn teniendo lugar.
 
Este  problema  no  es  especfico  de  Clipper  Summer '87, sino que es un 
reflejo  de un problema muy serio que presenta la programacin estructurada 
tradicional. Existe una relacin proporcional directa entre el  aumento  de 
prestaciones del software y el aumento de complejidad que hemos de soportar 
a  cambio.  Si  pensamos  en  disear  unas  funciones  lo  suficientemente 
poderosas  como  para poder contemplar cualquier situacin que se nos pueda 
presentar, acabaremos descubriendo que no tenemos ms remedio  que  definir 
un  enorme nmero de parmetros, y codificar funciones muy complejas en las 
que la simplicidad y la potencia que las funciones podan ofrecernos en  un 
principio, paradjicamente acaban perdindose.
 
 
OOPS: MAS POTENCIA CON MAS SENCILLEZ 
 
La solucin a este gran problema es el Sistema de Programacin Orientado al 
Objeto (OOPS). Este ha demostrado  ser  la  evolucin  de  la  programacin 
estructurada convencional. En OOPS se consigue un espectacular  aumento  de 
potencia y, sin embargo, la complejidad disminuye. Esto es algo que  tenan 
muy claro los Ingenieros de Nantucket, y es por esto que mientras  nosotros 
bamos descubriendo las limitaciones de DbEdit(), ellos  se  apresuraban  a 
disear la Clase TBrowse, cuyos Objetos, en Clipper  5,  pasan  a  ser  los 
sucesores de nuestra querida funcin DbEdit().
 
Se  trata  de entender que un Objeto, al igual que en el mundo real, consta 
de una serie de datos y de  unos  comportamientos  caractersticos  que  le 
vienen determinados por la Clase a la que pertenece  (os  recuerdo  que  en 
breve tendris disponible un  cuaderno  tcnico  de  OOPS  que  os  estamos 
preparando,  para  que  podis  estudiar  esto con todo detalle). Un Objeto 
TBrowse encierra dentro de s toda la complejidad que entraara el definir 
un buen nmero de variables que contuviesen toda la informacin que debemos 
controlar  para  disear una tpica tabla tipo browse y todas las funciones 
de  manipulacin  de la misma. Una primera aproximacin que os puede servir 
para  entender lo que es un Objeto es que pensis que sirve para reemplazar 
un montn de variables y de funciones de vuestro cdigo. Igual  que  podis 
utilizar funciones genricas de un programa a otro, si disponemos  de  unas 
Clases  lo  suficientemente  genricas, podemos ahorrarnos gran cantidad de 
variables y cdigo de un programa a otro.
 
Utilizando  Objetos,  no tenemos porque limitarnos a llamar a una funcin y 
cederle  a  ella  el  control  de  la  aplicacin.  Se  trata, ms bien, de 
comprender que establecemos un cierto entorno de trabajo, muy  estructurado 
y en el que en todo momento tenemos todo el control de lo que ocurre.
 
En  Clipper 5, utilizando un Objeto TBrowse podemos fcilmente construirnos 
cualquier tipo de tabla de datos que necesitemos. Los Objetos de  la  Clase 
TBrowse  no  tienen  porque  restringirse  a  tablas  de  visualizacin  de 
registros de un fichero de datos DBF. De hecho, podemos utilizar un  Objeto 
TBrowse  para  visualizar arrays, DBFs, hojas de clculo, un calendario, un 
puzzle, el contenido de otro Objeto, un fichero de Texto, un registro, etc.
Cualquier tipo de informacin que seamos capaces de estructurar  en  tablas 
puede ser visualizada y manipulada a travs de un Objeto TBrowse.
 
Parece  increble,  verdad? Gracias a la Clase TBrowse podemos disponer en 
nuestros  programas  de  un  nmero  ilimitado  de  Objetos  que solucionen 
mltiples problemas de diseo y  manipulacin  de  tablas  de  informacin. 
Evidentemente,  para  que  una  herramienta  ofrezca  tanta flexibilidad es 
necesario que su arquitectura sea  lo  suficientemente  abierta.  Esto  nos 
lleva  al  concepto  de  Clase Abstracta en OOPS. Tal y cmo ha manifestado 
Nantucket, en breve nos ofrecern un buen nmero de Clases  Abstractas  que 
nos  solucionarn  muchos  de los problemas habituales de diseo en los que 
solemos  perder  ms tiempo en vez de concentrarnos en la resolucin de los 
problemas especficos de  una  aplicacin.  Gestin,  listados,  etiquetas, 
edicin de textos, etc... en breve sern abordados desde el diseo OOPS.
 
Una  Clase  Abstracta  es aquella de la que no podemos generar directamente 
Objetos. Cuando el fabricante de Software nos ofrece una Clase Abstracta lo 
que hace es establecer unas reglas genricas de creacin  y  comportamiento 
de esa Clase de Objetos. Para poder llegar al Objeto  final  que  satisfaga 
las  necesidades  especficas  de nuestra aplicacin, tendremos que derivar 
una nueva Clase, completando as el diseo que ellos  inician.  Conseguimos 
as  disponer  de  unos  Objetos  que  se  adapten  exactamente  a nuestras 
necesidades.  Todo  el  mundo  queda  contento  y el fabricante de Software 
mantiene un estndar. Sencillamente extraordinario.
 
El  problema  actual  reside  en que Clipper an no nos ha facilitado plena 
capacidad  OOPS.  Para  poder  llegar  a comprobar la verdadera potencia de 
estos  diseos  hemos  de  recurrir  a  libreras  externas  que   explotan 
capacidades no documentadas de Clipper. Como os  explico  ms  adelante  en 
este artculo, la Clase TBrowse ha sido verdaderamente  diseada  para  ser 
utilizada a travs de la herencia, algo que nadie est  haciendo  ahora.  A 
travs de la herencia, el OOPS muestra su verdadera magia  y  se  consiguen 
espectaculares reducciones de cdigo y de complejidad.
 
 
UN OBJETO TBROWSE PASO A PASO 
 
Para generar un Objeto TBrowse en nuestros programas lo primero que debemos 
hacer  es  llamar  a la funcin TBrowseNew(). Cmo os explico ms adelante, 
esta funcin no es sino una forma  de  maquillar  lo  que  sera  la  forma 
correcta de generar un Objeto TBrowse ( TBrowse():New() ).
 
De hecho, Clipper nos ofrece dos mtodos constructores 'maquillados' en las 
funciones TBrowseNew() y TBrowseDb(), que corresponderan en realidad a los 
mensajes: TBrowse():New() y TBrowse():DbNew(). 
 
Consiguiendo as dos inicializaciones distintas, segn se trate de utilizar 
una   tabla   genrica   (no  especializada  en  DBFs)  o  una  tabla  para 
visualizacin de los registros de una DBF. Os recuerdo que en OOPS hay  que 
saber distinguir  entre  la  inicializacin  genrica  de  un  Objeto  (que 
consistira en la asignacin de la memoria necesaria y  el  establecimiento 
de todos sus datos a 'nil') que corre por  cuenta  del  compilador,  y  las 
mltiples  inicializaciones  especficas  segn  nuestras  necesidades  que 
correran  a cargo de los mtodos constructores que definisemos. Un mtodo 
constructor  se  debe  encargar  de  inicializar  aquellos  datos  que sean 
necesarios  para  conseguir  un  funcionamiento  por  defecto  correcto  de 
nuestros  Objetos.  Es  por esto,  que  podramos  definir  tantos  mtodos 
'constructors' como distintas inicializaciones  fusemos a  necesitar.  Por 
'herencia' del lenguaje C++ utilizaremos frecuentemente el nombre New  para 
identificar a nuestro mtodo constructor ms genrico.
 
No  es  mi intencin es este artculo el detallar todos los Datos y Mtodos 
propios  de los Objetos de la Clase TBrowse. Te remito a la gua Norton que 
acompaa a Clipper 5 o al  excelente  cuaderno  tcnico  que  ha  preparado 
nuestro  compaero  Valentn  Snchez.  Mi  intencin  es que comprendas la 
arquitectura de un diseo OOPS y cmo  debe  abordarse  la  utilizacin  de 
Objetos en nuestro cdigo.
 
As pues, lo primero que haramos en nuestro programa sera lo siguiente: 
 
     local brwPrueba := TBrowseNew( 3, 3, 20, 70 ) 
 
brwPrueba se habr convertido en un Objeto de Clase TBrowse, y sus Datos se 
habrn  inicializado  adecuadamente  segn  el  diseo  preparado  por  los 
Ingenieros   de  Nantucket.  En  concreto,  los  parmetros  que  le  hemos 
suministrado indican el rea en pantalla que  queremos  que  nuestra  tabla 
ocupe.
 
Acto  seguido  hemos  de  definir  y  aadirle a nuestro Objeto TBrowse las 
columnas que va a tener y con las que va a interaccionar. Nantucket tambin 
nos  proporciona  la  Clase  TbColumn  la  cual  genera  Objetos  diseados 
especficamente para ser utilizados desde Objetos TBrowse. Tambin la forma 
natural  de  inicializacin  de  estas  columnas  ha  sido 'maquillada' por 
Nantucket  en la llamada a la funcin TbColumnNew() que en realidad debiera 
haber sido TbColumn():New().
 
Una  vez  tenemos  definido  nuestro Objeto TBrowse y las columnas que va a 
contener,  comenzamos  a  interaccionar  con  l  utilizando  los mltiples 
mtodos  que  Nantucket pone a nuestra disposicin. Te recomiendo consultes 
los mltiples ejemplos que  te  ofrece  Valentn  Snchez  en  su  cuaderno 
tcnico acerca del TBrowse.
 
 
UTILICEMOS LA NOTACION HUNGARA 
 
Habris  observado  que  de  las  cuatro Clases predefinidas que nos ofrece 
Clipper  5, dos de ellas comienzan sus nombres con 'T': TBrowse y TbColumn. 
El comenzar el nombre de las Clases con la letra 'T' es algo que  heredamos 
directamente  del  C++.  Lo  que  en  un  principio  puede  parecernos algo 
arbitrario tiene una utilidad  bastante  grande:  es  una  forma  fcil  de 
distinguir el nombre de una Clase del de una funcin.
 
Si asumisemos  las  reglas  de  la  Notacin  Hngara  hasta  sus  ltimas 
consecuencias  lo  lgico  sera  comenzar  el nombre de las Clases con 'o' 
minscula, ya que al invocar cmo si fuese una funcin  el  nombre  de  una 
Clase,  Clipper  inicializa  por defecto y devuelve un Objeto de esa Clase. 
Luego sera coherente usar oBrowse() en vez de TBrowse(). Sin embargo,  una 
vez ms, asumiremos esta caracterstica heredada del C++, con la  intencin 
de establecer una estandarizacin al trabajar con lenguajes OOPS, partiendo 
de la idea de que el C++ es, hoy por hoy, el lder.
 
El C++ utiliza la letra 'T' cmo abreviatura de tipo.  As,  si  escribimos 
TWindow, queremos indicar que se trata de un Objeto de 'tipo' Window.
 
A la hora de declarar un Objeto TBrowse, o de cualquier otra Clase, habris 
observado que es frecuente encontrarnos declaraciones del tipo: 

      local oBrowse := TBrowseNew( ... ) 
 
Esto puede ser algo inmediato y  rpido  cuando  slo  vayamos  a  utilizar 
nicamente  un  solo  Objeto  en  nuestro  programa   (situacin   bastante 
improbable).  La Notacin Hngara nos sugiere que utilicemos tres letras en 
minsculas  para  especificar  la Clase a la que pertenezca nuestro Objeto. 
As, si vamos a utilizar tres  Objetos  TBrowse  en  nuestro  programa,  lo 
correcto es declararlos de la siguiente forma: 

      local brwClientes := TBrowseNew( ... )   // brw de TBrowse 
      local brwApuntes  := TBrowseNew( ... )   //         ^^ ^ 
      local brwCuentas  := TBrowseNew( ... ) 
 
Si quisiramos utilizar otros Objeto de otras Clases en nuestro cdigo, los 
declararamos cmo: 

      local getNombre := GetNew( ... ) 
      local colNombre := TbColumnNew( ... ) 

As  disponemos  de una informacin mucho ms completa que si slo ussemos 
la letra 'o'.
 
A los que ya os hayis iniciado en el estudio del OOPS, os habr  extraado 
que  la  forma  de  inicializar  un  Objeto TBrowse sea llamar a la funcin 
TBrowseNew()  en  vez de utilizar TBrowse():New( ... ). Por la misma razn, 
os habr resultado extrao que se utilice TBrowseDb() en vez de  Tbrowse():
Db() o que se utilice TbColumnNew() en  vez  de  TbColumn():New().  El  que 
Nantucket  nos  haya ofrecido esta sintaxis alternativa, radica en su deseo 
de  no  'asustar'  excesivamente  a  los  que  se  vayan iniciando en estas 
tcnicas. En el Fuente 1 vemos cmo de manera fcil podemos poner las cosas 
en su sitio y usar la sintaxis correcta del OOPS.
 
 
// ---Fuente 1: TEST.PRG -------------------------------------------------- 
 
// Compilad con Clipper test /n 
 
//------------------------------------------------------------------------// 
 
function Main() 
 
   local brwTest := TBrowse():New( 10, 10, 20, 40 ) 
 
   InKey( 0 ) 
 
return 
 
//-------------------------------------------------------------------------// 
 
function TBrowse() 
 
   static nHandle 
 
   if nHandle == nil 
      nHandle = TBrowseNew():ClassH() 
      __ClassAdd( nHandle, "New", "TBrowseNew" ) 
   endif 
 
return __ClassIns( nHandle ) 
 
//-------------------------------------------------------------------------// 
 
//--------- Fin Fuente 1 ------------------------------------------------ 
 
 
Este  ejercicio  os  servir  para  comprender el funcionamiento de algunas 
funciones indocumentadas de Clipper  5.  Gracias  al  mtodo  indocumentado 
ClassH()  obtenemos  el  'handle' de la Clase a la que pertenece un Objeto. 
Clipper, sin darse cuenta, ha dejado una puerta trasera abierta por la  que 
podemos modificar las cuatro  Clases  estndar  que  nos  ofrece.  Esto  no 
deberamos de hacerlo nunca ya que la forma correcta de modificar una Clase 
es  a  travs de la herencia del OOPS, es decir, deberamos crear una nueva 
Clase  derivndola a partir de otra, en la que aadiramos o modificaramos 
lo que necesitsemos. Tambin es cierto que lo que tampoco debe de  hacerse 
es  darnos  solamente cuatro Clases predefinidas y con las capacidades OOPS 
bloqueadas...
 
Utilizando  __ClassAdd()  conseguimos  aadir  mtodos  que  Clipper  no ha 
incluido  en  sus  Clases. En nuestro ejemplo, se trata de aadir el mtodo 
New  a  la  Clase  TBrowse,   con  lo  que  conseguimos  tener  un   mtodo 
'constructor' al ms puro estilo OOPS. Ahora, al enviar el mensaje New a un 
Objeto TBrowse, entra en accin la  funcin  TBrowseNew()  consiguiendo  de 
esta manera nuestro Objetivo con utilizacin de la sintaxis OOPS correcta.
 

LO QUE AUN NO HEMOS VISTO 
 
Cada vez que aparece un artculo acerca de la Clase  TBrowse  es  corriente 
encontrarnos  con  un buen montn de lneas de cdigo. La primera impresin 
que se lleva alguien que nunca haya tratado con un Objeto TBrowse,  es  que 
el pasar de Summer '87 a Clipper 5 lleva implcito el escribir un montn de 
lneas ms...
 
Y esto es precisamente lo contrario  que  persigue  el  OOPS.  El  problema 
fundamental radica en el hecho de que Clipper 5 an no nos ofrece  un  100% 
de  capacidad OOPS. El mejor consejo que puedo daros si tenis que utilizar 
muchos  Objetos TBrowse distintos en vuestros programas es que os consigais 
la  librera  Classy.  Utilizando  Classy  disponis prcticamente de plena 
capacidad OOPS. La ms importante caracterstica del OOPS es la herencia, y 
es precisamente a travs de la herencia cmo debemos de utilizar  la  Clase 
TBrowse.
 
Existe una manera de poder derivar nuevas Clases a partir de las cuatro que 
ya  nos  ofrece Clipper. Si disponis de la librera oClip o de mi librera 
Dialog,  podis  conseguir  esto si comprendis que podemos crear una Clase 
que contenga en uno de sus Datos un Objeto de la Clase de Clipper de la que 
queramos  derivar  otra  nueva.  Se  trata  de  volver a definir los mismos 
mtodos que los que tenga esa Clase y sencillamente redirigir los  mensajes 
al  Objeto  de esa Clase que contenemos en uno de los Datos. Este ejercicio 
slo  es recomendable para aquellos que ya tengan una buena comprensin del 
OOPS. Para los dems, os ser ms fcil haceros con la librera Classy.
 
Otra  primera  impresin  que  es  corriente hacerse al estudiar las Clases 
predefinidas de Clipper es pensar que no son lo suficientemente completas o 
que no contemplan todos los supuestos que nosotros podemos necesitar.  Como 
hemos visto anteriormente, la Clase TBrowse debe ser utilizada a travs  de 
la  herencia,  creando  una  jerarqua  de  Browsers  que  satisfagan todas 
nuestras necesidades. La siguiente  jerarqua  que  te  propongo  cubre  un 
amplio  rango  de problemas tpicos de programacin, pero a la vez mantiene 
una simplicidad de diseo realmente increble (Ver figura).
 
 
                    Jerarqua de Clases de Browsers 
                    =============================== 
 
     TBrowse                          // Clase que ofrece Clipper 
        
        TQBrowse                   // Clase QuickBrowse, creada 
                                     // por nosotros y que permite 
                                     // ahorrarnos mucho del cdigo 
                                     // tpico de manipulacin de 
                                     // un tpico Objeto TBrowse 
              
              TABrowse             // Browser para Arrays 
                   
                   TOBrowse       // Browser para Objetos 
                   
                   TRecBrowse     // Browser para registros 
              
              TTxtBrowse           // Browser para ficheros de texto 
              
              TDbBrowse            // Browser para ficheros DBFs 
 

Encontraris todo el cdigo fuente de esta Jerarqua en el cuaderno tcnico 
de  OOPS  que  os  ofreceremos  en  breve. Comprenderis que por razones de 
espacio nos es imposible incluirlo en  este  artculo,  adems  de  que  es 
necesario  ver  ejemplos  y  entender  ciertas  explicaciones  acerca de su 
funcionamiento.
 
Recuerda  esto:  la  verdadera potencia del OOPS se consigue a travs de la 
herencia. Al derivar una Clase a partir de otra podemos adecuar las  Clases 
a nuestras necesidades especficas sin necesidad de conocer la forma en que 
hayan sido codificadas las Clases ascendentes de las que derivemos.
 
Como  primicia  aqu  os incluyo el cdigo fuente de slo algunas de ellas. 
Fijaros en la nueva sintaxis que he desarrollado que es casi C++ !!! En los 
prximos nmeros de clippeRmana ir publicando el nuevo Kernel  para  OOPS 
que os adelanto se  llama  Objects.  Permite  utilizar  el  debugger,  usar 
herencia mltiple,  declarar  mtodos  virtuales,  guarda  los  mtodos  en 
funciones static y un montn de trucos ms! (Ver Fuentes 2 y 3)
 
 
// -------Fuente 2: ABROWSE.PRG------------------------------------------ 
 
#include "Objects.ch" 
 
//------------------------------------------------------------------------// 
 
CLASS TABrowse FROM TQBrowse 
 
   DATA aArray 
   DATA nItem 
   DATA nLen 
 
   METHOD New( nTop, nLeft, nBottom, nRight, aArray ) 
 
ENDCLASS 
 
//-------------------------------------------------------------------------// 
 
METHOD TABrowse::New( nTop, nLeft, nBottom, nRight, aArray ) 
 
   ::Parent:New( nTop, nLeft, nBottom, nRight ) 
 
   ::aArray       = aArray 
   ::nItem        = 1 
   ::nLen         = Len( aArray ) 
   ::GoTopBlock   = { || ::nItem := 1 } 
   ::GoBottomBlock= { || ::nItem := Len( aArray ) } 
   ::SkipBlock    = { | nTbSkip, nASkip | nASkip := If( Abs( nTbSkip ) >= ;  
                 If( nTbSkip >= 0, ::nLen - ::nItem, ::nItem - 1 ),;        
           If( nTbSkip >= 0, ::nLen - ::nItem, 1 - ::nItem ), nTbSkip ),;   
                    ::nItem += nASkip, nASkip } 
return Self 
 
//-------------------------------------------------------------------------// 
//------Fin Fuente 2 ------------------------------------------------------- 
 
 
//---Fuente 3: RECBROWS.PRG ------------------------------------------------ 
 
#include "Objects.ch" 
 
//-------------------------------------------------------------------------// 
 
CLASS TRecBrowse FROM TABrowse 
 
   METHOD New( nTop, nLeft, nBottom, nRight ) 
   METHOD PageUp() 
   METHOD PageDown() 
 
ENDCLASS 
 
//-------------------------------------------------------------------------// 
 
METHOD TRecBrowse::New( nTop, nLeft, nBottom, nRight ) 
 
   local acFields := Array( FCount() ) 
 
   AEval( acFields, { | cField, n | acFields[ n ] := Field( n ) } ) 
 
   ::Parent:New( nTop, nLeft, nBottom, nRight, acFields ) 
 
   ::AddColumn( TBColumnNew( "",; 
                { || PadR( acFields[ ::nItem ], 10 ) } ) ) 
   ::AddColumn( TBColumnNew( "",; 
                { | u | If( PCount() > 0, FieldPut( ::nItem, u ),;          
             FieldGet( ::nItem ) ) } ) ) 
   ::GetColumn( 2 ):Width = 65 
 
return Self 
 
//-------------------------------------------------------------------------// 
 
METHOD TRecBrowse::PageUp() 
 
   ::Parent:PageUp() 
   if ::nItem == 1 
      SKIP -1 
      ::RefreshAll() 
   endif 
 
return 
 
//-------------------------------------------------------------------------// 
 
METHOD TRecBrowse::PageDown() 
 
   ::Parent:PageDown() 
   if ::nItem == ::nLen 
      SKIP 
      ::RefreshAll() 
   endif 
 
return 
 
//-------------------------------------------------------------------------// 
// -------- Fin Fuente 3 --------------------------------------------------- 

 
USEMOS EL PREPROCESADOR 
 
Una  vez  hayamos  realizado un diseo OOPS y hayamos codificado y depurado 
las Clases  que  vayamos  a  utilizar,  la  mejor  manera  de  realizar  la 
implementacin  en  nuestros  programas  es  a  travs del Preprocesador de 
Clipper.
 
El Preprocesador de Clipper puede resultar un  poderoso  aliado  del  OOPS. 
Gracias  al  Preprocesador  podemos hacer que toda la estructura OOPS quede 
oculta, evitndonos as tener  que  pensar  constantemente  acerca  de  los 
Objetos  que  estemos  usando.  No  olvidemos  que  en ltima instancia los 
Objetos son para ser usados y no para tener que estar pendiente de ellos.
 
Veamos ahora cmo podemos completar el estudio que hemos  realizado  acerca 
de la Clase TBrowse desarrollando una serie de UDCs (User Defined  Commands 
= Comandos Definidos por el Usuario) que oculten totalmente la  complejidad 
de la sintaxis OOPS.
 
Como  vais  a  descubrir, las posibilidades son prcticamente infinitas. Mi 
recomendacin es que  busquis  la  simplicidad  en  vuestro  cdigo  y  su 
utilidad. Lo importante es conseguir un cdigo limpio, que prcticamente no 
necesite comentarios y fcil de mantener.
 
Combinando  el  Preprocesador  y  el  OOPS  podemos  llegar a conseguir una 
extensibilidad del lenguaje con respecto al lenguaje Clipper estndar igual 
o  superior  a la que nos ofrece FoxPro, pero con una clara ventaja a favor 
de Clipper: dispondremos en todo momento de un control total. Ver Fuentes 
4 y 5.
 
 
// Fuente 4: TBROWSE.CH --------------------------------------------------- 
 
#xcommand BROWSE <oBrw> FROM <nTop>, <nLeft> TO <nBottom>, <nRight>   ; 
          [ COLOR <cColors> ] =>                                      ; 
                                                                      ; 
          <oBrw> = TBrowseNew( <nTop>, <nLeft>, <nBottom>, <nRight> ) ; 
          ; <oBrw>:HeadSep    = "-"                                   ; 
          ; <oBrw>:ColSep     = " | "                                 ; 
          ; <oBrw>:FootSep    = "-"                                   ; 
          [; <oBrw>:ColorSpec = <cColors> ]                           ; 
          ; #define THIS_BROWSE <oBrw> 
 
#xcommand BROWSE DBF <oBrw> FROM <nTop>, <nLeft> TO <nBottom>, <nRight> ; 
          [ COLOR <cColors> ] =>                                        ; 
                                                                        ; 
          <oBrw> = TBrowseDb( <nTop>, <nLeft>, <nBottom>, <nRight> )    ; 
          ; <oBrw>:HeadSep    = "-"                                     ; 
          ; <oBrw>:ColSep     = " | "                                   ; 
          ; <oBrw>:FootSep    = "-"                                     ; 
          [; <oBrw>:ColorSpec = <cColors> ]                             ; 
          ; #define THIS_BROWSE <oBrw> 
 
#xcommand COLUMN [ TITLE <cTitle> ] SHOW <uData>                      ; 
          [ FOOTER <cFooter> ] =>                                     ; 
                                                                      ; 
          THIS_BROWSE:AddColumn( TbColumnNew( <cTitle>, <{uData}> ) ) ; 
     [; THIS_BROWSE:GetColumn( THIS_BROWSE:ColCount ):Footing = <cFooter> ] 
 
 
#xcommand END BROWSE => #undef THIS_BROWSE 
 
#xcommand DISPLAY BROWSE <oBrw> => do while ! <oBrw>:Stabilize(); enddo 
 
#xcommand BROWSE <oBrw> DOWN   => <oBrw>:Down() 
#xcommand BROWSE <oBrw> UP     => <oBrw>:Up() 
#xcommand BROWSE <oBrw> LEFT   => <oBrw>:Left() 
#xcommand BROWSE <oBrw> RIGHT  => <oBrw>:Right() 
#xcommand BROWSE <oBrw> TOP    => <oBrw>:GoTop() 
#xcommand BROWSE <oBrw> BOTTOM => <oBrw>:GoBottom() 
 
//----- Fin Fuente 4 ------------------------------------------------------- 

 
//----- Fuente 5: BRWPREP1.PRG --------------------------------------------- 
 
// Construid el fichero Clientes.dbf con los campos: 
// NOMBRE C 30, APELLIDOS C 30 y TELEFONO C 10.
// Introducidle algunos registros.
 
// Compilad con Clipper BrwPrep1 /n 
 
#include "TBrowse.ch" 
#include "InKey.ch" 
 
//----------------------------------------------------------------------// 
 
function Main() 
 
   local brwClientes 
   local nKey, lEnd := .f.
 
   BROWSE DBF brwClientes FROM 3, 3 TO 20, 70 COLOR "W+/B, N/BG" 
      COLUMN TITLE "Nombre"    SHOW Clientes->Nombre 
      COLUMN TITLE "Apellidos" SHOW Clientes->Apellidos 
      COLUMN TITLE "Telfono"  SHOW Clientes->Telefono  FOOTER "Total..." 
   END BROWSE 
 
   SET COLOR TO "W+/BG" 
   SET CURSOR OFF 
   CLS 
 
   USE Clientes 
 
   do while ! lEnd 
 
      DISPLAY BROWSE brwClientes 
 
      nKey = InKey( 0 ) 
 
      do case 
         case nKey == K_ESC 
              lEnd = .t.
 
         case nKey == K_UP 
              BROWSE brwClientes UP 
 
         case nKey == K_DOWN 
              BROWSE brwClientes DOWN 
 
         case nKey == K_LEFT 
              BROWSE brwClientes LEFT 
 
         case nKey == K_RIGHT 
              BROWSE brwClientes RIGHT 
 
         case nKey == K_CTRL_PGUP 
              BROWSE brwClientes TOP 
 
         case nKey == K_CTRL_PGDN 
              BROWSE brwClientes BOTTOM 
      endcase 
 
   enddo 
 
   USE 
 
return 
 
//-----------------------------------------------------------------------// 
//---- Fin Fuente 5 ------------------------------------------------------ 
 
 
Como veis, podemos utilizar OOPS a plena potencia y nuestro cdigo no tiene 
porque apartarse de la tpica sintaxis de los dialectos xBase.
 
Espero  que  este  artculo  os  haya  servido  para ir entendiendo algunos 
detalles muy importantes con los que debis contar a la hora de realizar un 
anlisis  OOPS.  El  TBrowse estndar de Clipper puede ser un buen punto de 
partida para poner el OOPS en accin. En el prximo nmero veremos  como  a 
travs del polimorfismo del OOPS podemos extender el Get System de Clipper, 
de una manera verdaderamente revolucionaria.
 
Hasta entonces, !Happy Clipping!

*****************************************************************************
*****************************************************************************

En el ltimo nmero os mostr cdigo OOPS escrito con un nuevo  kernel  que 
he  desarrollado  al que llamar "Objects" en lo sucesivo. Como supongo que 
estaris deseosos de poder utilizarlo, y adems  va  a  ser  imprescindible 
para poder seguir mis prximos artculos, vamos antes que nada a abordar su 
arquitectura y que veis, paso a paso, como ha sido construido.

Segn ha comunicado Computer Associates, la nueva versin de Clipper  (5.2) 
ha comenzado su fase de testeo final (fase beta) a principios de noviembre. 
Allan Davies, muy optimista, manifest en Madrid que esta fase slo durara 
hasta  mediados de diciembre. Durante y despus de la conferencia, a la que 
tuve la oportunidad de asistir  gracias  al  Grupo  EIDOS,  aprovech  para 
formularle todo tipo de preguntas acerca de la nueva  versin  de  Clipper. 
Adems,  durante  slo  unos instantes, mostraron unas imgenes con algunas 
pantallas  del  proyecto  Aspen (Visual Objects for Clipper). Lo que ms me 
interes fue el poder ver, por fin, la sintaxis OOPS  que  van  a  adoptar. 
All, ante todos, en una pantalla gigante estaba el cdigo de una Clase en 
puro  Aspen corriendo en Windows!. Como podis suponer, me apresur a tomar 
buena nota de la sintaxis...
 
Allan Davies manifest que estn colaborando con Class(y) y con  SuperClass 
para estandarizar la sintaxis OOPS en Clipper. A mis preguntas contest que 
no  soportarn  de momento Herencia Mltiple, que el Get System contina su 
evolucin -algo que muchos suponamos- y que nos  ofrecern  una  jerarqua 
de Browsers -os acordis de mi ltimo artculo?-. Le puse en el compromiso 
de elegir entre Class(y) y SuperClass y con mucha diplomacia se decidi por 
SuperClass.  Curiosamente,  al  poco tiempo de terminar mi librera Dialog, 
mantuve una interesante conversacin con Chris Sennit, autor de SuperClass. 
Gracias a esa  conversacin  comprend  una  serie  de  cuestiones  que  me 
hicieron llegar a "Objects". Es por esto que SuperClass  y  Objects  tienen 
muchas cosas en comn...

La  cuestin  es  que  puedes  utilizar  Objects  con  toda  confianza.  Yo 
personalmente  llevo  ya  meses  usndolo  y  no he tenido ningn problema. 
Durante los ltimos meses ha sido probado por un  equipo  de  ms  de  diez 
Beta-Testers aparte de haber sido usado por los asistentes a los cursos  de 
OOPS  que  hemos  impartido en el Aula Vulcan del Grupo Eidos. No creas que 
slo  ha  sido  usado  para  pequeos  ejercicios.  En  la  actualidad est 
soportando  sin  ningn  problema  toda  la  arquitectura interna del nuevo 
ContaPlus  para  MsDos que ver la luz pronto. Son tres las razones de peso 
para que te decidas a usarlo: es gratis! es muy rpido! y tienes todo el 
cdigo fuente! Tres razones que ninguna otra librera te ofrece.

Cuando  tengamos el nuevo Clipper nos olvidaremos de Objects, pero mientras 
tanto nos va a permitir desarollar nuestras  aplicaciones  usando  las  ms 
modernas tecnologas.
 
 
FUNCIONES Y METODOS INDOCUMENTADOS
 
Dentro  de  la librera Clipper.lib se encuentran ya una serie de funciones 
que  son las que se van a utilizar para construir el kernel OOPS. Es decir, 
lo  que  tan  slo  hace  falta  es  que Clipper utilizando esas funciones, 
construya un motor capaz de generar Clases cmodamente y  de  organizar  la 
herencia del OOPS. Como vais a ver a continuacin, ya est todo hecho en un 
50%  -aunque no documentado-. Os preguntaris que cmo hemos descubierto la 
sintaxis indocumentada de todo  esto:  A  base  de  rebuscar  aqu  y  all 
(Class(y), SuperClass, oClip, etc... ) y con mucha paciencia...
 
Aqu  tenis  la sintaxis de las funciones indocumentadas contenidas dentro 
del  mdulo  SEND  de  Clipper,  y  de  los  tres  mtodos  indocumentados. 
Encontraris en el cuaderno tcnico de OOPS -ya falta muy poco para que  lo 
tengis- una explicacin extensa de cada una de ellas y ejemplos de uso:

 
   - Funciones Indocumentadas 
 
   __ClassNew( cClase, nDatos )  --> nHandle        
   __ClassAdd( nHandle, cDato--cMtodo, cUDF )     
   __ClassIns( nHandle )         --> oObjeto        
   __ClassSel( nHandle )         --> acDatosMtodos 
   __ClassNam( nHandle )         --> cClase         
   QSelf()                       --> oSelf          

 
   - Mtodos Indocumentados  
 
   oObjeto:ClassName()           --> cClase       
   oObjeto:ClassH()              --> nClaseHandle 
   bBloqueCodigo:Eval()          --> uResultado   

 
Esas  son  las herramientas de las que dispone Clipper para crear un kernel 
OOPS.  En  el  mercado  han aparecido distintas libreras que han utilizado 
precisamente  estas funciones y estos mtodos indocumentados para construir 
el  kernel  OOPS:  Class(y),  SuperClass,  oClip,  Dialog  y...  Objects!. 
Class(y) fue la primera en aparecer, pero el principal problema que plantea 
es que ha reemplazado algunas de estas funciones por otras propias, por  lo 
que  corremos  el  peligro  de  incompatibilidad  con  futuras versiones de 
Clipper. SuperClass respeta toda la maquinaria del mdulo SEND  y,  adems, 
permite herencia mltiple, algo que no permite Class(y).

La nica pega que se le puede achacar es que es bastante cara. La principal 
ventaja de oClip es que es gratis -todos vosotros la tenis-. oClip permite 
dar  unos  primeros pasos en OOPS y hacernos nuestras primeras Clases, pero 
no funciona si intentamos utilizar la herencia del OOPS  -que  es  lo  ms 
importante!- Adems de eso, por su diseo, no permite usar el debugger, por 
lo que perdemos una de las facetas ms didcticas a la hora de  estudiar  y 
usar  OOPS.  Dialog  resuelve los problemas de herencia de oClip pero sigue 
manteniendo  la limitacin de no poder usar el debugger. Objects representa 
un cambio radical frente a Dialog. Internamente est igual  construida  que 
SuperClass, es muy rpida y permite usar el debugger a toda potencia!
 
Bien, quiz en este punto sobran ya ms comentarios. En este nmero tenis, 
por fin, la nueva librera Objects a vuestra disposicin. Va completa y con 
plena  operatividad.  Son  dos  ficheros:  el  de cabecera (OBJECT.CH) y la 
librera (OBJECT.LIB)
 
OBJECTS EN ACCION: LAS VENTANAS DE DBASE IV
 
Como siempre, un ejemplo vale ms que mil palabras. Para que  veis  de  lo 
que es capaz Clipper y el OOPS aqu tenis una primera versin de la  Clase 
TWindow con la que conseguimos,  gracias  al  fantstico  preprocesador  de 
Clipper, disponer de los mismos mandatos para la creacin y manipulacin de 
ventanas que dBase IV.
 
El  fuente  1  y  el  fuente  2  son  un claro ejemplo de cmo realizar una 
implementacin  OOPS y de cmo gracias al Preprocesador se consigue ocultar 
toda  la  complejidad  OOPS,  obteniendo  una  extensibilidad  del lenguaje 
realmente increible.

// Fuente 1. WINDOW.CH ----------------------------------------------------
                                                                           
#xcommand DEFINE WINDOW <oWnd>                                            ;
             FROM <nTop>, <nLeft> TO <nBottom>, <nRight>                  ;
             [ TITLE <cTitle> ]                                           ;
             [ COLOR [<cClrFrame>] [,<cClrTitle>] ]                       ;
             [ BORDER <cBorder: DOUBLE, SINGLE, PANEL, NONE> ]            ;
                                                                          ;
          =>                                                              ;
                                                                          ;
          <oWnd> := TWindow():New( <nTop>, <nLeft>, <nBottom>, <nRight>,  ;
              [<cTitle>], [<cClrFrame>], [<cClrTitle>], [<(cBorder)>] )    
                                                                           
#xcommand ACTIVATE WINDOW <oWnd> => <oWnd>:Activate()                      
                                                                           
#xcommand DEACTIVATE WINDOW <oWnd> => <oWnd>:DeActivate()                  
                                                                           
#xcommand @ <nRow>, <nCol> SAY <uExpr> [ COLOR <cColor> ] ;                
          => ;                                                             
          DevPos( nWndTop() + <nRow>, nWndLeft() + <nCol> ) ;;             
          DevOut( <uExpr>, cWndColor( [<cColor>] ) )                       
                                                                           
#xcommand @ <nRow>, <nCol> GET <uExpr> ;                                   
          => ;                                                             
          DevPos( nWndTop() + <nRow>, nWndLeft() + <nCol> ) ;;             
          AAdd( GetList, _GET_( <uExpr>, <(uExpr)> ) ) ;;                  
          ATail( GetList ):Display()                                       
// FIN DEL FUENTE 1 -------------------------------------------------------

// Fuente 2 - WINDOWS.PRG -------------------------------------------------
#include "Objects.ch"
#include "Box.ch"

static aWndActives := {}

CLASS TWindow
   DATA nTop, nLeft, nBottom, nRight
   DATA cBorder, cTitle, cBack
   DATA cClrFrame, cClrTitle

   METHOD New( nTop, nLeft, nBottom, nRight, cTitle, cClrFrame, cClrTitle,;
               cBorder )
   METHOD Activate()
   METHOD DeActivate()
ENDCLASS

//-----------------------------------------------------------------//

METHOD TWindow::New( nTop, nLeft, nBottom, nRight, cTitle, cClrFrame,;
                     cClrTitle, cBorder )

   DEFAULT cTitle := "", cClrFrame := "W+/B", cClrTitle := "N/W",;
           cBorder := Space( 9 )

   ::nTop      = nTop
   ::nLeft     = nLeft
   ::nBottom   = nBottom
   ::nRight    = nRight
   ::cTitle    = cTitle
   ::cClrFrame = cClrFrame
   ::cClrTitle = cClrTitle

   do case
      case cBorder == "DOUBLE"
           ::cBorder = B_DOUBLE + " "

      case cBorder == "SINGLE"
           ::cBorder = B_SINGLE + " "

      case cBorder == "PANEL"
           ::cBorder = Replicate( "", 8 ) + " "

      case cBorder == "NONE"
           ::cBorder = Space( 9 )

      otherwise
           ::cBorder = cBorder
   endcase

return Self

//----------------------------------------------------------------//

METHOD TWindow::Activate()

   DispBegin()
    ::cBack = SaveScreen( ::nTop, ::nLeft, ::nBottom + 1, ::nRight + 2 )

    @ ::nTop, ::nLeft, ::nBottom, ::nRight BOX ::cBorder COLOR ::cClrFrame
    Shadow( ::nBottom + 1, ::nLeft + 2, ::nBottom + 1, ::nRight + 2 )
    Shadow( ::nTop + 1, ::nRight + 1, ::nBottom, ::nRight + 2 )

    if ! Empty( ::cTitle )
       @ ::nTop, ::nLeft SAY PadC( ::cTitle, ::nRight - ::nLeft + 1 ) ;
         COLOR ::CClrTitle
    endif
    DevPos( ::nTop + 1, ::nLeft + 1 )
   DispEnd()

   AAdd( aWndActives, Self )

return

//-------------------------------------------------------------------//

METHOD TWindow::DeActivate()

   RestScreen( ::nTop, ::nLeft, ::nBottom + 1, ::nRight + 2, ::cBack )
   ::cBack = nil
   ASize( aWndActives, Len( aWndActives ) - 1 )

return

//-----------------------------------------------------------------//

function nWndTop()                  // 'Friend' function

return If( Len( aWndActives ) > 0, ATail( aWndActives ):nTop, 0 )

//------------------------------------------------------------------//

function nWndLeft()                 // 'Friend' function

return If( Len( aWndActives ) > 0, ATail( aWndActives ):nLeft, 0 )

//-------------------------------------------------------------------//

function cWndColor( cColor )        // 'Friend' function

return If( cColor != nil, cColor,;
       If( Len( aWndActives ) > 0, ATail( aWndActives ):cClrFrame, nil ) )

//----------------------------------------------------------------------//

static function Shadow( nTop, nLeft, nBottom, nRight )

return RestScreen( nTop, nLeft, nBottom, nRight,;
       Transform( SaveScreen( nTop, nLeft, nBottom, nRight ),;
       Replicate( "X" + Chr( 7 ), ( nBottom - nTop + 1 ) * ;
       ( nRight - nLeft + 1 ) ) ) )

//-----------------------------------------------------------------------//

// Fin del Fuente 2 -------------------------------------------------------


El Fuente 3 es un ejemplo de cmo utilizar esta implementacin OOPS para 
crear ventanas con la sintaxis de dBase IV.

// Fuente 3 ---------------------------------------------------------------
// EJEMPLO DE USO DEL FUENTE 1 Y EL FUENTE 2                               
                                                                           
#include "Window.ch"      // Cmo ves, no se ve 'OOPS' por ninguna parte...
                                                                           
function Main()                                                            
   local wndClientes, wndCuentas, wndArticulos                             
   local cNombre := Space( 10 )                                            

   DEFINE WINDOW wndClientes FROM 3, 3 TO 15, 40 ;                         
      TITLE "Clientes" ;                                                   
      COLOR "W+/B", "W+/RB"                                                

   DEFINE WINDOW wndCuentas FROM 10, 14 TO 20, 65 ;                        
      TITLE "Cuentas" BORDER DOUBLE COLOR "W+/R", "R+/W"                   

   DEFINE WINDOW wndArticulos FROM 5, 45 TO 12, 70 ;                       
      TITLE "Artculos" BORDER SINGLE                                      

   SET COLOR TO "W+/BG"                                                    
   CLS                                                                     
   ACTIVATE WINDOW wndClientes                                             
   @ 2, 2 SAY "OOPS - Es fantstico!" COLOR "W+/G"                         
   ACTIVATE WINDOW wndCuentas                                              
   @ 3, 5 SAY " Las ventanas de dBase IV !!!" COLOR "W+/B"              
   ACTIVATE WINDOW wndArticulos                                            
   @ 3,  3 SAY "Cdigo:"                                                   
   @ 3, 11 GET cNombre                                                     
   READ                                                                    

   DEACTIVATE WINDOW wndArticulos                                          
   InKey( 0 )                                                              
   DEACTIVATE WINDOW wndCuentas                                            
   InKey( 0 )                                                              
   DEACTIVATE WINDOW wndClientes 

RETURN
// Fin del Fuente 1 ------------------------------------------------------- 

Puesto  que ya tenis la librera slo tendris que hacer lo siguiente para 
poder usar las ventanas: 
 
   CLIPPER WndTest /n                    
   CLIPPER Window  /n                    
   RTLINK fi WndTest, Window LIB Objects 
 
Y  ejecuta  WndTest!  Pensars que ests usando dBase IV. Es sencillamente 
impresionante lo que se puede llegar a conseguir usando Clipper 5,  OOPS  y 
un  poco de imaginacin. Te dejo ahora que experimentes con todo esto y ya, 
en  el  prximo  nmero, analizaremos muchas conclusiones que podemos sacar 
del estudio de este ejercicio. Por cierto, no olvides  echarle  un  vistazo 
con el debugger, te encantar!
 
En el prximo nmero, mucho ms OOPS en accin. Happy OOPS-Clipping!

****************************************************************************
