A simple TDataPilot "toolbar" navigator instance.

The data pilot component is a toolbar-like navigator used to navigate a list of items (using a TPilot) or a dataset (using a TDataPilot). Further descendants introduce more data aware functionality.

In addition to standard (but optional) data aware buttons, you may add and define as many user buttons as you wish using the design time component editor. Buttons may be arranged in any order, variably spaced, and their visibility differentially controlled in browse or edit states (eg some buttons may only be visible while browsing, others only while editing, others visible at all times).

Go to the Other VCL Component Downloads page to get a copy of the component source code.

The Pilot component hierarchy:

Base Class       Published Class Delphi Unit
|---> TCustomPilot     TPilot BRPilot.pas
|---> TCustomDataPilot     TDataPilot BRPilot.pas
  |---> TCustomDualDataPilot   TDualDataPilot BRPilot.pas
  |---> TCustomdxDBGridPilot   TbxDBGridPilot BRubxPilot.pas
    |---> TCustomdxDualDBGridPilot TbxDualDBGridPilot BRubxPilot.pas


The Pilot Component Editor

Much of the behaviour of pilot navigators can inferred from the component editor, which presents most of the pilot properties and looks like:

TDataPilot Component Editor
Component Editor for the TDataPilot

Most items in the editor represent underlying pilot or pilot button properties. Working through the editor fields as displayed:

Button List: Appears on the left. Standard dataset navigation buttons (those that are functionally linked to a dataset) are created by default with each new pilot instance, and are prefixed with "Data:". As many User buttons as you require may be added to this list, each having a unique user button ID indicated in its' name (prefix "User" followed by the ID). You can change the ID to suit. Data buttons always have ID = 0.

Button Order: Buttons may be re-ordered in any sequence using <Move Up> and <Move Down> buttons, or drag-n-drop. The default button sequence with Data buttons first, followed by User buttons ordered numerically by ID can be restored by pressing <Default Order>.

Button Face, Size, Hints & Margins: A caption may be added to each button, default = none. The relative size or width of a button is expressed in "button half-units", from 1 to 9. Defaults are 2 units for Data buttons and 6 units for User buttons. Given these relative sizes, buttons resize with respect to the total width of the pilot. A button hint can be set. The hint defaulting buttons <Data Hints> (for all Data Buttons) or <Hint> (for the selected Data Button) will reset the Data Button hints. The space between buttons can be affected using <Button Left Margin> and <Button Right Margin> in pixels. The margins are fixed as specified - they do not adjust relative to the overall pilot width.

Button Margins & ID: <Glyph/Text Margin> sets the button face margins relative to the glyph layout (see glyph details below). Default = -1 centres the glyph/text. The unique <User Button ID> can be changed here.

Button Spacing: <Spacing> introduces the same spacing value between all buttons, in addition to any left/right spacing defined for a given button. The default button state in edit mode is having only the <Data: Cancel> and <Data: Post> buttons visible. Rather than have these two buttons utilise the entire pilot bar width, you can better balance the appearance of the buttons by introducing leading (left-most) or trailing (right-most) space measured in button half-widths. The defaults are leading = x6HalfUnit and trailing = x0HalfUnit. This right justifies the visible buttons.

Button Enablement: Buttons are enabled manually <By User> (in code) or <By Data> (the dataset). When controlled manually, you set the initial button state as either enabled or disabled using the <Enabled> check box. There procedures to alter the enablement of single or multiple buttons at run time. When controlled by the dataset, Data buttons will be enabled as appropriate - eg the delete button is disabled when the dataset is empty or read only. You can modify this basic behaviour with the options <When No Data> (if true, the button will remain enabled even if the dataset is empty) or <When Read Only> (if true, the button will remain enabled even if the dataset is read only).

