James Bott wrote:Colin,
The problem occurs when you edit a record in a browse with a dialog. Whenever the browse is repainted (as when the user switches to another application and back during a dialog edit) the buffer is reloaded. In order to repaint the window the entire browse is re-read from disk and redisplayed. This reloads the buffer for each visible record as it is repainted including the one being edited. Also, the data that has already been changed on the screen in the dialog remains displayed as changed, but the buffer contains the original data. I have also seen cases where the data was saved into the wrong record after a repaint, but I never figured out what the cause of this was (I did figure out a way to prevent it though--see below).
TRecord acts a second buffer and the data is not changed by a browse repaint. TRecord also has other nice features since it will automatically find its own record in the database to save the data into, or if it is a new record without a recno, then it will append a new record then save itself.
Regards,
James
This is really a very serious problem. This appears to be least known and least taken care of. It may happen rarely, but when it happens, it spoils the data and the user may not even know. Mr. James Bott recognized this long time back and provided tRecord class. Programs browsing data with TData class should always offer pop-up edits using TRecord class.
But this problem is not only with TData or TDatabase, but with all data sources, be it RDD, ADO or any kind of data source, with XBrowse or any other browse.
While using TRecord is a good solution when browsing TData, we need a generic solution for browsing any kind of data.
Fivewin does not provide TRecord like facility for its TDatabase class, but provides a more generic solution in XBrowse.
oBrw:CurrentRow()
oBrw:CurrentRow() returns a Row object which can be used for pop-up edits. This works whether the data being browsed is TData, TDatabase, RDD, ADO or any other data source. The values of remain static even when the main browse is navigated for any reasons. This object also provides methods to save and undo.
Here is a sample using the oBrw:CurrentRow() object to use in pop-up edits. (\fwh\samples\XbRowEd.Prg in FWH 10.2). The pop-up edit is provided in a non-modal dialog, so that we can test it by navigating the browse while edit is in progress.
#include 'fivewin.ch'
#include 'adodef.ch' // in \fwh\include folder
#include 'ord.ch'
#include 'xbrowse.ch'
REQUEST DBFCDX
function Main()
local oBrw, oDlg, uData
local oFont
SET EXCLUSIVE OFF
SET DELETED ON
SET OPTIMIZE ON
SetGetColorFocus()
uData := OpenData()
DEFINE FONT oFont NAME 'TAHOMA' SIZE 0,-12
DEFINE DIALOG oDlg SIZE 640,460 PIXEL TITLE 'XBrowse Row Edit' ;
FONT oFont
@ 10,10 XBROWSE oBrw SIZE -10,-30 PIXEL OF oDlg ;
COLUMNS 'First', 'City', 'Age', 'Salary' ;
PICTURES nil, nil, '999', '99,999,999.99' ;
OBJECT uData ;
AUTOSORT CELL LINES NOBORDER ;
ON DBLCLICK RowEdit( oBrw:CurrentRow() )
WITH OBJECT oBrw
:nStretchCol := STRETCHCOL_WIDEST
:CreateFromCode()
END
@ 210, 10 BUTTON 'Edit' SIZE 40,14 PIXEL OF oDlg ACTION RowEdit( oBrw:CurrentRow() )
@ 210,270 BUTTON 'Close' SIZE 40,14 PIXEL OF oDlg ACTION oDlg:End()
ACTIVATE DIALOG oDlg CENTERED
RELEASE FONT oFont
CloseData( uData )
return nil
static function RowEdit( oRow )
LOCAL oDlg
DEFINE DIALOG oDlg SIZE 300,200 PIXEL TITLE 'Edit Customer' ;
FONT oRow:oBrw:oFont
@ 10, 10 SAY 'First' SIZE 40,10 PIXEL OF oDlg RIGHT
@ 10, 60 GET oRow:First SIZE 80,12 PIXEL OF oDlg UPDATE ;
VALID ( oDlg:AEvalWhen(), .t. )
@ 25, 10 SAY 'City' SIZE 40,10 PIXEL OF oDlg RIGHT
@ 25, 60 GET oRow:City SIZE 80,12 PIXEL OF oDlg UPDATE ;
VALID ( oDlg:AEvalWhen(), .t. )
@ 40, 10 SAY 'Age' SIZE 40,10 PIXEL OF oDlg RIGHT
@ 40, 60 GET oRow:Age ;
SIZE oRow:oBrw:Age:nWidth / 2,12 PIXEL OF oDlg UPDATE ;
PICTURE oRow:oBrw:Age:cEditPicture RIGHT ;
VALID ( oDlg:AEvalWhen(), .t. )
@ 55, 10 SAY 'Salary' SIZE 40,10 PIXEL OF oDlg RIGHT
@ 55, 60 GET oRow:Salary ;
SIZE oRow:oBrw:Salary:nWidth / 2,12 PIXEL OF oDlg UPDATE ;
PICTURE oRow:oBrw:Salary:cEditPicture RIGHT ;
VALID ( oDlg:AEvalWhen(), .t. )
@ 80, 10 BUTTON 'Undo' SIZE 30,12 PIXEL OF oDlg ;
WHEN oRow:Modified() ACTION ( oRow:Undo(), oDlg:Update() )
@ 80, 78 BUTTON 'Save' SIZE 30,12 PIXEL OF oDlg ;
WHEN oRow:Modified() ACTION ( oRow:Save(), oDlg:aEvalWhen() )
@ 80,110 BUTTON 'Close' SIZE 30,12 PIXEL OF oDlg ACTION oDlg:End()
ACTIVATE DIALOG oDlg CENTERED NOMODAL
return nil
// ---- DATA OPEN AND CLOSE FUNCTIONS ---- //
static function OpenData
local uData, nDataType
nDataType := Max( 1, Alert( 'DataType', { 'DBFCDX', 'TDataBase', 'ADO' } ) )
if nDataType > 2
uData := OpenADO()
else
USE CUSTOMER NEW ALIAS CUST SHARED VIA 'DBFCDX'
DATABASE uData
if nDataType == 1
uData := 'CUST'
endif
endif
return uData
static function OpenADO
local oCn, oRs, cPath := CURDRIVE() + ':' + Chr(92) + CURDIR()
oCn := TOleAuto():New( 'ADODB.Connection' )
WITH OBJECT oCn
:ConnectionString := 'Driver={Microsoft dBASE Driver (*.dbf)};DriverID=277;Dbq=' + cPath
:CursorLocation := adUseClient
:Open()
END
oRs := TOleAuto():New( 'ADODB.RecordSet' )
WITH OBJECT oRs
:ActiveConnection := oCn
:Source := 'CUSTOMER'
:LockType := adLockOptimistic
:Open()
END
return oRs
static function CloseData( uData )
local oCn
if ValType( uData ) == 'O'
if Upper( uData:ClassName ) = 'TOLEAUTO'
oCn := uData:ActiveConnection
uData:Close()
oCn:Close()
else
uData:Close()
endif
else
CLOSE DATA
endif
return nil
Independent of the edit in the dialog, the browse can be navigated but the data will be saved in the correct record when the saved.
In this example the user can choose either RDD, TDatabase or ADO. In all cases the program works identically.
This example also demonstrates how both xbrowse and CurrentRow() objects can be coded without the knowledge of where the data is read from or written to. XBrowse automatically recognizes the data source and builds codeblocks to handle the data appropriately including locking and unlocking. This way of coding can be useful to write generic interface which can be plugged to different kinds of data sources.
The default save method works well normally in many cases. But we can also customize the save behavior by assigning our own codeblock to oRow:bSave. We can see other data and methods in the static class TXbrRow in the xbrowse.prg.
In quite a few cases, we offer only some selected columns in the browse but we need to edit more columns in the edit dialog. In such cases, we can add all the columns to be edited to xbrowse and hide them with oCol:lHide := .t.. Row object will contain all columns to be edited, though the browse shows only the columns that are not hidden.
Though this method was available in XBrowse for quite long, it had a few minor bugs prior to version 10.2 and also this feature and its benefits were little known. With the following 3 bug fixes, this features works perfectly in all previous versions also.
In
METHOD Save() CLASS TXBrRow:
1. Replace the old code
with this correct code
Eval( ::oBrw:bBookMark, ::nRecNo )
2. Replace the old code
oCol := ::oBrw:oCol( ::Headers[ n ] )
with this correct code
oCol := ::oBrw:oCol( ::aHeaders[ n ] )
3. and Insert one line as indicated
lSaved := .t.
ACopy( ::aValues, ::aOriginals ) // Insert this line. This line was not in previous versions
endif