Recently, more precisely two weeks before the deadline for this issue, Michael Nuwer had the idea to transform our on-line search engine into a desktop dBulletin search engine. dBASE being a RAD (Rapid Application Development) platform, we were reasonably optimistic that we could meet the deadline. Effectively, we were able to include that application in this issue of the magazine.
Instructions
In the entryfield near the top of the form, enter the string to be searched for, and press the Enter key. Don't use quotes unless they are part of the expression you are looking for. Neither should you use quotes if your string contains a space. The dBulletin search engine is programmed to know that this space is part of the string to search for.
Use only a comma to separate different strings; any other sign (semi-colon, period, slash, etc.) will be interpreted as part of the string to search for. For example, if you are looking for the strings “grid” and “calculated field”, they should be entered this way:
Words with special capitalization (like “dBulletin” and “dBASE”) do not need any special precautions. For example, you will get the same results whether you type “dbase”, “dBASE” or “DBASE”.
The stock Editor class supports the HTML tags needed to format words and paragraphs but can't be used to display an HTML page. So we turned to the Microsoft Web Browser ActiveX as the control to display HTML pages in our application. In order to be able to take advantage of this ActiveX control, Internet Explorer has to be installed on your computer. This ActiveX control can be used to read our web pages stored on disk, and that’s why we took advantage of it.
Herewith are its properties, methods and events as obtained from the Microsoft web site. Note: We've put in bold letters the limited number of those used in our application:
Properties | Comments | |
AddressBar | Sets or retrieves whether the address bar of the object is visible or hidden. | |
Application | Retrieves the automation object for an application that is hosting the WebBrowser Control. | |
Busy | Retrieves a Boolean value indicating whether the object is engaged in a navigation or downloading operation. | |
Container | Retrieves an object reference to a container. | |
ü | Document | Retrieves the automation object of the active document, if any. |
FullName | Retrieves the fully qualified path of the Internet Explorer executable file. | |
FullScreen | Sets or retrieves a Boolean value that indicates whether Internet Explorer is in full-screen or normal window mode. | |
Height | Sets or retrieves the height of the Internet Explorer main window. | |
HWND | Retrieves the handle of the Internet Explorer main window. | |
Left | Sets or retrieves the screen coordinate of the left edge of the main window of the object. | |
LocationName | Retrieves the name of the resource that Internet Explorer is currently displaying. | |
LocationURL | Retrieves the URL of the resource that Internet Explorer is currently displaying. | |
MenuBar | Sets or retrieves a Boolean value that indicates whether the Internet Explorer menu bar is visible. | |
Name | Retrieves the name of the object that contains the WebBrowser Control used by Internet Explorer. | |
Offline | Sets or retrieves a Boolean value that indicates whether the object is currently operating in offline mode. | |
Parent | Retrieves the parent of the object. | |
Path | Retrieves the full path to the object. | |
ReadyState | Retrieves the ready state of the object. | |
RegisterAsBrowser | Sets or retrieves a value that indicates whether the object is registered as a top-level browser for target name resolution. | |
RegisterAsDropTarget | Sets or retrieves a value that indicates whether the object is registered as a drop target for navigation. | |
Resizable | Sets or retrieves a value that indicates whether the object can be resized. | |
Silent | Sets or retrieves a value that indicates whether the object can show dialog boxes. | |
StatusBar | Sets or retrieves a value that indicates whether the status bar for the object is visible. | |
StatusText | Sets or retrieves the text in the status bar for the object. | |
TheaterMode | Sets or retrieves a value that indicates whether the object is in theater mode. | |
ToolBar | Sets or retrieves a value that indicates whether the toolbar for the object is visible. | |
Top | Sets or retrieves the screen coordinate of the top edge of the main window of the object. | |
TopLevelContainer | Retrieves a value that indicates whether the object is a top-level container. | |
Type | Retrieves the type name of the contained document object, that is, Microsoft Windows® HTML Viewer. | |
Visible | Sets or retrieves a value that indicates whether the object is visible or hidden. | |
Width | Sets or retrieves the width of the main window for the object. | |
Methods | ||
ClientToWindow | Converts the client coordinates of a point to window coordinates. | |
ExecWB | Executes a command on an OLE object and returns the status of the command execution using the IOleCommandTarget interface. | |
GetProperty | Retrieves the value of a property associated with the given object. | |
ü | GoBack | Navigates backward one item in the history list. |
ü | GoForward | Navigates forward one item in the history list. |
GoHome | Navigates to the current home or start page. | |
GoSearch | Navigates to the current search page. | |
Navigate | Navigates to a resource identified by a URL or to the file identified by a full path. | |
ü | Navigate2 | Navigates the browser to a location that might not be able to be expressed as a URL, such as a pointer to an item identifier list (PIDL) for an entity in the Windows shell namespace. |
PutProperty | Sets the value of a property associated with the object. | |
QueryStatusWB | Queries the OLE object for the status of commands using the IOleCommandTarget interface. | |
Quit | Closes the object. | |
Refresh | Reloads the file that is currently displayed in the object. | |
ü | Refresh2 | Reloads the file that is currently displayed in the object. Unlike Refresh, this method contains a parameter that specifies the refresh level. |
ShowBrowserBar | Shows or hides a specified browser bar. | |
Stop | Cancels any pending navigation or download operation and stops any dynamic page elements, such as background sounds and animations. | |
Events | ||
BeforeNavigate2 | Fires before navigation occurs in the given object (on either a window or frameset element). | |
ClientToHostWindow | Fires to request that the client window size be converted to the host window size. | |
CommandStateChange | Fires when the enabled state of a command changes. | |
DocumentComplete | Fires when a document has been completely loaded and initialized. | |
DownloadBegin | Fires when a navigation operation is beginning. | |
ü | DownloadComplete | Fires when a navigation operation finishes, is halted, or fails. |
FileDownload | Fires to indicate that a file download is about to occur. If a file download dialog is to be displayed, this event is fired prior to the display of the dialog. | |
NavigateComplete2 | Fires after a navigation to a link is completed on either a window or frameSet element. | |
NavigateError | Fires when an error occurs during navigation. | |
NewWindow2 | Fires when a new window is to be created. | |
OnFullScreen | Fires when the FullScreen property is changed. | |
OnMenuBar | Fires when the MenuBar property is changed. | |
OnQuit | Fires before the Internet Explorer application quits. | |
OnStatusBar | Fires when the StatusBar property is changed. | |
OnTheaterMode | Fires when the TheaterMode property is changed. | |
OnToolBar | Fires when the ToolBar property is changed. | |
OnVisible | Fires when the Visible property of the object is changed. | |
PrintTemplateInstantiation | Fires when a print template has been instantiated. | |
PrintTemplateTeardown | Fires when a print template has been destroyed. | |
PrivacyImpactedStateChange | Fired when an event occurs that impacts privacy or when a user navigates away from a URL that has impacted privacy. | |
ProgressChange | Fires when the progress of a download operation is updated on the object. | |
PropertyChange | Fires when the PutProperty method of the object changes the value of a property. | |
SetSecureLockIcon | Fires when there is a change in encryption level. | |
StatusTextChange | Fires when the status bar text of the object has changed. | |
TitleChange | Fires when the title of a document in the object becomes available or changes. | |
UpdatePageStatus | Not currently implemented. | |
WindowClosing | Fires when the window of the object is about to be closed by script. | |
WindowSetHeight | Fires when the object changes its height. | |
WindowSetLeft | Fires when the object changes its left position. | |
WindowSetResizable | Fires to indicate whether the host window should allow or disallow resizing of the object. | |
WindowSetTop | Fires when the object changes its top position. | |
WindowSetWidth | Fires when the object changes its width. | |
Enumerations | ||
SecureLockIconConstants | Contains values used by the SetSecureLockIcon event. | |
Constants | ||
NavigateError Event Status Codes | The following tables list the possible errors returned by the StatusCode parameter of the NavigateError event handler. For more information on HTTP status codes, click here. | |
We wanted the Form Title bar to show the name of the HTML document displayed in the ActiveX control. We took advantage of the DownloadComplete() event. This event is called when the control has finished reading a new document. The LocationName property could have been used to get the HTML file name. The LocationURL property gives that file name and its path. While we were inspecting the properties of the ActiveX control when it was displaying an HTML file, we discovered that the document property is like the columns property of a dBL grid. It is an object that has its own properties. When the “I” tool beside the document property is clicked, we get access to 83 properties, 31 events and 32 methods. One of these properties is called URL. That's what we’ve finally used to display the name of the displayed document in our ActiveX control. |
this.ACTIVEX1 = new ACTIVEX(this) with (this.ACTIVEX1) ... classId = "{8856F961-340A-11D0-A96B-00C04FD705A2}" endwith with (this.ACTIVEX1.nativeObject)
Function DownloadComplete
|
|
Like the LocationURL property, the document.URL property often returns some odd characters within the file name or its path (%20 instead of a space, %5B instead of an opening square bracket, %5D instead of a closing square bracket, etc.) So the returned value needs to be parsed. That's what we did.
Inspecting ActiveX control has revealed to us that even if the documentation on the MS web site refers to 31 properties for the control, only 9 of them are surfaced by the Inspector. However, we should not take for granted that only those seen in the Inspector are available to dBL developers. For example, the GoBack(), GoForward() and Refresh() methods are not seen in the Inspector when the ActiveX1.nativeObject is inspected, yet our application takes advantage of them and they work flawlessly under dBASE.
When the ActiveX control has to navigate, it adds the URL to its history list. Developers can thus take advantage of the GoBack() and the GoForward() methods to add a Back or a Forward button to their applications. However, that history list is very primitive. For example, if the user goes back through the history list to the first page requested and if, from that page, he requests a new page, that new page will not be added at the end of the history list but will become the second page in the history list, replacing all the other ones.
We didn't use the
GoHome() method. It starts
a connection to the Internet and navigates to the home page set in Internet
Explorer. However, we've used extensively the
Navigate2() method. It
needs a parameter: the full URL (the path and the name of the HTML page
to be displayed).
function Navigate_To(cHTML_Page) local cPath cPath = set('DIRECTORY') cPath = 'file:///' + cPath + '/' form.activex1.nativeobject.navigate2(cPath + cHTML_Page) return |
|
The only real bug we've met in doing this application was the inability to give focus to the form when it opens. This could be done if the form was MDI, but the application had to be SDI because we wanted its toolbar and its menu to be attached to the form, not to a visible shell. The best solution that we've found was provided by Rich (from AutoTracker Inc.). It consisted of building the ActiveX control on-the-fly in the Form.onOpen() event (after the form had opened). This explains the flashing of the application when it opens. That’s not perfect but it’s the best solution we’ve found.
The Search Engine
For our search engine, we’ve used an adapted version of the dBulletin on-line Search engine. Since that custom class is a trade secret, only the compiled .co file is supplied. Actually Search_engine.co is sub-classed from the Paintbox object, which has been used here to produce a “visual” custom control from what would otherwise have been “non-visual” function code. Once our custom class has been registered in the Component Palette, it can be drag’n dropped onto a form in the Form Designer like any stock control. However, we don't recommend you do that because our custom class was not made to be user-friendly. Our goal was to create a powerful and reliable HTML search engine. For example, parts of the result pages have to be created outside the engine, some parts are created within. In a nutshell, since you don't have access to the control’s source code, it is difficult to use it properly.
If the custom control is Inspected
in the Form Designer, it will show only the stock properties of the Paintbox
class. However, in the Start_the_Search()
function
of our form, if a new line that says Inspect(Form.Search_Engine1)
is
added, the instance of our Search_Engine
custom
class will be inspected. By the time the Inspector appears on-screen, many
custom properties will have been created. So if you select any tab and
come back to the Properties tab, more items will be listed.
Function Start_the_Search (...) Form.Search_Engine1.init() Form.Search_Engine1.MonoSearch = iif(Form.Container1.R_One.value, "true", "false") Form.Search_Engine1.StringToSearch = form.Container1.EntryField1.value Form.Search_Engine1.LocalSearch = true Form.Search_Engine1.AlertBox_On = true inspect(Form.Search_Engine1) // line to be added (...) return |
|
Among its User-defined properties, only four can be accessed externally:
The User Interface
In this application, the user interface is made up of a menu, a toolbar and some stock objects above the ActiveX control. Nearly all the images used in this application are taken from a custom DLL called Search_Toolbar.dll made with Borland's C++ Builder. Since we wanted the background to be visible through most of the form and since rectangles don't have a transparent property, all the controls above the ActiveX were put inside a transparent container of which only the border is visible.
The items in the menu are duplicates of what the user can do with the toolbar or with the controls above the ActiveX. That duplication is needed because the user could have his mouse out of order. Moreover, he has the choice to hide the toolbar and these controls. Without a menu, the user might not be able to reset the form to its previous state.
When the user clicks on the small
button at the top left corner of the ActiveX, the latter is maximized
inside the form. Actually its anchor property is changed. Using the Ctrl-H
shortcut does the same thing. Below are the shortcuts used in this application
Tasks | Keyboard Shortcuts |
To search the string typed in the entryfield | Enter |
To display the previous article in the history log of the ActiveX | Ctrl - Left Arrow Key |
To display the next article in the history log of the ActiveX | Ctrl - Right Arrow Key |
To print | Right-click on the article and select Print from the pop-up menu |
To hide controls | Ctrl - H |
To show controls | Ctrl - S |
To change to normal view | Ctrl - N |
To change to maximized view | Ctrl - M |
To maximize the form and see the dBulletin home page | Ctrl - Home |
To maximize the form and see the dBulletin index page | Ctrl - I |
To quit | Ctrl - Q |
We want the entryfield to keep focus so that it is always ready to receive input from the user. Consequently, the user will never have to click on the entryfield in order to type in the string to be searched for. When the user has finished entering the string, they just have to press the Enter key to start the search.
When the form opens, its onGotFocus() event gives focus to the entryfield. This has another consequence: when any message box is closed, the calling form receives focus and then passes it automatically to the entryfield. So we kill two birds with one stone. In order to give focus back to the entryfield after the user has clicked a radiobutton, we used their onGotFocus() events. However, for the checkboxes, we use their onChange() events.
How can we set the Enter
key to start a new search automatically? In one of the earliest versions
of our application, we used the following entryfield
key() event:
Function Entryfield1_onKey(nChar, nPosition,bShift,bControl) if nChar = 9 // That's the Enter key Form.Start_the_Search() endif return |
|
Since we’ve added a menu shortcut, activated by the enter key, that function became redundant and was deleted. We are publishing our code here in case someone might need something similar.
The Toolbar
Our Toolbar has ten toolbuttons:
Two of these toolbuttons are toggle
buttons. Under Win 9.x, when the bitmap
property
of a toolbutton is changed on-the-fly, the images displayed on all the
toolbuttons become corrupted — not the image file stored in the DLL but
their instances displayed on the toolbuttons. Also the speedtips of all
the toolbuttons become messy, their text acquiring plenty of question marks!
These problems are not seen under Windows 2000 or NT. This indicates that
the bug is caused by the operating system. Anyway, the solution is to repeat
the line that sets the new bitmap property
and to reset the speedtips.
Function C_NewWindow_onChange if this.value Form.T1.Toolbutton11.Bitmap = 'resource NoNew_Window "Search_Toolbar.dll"' // twice the same line of code. Do NOT take out Form.T1.Toolbutton11.Bitmap = 'resource NoNew_Window "Search_Toolbar.dll"' Form.Reset_Toolbar_Speedtips() Form.Load_in_New_Window() else Form.T1.Toolbutton11.Bitmap = 'resource New_Window "Search_Toolbar.dll"' // twice the same line of code. Do NOT take out Form.T1.Toolbutton11.Bitmap = 'resource New_Window "Search_Toolbar.dll"' Form.Reset_Toolbar_Speedtips() Form.Load_in_ActiveX() endif return |
|
When
a toolbar is docked vertically, dBASE doesn’t transform the separators
into horizontal separators. The result is quite inelegant. So we don’t
want the toolbar to be anchored anywhere else but at the top of the form.
The floating
property
of the Toolbar class sets the toolbar to be docked when the form opens.
It doesn’t prevent the user from making it float thereafter. To make the
toolbar bounce back to its original position when the user tries to make
it float, we took advantage of the toolbar
onUpdate() event:
class SearchToolbar of TOOLBAR with (this) onUpdate = {; this.floating = false } (...) endwith |
|
Menus are essential to implement OOP shortcuts in an application. When deciding what the shortcuts should be, it is important to avoid using those that might interfere with the normal use of your application. For example, in one of our previous versions, the left-arrow and right-arrow keys were used as shortcuts to navigate in the history list instead of the Ctrl-LeftArrow and Ctrl-RightArrow keys used in the final version. That was quite handy. Unfortunately, the consequence was that when we wanted to use the arrow keys to move the cursor in the entryfield, it was impossible.
The menu file of this application has bitmaps to illustrate menu items. For many days, I was undecided on whether the final version of this application should ship with these bitmaps or not. Finally, I’ve decided to let you choose whether you want them or not.
Beside menu items, the area available for bitmaps is 13 pixels x 13 pixels. The bitmap itself can be smaller than that. If it is too big, the image will be cropped. Contrary to the bitmaps used on toolbuttons, the purple color will not be seen as transparent in the images displayed on the menu but rather as it is, i.e.- purple. In this situation, any pale color will be seen as transparent.
These bitmaps are set through the checkedbitmap and uncheckedbitmap properties of the menu item. When the menu item is enabled, the bitmap that will be displayed depends on the checked property: if this property is true, the bitmap set in the checkedbitmap will be shown — if it is false, the bitmap set in the uncheckedbitmap will be displayed. When the menu item is disabled, dBASE creates an on-the-fly grayed-out version of one of these bitmaps. Since all this is a little bit complicated, I have prepared the following table:
Enabled = true
Checked = true |
checkedbitmap |
Enabled = true
Checked = false |
uncheckedbitmap |
Enabled = false
Checked = true |
grayed-out version of the checkedbitmap |
Enabled = false
Checked = false |
grayed-out version of the uncheckedbitmap |
The Custom Error Messages Boxes
Our application uses custom error message boxes called “AlertBoxes” instead of the ordinary message boxes. This class is defined inside Search_dBulletin.cc. An AlertBox has five objects: four visible objects and the last one, hidden.
First, there is a transparent text object whose only visible part is the border: that’s the fake rectangle in which all the other objects seem to be placed. We didn’t use a real rectangle because a real one can’t be made transparent.
Secondly, the grey-on-red “Error!” text object, rotated 270 degrees, is used to show to the user that this form is an error message box. It is also used as a rectangle since its border is raised.
Thirdly, the “Press Any Key to Continue…” text object serves as a reminder and as a rectangle. Closing something when a keyboard key is pushed was a popular feature under DOS. It is now obsolete. Windows applications would rather ask the user to click a pushbutton to close a form. We came back to this ancestral tradition because it is handy.
Fourthly, a black-on-white text object displays the error message. We’ve used a text object (it could have been an editor) because we wanted to be able to format individual words — as in the message shown here, where “before” is set in bold letters.
Finally, there is an entryfield
hidden under the error message text object. It’s only purpose is to catch
any keyboard key that the user might have pressed. This works for almost
all keys other than the arrow keys, the function keys and a few others.
Also, an AlertBox will close when it is clicked or when it loses focus.
// In Search_dBulletin.cc class AlertBox of FORM
this.E_HIDDEN = new ENTRYFIELD(this)
this.MESSAGE1 = new TEXT(this)
(...) endclass // In the calling form Function P_Before_onClick
|
|
Like the ActiveX control, this
application has its own history log. When a first search is requested,
the criteria of that search are stored in the first column of a two-column
array. The results page is created and its name is stored in the second
column of that same array. When another search is requested, the application
will first look in this array to check if an identical search was already
requested. If so, the results page is not created again, but simply loaded
from the hard disk. If it is a new request, the search is performed and
the results page is created on-the-fly.
Function Start_the_Search local criterion, cFileName criterion = form.Container1.entryfield1.value; + iif(form.Container1.R_All.value = true, "T", "F") if Form.aResultPages.size = 0 Form.aResultPages.add(criterion) Form.aResultPages.grow(2) else local Was_asked_before // ceiling() because, being in the first column, its element number is an uneven number Was_asked_before = ceiling(Form.aResultPages.scan(criterion)/2) if Was_asked_before > 0 Form.activex1.nativeobject.navigate2(Form.aResultPages[Was_asked_before,2]) return else Form.aResultPages.grow(1) Form.aResultPages[Form.aResultPages.size/2,1] = criterion endif endif (...) cFile_Name = "Search_" + funique( '???') + ".htm" Form.aResultPages[Form.aResultPages.size/2,2] = cFile_Name (...) return |
|
When the application closes, it looks in its array for the names of the result pages it has created on the hard drive and erases them.
Loading in a New Window
The first time the user asks for the clicked URL to be displayed in a new Window, the application looks in its results page array and creates a new version of all the result pages created so far. In these new versions, a piece of JavaScript is inserted in their HTML code. The old result pages are renamed with the “.htmL” extension while the new results pages are stored under the “.htm” extension, replacing the previous ones. If the value of “Open links in new window” checkbox is changed anew, the application looks in the results array and builds only the versions needed to complete the two sets of files.
The Speed of dBASE
For the first time, our readers have at their disposal a dBL application that does exactly the same thing as an application created with another programming language. The search engine used in the off-line version of our magazine is a prestigious Java shareware applet that has won international awards. Can the speed of dBASE compete with the speed of that applet?
Soon in our experiments, we discovered that when the time is calculated by the dBL application (between the very beginning and near the end of the Start_the_Search() function), the time calculated by dBASE is not accurate since it is faster than the real time. Why? Because Windows being a multitask environment, the function ends a lot sooner than the navigate2() function of the ActiveX. So the time has to be calculated with a watch at hand (a gross measuring instrument). My desktop computer (a 1,400 MHz Athlon), Michael Nuwer's and Ronnie MacGregor’s test computers (both Pentium-II 400 MHz) were too fast to give anything else but qualitative results, although the application speed difference was still very apparent.
So I’ve used a Pentium-I 90 MHz
Notebook computer under Win95: it was slow enough to show quantitative
differences between the Java applet and our dBL application. The experiments
were done on the articles published in the first eleven issues of the magazine
(those distributed on the dB2K 0.1 CD-ROM). In the table below, the time
displayed is the average for three searches. When one of them was deviant
by more than three seconds from the two other ones, two additional searches
were done: if those searches confirmed the deviation, the spurious result
was not taken into consideration.
Tasks | Java Applet | dBL application |
First loading | 37.7 seconds | 36.3 seconds | ||
Subsequent loading | 33.3 seconds | 20.0 seconds | ||
First search (on "Listbox") — 7 to find | 10 items found | 50.3 seconds | 7 items found | 27.7 seconds |
Search on "form" — 69 to find | 30 items found | 12.0 seconds | 69 items found | 18.7 seconds |
Search on "grid" — 14 to find | 27 items found | 46.3 seconds | 14 items found | 13.3 seconds |
Search on "combobox" — 12 to find | 21 items found | 46.3 seconds | 12 items found | 12.3 seconds |
Search on "notebook" — 4 to find | 4 items found | 46.0 seconds | 4 items found | 12.0 seconds |
Search on "Web" — 29 to find | 30 items found | 34.7 seconds | 29 items found | 13.7 seconds |
Search on "dBulletin" — 10 to find | 17 items found | 45.0 seconds | 10 items found | 13.0 seconds |
Search on "grid,calculated field" (all words) — 7 to find | 12 items found | 46.0 seconds | 7 items found | 16.0 seconds |
On a freshly booted machine, the Java applet took about the same time to load as the dBL application: on subsequent loadings, the dBL application was clearly faster. Beside the search on “form”, the dBL application was between two and four times faster than the Java applet. If the Java applet seems to perform better on “form”, that’s simply because it is limited to 30 items found. It just stops searching after that.
The Java applet is supposed to search only the files listed in bu00lis.htm (it’s a configuration file in which article file names are listed). Despite this, however, it reports web pages not listed in that HTML file. Moreover, it reports duplicate values. That bug explains why more articles seem to have been found by the Java applet. Since our application was especially designed for the dBulletin, the index pages and the HTML files needed for the interface of our magazine are excluded from its searches. This exclusion doesn’t explain the superiority of our application since it is still substantially faster even when these files are included in its searches.
In another series of tests we did, we discovered that a search done by the Java applet on “calculated field” initially seemed more complete since it also listed “The GridScrollbar Class” among the articles found. However, when we used a text editor to search “calculated field” in that HTML file, the string was not found: only “calculated” and “field”.
Conclusion
Creating this application has been an occasion to implement many features of dBASE: textured background, transparent objects, rotated text, toggled items on toolbars, icons beside menu items, objects created on-the-fly, API calls, ActiveX support, and parsing strings and HTML files. It also has been an occasion to compare two “real-world” applications made with two different programming languages.
When we think about a fast programming language, Java is not the first one that comes to mind. Our experience has shown that dBASE can be many times faster than one of the most popular programming languages these days. How fast is dBASE compared to C#, C++ or Delphi? We don't know. But it is clear that dBASE can stand up to comparision with mainstream development platforms. On my machine (an Athlon 1400 MHz computer equipped with 512 Mb 266 MHz RAM), the dBL application takes less than 2 seconds to search a string among all the articles published so far. So even if a version done in C++ proved to be slightly faster, the time saved would be minimal, at least on a machine like mine.
Since overall, our application has proven to be faster and more reliable, we recommend that our off-line readers put, in the same folder, all the dBulletin articles and all the files contained in this zipped file. If some of you don’t have the dBASE Plus runtime, it is available from this URL. Lastly, it is recommended that you create a shortcut on your desktop leading to Seach_dBulletin.exe.
Michael Nuwer, Romain Strieff and I hope that the Search_dBulletin application will improve the experience of reading our magazine.