DBFinders ========= Freeware components and source code for 32-bit Delphi by Deborah Pate Version 1.7, November 1998 Purpose: -------- Controls to make searching for database records easy. Simply connect one of these controls to a datasource and specify the datafield to search on. Then typing in the control (for DBFinderEdits) or selecting an item (for DBFinderCombos) will take you to the first record that matches its contents. Features: --------- · Search on one or more fields, indexed or not indexed · Specify filtered or unfiltered searches · Set the Enter key to act like the Tab key · Set events to happen when a record is, or is not, found DBFinderCombo only · Search on one field, display another as the result DBFinderEdit only · Search incrementally or on leaving the control · Prevent the user from leaving the control until a matching record has been found · The control automatically uses the field definition to decide which key presses are valid Limitations: ------------ DBFinders use the Locate method of a dataset, and so have the following limitations: · 32-bit Delphi only; · searches on non-calculated fields only (though see DBFinderCombo's DisplayField property); · incremental searching (with loPartialKey) doesn't work with TFloatFields; · searches on multiple fields only if they are all from the same dataset. (Of course, you can have several DBFinders on a form pointing to different datasets: but if you put them on the same parent and give them the same GroupIndex (greater than 0), an exception will be raised when the search is performed.) Note: These are my first components, so I'm sure they could be done much better - perhaps they have been? I wrote DBFinderEdit because I had a database with a unique numerical field, and I wanted to be able to move quickly to a record just by typing its number in. But all the components I looked at were really for data entry, or they were dialog-based (too slow), or you had to fill a list box with however many thousand entries. Am I missing something? Do the rest of you know an easier way to do this? If so, please let me know! Installation for Delphi 2 ========================== Just copy DBFinders.pas, DBFinderEdit.pas, DBFinderCombo.pas and DBFinders.dcr to the directory where you keep your components. Then, in Delphi, click on the Component | Install menu. Click on the Add button, choose DBFinders.pas, and click on OK. The components will be installed in the DataControls page. Installing Context-sensitive help --------------------------------- Copy DBFinder.hlp and DBFinder.kwf to your Delphi\Bin or Delphi\Help folder. Run Helpinst.exe (in the Delphi\Help\Tools folder). Click the Open button to open the Delphi.Hdx file. Click the Add button to add the DBFinder.kwf keyword file. Click the Save button to compile and save the .hdx file. That's it. You may find that you have to tell Delphi where to find the help file the first time you call it up, but after that it will remember. Note: I’ve only just upgraded to Delphi 4 (from Delphi 2) and found out why the previous version didn’t work with it (or with Delphi 3). It was only a small problem, so I decided to rush out this fixed version without taking time to improve the code for Delphi 4 (using resourcestrings etc) or updating the installation instructions. Hope you can manage - I’ll produce better instructions for Delphi 4 when I know what I’m doing myself ... How to use DBFinders ===================== Drop a DBFinder on your form, and set its DataSource and DataField properties to link it to a dataset. For single field searches, that's all that's needed. You may wish to change the EnterAsTab, FilterMode, LocateOptions properties. For DBFinderEdits, you may also wish to change the IncrementalSearch and MustFind properties. ForDBFinderCombos, you may also wish to change the DisplayField property, to make the control display a calculated field. To search on more than one field --------------------------------- Drop as many DBFinders as you require onto the same parent control (e.g. a TPanel or a TGroupBox), and set their DataSource and DataField properties. (They must all be connected to the same Datasource.) Give them all the same GroupIndex value, which must be greater than 0. You may also wish to set the AutoClear property to True. Efficiency note: At run time, changing the text in a DBFinder that has a GroupIndex greater than 0 will cause it to examine all the child controls of its own parent, looking for other DBFinders with the same GroupIndex. So for greater efficiency you should put grouped DBFinders on parent controls that have as few as possible other child components. Methods ======== TDBFinderEdit is a descendant of TCustomEdit. It has the same public methods as Tedit. TDBFinderCombo is a descendant of TCustomCombo. It has all the methods of TComboBox, plus the following: Populate The Populate method clears the DBFinderCombo's items property, then fills it with a sorted list of the values found in the associated field of a dataset. This method may be called to refresh the DBFinderCombo's items if the data has changed, or if the DBFinderCombo has been connected to a different datafield. When it has finished, the Populate method calls the OnPopulate event, if one has been assigned. Properties =========== AutoClear If AutoClear is set to True, a change to a value in the DBFinder will automatically clear the text in any other DBFinders in the same group that are lower down the TabOrder. The default is False, which is appropriate for searches on a single field. DBFinders are in the same group if they are all on the same parent control (TForm, TPanel or TGroupBox) and all have the same GroupIndex, which is greater than 0. (You can change the TabOrder of controls at design time by right-clicking on them.) Example: Say you have three DBFinders on a panel, all with GroupIndex = 1, linked to the Surname, FirstName, and City fields in that TabOrder. Your last (successful) search was for Brown, Mary, of Leicester, and you now want to search for Brown, Henry, of Newcastle. If AutoClear is False, when you change FirstName to Henry the DBFinder will search for Brown, Henry, of Leicester. The search will (probably!) fail, and the OnNotFound event will be called. If AutoClear is True, the DBFinder will search for Brown, Henry. The search will succeed, an OnFound event will be called, and the third DBFinder will display 'Newcastle'. DataSource Set this to link the DBFinder with a datasource. DataField Set this to specify the field you want to search. DisplayField (DBFinderCombo only.) A string property that specifies the name of the field that the DBFinderCombo items should display, where this is different from the Datafield on which the search will be performed. The Datafield must be a unique field, and the DBFinderCombo will not act as part of a group as long as its DisplayField property is not empty (set to ' '). What is this property for? DBFinderCombo uses the Locate method of a dataset to find the desired record, and Locate does not work with calculated fields. This property allows you to search on one, unique field (e.g. CustID), but display a more informative, calculated field in the DBFinderCombo, such as Name ( = Firstname + Surname). As this obliges the DBFinderCombo to keep track of two lists, this property should not be used for non-calculated fields, which can simply be selected as the DataField. When using this facility, remember that the DBFinderCombo's Value property will refer to the Datafield that is being searched, not the field it displays. The value of the DisplayField can be ascertained at run-time through the DisplayValue property. DisplayValue (DBFinderCombo only.) A run-time property allowing direct access to the field referred to by the DisplayField property. If you are using a DisplayField, remember that the Value property refers to the Datafield (the searched field). EnterAsTab Set this to True if you want the user to be able to leave the control by pressing the Enter or Return key. The default is False. FilterMode TFilterMode = (fmApplyFilter, fmIgnoreFilter, fmRemoveFilter); Set this to fmApplyFilter if you want only the records in an existing (but possibly inactive) dataset filter to be searched. To search the entire dataset, choose fmRemoveFilter. To accept the current Filtered status of the table being searched, choose fmIgnoreFilter (the default). For DBFinderCombos, the filter is applied by the Populate method, so only data from records that match the filter will appear in the DBFinderCombo's items. For DBFinderEdits, the filter is applied (or removed) on searching. Note that fmApplyFilter and fmRemoveFilter change the dataset's Filtered property permanently, unless you manually change it back. This is to prevent the situation where the DBFinder can't display the item it's found because a filter that excludes it has been reimposed. Found A run-time and read-only boolean property. After the DBFinder has performed a search, the Found property reports whether a matching record was found. Example: if MyFinder.Found then AVariable := MyFinder.Value; GroupIndex The default is 0, which means that the DBFinder is not part of a group and will search only on its own field. To perform a search on multiple fields (e.g. Surname='Smith' and FirstName>='J'): 1. make sure that the associated DBFinders have the same parent control; 2. give them all the same GroupIndex property, which must be greater than zero. Changing this property at run time is a simple way to link and unlink search fields. Efficiency note: At run time, changing the text in a DBFinder that has a GroupIndex greater than 0 will cause it to examine all the child controls of its own parent, looking for other DBFinders with the same GroupIndex. So for greater efficiency you should put grouped DBFinders on parent controls that have as few as possible other child components. DBFinderCombos only Note that DBFinderCombos which have a non-empty DisplayField property cannot be part of a group and will set their GroupIndex properties to 0. IncrementalSearch property (DBFinderEdit only.) If this is set to True, the control will search after each valid key press, rather than waiting until the user leaves the control. The default is True. If you find this isn't working as you expect, check that you haven't accidentally set LocateOptions|loPartialKey to False. Note also that loPartialKey does not work with TFloatFields. LocateOptions property The set of TLocateOptions to be used in the search. Set loCaseInsensitive to False for a case-sensitive search; set loPartialKey to False if you don't want to find partial matches. (E.g. if you don't want an input of 1 to find 1000.) You can look these up in Delphi's help under TLocateOptions. The default is for both options to be True. MustFind property (DBFinderEdit only.) If this is set to True, the user won't be able to leave the DBFinderEdit until a record is found. The default is False. Value property A run-time and read-only property allowing direct access to the datafield associated with the DBFinder. Example: if MyFinderEdit.Found then AVariable := MyFinderEdit.Value; DBFinderCombo only: If you are using a DisplayField, remember that the Value property refers to the Datafield (the searched field). To access the field that is displayed by the DBFinderCombo, use the DisplayValue property. Events ======= OnFound Triggered whenever a search is successful. OnFoundElsewhere Triggered when the record is changed by another control (such as a TDBNavigator component, for example). OnNotFound Triggered whenever no record can be found to match the input. Be careful when using these with incremental searching ... OnPopulate (DBFinderCombo only.) Triggered when the Populate method of a DBFinderCombo has finished. Conditions =========== The DBFinder components are freeware; you can use them in your code, whether freeware or commercial, and you can give them away - but you can't sell them. If you do give them to anyone, please give them all the source code and help files with them. As far as I know the components are completely safe to use, but I can't be held responsible for any losses or damage incurred as a result of their use. Please let me know about any bugs, or ways to improve the code. You can email me (dpate@hotmail.com) or write to me c/o 36 Southleigh Road, Bristol, UK, BS8 2BH. (If it's important, write, because I don't go to the internet cafe very often. ;) A request Does anyone knows how to get the FilterOptions property of a dataset to work sensibly? If so, please tell me how! As far as I can see, foCaseInsensitive only works with exact matches (using the '=' operator), and foNoPartialCompare doesn't work at all - or rather, it insists on working all the time. I want case insensitive partial key matching where, if I type in 'MIL', I get 'Miles', and 'millipede', but NOT 'aardvark'. Can it be done? Acknowledgements I learned how to make data-aware controls and context-sensitive help from Ray Lischner's Secrets of Delphi 2, Waite Group Press (1996). An excellent book which I warmly recommend. Versions ========= 1.7 November 1998 The DBFinders now work with Delphi 4 - and so with Delphi 3?. Fixed the 'Invalid variant operation' in the DBFinderEdit. Finally (I hope!) got the controls to work properly with DBNavigators, and fire the OnFoundElsewhere property appropriately. 1.6 April 1998 DBFinderCombo now populates itself automatically at start up. The controls now behave slightly differently after an unsuccessful search, and revert automatically to the previously shown field. DBFinderCombos also beep when the search is unsuccessful. This shouldn't happen when a DBFinderCombo is used alone, but when it is part of a group the user may not be able to change its value if other members of the group, higher up the tab order, have left only one possible value for the DBFinderCombo. A better solution would be to repopulate the DBFinderCombo, limiting its entries to the possible values - I may implement this, if there's a demand, but in the meantime a warning beep will have to do! Various code improvements, and DBFinders uses simple functions instead of messing around with class functions. 1.5 October 1997 Added DBFinderCombo, and the TDBFinders class. Tweaked code of DBFinderEdit to make it TDBFinders-aware. Improved help file, and added context-sensitivity. 1.1 August 1997 Made the Found property public. Added the Value property and OnFoundElsewhere events. Fixed the bug that caused an exception when a record was inserted. Fixed minor bugs. 1.00 June 1997 The original release - TDBFinderEdit only.