Page 1 of 2

Problema usando tDatabase

PostPosted: Wed Nov 28, 2007 4:19 pm
by Biel EA6DD
Hola foro,
Necesito ayuda, estoy algo confuso, y no doy con la solución del siguiente problema.

Quiero utilizar la clase tDatabase, y la estoy probando con un sencillo mantenimiento de fichero maestro. Visualizo una tabla en un browse, y edito o añado datos en un dialogo.
Pretendo que solo exista un objeto tDatabase, que es el que uso en el browse y en dialogo.

He aqui el problema, puesto que al al intentar añadir registros, ejecuto el metodo Blank, que deja vacio el buffer de edicion, pero mientras estoy editando, o al cerrar el dialogo, supongo que desde el browse se ejecuta el metodo load, con lo que el buffer que estoy editando se carga con los datos del registro sobre el que esta posicionado el browse.
No se si me explico, aqui os dejo un ejemplo autocontenido, probad de dar de alta un registro.
Code: Select all  Expand view
#include "Fivewin.ch"
#include "xBrowse.ch"
STATIC oDbf
FUNCTION MAIN()
   LOCAL oWnd,oBrw,oCol,oBar
   CrtTmp()
   oDbf:=tDatabase():New()
   DEFINE WINDOW oWnd
   DEFINE BUTTONBAR oBar OF oWnd
   *-
   DEFINE BUTTON OF oBar ACTION EdtTmp(.t.) PROMPT 'Altas'
   DEFINE BUTTON OF oBar ACTION EdtTmp(.f.) PROMPT 'Modif'
   *--
   oBrw := TXBrowse():New(oWnd)
   oBrw:nMarqueeStyle       := MARQSTYLE_HIGHLROW
   oBrw:nColDividerStyle    := LINESTYLE_BLACK
   oBrw:lColDividerComplete := .T.
   *--
   oCol:=oBrw:AddCol()
   oCol:bStrData:= {||oDbf:Cod}
   oCol:cHeader := 'Cod.'
   *--   
   oCol:=oBrw:AddCol()
   oCol:bStrData:= {||oDbf:Nom}
   oCol:cHeader := 'Descripción'
   *--
   oBrw:bGoTop    := {|| oDbf:GoTop() }
   oBrw:bGoBottom := {|| oDbf:GoBottom() }
   oBrw:bSkip     := {| n | iif( n == nil, n := 1, ), oDbf:Skipper( n )  }
   oBrw:bBof      := {|| oDbf:Bof()  }
   oBrw:bEof      := {|| oDbf:Eof()}
   oBrw:bBookMark := {| n | iif( n == NIL, oDbf:RecNo() , oDbf:Goto( n ) )  }
   
   oBrw:CreateFromCode()

   oWnd:oClient := oBrw
   oBrw:lRecordSelector:=.t.
   
   ACTIVATE WINDOW oWnd
   RETURN NIL
//-------------------------------
STATIC FUNCTION EdtTmp(lAdd)
   LOCAL oDlg,lSave := .F.,oFont,oBtn, cCod,cNom
   //LOCAL oDbf:=tDatabase():New()  <<<<<<<<<<<<<<<<Si defino otro objeto local, funciona Ok.
   IF (lAdd,oDbf:Blank(),oDbf:Load())
   
   DEFINE FONT oFont NAME "MS Sans Serif" SIZE 0, 8
   DEFINE DIALOG oDlg FROM 100, 100 TO 230,494 PIXEL FONT oFont
   oDlg:SetText('('+ProcName()+') '+ IF(lAdd,'Alta','Modificación')+' de registros' )
   @ 12, 10 SAY "Cod:" OF oDlg SIZE 15, 8 PIXEL FONT oFont
   @ 26, 10 SAY "Nom:" OF oDlg SIZE 17, 8 PIXEL FONT oFont
   @ 10, 32 GET oDbf:Cod OF oDlg SIZE 15, 12 PIXEL FONT oFont WHEN lAdd
   @ 24, 32 GET oDbf:Nom OF oDlg SIZE 155, 12 PIXEL FONT oFont

   @ 46, 107 BUTTON oBtn PROMPT "OK" OF oDlg SIZE 42, 14 PIXEL FONT oFont DEFAULT ;
                         ACTION (MsgStop("Valor nombre antes de cerrar el dialogo:"+CRLF+oDbf:Nom),oDlg:End(), lSave := .T.)
   @ 46, 151 BUTTON oBtn PROMPT "Cancelar" OF oDlg SIZE 42, 14 PIXEL FONT oFont CANCEL ACTION (oDlg:End())

   ACTIVATE DIALOG oDlg CENTERED
   MsgStop("Valor nombre despues de cerrar el dialogo:"+CRLF+oDbf:Nom)
   IF lSave
      IF lAdd
         oDbf:Append()
      ENDIF
      oDbf:Save()
   ENDIF
