The dBulletin Desktop Search Engine
by Jean-Pierre Martel, editor of the dBulletin

| Instructions The Display Engine Web Browser ActiveX Properties Web Browser ActiveX Methods Web Browser ActiveX Events The Search Engine The User Interface Shortcuts Keeping focus on the entryfield The Toolbar The Menu The Custom Error Messages The History Log Loading in a New Window The speed of dBASE Conclusion

OFF-LINE READERS of our magazine are currently using a local search page which utilizes a Java shareware applet called “Site Search Applet”. That Java application has won a Five Star rating from ZDNet. A long time ago, we tested it as our on-line search engine. It was so slow that we had to develop our own, written in dBL. That’s why “Site Search Applet” is restricted to local use in our search page.

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 Display Engine

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.

References : http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/webbrowser/reflist_vb.asp

| 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)` `DownloadComplete = class::DOWNLOADCOMPLETE` `endwith` `Function DownloadComplete` `try` `local c, n` `c = this.document.url // file://C:\dBulletin\dBulletin%20-%20No%2016\bu16srch.htm` `// we take out "file://"` `c = substr(c,8) // C:\dBulletin\dBulletin%20-%20No%2016\bu16srch.htm` `// we replace each "%20" with a space` `do while "%20" $ c` `c = c.stuff(at("%20",c)-1,3," ")` `enddo` `// dBulletin Search Engine => C:\dBulletin\dBulletin - No 16\bu16srch.htm` `Form.text = " dBulletin Search Engine => " + c` `* inspect(form.ActiveX1.nativeObject.document)` `catch(exception e)` `endtry` `return`
| ``

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:

- `AlertBox_on`: a parameter that is `true` when the user wants Search_Engine.cc to use custom AlertBoxes instead of regular message boxes.
- `LocalSearch`: a string (`"true"`) passed as a parameter for desktop applications.
- `MonoSearch`: a string (`"true"`) also passed as a parameter when the articles requested should have any of the words entered in the string to search
- `StringToSearch`: the string to search (actually the text entered by the user in the form entryfield). This string can be made of many words or expressions separated by commas.

In our 15 issues, there are 124 articles. The total size of these articles is 3.36 Mb. That means that our Search engine can search through several megabytes of data in just a few seconds.

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

Keeping focus on the entryfield

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:

- Start the search
- Navigate to dBulletin Home Page
- Navigate to dBulletin Index Page
- Navigate to previous HTML page in the ActiveX history list
- Navigate to next HTML page in the ActiveX history list
- Refresh document
- Display clicked links in a new window/in the same window
- Find articles containing all the words/any of the words in the entryfield
- Quit the application
- Help

It should be noted that the images on the toolbar are not visual reminders. For example, when the application is set to open a new window displaying any clicked link, the toolbar will not show the “two windows” icon as in the image above. The “two windows” icon means that if it’s clicked, that’s the setting that will come into effect. If you prefer the reverse, you have the source code — make all the changes that please you.

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`
| ``

The Menu

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` `with (this)` `onGotFocus = {; this.E_Hidden.setFocus() }` `onLostFocus = {; form.close() }` `onLeftMouseDown = {; form.close() }` `(...)` `topMost = true` `autoCenter = true` `mdi = false` `sysMenu = false` `maximize = false` `minimize = false` `sizeable = false` `endwith` `this.E_HIDDEN = new ENTRYFIELD(this)` `with (this.E_HIDDEN)` `key = {; form.close() }` `borderStyle = 10 // Etched Out` `(...)` `endwith` `this.MESSAGE1 = new TEXT(this)` `with (this.MESSAGE1)` `onLeftMouseDown = {; form.close() }` `(...)` `colorNormal = "BtnText/white"` `borderStyle = 10 // Etched Out` `endwith` `(...)` `endclass` `// In the calling form` `Function P_Before_onClick` `try` `form.activex1.nativeobject.goback()` `catch (exception e)` `local AB` `AB = new AlertBox()` `AB.text = " Navigation impossible..."` `AB.Message1.text = " Can't navigate <b>before</b>."` `AB.open()` `endtry` `return`
| ``

The History Log

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.

The author would like to thank Michael Nuwer and Romain Strieff for their invaluable help in creating this search engine, and Rich (from AutoTraker Inc.) for the solution he provided to the bug we had with this ActiveX control. Finally, a special thankyou goes to Ronnie MacGregor for proof-reading this article, for beta-testing this application and his suggestions which led to important improvements in this application. Thanks also to Barbara Coultry and David L. Stone for the numerous improvements they suggested to this text.
the author