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

 
   

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:

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:

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