On one particular job I was at the clientís site on a weekly basis during the development of the application, and I would always make a full backup of the data to a set of floppy disks for my own use back in my office. However, this was a five-minute process to zip the files onto three disks and the client just would not do it on a daily basis. My first question when getting a problem call would be, ďWhen did you last back up the data?Ē, and the response was usually, ďOh, a week or so ago, I thinkÖĒ Consequently, I added Quick BackUp and Quick Restore buttons to the main form. This has helped, but I subsequently added an OnClose method that offers the opportunity to run the Quick BackUp before closing the application. I now make a practice of adding these two buttons to every application I develop.
One point I wish to make very strongly here is that, while I knew how to use COPY TO... to accomplish what I had in mind, it was the acquisition of ideas from the dBASE newsgroups that enabled me to compact the code in such a way as to make it easily reusable. I picked up the ISDIR(cDir) from reading several posts to somebody elseís question. I attribute this function to both Bowen and Romain in my documentation because, as I recall, both added to my understanding of what was being accomplished with this function. The AllCopy( ) method was something that had its origin in a direct answer from Romain in response to my specific question about how to accomplish what I needed to do. I only had to make some small changes to what he had posted to make it work for me. (Hey, nobody is going to write all your code for you.)
#define ISDIR(cDir) .Not. Empty(funique(cDir+"\isdir???.???"))
*-- The above line of code is from Bowen Moursund & Romain Strieff
If .not. ISDIR("&cAppPath\backup")
*** CLASS::AllCopy("&cDataPath\*.mdx", "&cAppPath\backup\") && Left out to increase speed
MSGBOX("The Quick Backup procedure is completed.","Information",64)
The &cAppPath is needed because I have a utility that allows the user to set the path to the database tables. Rather than using a BDE alias, I have a table that maintains data about the path to the application and the path to the DBFs. The reason for this is that the application for which this was originally developed provided the capability of using two or more identically structured sets of tables. Refer to the Set Path code paragraph below to understand the meaning of &cAppPath and &cDataPath.
Letís first attempt to understand Bowenís ISDIR(cDir) function. While it is quite possible to use the things you glean from the newsgroup without understanding how or why they work (and we all do this sometimes), I believe you will discover it worthwhile to figure it out so you can modify it for use under a different set of circumstances.
OLH (or Language Reference) you will learn that
to create a file with a unique filename,
where the digits are random numbers. So,
to create a file in the folder you supply as the parameter
which in this case is the \backup folder under the application folder,
If ISDIR(cDir) returns
an empty string, the folder must be created. Otherwise the DBFs and DBTs
are just copied to the \backup
I do not copy the MDXs because they can be easily recreated, and they may
more than double the required time for backing up the database. Remember,
you want this to be quick enough so the user will not be hesitant to use
MSGBOX("The Quick Restore procedure is completed."+chr(13)+chr(13)+ ;
"You must Rebuild Indexes before proceeding!","Information",64)
The Quick Restore procedure is almost identical, except that you donít need to check for the existence of the folder since you know itís there, and you need a MsgBox( ) to remind the user to run the Rebuild Indexes utility. (You might want to add an automatic reindex to this method.)
look at Romainís AllCopy() function.
Two parameters are required: 1) where the files are, and 2) where you want
to copy them. The two variables
not be a problem for you as they are fully documented in the online Help
(OLH). The array aFiles is
declared and aFiles.Dir()returns
the name of the file, the size of the table, the date stamp, the time stamp,
and the DOS attribute. See the OLH for
This implies that
return the number 5, since there are 5 elements in the array: name, size,
date stamp, time stamp, and DOS attribute for each DBF file. But we only
need the filename, so we use aFiles.Size/5
the upper limit in the For...Next
Procedure AllCopy(cFromSkeleton, cToDir)
Set Safety Off
nAt = rat("\", cFromSkeleton)
cFromDir = left(cFromSkeleton, nAt)
aFiles = new array()
For n=1 To aFiles.Size/5 && Need only 1 of the 5 fields
*-- Note: According to Romain Strieff the color problem in the
*-- following code is because the editor is confused by delimiters.
*-- The code works properly as is.
cCommand = [Copy file "]+cFromDir+aFiles[n,1] ;
+[" to "]+cToDir+aFiles[n,1]+["]
Set Safety On
a complex combination of parameters and other variables, and this complexity
actually confuses the editor. You get colors that indicate you have made
an error, but you havenít. Assume the first file to be copied from the
to the \pmm5\backup folder
Element aFiles[1,1] will
Element aFiles[1,2] contains
the size of names.dbf,
but you donít need this, nor do you need
Therefore, the literal, &cCommand,
Copy file c:\myapp\names.dbf to c:\myapp\backup\names.dbf
The [n,1] element of the array, aFiles, contains the names of each of the DBF and DBT files in the cFromDir, and the For...Next loop determines that each filename is copied to the \myapp\backup folder. Hence, you have a quick backup of all the tables in your database. In one application for which I included this procedure, the database resides on one of the three workstations, the backup files occupy approximately 20 Meg (without the MDX files), and the procedure requires less than 10 seconds! This should encourage your client to back up the database tables. Furthermore, the user at each of the other workstations can also back up the files to their local hard drive, which means that you potentially have two additional copies of the database should the server crash.
The Set Path code
I use Romainís CreTable (another
jewel acquired as a lurker) to recreate the
itís missing, otherwise I just
Set Path To... the
contents of the cPath field.
PUBLIC cPath, cAppPath, cDataPath
*** Determine the directory in which the program is running. PROGRAM(1) returns
*** the full path of the running program.
*** RAT() locates the position of the last \, to get the length of the SUBSTR()
*** for the mPath memory variable.
SET FULLPATH ON
cAppPath = SUBSTR(PROGRAM(1), 1, RAT("\",PROGRAM(1))-1)
*-- Added Romain Strieff's CreTable module
in case dbpath.dbf has been deleted or moved.
I have another app that is used by missionaries around the world, and it occurred to me that I needed to abandon my provincial roots and enable my users to select date formats more in keeping with the part of the world they were living in, even though many were Americans and quite accustomed to mm/dd/yy. The result of this realization was a set of three custom controls that I put together in DateBtns.cc. Depending on the application in development I include one or all of these three buttons. On a typical form they might look like the figure shown below.
The dates would appear as
If the user chooses the European format of dd/mm/yy by clicking the first button, and then also chooses to revert to the two-digit year form by clicking the second button, the screen image would change to the next figure.
The dates would now appear as
Finally, if the user resides in a country that employs the period as a separator rather than the forward slash, and chooses the favored four-digit year, they would press the third button. The screen image would be as shown in the following figure.
The date in all form fields would appear as dd.mm.yyyy.
The text on the button displays what the format will be if you press the button, not what currently is, as you can already see that on screen. Obviously these buttons are toggle switches that work with Set Date To, Set Century On/Off, and Set Mark To. This set of buttons, in conjunction with one other feature I included in this application makes this truly a global application. DateBtns.cc makes the three buttons available on the control palette so that one or more of them can be placed on any form. (This .cc may still be available on Dan Howardís web site, but you may also request a copy directly from me if you would like to try it.)
The additional feature I have added to my forms is the ability to instruct any reports in the application to place the postal code field in front of the city field. The form appears as shown in the next image. If a particular record has this field as True, Crystal will precede the city with the postal code.
Now, if this individual lived in, say, England, where the postal code is traditionally placed before the city, the user would mark the checkbox. (The green text indicates that this field is editable without clicking the Edit button.) Once this is done, any reports will place the postal code before the city, i.e., 30087 Stone Mountain.
Incidentally, the Clip button
is another feature recently added to the application. The upper one copies
the entire name and address fields to the Windows clipboard, and the lower
one includes the email address. So, If you want to write a note to this
person, you press the Clip button, open your word processor, and
press Ctrl-V to
place the address in the document. I got this one from the newsgroup,
too. It uses clip.prg, finalized by Gary Warren King, but based on API
code by Jim Sare.
FORM.efHidden.Value = Trim(form.entryfield1.value)+" "+Trim(form.entryfield2.value) ;
+" "+Trim(form.entryfield3.value)+chr(13)+Trim(form.entryfield4.value) ;
+ chr(13)+Trim(form.entryfield5.value)+chr(13)+Trim(form.entryfield6.value) ;
+ ", "+Trim(form.entryfield7.value)+" "+Trim(form.entryfield8.value) ;
FORM.efHidden.Enabled = .T.
set procedure to clip.prg additive
cb = new clipboard( )
*-- Put stuff into the object
cb.FillContents( clipstring )
*-- Dump the object contents onto the clipboard
*-- Empty the object
cb.FillContents( "" )
*-- Release the object
msgbox("The contents of the Name and Address fields are on the clipboard.","Info",64)
FORM.efHidden.Enabled = .F.
The DateBtns.cc Code
I have included only the code for
the m/d/y button for illustrative purposes; the code for the other
two is nearly identical
*-- File: DateBtns.cc
*-- Copyright (c) 1997, 1998 BoKen Enterprises
*-- Robert W. Newman rnewman_dBulletin_@mindspring.com (take out ď_dBulletin_Ē).
*-- 12/06/1997 -- Created version 1.0
*-- 01/18/1998 -- Version 1.3
*-- This .CC file is designed to give the designer access to
*-- three buttons to modify the date format in an application.
*-- MdyDmyBtn toggles between MDY and DMY.
*-- CentBtn toggles between YY and YYYY format.
*-- MarkBtn togles between / and . as the separator character.
*-- The .CC uses FmtDate.DBF, which should contain fields:
*-- MDYDMY C(3) && value MDY or DMY
*-- CENTURY C(3) && value ON or OFF
*-- MARK C(1) && value / or .
*-- (This dbf will be created if it does not exist.)
*-- Add this .CC as a custom control to the control palette.
*-- It provides 3 buttons for modifying the date format.
*-- The file FmtDate.DBF should be in the app directory. It
*-- will be created if it does not already exist.
*-- There need not be a code segment in your opening .PRG file
*-- to read the contents of the FmtDate.dbf and SET the
*-- parameters to the values chosen by the user during the
*-- previous session. This will be done by Init() procedure.
*-- The .CC file will check the state of the SET values for
*-- MDY, Century, and Mark and adjust the text on the
*-- buttons accordingly.
*-- Button to toggle between mm/dd/yy and dd/mm/yy format
CLASS MDYDMYBUTTON(FormObj,Name) OF PUSHBUTTON(FormObj,Name)
The If...Endif pertaining to the (cAlias) allows these buttons not only to be offered as a Utility, where you would not expect a table to be open in a workarea, but also on a form where you know there will be a table in use. That means the utility can be used in a dynamic way during data access.
The reader may also wonder why there is an onOpen() method in the DateBtns.cc that seems to duplicate the onClick() method. This event fires after the form.OnOpen event, and checks to see if there is an active Alias() before opening the FmtDate table to configure the text on the button and the speedtip to match the settings with the current date configuration. If the form.OnOpen() method sets a view, for example, the DateBtns OnOpen() method saves the selected Alias() to cAlias and then restores it after configuring the Date button. The OnClick() method does the same, because the FmtDate table must be opened in a workarea to save the new settings.
Some final words for encouragement
In closing, I want to return to the AllCopy() method that I got working as a result of several interchanges with Romain Strieff. I remember that when I finally got it working properly and posted my code, Romain responded with a one word reply, ďNice.Ē And believe me, that made my day, and probably my week. The message from this is: ask, try it, ask again, try again, get clarification, and when it finally works for you, tell the ones that helped you see the way, and be sure to thank them.
There is a description and screen images from one of the programs that employ these features at: http://www.mindspring.com/~rnewman/PMM51.html
To download my DateBtns
Custom Class, click here
(itís a 2Kb zipped file)