RETURN NIL
//-------------------------------
STATIC FUNCTION CrtTmp()
   LOCAL i
   DBCreate('tmp.dbf',{;
                        { "COD"       , "C",     2,    0 },;
                        { "NOM"       , "C",    30,    0 } ;                                       
                      })   
   DBUseArea(.T.,,'tmp',,.T.)
   
   FOR i:=1 TO 50
       Tmp->(dbAppend())
       Tmp->Cod:=Str(i,2)
       Tmp->Nom:='Nombre '+Tmp->Cod
   NEXT         
   Tmp->(dbGoTop())
   RETURN NIL

Veis alguna posible solucion, o por el contrario la unica via es crear dos objetos tDatabase, para que sea independiente el browse del dialogo.
Ahora que escribo esto, estoy pensando que si ejecuto el metodo blank en el browse y no el dialogo, puede que tambien funcione, ya que tanto el browse como el dialogo estarian sobre un registro vacio.

PostPosted: Wed Nov 28, 2007 4:53 pm
by Armando
Biel:

Prueba utilizando el alias de la DBF en el browse y el objeto oDbf para edición, ejemplo del browse:

*--
oCol:=oBrw:AddCol()
oCol:bStrData:= {||cAlias->Cod}
oCol:cHeader := 'Cod.'
*--

De esta forma no he tenido problemas.

Saludos

PostPosted: Wed Nov 28, 2007 5:03 pm
by Biel EA6DD
Gracias Armando, por la respuesta.
Bueno la idea original es no usar alias, para que en un futuro y sin retocar más de una linea (oDbf:= ...) pueda servirme indistintamente para DBF como para acceso via ADO.

PostPosted: Wed Nov 28, 2007 5:04 pm
by karinha
//IF (lAdd,oDbf:Blank(),oDbf:Load())

SELECT( oDbf:cAlias )
DATABASE oDbf

IF lAdd //-> Para Anadir
oDbf:SetBuffer( .T. )
oDbf:Blank()
ELSE //-> Para Alterar
oDbf:Load()
ENDIF

O

/*
IF NetUse( "CADETIQ", .T. )
OrdListAdd( "CADETIQ", "RAZ_MATRIC", "DEST_NOME", "END", ;
"NOME_ARTIS", "CPF", "CNPJ", ;
"TELEFONE" )
OrdDescend( ,,.F. ) // - Decrescente
DATABASE DbClientes // Como Escrevemos em Ingles
DbClientes:Gotop()
DbClientes:Load()
DbClientes:SetBuffer( .T. ) // Assim, .T. a Op‡Æo (Cancelar) Funciona.
ELSE
MsgStop( "Banco de Dados Bloqueado", "Cuidado!" )
RETURN NIL
ENDIF

IF lAppend //-> Inclusao

DbClientes:SetOrder( 1 )
DbClientes:GoBottom()

nCodigo := ( DbClientes:Raz_Matric )

DbClientes:SetBuffer( .T. )
DbClientes:Blank()

ELSE //-> Altera‡Æo

nRecNo := (DbClientes:cAlias)->( RecNo() )
DbClientes:GoTo( nRecNo )
nOldRecNo := (DbClientes:cAlias)->( RecNo() )

ENDIF

TRAVEREG(0) //-> APPEND BLANK O DBAPPEND() Y TRABA DEL REGISTRO
DbClientes:Save()
DESTRAVA(0)
*/

PostPosted: Wed Nov 28, 2007 5:34 pm
by Biel EA6DD
Gracias João,
He probado la primera parte de tu codigo, y me produce el mismo efecto erroneo. Das de alta un registro, pero graba los datos del registro sobre el que esta posicionado el browse.

Code: Select all  Expand view
SELECT( oDbf:cAlias )
DATABASE oDbf

oDbf esta definida como variable estatica, asi lo unico que hace es volver a crear un objeto tDatabase en la misma variable.
Si oDbf la definimos local, funciona bien, pero me gustaria no terner que definrla local a la funcion. La idea es usar un solo oDbf generico para todo el programa.
Code: Select all  Expand view
//LOCAL oDbf:=tDatabase():New()  <<<<<<<<<<<<<<<<Si defino otro objeto local, funciona Ok.
Si descomentas esta linea del codigo que he puesto, veras como funciona ok, sin cambiar nada mas.
Code: Select all  Expand view
IF lAdd //-> Para Anadir
oDbf:SetBuffer( .T. )
oDbf:Blank()
ELSE //-> Para Alterar
oDbf:Load()
ENDIF

