There are two different kinds of JVCL controls. One that support the TDataSource
component and start with the TJvDB prefix. And the other that do not show their
data binding in the name. The TJvDB controls can be connected to a DataSet by
simply setting their DataSource property to a TDataSource. But the more interesting
controls are the so called JvDataSource-aware controls.
These components have a DataConnector property that handles the data binding for
the control.
The DataConnector does not depend on any database code. This allows the component to be data aware when you have a Delphi/BCB/BDS SKU that has the DB.pas unit and to use the component without a DataSet when you have a Personal Edition. This is made possibile by the IJvDataSource interface that is used by the DataConnector to communicate with the TJvDataSource component. The TJvDataSource implements the IJvDataSource interface what TDataSource doesn't do. So it is only possible to connect the DataConnector to a TJvDataSource.
The following components have a DataConnector property:
The following predefined DataConnectors are available:
As an example the TEdit should be made JvDataSource aware.
The TEditExDataConnector class connects the FieldDataConnector to the TEditEx class.type
TEditEx =class
; TEditExDataConnector =class
(TJvFieldDataConnector)private
FEdit: TEditEx;protected
procedure
RecordChanged;override
;procedure
UpdateData;override
;property
Edit: TEditExread
FEdit;public
constructor
Create(AEdit: TEditEx);end
;
constructor
TEditExDataConnector.Create(AEdit: TEditEx);begin
inherited
Create; FEdit := AEdit;end
;procedure
TEditExDataConnector.RecordChanged;begin
if
Field.IsValidthen
begin
FEdit.ReadOnly :=not
Field.CanModify; FEdit.Text := Field.AsString;end
else
begin
FEdit.Text :=''
; FEdit.ReadOnly := True;end
;end
;procedure
TEditExDataConnector.UpdateData;begin
Field.AsString := FEdit.Text; FEdit.Text := Field.AsString;// update to stored value
end
;
TEditEx =class
(TEdit)private
FDataConnector: TEditExDataConnector;procedure
SetDataConnector(const
Value: TEditExDataConnector);protected
procedure
KeyPress(var
Key: Char);override
;procedure
DoExit;override
;procedure
Change;override
;function
CreateDataConnector: TEditExDataConnector;virtual
;public
constructor
Create(AOwner: TComponent);override
;destructor
Destroy;override
;published
property
DataConnector: TEditExDataConnectorread
FDataConnectorwrite
SetDataConnector;end
;
The next step is to add the new DataConnector class to the TEdit control. For this we need
a DataConnector property that has a setter method. Without the setter Delphi would not store the
properties of the DataConnector to the DFM/NFM/XFM. The virtual CreateDataConnector method allow
decendants to implement an own DataConnector class.
In the KeyPress method we test if the user has pressed the ESCAPE key and reset the control's data
by calling the DataConnector.Reset method.
In the Change method we inform the data source that we want to edit it's data.
The
The DataConnector ignores all actions if no JvDataSource is connected to it.
constructor
TEditEx.Create(AOwner: TComponent);begin
inherited
Create(AOwner); FDataConnector := CreateDataConnector;end
;destructor
TEditEx.Destroy;begin
FDataConnector.Free;inherited
Destroy;end
;function
TEditEx.CreateDataConnector: TEditExDataConnector;begin
Result := TEditExDataConnector.Create(Self);end
;procedure
TEditEx.SetDataConnector(const
Value: TEditExDataConnector);begin
if
Value <> FDataConnectorthen
FDataConnector.Assign(Value);end
;procedure
TEditEx.Change;begin
DataConnector.Modify;inherited
Change;end
;procedure
TEditEx.DoExit;begin
DataConnector.UpdateRecord;inherited
DoExit;end
;procedure
TEditEx.KeyPress(var
Key: Char);begin
inherited
KeyPress(Key);if
(Key =#27
)and
DataConnector.Activethen
begin
DataConnector.Reset; Key :=#0
;end
;end
;
In this case we use a TJvFieldDataConnector because the ListBox should display the field values. The ListBox is used as a navigator and as such it does not need to update any data to the data source.
type
TJvCustomListBoxDataConnector =class
(TJvFieldDataConnector)private
FListBox: TCustomListBox; FMap: TList; FRecNoMap: TBucketList;protected
procedure
Populate;virtual
;procedure
ActiveChanged;override
;procedure
RecordChanged;override
;property
ListBox: TCustomListBoxread
FListBox;public
constructor
Create(AListBox: TCustomListBox);destructor
Destroy;override
;procedure
GotoCurrent;end
;
The Populate method iterates the data source's records and fills the Items property of the ListBox.
The GotoCurrent method is called by the CM_CHANGED message handler of the ListBox which is invoked
when the ItemIndex has changed.
The two maps are used to find the record and the list item.
constructor
TJvCustomListBoxDataConnector.Create(AListBox: TCustomListBox);begin
inherited
Create; FListBox := AListBox; FRecNoMap := TBucketList.Create; FMap := TList.Create;end
;destructor
TJvCustomListBoxDataConnector.Destroy;begin
FMap.Free; FRecNoMap.Free;inherited
Destroy;end
;procedure
TJvCustomListBoxDataConnector.ActiveChanged;begin
Populate;inherited
ActiveChanged;end
;procedure
TJvCustomListBoxDataConnector.GotoCurrent;begin
if
Field.IsValidand
(FListBox.ItemIndex <> -1)then
DataSource.RecNo := Integer(FMap[FListBox.ItemIndex]);end
;
The ActiveChanged method populates the list when the DataSource' state has changed.
By setting DataSource.RecNo the
procedure
TJvCustomListBoxDataConnector.RecordChanged;var
Index: Integer;begin
if
Field.IsValidthen
begin
if
FListBox.Items.Count <> DataSource.RecordCountthen
Populateelse
if
FRecNoMap.Find(TObject(DataSource.RecNo), Pointer(Index))then
begin
FListBox.Items[Index] := Field.AsString; FListBox.ItemIndex := Index;end
;end
;end
;
The RecordChanged event either populates the list again if a new record was inserted or a record was deleted, or it updates the list item with a new value and selects the current record.
procedure
TJvCustomListBoxDataConnector.Populate;var
Index: Integer;begin
FMap.Clear; FRecNoMap.Clear; FListBox.Items.BeginUpdate;try
FListBox.Items.Clear;if
Field.IsValidthen
begin
DataSource.BeginUpdate;try
DataSource.First;while
not
DataSource.Eofdo
begin
Index := FListBox.Items.Add(Field.AsString); FMap.Add(TObject(DataSource.RecNo)); FRecNoMap.Add(TObject(DataSource.RecNo), TObject(Index)); DataSource.Next;end
;finally
DataSource.EndUpdate;end
;if
FRecNoMap.Find(TObject(DataSource.RecNo), Pointer(Index))then
FListBox.ItemIndex := Index;end
;finally
FListBox.Items.EndUpdate;end
;end
;
The Populate method fills the list and builds the two maps. Because it wouldn't be a lot of flicker if when the DataDource's records are iterated, the Populate method uses the DataSource.BeginUpdate and DataSOurce.EndUpdate methods. Those save the current record number and disable the controls.
type
TListBoxEx =class
(TListBox)private
FDataConnector: TJvCustomListBoxDataConnector;procedure
SetDataConnector(const
Value: TJvCustomListBoxDataConnector);protected
function
CreateDataConnector: TJvCustomListBoxDataConnector;virtual
;procedure
CMChanged(var
Message: TCMChanged); message CM_CHANGED;public
constructor
Create(AOwner: TComponent);override
;destructor
Destroy;override
;published
property
DataConnector: TJvCustomListBoxDataConnectorread
FDataConnectorwrite
SetDataConnector;end
;{ TListBoxEx }
constructor
TListBoxEx.Create(AOwner: TComponent);begin
inherited
Crreate(AOwner); FDataConnector := CreateDataConnector;end
;destructor
TListBoxEx.Destroy;begin
FDataConnector.Free;inherited
Destroy;end
;function
TListBoxEx.CreateDataConnector: TJvCustomListBoxDataConnector;begin
Result := TJvCustomListBoxDataConnector.Create(Self);end
;procedure
TListBoxEx.CMChanged(var
Message: TCMChanged);begin
inherited
; DataConnector.GotoCurrent;end
;procedure
TListBoxEx.SetDataConnector(const
Value: TJvCustomListBoxDataConnector);begin
if
Value <> FDataConnectorthen
FDataConnector.Assign(Value);end
;