Page 1 of 4
A Question Abot DIALOGs
Posted: Tue Apr 08, 2008 10:05 pm
by xProgrammer
Hi FiveWinnners
I have some questions about DIALOGs that I probably should have asked some time ago, and maybe just shows that I don't quite understand, but here goes:
OK I have DIALOGS (objects of class TDialog) which I create programmatically. In other words my code looks something like this:
Code: Select all
DEFINE DIALOG dlgPATIENT TITLE sTitle SIZE 950, 650
@ 3, 1 SAY "Key" OF dlgPATIENT SIZE 90, 25
// rest of definition
@ 60, 51 BUTTON btnQUIT PROMPT "Quit" ACTION ( ::cExitCode := "Q", dlgPATIENT:End() ) OF dlgPATIENT
ACTIVATE DIALOG dlgPATIENT CENTERED
So dlgPATIENT points to an object of class TDialog. I want to use that TDialog object many times. But it seems that dlgPATIENT:End() partly destroys it, because if I try to reuse it later with:
Code: Select all
ACTIVATE DIALOG dlgPATIENT CENTERED
The screen will often come up the second time but not properly and is completely skew wiff by the third time.
I assume I have to call dlgPATIENT:End() to stop its message loop. Is that correct? Is there some other way?
Is anyone out there reusing their DIALOGs in such a manner or do you go through your code that creates them (DEFINE DIALOG) each time you need to use them.
Thanks
xProgrammer
Posted: Tue Apr 08, 2008 10:49 pm
by James Bott
>Is anyone out there reusing their DIALOGs in such a manner or do you go through your code that creates them (DEFINE DIALOG) each time you need to use them.
You need to call the code each time.
If you need to call it from multiple places, then just put it into a function or a method of a custom class.
James
Posted: Tue Apr 08, 2008 11:02 pm
by xProgrammer
Hi James
Thanks for the quick reply. It's nice to know that it's not just me.
I do build my DIALOGs in class methods so it's not difficult to rebuild them from scratch, just a call.
However I wonder if this lack of re-usability need be the case? Maybe Antonio could comment on the possibility of being able to keep a defined DIALOG in memory ready to go as it were? It would seem to be a lot more efficient both in terms of re-defining the DIALOG and the resultant garbage collection. Most people doing data entry use just a few screens but use them many times over.
Thanks
Doug
(xProgrammer)
Posted: Tue Apr 08, 2008 11:15 pm
by Antonio Linares
Doug,
You can hide it ( oDlg:Hide() ) and later on show it ( oDlg:Show() ) again instead of end it.
You may need to create it as non modal, and to simulate modal behavior, disable other windows and dialogs doing oWnd:Disable(). Later on enable them doing oWnd:Enable()
Posted: Wed Apr 09, 2008 12:10 am
by James Bott
As Antonio points out this may be a lot of work.
Are you having a specific problem you are trying to solve, or are you looking for a solution for a problem that may not exist, or a least may not be significant?
James
Posted: Wed Apr 09, 2008 1:03 am
by xProgrammer
Hi James
I guess I don't have any major problem in that I can and have written functional software as is.
However something that I thought might have worked and would be more efficient didn't work and I wondered why. I like to understand my programming environment and its limitations.
Interestingly Antonio's suggestions are pretty much in line with the general direction I might want to head in with my software. Whilst my modal dialogs are in place the user can't access the top window menu so any actions pretty much have to be buttons on the DIALOG.
Thinking forward it may well be that I should make my DIALOGs non-modal but where necessary enable only a single instance to be created or active at any time.
One type of functionality is to have a calendar dialog that controls other dialogs pertaining to what is happening on a given day at a given site. For each site and day you might want to display staffing, or bookings, or work done etc.
These different aspects would relate to their own classes and it would be nice to keep the UI aspects of them sitting there.
If you think of it the sort of behaviour I am looking for, it is really in keeping with the spirit of OO. Instantiate an object of class X and you can keep it there for repeated use until you destroy it - I am trying to make the UI component of my classes match that paradigm.
I have programmed for a stateless environment but where I have the luxury of state let me use it.
Thanks for your input. I normally have a fairly pragmatic approach to programming, even if this query seems to be an example of the opposite. Indeed I wonder at the effort here consumed on (for example) the style of buttons, whereas I only need much more basic functionality. There's some very clever coding going on behind some of this, and I guess people are attracted by the latest style of button so programmers feel a need to respond, but in reality its all driven by mass marketing and doesn't really improve software usability at all IMHO.
Regards
Doug
Posted: Wed Apr 09, 2008 1:24 am
by James Bott
Doug,
>One type of functionality is to have a calendar dialog that controls other dialogs pertaining to what is happening on a given day at a given site. For each site and day you might want to display staffing, or bookings, or work done etc.
>These different aspects would relate to their own classes and it would be nice to keep the UI aspects of them sitting there.
OK, this sounds like a description of a Single Document Interface (SDI) similar to that of Outlook. I am a big fan of SDI.
I generally don't think of this as a dialog issue, although some dialogs may be used inside a folder, window, etc. although generally they don't look or act like dialogs.
>If you think of it the sort of behaviour I am looking for, it is really in keeping with the spirit of OO. Instantiate an object of class X and you can keep it there for repeated use until you destroy it - I am trying to make the UI component of my classes match that paradigm.
With a SDI I just show and hide the screens.
>Thanks for your input. I normally have a fairly pragmatic approach to programming, even if this query seems to be an example of the opposite.
Noted.
>Indeed I wonder at the effort here consumed on (for example) the style of buttons, whereas I only need much more basic functionality. There's some very clever coding going on behind some of this, and I guess people are attracted by the latest style of button so programmers feel a need to respond, but in reality its all driven by mass marketing and doesn't really improve software usability at all IMHO.
You make valid points but you forget that without sales the usability of the software is of little importance. First you must have sales.
Users will buy a product based on the look of its interface. I even find myself leaning in this direction. If I see a screenshot of a program that has a 10 year old interface design, I am much less inclined to buy it that one with an up-to-date interface. One assumes that the functionality may be as old as the interface.
You may be interested in this article:
Magic and Software Design
http://www.asktog.com/papers/magic.html
Also, remember that we programmers are of a different species, Homo Logicus. Users have a completely different view of the world than we do.
Regards,
James
Posted: Wed Apr 09, 2008 3:18 am
by xProgrammer
Hi James
Thanks so much for your interest.
> OK, this sounds like a description of a Single Document Interface (SDI) similar to that of Outlook. I am a big fan of SDI.
Without really being 100% sure what SDI is exactly I am certainly with you there. When I started with xHarbour/FiveWin (now FiveLinux) I was just concerned with getting some productive software out. I certainly didn't want a full MDI implementation because normally my user would be dealing with 1 patient, 1 booking, 1 report whatever. (When we get to bookings there may be a from slot and a to slot when moving a booking but that still doesn't need or even make great sense in MDI.) But they are likely to be dealing with multiple different objects at the same time (like a patient, a booking, a report, a referring physician etc). So I opted for modal dialogs which made coding pretty much straight forward but as you would know isn't the best fit for the real world that the users are facing and that I am modelling in my software. So SDI here we come!
I have yet to embark down the SDI track so I don't rightly know what problems I will face but I would greatly appreciate your advice when I stumble.
> First you must have sales.
I am only too well aware, both from my own experience and watching what products succeed in the market place. But I have also spent some time working as development manager for a fair sized institution and I know the cost of doing some of these things - not just the money but the extended delivery schedules that often result. I am not criticizing programmers driven by market forces but do wish that we could educate people more. I certainly try to.
Thanks and regards
Doug
(xProgrammer)
Posted: Wed Apr 09, 2008 6:02 am
by Otto
Hello Doug,
May I ask you for an example screen shot of “a booking“.
As I program hotel management software maybe we can
exchange some ideas.
Regards,
Otto
Posted: Wed Apr 09, 2008 6:37 am
by xProgrammer
Hi Otto
I haven't got that far with my software yet.
Bookings are complex in my situation because a single (nuclear medicine) test is in 2 parts (most of them) on the same day both of which require camera time (different amounts) and also doctor time (which is the other limiting resource in general). Plus different tests take different amounts of time so it is complicated to judge if I do 4 less of these can I do two more of some other test. But that is the sort of arrangement I will have to get down to - a kind of trading scheme.
Booking for say a general practitioner (whatever you call your normal day to day doctor) is much simpler because they just allocate standard time slots and book known long consults into 2 or more contiguous slots.
I plan to pre-schedule slots for the more common tests and implement a trading scheme. But ther are further compliactions in that some doctors don't do some tests and the number of patients they are able to see in a day varies. That is not so bad - have a standard schedule for Dr X at site A on a Monday for example. But then the doctors' roster gets changed for some reason - how do I change pre-scheduled slots with some bookings already allocated to these slots?
I am happy to share any design or code I produce but at the moment there isn't anything to share.
I started with the Patient and Patient-File classes as there was a desperate need for off-site archiving and I am working through classes progressively.
I was involved in the early stages of reviewing a design for bookings in a large hospital but the design got far too complicated.
Hopefully I can be of more help shortly.
Regards
Doug
(xProgrammer)
Posted: Wed Apr 09, 2008 6:48 am
by xProgrammer
Hi Otto
By the way their current (manual paper-based) system was chaotic and not working to anyone's satisfaction so we are improving that to prove up our ideas for improvement before attempting to implement in code.
Regards
Doug
Posted: Wed Apr 09, 2008 11:32 am
by James Bott
Doug,
>Bookings are complex in my situation because a single (nuclear medicine) test is in 2 parts (most of them) on the same day both of which require camera time (different amounts) and also doctor time (which is the other limiting resource in general). Plus different tests take different amounts of time so it is complicated to judge if I do 4 less of these can I do two more of some other test. But that is the sort of arrangement I will have to get down to - a kind of trading scheme.
This is a classic case for buisness objects that are able to schedule themselves. I highly recommend getting a copy of Buisness Engineering with Object Technology. You can find more about it on my website here:
http://ourworld.compuserve.com/homepage ... rogram.htm
And there is a link to Amazon where you can buy it used (it is out of print) for 1 cent (plus shipping). I would get a copy ASAP. It discusses the kinds of things you are trying to do. On page 122 there is a section called
Techniques for Time Management in which he discusses creating a scheduling class and a commitment class. He discusses schedules that require multiple resources, and the use of tenative and firm commitments. This sounds like just what you need.
Coincidentally, I am also working on a medical application. Although my portion of it doesn't deal with scheduling, I have done a lot of work defining the domain (patient, provider, operation, etc.) and trying to understand the interaction between the business objects. So, perhaps the three of us (including Otto) can do some collaboration.
James
Posted: Wed Apr 09, 2008 9:09 pm
by xProgrammer
Hi James
Thanks for the advice. I look forward to future discussions.
My code consists of a small module that instantiates the required objects, calls for user logon (via a USER_Class) launches a main window with a menu has a couple of utility functions but all the rest is done via objects that interact with each other.
I read your articles before starting and gained a lot from them - part technical but perhaps more so confidence to just "do it". xBase was fine - I had spent quite atime with dBase then Clipper, albeit a long time ago. And I had done some OO work more recently. But OO and xBase was new and strange but actually easy and fun once I got started. I did deviate a little from your methodology in that I have a DATAFILE_Class but that doesn't do automated "gather and scatter" - I think thats what you called it? It does however control the allocation of keys and the standard fields included in all my tables which are:
who last updated the record (user-key)
what action they took [update|inset|delete]
when this occurred (date and time)
I also have, for example, a PATIENT_LIST_Class and a PATIENT_Class even though both are basically dealing with the same data table.
In Australia the government assigns provider numbers per physical location so I have a DOCTOR_LIST_Class, a DOCTOR_Class, a PROVIDER_LIST_Class and a PROVIDER_Class.
Bookings not only involves the complications of scheduling but also the data that you have is imperfect - so that you can't be certain that the patient's name is spelled correctly and we don't have date of birth and what they are booked in for might be incorrect and they might be referred by a doctor who either isn't in your system or whose name they don't know or are uncertain of. Much of this is resolved when they present, but some details (eg the particular test) can't be resolved they are seen by the doctor or in some cases even later depending upon initial test outcomes.
Also in terms of scheduling some tests involve multiple appointments with required timelines between them so you can't just reschedule one part of such a series.
Regards
Doug
Posted: Thu Apr 10, 2008 2:08 am
by James Bott
Doug,
>I read your articles before starting and gained a lot from them - part technical but perhaps more so confidence to just "do it". xBase was fine - I had spent quite a time with dBase then Clipper, albeit a long time ago. And I had done some OO work more recently. But OO and xBase was new and strange but actually easy and fun once I got started.
Glad to hear all that. I have convinced very few people to start using OOP. It is all very strange at first, but the benefits are tremendous.
>I did deviate a little from your methodology in that I have a DATAFILE_Class but that doesn't do automated "gather and scatter" - I think that's what you called it? It does however control the allocation of keys and the standard fields included in all my tables which are:
>who last updated the record (user-key)
>what action they took [update|inset|delete]
>when this occurred (date and time)
After reading the above I am somewhat concerned that you are missing some OOP understanding.
There is no reason that you couldn't add the above functionality to the existing database class and also get the built in scatter-gather (and all the other good features). You can just subclass TDatabase or my TData class and add your features. May I see your DATAFILE_Class class? Perhaps I can save you some, or lots, of work. BTY, I would not add "_Class" to the class name--it seems redundant.
>I also have, for example, a PATIENT_LIST_Class and a PATIENT_Class even though both are basically dealing with the same data table.
Is you PATIENT_LIST_Class a table and PATIENT_Class a record? Why not call them a Patients class and a Patient class? You really should be using a database class as the basis for a table class. I fear you a making it more difficult than it could be. Is the PATIENT_LIST_Class a subclass of your DATAFILE_Class?
In Australia the government assigns provider numbers per physical location so I have a DOCTOR_LIST_Class, a DOCTOR_Class, a PROVIDER_LIST_Class and a PROVIDER_Class.
Isn't a doctor also a provider? Are you listing them in both files? Or, are you only considering an organization a provider?
The definition we use here is:
Provider- An individual or institution who provides medical care, including a physician, hospital, skilled nursing facility, or intensive care facility.
>Bookings not only involves the complications of scheduling but also the data that you have is imperfect - so that you can't be certain that the patient's name is spelled correctly and we don't have date of birth and what they are booked in for might be incorrect and they might be referred by a doctor who either isn't in your system or whose name they don't know or are uncertain of. Much of this is resolved when they present, but some details (eg the particular test) can't be resolved they are seen by the doctor or in some cases even later depending upon initial test outcomes.
How to handle imperfect data is discussed at length in "About Face The Essentials of User Interface Design," also listed on my website. I'm sure this would also be valuable to you.
Some of the techniques I use with "imperfect" or questionable data is to color the fields yellow and perhaps also popup a tooltip explaining what is commonly placed in the field. Also critical fields are colored pink until the data is entered. And you can flag all records with missing critical data and/or questionable data with a flag so they can later be found and resolved.
>Also in terms of scheduling some tests involve multiple appointments with required timelines between them so you can't just reschedule one part of such a series.
Yes, I realize your scheduling is quite complex--this is all the more reason to use objects to do it. Can you write up just a description of the rules that you need to follow to schedule one of these procedures? I always start with a description of the domain before starting coding. Next is usually an entity-relationship diagram. Then I usually code the very simplest model I can just so I can do test senarios to work out the logic. Making them simple makes them much easier to work with. In your case you could come up with a dozen different test procedures and store them in a file so you can use them to test your scheduler class as you are working on it.
James
Posted: Thu Apr 10, 2008 4:31 am
by xProgrammer
Hi James
Have to go trail an old car (1928 vintage) 720 kilometres for a friend's wedding so can only make quick reply at this stage.
>Isn't a doctor also a provider? Are you listing them in both files? Or, are you only considering an organization a provider?
A doctor is in effect a different provider for each physical address from which he works, so each provider record points to a doctor record. A doctor record may have zero to many provider records pointing to it. If there are zero records pointing to it then the doctor can't function as a provider.
> Is you PATIENT_LIST_Class a table and PATIENT_Class a record?
Yes
> Why not call them a Patients class and a Patient class?
I like PATIENTLIST because it is unequivocal. I have seen the plural name misused too often.
> I fear you a making it more difficult than it could be. Is the PATIENT_LIST_Class a subclass of your DATAFILE_Class?
No. But PATIENTLIST has the object of type DATAFILE that opened the PT_PATIENT datafile as a property so whilst it isn't a subclass it has many of the benefits. This was a deliberate choice partly to ease data back end migration.
xDATAFILE Class follows. You probably need to see some of the other objects to see how it fits together.
Code: Select all
/******************************************************************************
* *
* xDATAFILE Class *
* *
* This class encapsulates the functionality of xBase data files *
* *
******************************************************************************/
CLASS xDATAFILE
DATA WorkArea
DATA KeyRecord
DATA Alias
DATA FileName
DATA MsgName
DATA Index1
DATA Index2
DATA Index3
DATA iNumIndx
DATA bSuccess
DATA sMessage
DATA iKey
DATA sKey
DATA iNumIndx
DATA lConsole
DATA lQuit
DATA lShared
METHOD New( WorkArea, KeyRecord, Alias, FileName, MsgName, Index1, Index2, Index3 ) CONSTRUCTOR
METHOD OpenRaw()
METHOD OpenIndexed()
METHOD OpenFile()
METHOD SetIndices()
METHOD Select()
METHOD LockFile()
METHOD LockRecord()
METHOD Unlock()
METHOD Close()
METHOD GoTo( RecNum )
METHOD GoTop()
METHOD GoBottom()
METHOD AppendBlank()
METHOD BOF()
METHOD EOF()
METHOD GetKey()
METHOD RptKey()
METHOD SetKey( xValue )
METHOD KeyValue()
METHOD Seek( Value )
METHOD SetOrder( Index )
METHOD RecCount()
METHOD Save( CallObj )
METHOD FileInfo()
METHOD Skip( SkipCount )
METHOD LastUpdated()
ENDCLASS
METHOD New( iWorkArea, iKeyRecord, sAlias, sFileName, sMsgName, lConsole, lQuit, Index1, Index2, Index3 ) CLASS xDATAFILE
::lConsole := lConsole
::lQuit := lQuit
::WorkArea := iWorkArea
::KeyRecord := iKeyRecord
::Alias := sAlias
::FileName := sFileName
::MsgName := sMsgName
::lShared := .T.
::iNumIndx := PCOUNT() - 7
IF ::iNumIndx < 0
::iNumIndx := 0
ENDIF
IF ::iNumIndx > 0
::Index1 := Index1
IF ::iNumIndx > 1
::Index2 := Index2
IF ::iNumIndx > 2
::Index3 := Index3
ENDIF
ENDIF
ENDIF
RETURN self
METHOD OpenRaw() CLASS xDATAFILE
LOCAL lbRet
SELECT ( ::WorkArea )
lbRet := ::OpenFile()
RETURN lbRet
METHOD OpenIndexed() CLASS xDATAFILE
SELECT ( ::WorkArea )
IF ::OpenFile()
IF ::lConsole
? ::MsgName + " opened in work area " +ALLTRIM( STR( ::WorkArea ) )
ENDIF
::SetIndices()
RETURN .T.
ENDIF
IF ::lConsole
? "Error Opening " + ::sMsgName
ENDIF
IF ::lQuit
QUIT
ENDIF
RETURN .F.
METHOD OpenFile() CLASS xDATAFILE
IF ::lShared
USE ( ::FileName ) ALIAS ( ::Alias ) SHARED
ELSE
USE ( ::FileName ) ALIAS ( ::Alias ) SHARED
ENDIF
/* LOCAL lsFName
lsFName := ::FileName
OPEN lsFName */
IF NETERR()
? "Error opening " + ::FileName
RETURN .F.
ENDIF
RETURN .T.
METHOD SetIndices() CLASS xDATAFILE
DO CASE
CASE ::iNumIndx = 1
SET INDEX TO ( ::Index1 )
?? " Index set to " + ALLTRIM( ::Index1 )
CASE ::iNumIndx = 2
SET INDEX TO ( ::Index1 ), ( ::Index2 )
?? " Indexes set to " + ALLTRIM( ::Index1 ) + ", " + ALLTRIM( ::Index2 )
CASE ::iNumIndx = 3
SET INDEX TO ( ::Index1 ), ( ::Index2 ), ( ::Index3 )
?? " Indexes set to " + ALLTRIM( ::Index1 ) + ", " + ALLTRIM( ::Index2 ) + ", " + ALLTRIM( ::Index3 )
ENDCASE
RETURN .T.
METHOD Select
SELECT ( ::WorkArea )
RETURN
METHOD Unlock() CLASS xDATAFILE
SELECT ( ::WorkArea )
UNLOCK
RETURN
/*
METHOD LockRecord() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN RLOCK()
*/
METHOD LockRecord() CLASS xDATAFILE
LOCAL iNumTries
LOCAL lIsLocked
SELECT ( ::WorkArea )
iNumTries := 1
DO WHILE iNumTries < 6
lIsLocked := RLOCK()
IF lIsLocked
RETURN .T.
ENDIF
INKEY(0.5)
iNumTries += 1
ENDDO
MsgInfo( "Unable to lock " + ::FileName )
RETURN .F.
METHOD LockFile() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN FLOCK()
METHOD Close() CLASS xDATAFILE
SELECT ( ::WorkArea )
CLOSE
RETURN
METHOD AppendBlank() CLASS xDATAFILE
SELECT ( ::WorkArea )
APPEND BLANK
RETURN ( !NETERR() )
METHOD BOF() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN BOF()
METHOD EOF() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN EOF()
METHOD GetKey() CLASS xDATAFILE
SELECT ( defWA_KEY )
IF ::KeyRecord < 1
RETURN .F.
ENDIF
IF ::KeyRecord > RECCOUNT()
RETURN .F.
ENDIF
GOTO ( ::KeyRecord )
::iKey := KY_LASTKEY
::iKey += 1
IF RLOCK()
REPLACE KY_LASTKEY WITH ::iKey
UNLOCK
::sKey := PADL( ALLTRIM( STR( ::iKey)), 16, "0" )
RETURN .T.
ENDIF
RETURN .F.
METHOD RptKey() CLASS xDATAFILE
LOCAL liKey
SELECT ( defWA_KEY )
IF ::KeyRecord < 1
RETURN 0
ENDIF
IF ::KeyRecord > RECCOUNT()
RETURN 0
ENDIF
GOTO ( ::KeyRecord )
liKey := KY_LASTKEY
SELECT ( ::WorkArea )
RETURN ( liKey )
METHOD SetKey( xValue ) CLASS xDATAFILE
ALERT( "SetKey" )
SELECT ( defWA_KEY )
IF ::KeyRecord < 1
RETURN .F.
ENDIF
IF ::KeyRecord > RECCOUNT()
RETURN .F.
ENDIF
// ALERT( TYPE( xValue ) )
GOTO ( ::KeyRecord )
IF RLOCK()
REPLACE KY_LASTKEY WITH xValue
UNLOCK
::iKey := xValue
::sKey := PADL( ALLTRIM( STR( ::iKey)), 16, "0" )
RETURN .T.
ENDIF
RETURN .F.
METHOD KeyValue() CLASS xDATAFILE
RETURN ::sKey
METHOD Seek( Value ) CLASS xDATAFILE
LOCAL llFound
SELECT ( ::WorkArea )
SEEK Value
llFound := FOUND()
IF llFound
// ALERT( "Found" )
ELSE
// ALERT( "Not Found" )
ENDIF
RETURN llFound
METHOD SetOrder( Index ) CLASS xDATAFILE
SELECT ( ::WorkArea )
SET ORDER TO ( Index )
RETURN
METHOD RecCount() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN RECCOUNT()
METHOD Save( CallObj ) CLASS xDATAFILE
LOCAL llSuccess
SELECT ( ::WorkArea )
CallObj:sLUBy := oUSER:sKey
CallObj:sLUWhen := oDATETIME:Now()
IF CallObj:lInsert
IF ::GetKey()
CallObj:sKey := ::sKey
CallObj:cLUActn := "I"
llSuccess := ::AppendBlank()
ELSE
ALERT("Failed to retrieve key")
ENDIF
ELSE
CallObj:cLUActn := "U"
llSuccess := ::LockRecord()
ENDIF
IF llSuccess
CallObj:Write()
dbCOMMIT()
UNLOCK
ENDIF
RETURN llSuccess
METHOD FileInfo() CLASS xDATAFILE
LOCAL lcRet
LOCAL lcLKey
SELECT ( ::WorkArea )
lcRet := PADR( ::FileName, 16 ) + " WA= " + LTRIM( STR( ::WorkArea ) ) + " has " + ;
LTRIM( STR( RECCOUNT() ) ) + " records - updated " + DTOS( LUPDATE() ) + " last key " + STR( ::RptKey() )
/*
lcRet := PADR( ::FileName, 16 ) + " WA= " + LTRIM( STR( ::WorkArea ) ) + " has " + ;
LTRIM( STR( RECCOUNT() ) ) + " recs - updated " + DTOS( LUPDATE() ) + "last key "
IF EMPTY( ::RptKey() )
lcLKey := "0"
ELSE
lcLKey := STR( ::RptKey() )
ENDIF
lcRet += lcLKey
*/
RETURN lcRet
METHOD Skip( SkipCount ) CLASS xDATAFILE
LOCAL lnCount
IF PCOUNT() < 1
lnCount := 1
ELSE
lnCount := SkipCount
ENDIF
SELECT ( ::WorkArea )
SKIP ( lnCount )
RETURN
METHOD GoTo( RecNum ) CLASS xDATAFILE
SELECT ( ::WorkArea )
GOTO RecNum
RETURN
METHOD GoTop() CLASS xDATAFILE
SELECT ( ::WorkArea )
GOTO TOP
RETURN
METHOD GoBottom() CLASS xDATAFILE
SELECT ( ::WorkArea )
GOTO BOTTOM
RETURN
METHOD LastUpdated() CLASS xDATAFILE
SELECT ( ::WorkArea )
RETURN DTOS( LUPDATE() )
RETURN nil