Este codigo es practicamente igual al que yo uso
Code: Select all  Expand view
IF (lAdd,oDbf:Blank(),oDbf:Load())

varia en que fuerzas el uso del buffer cn Setbuffer(.t.), pero por defecto lBuffer esta a verdadero cuando se crea el objeto, SetBuffer ademas de poner lBuffer a .T. fuerza un Load, pero no varia el comportamiento.

PostPosted: Wed Nov 28, 2007 5:42 pm
by karinha
nRecNo := (DbClientes:cAlias)->( RecNo() )
DbClientes:GoTo( nRecNo )

nOldRecNo := (DbClientes:cAlias)->( RecNo() )

Cuando en la Gravacion:

GoTo( nOldRecno )

PostPosted: Wed Nov 28, 2007 6:55 pm
by RenOmaS
Experimenta esto
Code: Select all  Expand view
DEFINE BUTTON OF oBar ACTION EdtTmp(.t., oClone( oDbf )) PROMPT 'Altas'
DEFINE BUTTON OF oBar ACTION EdtTmp(.f., oClone( oDbf )) PROMPT 'Modif'
...
...

STATIC FUNCTION EdtTmp(lAdd, oDbf )
   LOCAL oDlg,lSave := .F.,oFont,oBtn, cCod,cNom
   IF (lAdd,oDbf:Blank(),oDbf:Load())

.....




Salu2

Re: Problema usando tDatabase

PostPosted: Wed Nov 28, 2007 7:31 pm
by FiveWiDi
Biel,

prueba con esto:

*--
oCol:=oBrw:AddCol()
oCol:bStrData:= {|| (oDbf:cAlias)->Cod }
oCol:cHeader := 'Cod.'
*--

De esta manera el browse estará leyendo directamente del campo de la DBF, prescindiendo del buffer.

Saludos
Carlos G.

Re: Problema usando tDatabase

PostPosted: Wed Nov 28, 2007 7:40 pm
by FiveWiDi
Biel,

olvida mi primer mensaje, entendí mal.

Ahora bien si haces:

ACTION ( MsgStop("Valor nombre antes de cerrar el dialogo:"+CRLF+oDbf:Nom), ;
If(lAdd, oDbf:Append(),Nil), ;
oDbf:Save(), ;
oDlg:End() )


Creo que conseguirás lo que quieres.

Saludos
Carlos G.

PostPosted: Thu Nov 29, 2007 5:46 am
by Manuel Valdenebro
Gabriel,

Desde hace tiempo vengo utilizando la clase Database con Browse, sin ningun tipo de problemas. No comprendo para que necesitas crear el objeto oDbf en el Browse. En el Browse, yo me muevo directamente con los registros de la tabla (DBGOTOP(), DBGOBOTTOM, etc.) y es en la funcion de ALTA y MODIFICACION, donde genero el objeto oDbf como cvariable Local y en su caso, doy de alta un nuevo registro o lo modifico el registro. Al volver al browse, el registro ya esta dado de alta o modificado en la dbf y por tanto realizo un refresh(). Tal cual tu lo haces, creo no va a funcionar, sobretodo, en las altas.

Te envio parte de un código a tu correo, por si fuera de tu interes.

Si deseas continuar, de todos modos, con el mismo sistema, en mi opinión, podrias hacer lo siguiente:

1) Dar de alta en la funcion Browse y como Local oDbf.
2) Pasar oDbf como parametro a la funcion Alta/Modificar y que esta funcion devolviera en Return (oDbf).

P.D.: Te he enviado un correo a tu dirección xxxx@ctv.es pero ha venido devuelto.

PostPosted: Thu Nov 29, 2007 9:04 am
by Carles
Hola Biel,

Hace tiempo me encontre con este caso, y el problema esta en que al crear un dialogo, constantemente puedes mandar mensajes de ::Display() a la ventana del TXBrowse, con lo q evalua el metodo ::Paint() y este constantemente esta realizando movimientos de ::Skip(), con lo q es normal esta falta de control del puntero del registro.

Puedes intentar varias soluciones (en principio poco elegantes) para engañar al sistema, pero lo mejor es (por si quieres seguir usando objetos TDatabase y que la aplicacion quede igual tanto para DBF como ADo) el crear, cuando entras en una funcion de mantenimiento, otro objeto q se cree a partir del oDbf del Browse. Al salir del mantenimiento refrescaras el oDbf del Browse y listo. No es decabellado y tienes el completo control tanto en el Browse como en el dialogo.

Yo recuerdo que intente cuando entraba en la funcion de mantenimento:

Code: Select all  Expand view


FUNCTION Manteniment()
...
oBrw:lCreated := .F. 
...

Dialogo


oBrw:lCreated := .T. // Al final de la funcion