Button Visibility: A button may or may not be "used" in the current pilot configuration (visible or not visible). This allows button visibility to be turned on or off (eg because The pilot may be in browse state or (dataset) edit state. You may nominate whether a given button is visible in browse state with <When Browsing>, and/or visible in edit state with <When Editing>.

Button Glyphs: Set the number of glyphs in the image, and position it using the <Layout> option. You can <Load> an image from disc, select a <System> image, or <Remove> an image. The "system" images can be replaced or extended by modifying the component resource file BRPilotButtons.res.

Adding Buttons: Click <+ User> to add a new user button (as many as you need), or <+ Data> for a Data button (from a fixed list - only unused Data buttons will be listed). <+ Default User> will add a user button selected from a list of predefined buttons.

Pilot Edit State: Since buttons may or may not be visible in edit state, you can switch to edit state (by checking <Show in Edit State>) to see how the pilot will look while editing. For example, with a LeadingEditSpace of x6HalfUnit positioning the Cancel and Post buttons to the right end of the pilot (all other buttons in this case are not visible in edit state), it may look something like:

TDataPilot in Edit State
Example: A TDataPilot component in Edit State

NOTE: Buttons can be identified by a button type:

TBaseButtonType = (
btSearch, btPrint,
btRefresh, btAppend, btInsert, btDelete, btEdit, btCancel, btPost,
btFirst, btPrior, btNext, btLast,

All user buttons are btUser and rely on a unique ButtonID to identify them.

Additional button types are used to set a buttons image to a "predefined system button". They do not represent an actual Pilot button as such.

btPlus, btMinus, btReport, btFilter, btFilterRemove,
btNew, btTrash, btHelp, btExit);

Some Other Pilot Properties

property AutoHideScrollButtons (T/F)

By setting AutoHideScrollButtons = True, the scroll buttons will be hidden when there are insufficient records to make logical use of them. If the dataset is empty or has only one record, the Prior and Next scroll buttons are hidden because there is no other record to scroll to. Similarly, if there is not at least three records, the First and Last scroll buttons are hidden because there is no record to scroll to that is not logically covered by the Prior and Next scroll buttons.

property DataButtonsDisabled (T/F)

Collectively disables ALL buttons defined as "enabled by data".

property IgnoreStateChange (T/F)

By setting IgnoreStateChange = False, you can prevent the pilot from responding to state changes (browse/edit/insert state changes). This is useful when executing coded updates to a dataset - it prevents the pilot "flashing" in response.

property PilotButtons

PilotButtons represents the button collection (of TPilotButtons) in the Object Inspector and gives you access to the pilot component editor.

NOTE: TPilotButton is a descendant of TSpeedButton.

property PilotState

Indicates in which state the pilot is in (psBrowse or psEdit), or, allows you to trip the pilot in to or out of edit state by setting the value.

Pilot Events

OnBeforeClick(Sender: TObject; PilotButton: TButtonType; UserButtonID: Integer; var Accept: Boolean);

Fires before any button click action is executed. Allows you to cancel the click (by setting Accept := False) and optionally take some other action instead. PilotButton identifies the type of button clicked. Type btUser indicates the button is a user defined button having ID = UserButtonID. A user button action can be implemented in this handler, usually setting Accept := False to prevent any further processing.

Note that only some buttons perform an "internal action". eg btInsert performs a DataSet.Insert (so you can block the action OnBeforeClick), but btPrint or any user button (btUser) does not automatically perform any action at all - rather, you must respond to such a click either in OnBeforeClick or in OnAfterClick.

procedure TForm1.DataPilot1BeforeClick(Sender: TObject;

PilotButton: TButtonType; UserButtonID: Integer; var Accept: Boolean);


Accept := False;
case PilotButton of

btPrint: GenerateMyReport;
btSearch: PerformMySearchFunction;
btInsert: DoMyOwnInsert;  //internal DataSet.Insert is blocked because button click is cancelled

//respond to user buttons
case UserButtonID of

1: ExecuteMyFunctionA;
2: ExecuteMyFunctionB;
3: CloseTheForm;


//accept any other user button click (handled in OnAfterClick event instead)
Accept := True;



//accept any other button click (internal action performed, if any)
Accept := True;




OnAfterClick(Sender: TObject; PilotButton: TButtonType; UserButtonID: Integer);

Fires after a button click action has been executed. PilotButton identifies the type of button clicked. Type btUser indicates the button is a user defined button having ID = UserButtonID. A user button action can be implemented in this handler.

procedure TForm1.DataPilot1AfterClick(Sender: TObject;

PilotButton: TButtonType; UserButtonID: Integer);


case PilotButton of

btPrint: GenerateMyReport;
btSearch: PerformMySearchFunction;

//respond to user buttons
case UserButtonID of

1: ExecuteMyFunctionA;
2: ExecuteMyFunctionB;
3: CloseTheForm;





OnEditingChange(Editing: Boolean);

Fires when the dataset toggles in to or out of edit mode. "Editing" indicates whether the current is editing, else browsing.

OnScroll(DataSet: TDataSet);

Fires whenever the dataset (or data source) scrolls from one record to another.

OnConfirmXXX(DataSet: TDataSet; var Accept: Boolean);

The confirmation events allow you to prompt and/or block the corresponding action (by setting Accept := False)

The events are OnConfirmCancel, OnConfirmDelete, OnConfirmEdit, OnConfirmInsert, OnConfirmPost, OnConfirmRefresh.

OnGetScrollDataCount (var ADataCount: Integer);

Queries the data item count (record count) for scroll purposes. In some cases the count must be provided. For example, if you are navigating items in a list, the list item count would be returned here.


TPilot is a "non-data aware" navigator in the sense that it is not directly linked to, or responsive to, a dataset as such. It is used to navigate a "generic list", such as a ListBox or TreeView or StringGrid or text file - basically, anything with an ordered list of items. (In theory, this could be an actual dataset - but TDataPilot is better for that!) In order for TPilot to achieve this, you must code a series of event handlers with appropriate responses to given button clicks on the navigator. The events are fairly self explanatory.

Scrolling is implemented through scroll events: OnDoScrollFirst, OnDoScrollPrior, OnDoScrollNext and OnDoScrollLast. For example, these events would move the focused (selected) item in a ListBox by manipulating the lists ItemIndex property.

"OnIsDataXXX" events allow the pilot to query list metrics to support scrolling and data events: OnIsDataActive, OnIsDataEditing, OnIsDataEmpty, OnIsDataFirst, OnIsDataLast, OnIsDataReadOnly, OnIsDataSelected.

"Data" events are: OnDoCancel, OnDoDelete, OnDataEdit, OnDoInsert, OnDoPost, OnDoRefresh.

property NewRecordModeMode allows to specify whether a new record is inserted (nrmInsert, default) or appended (nrmAppend).

All that said, TPilot behaves just like TDataPilot in everything else, and is edited by the same component editor. See the TDataPilot description for more details.


This pilot is akin to the standard TDBNavigator. It navigates a dataset. There are a variety of methods available to manipulate the appearance of the pilot bar.

> Adding or removing buttons in code:

function AddDataButton(AButtonType: TDataButtonType): TPilotButton;
function AddUserButton: TPilotButton;
procedure RemoveDataButton(ADataButtonType: TDataButtonType);
procedure RemovePilotButton(AUserButton: TPilotButton);
procedure RemoveUserButton(AUserButtonID: Integer);

NB RemoveDataButton and RemoveUserButton do nothing if the specified button does not exist.

> Changing the usage (visibility) or enable state of buttons:

procedure ChangeDataButtonUsage(OnDataButtonSet, OffDataButtonSet: TDataButtonSet);
procedure ChangeDataButtonEnablement(OnDataButtonSet, OffDataButtonSet: TDataButtonSet);
procedure ChangeUserButtonUsage(OnButtonIDArray, OffButtonIDArray: array of Integer);
procedure ChangeUserButtonEnablement(OnButtonIDArray, OffButtonIDArray: array of Integer);

where the "on set" and/or the "off set" are the buttons to be altered. Data buttons are controlled using TDataButtonSet sets, user buttons (being identified by button IDs) are controlled by integer arrays. In addition, for button usage, you can explicitly set the full set of used buttons (turning all others off) using the procedures:

procedure SetDataButtonsUsed(ADataButtonSet: TDataButtonSet);
procedure SetUserButtonsUsed(AButtonIDArray: array of Integer);

procedure CheckButtonEnablement(APilotButton: TPilotButton);

Forces the enablement of APilotButton to be reassessed (where some change does not notify the Pilot).

> Referencing specific buttons (TPilotButtons):

function DataButton(AButtonType: TDataButtonType): TPilotButton;
function UserButton(AButtonID: Integer): TPilotButton;

NB The DataButton and UserButton functions raise an exception if the specified button does not exist. To reference a button safely, use the FindXXXButton functions:

function FindDataButton(AButtonType: TDataButtonType; APilotButton: TPilotButton): Boolean;
function FindUserButton(AButtonID: Integer; APilotButton: TPilotButton): Boolean;

NB The FindDataButton and FindUserButton functions return False if the specified button does not exist.

function PilotButton(APilotButtonIndex: Integer): TPilotButton;

The PilotButton function returns a TPilotButton by index in the buttons collection.

> Positioning a popup relative to a specific PilotButton:

function PilotButtonPopupPoint(AButtonType: TButtonType; AUserButtonID: Integer = 0; PopPos: TPopPos = ppBottomRight): TPoint;

Use the point returned by this function to, for example, position a popup menu relative to a given button.
The PopPos positions are:

TPopPos = (ppTopLeft, ppTopCentre, ppTopRight, ppMiddleLeft, ppMiddleCentre, ppMiddleRight, ppBottomLeft, ppBottomCentre, ppBottomRight);

This example causes a TPopupMenu to pop up with it's top left corner positioned at the bottom centre of Pilot user button ID = 1

with DataPilot1.PilotButtonPopupPoint(btUser, 1, ppBottomCentre) do

PopupMenu1.Popup(X, Y);

> Simulating a PilotButton click event:

procedure PilotButtonClick(AButtonType: TButtonType; AUserButtonID: Integer = 0);


The dual pilot can be very useful in certain circumstances. Basically, it is linked to TWO datasets (hence "dual"). The pilot scrolls one dataset (the master dataset), and responds to data events for the other dataset (the detail dataset).

For example, consider a list of customers on one form for which you allow the user to open a full customer record in another form. The customer form queries "details" for a single customer based on the "master" list record - a master/detail association based on, say, a CustomerID field. Rather than make the user close the customer form, return to the list and then select the next customer, it would be convenient if they could simply scroll to the next customer from the customer form instead. A dual pilot manages this situation by allowing you to set the usual DataSource property with the dataset whose "data functions" (edit/post/delete etc) you wish to control, and a DataSourceMaster dataset property indicating the dataset you wish to scroll (first, prior, next, last).

Each new customer can be queried in the pilots OnScroll event.

TbxDBGridPilot & TbxDualDBGridPilot

These Pilot components are extensions specifically designed to work with Developer Express Quantum Grid. They link to grid views and their underlying datasets, but use grid functionality to navigate the dataset and implement data functions (edit/post/delete etc) for consistency with grid behaviour.

See the <Developer Express - under construction> section for more information.