RETU NIL



Con esto evitaba q se evaluara el metodo ::Paint() con el consiguiente movimiento del puntero del oDbf, pero me quedaba pendiente la solucion del refresco de la pantalla por si movia un dialogo por encima. Total, q empeze a intentar redefinir el metodo ::Paint(), pero desisti :x . Creee un metodo para crear objetos TDatabase a partir de otro y listos.

Salutacions.
C.

PostPosted: Thu Nov 29, 2007 10:20 am
by Biel EA6DD
Muchas gracias a todos, la verdad no esperaba tantas respuestas. Me encanta el foro, y que siempre haya gente dispuesta a ayudar.

Voy a ir por partes, lo primero de todo es explicar porque quiero usar tDatabase en el browse y en el dialogo.

La verdad es que es complicarse un poco la vida, de hecho yo hasta ahora no lo usaba así, sino que el browse lo referencia con el alias de la tabla, y en el dialogo de mantenimiento creaba el objeto tDatabase.
Pero claro, soy un poco masoca y me gusta experimentar y darle otra vuelta de tuerca a mis programas, entonces pensando en la posibilidad de usar difierentes motores de base de datos, pense que una buena manera de aislarlo era usar la clase tDatabse por todos lados, y no usar el alias de la tabla. El segundo paso seria crearme una clase clon a tDatabase con los mismos metodos, pero atacando origenes de dato SQL via ADO.
De esta forma cambiando la linea donde se crea oDbf, un mismo programa podria ser contra DBF, o cualquier origen ADO.
Por ejemplo:
Code: Select all  Expand view
IF lAdo
     oDbf:=tAdoDb():New() //Clase para manejor de Rs ADO
ELSE
     oDbf:=tDatabase():New()
ENDIF


Tras leer los mensajes, he revisado las soluciones que me comentais, todas son funcionales, pero por el motivo que os he explicado antes, no son exactamente el tipo de solución que estoy buscando.
El controlar el puntero como comenta Joao, es complicado, mas aun cuando el metodo blank se mueve al registro posterior a Eof, y luego reposiciona en registro actual.

Cambiar el lugar donde se crea y graba el registro tampoco es valido, pues con solo mover el dialogo, podreis ver que el buffer de edicion cambia.

Lo mas sencillo de aplicar, sin usar alias, parece que pasa por clonar el objeto tDatabase, o crear uno de nuevo en el dialogo, en principio esa idea tampoco me gustaba demasiado, pero por ahora me parece la menos mala.

Sin duda como comenta Carles, el problema es que la ventana que tiene el browse va recibiendo y evaluando mensajes que hacen que el buffer de edicion cambie. Intentare investigar algo mas por las vias que comenta Carles, y sino pues clonaremos objetos.

PostPosted: Thu Nov 29, 2007 1:30 pm
by Antonio Linares
Biel,

Asi es, como comenta Carles, el browse recibe mensajes de pintado y eso afecta al objeto TDataBase que usas tambien desde el diálogo

Tal vez te sirva abrir la misma DBF en otra area de trabajo simultaneamente, para que asi una no afecte a la otra

PostPosted: Thu Nov 29, 2007 1:33 pm
by Frafive
Hola Biel

Yo lo que hago es clonar el objeto cuando entro en modo de edicion y despues tambien he modificado el metodo blank y me funciona perfectamente.

un saludo


METHOD Blank() CLASS TDataBase

local nFor, nLen
local cType

::aBuffer=Array(::FCount() )

nLen :=len(::aBuffer)

for nFor := 1 to nLen
cType := ::FieldType( nFor )



do case
case cType == "C"
::aBuffer[ nFor ] := Space(::FieldLen( nFor ) )
case cType == "D"
::aBuffer[ nFor ] := Ctod("")
case cType == "N"
::aBuffer[ nFor ] := 0
case cType == "L"
::aBuffer[ nFor ] := .f.
case cType == "M"
::aBuffer[ nFor ] := ""
end case

next

Return Self

PostPosted: Thu Nov 29, 2007 3:13 pm
by Marcelo Via Giglio
Biel,

yo hice una clase Tarray para trabajar con arrays como si fuera un dbf (hay varias implemenatciones de esto po ahi) y claro esta, hay cosas basadas en tDatabase, pero este problema que mencionas tambien se reproducia, como lo solucione?, es crear un buffer adicional, algo si

oData := dbData:buffer_blank() // te devuelve una estructura en blanco similar a la DB y pudes utilizar oData:<campo>, etc
oData := dbData:buffer_load() // te devuelve la estructura con los datos del reg actual
dbData:buffer_save( oData ) // salva el buffer en el registro actual

Espero esto te ayude

saludos

Marcelo