Provider Mode
- 7 minutes to read
In Provider mode, the ExpressVerticalGrid control work with a custom data source. A specially designed grid component was developed to support this mode – TcxVirtualVerticalGrid. It represents a fully functional vertical grid extended with the capability to bind to custom data sources. This topic uses the Provider Mode Demo application shipped with this product as a base to describe the Provider Mode implementation.
The custom data source can be for example, a flat ASCII text file with the data tabulated (as for this example, named contacts.txt).
To work with the custom data source you should implement the TcxCustomDataSource descendant. In this example the descendant class named TCustomerDataSource is used. The TCustomerDataSource class overrides the following TcxCustomDataSource class methods:
GetRecordCount retrieves the number of records;
GetValue and SetValue – the former returns a value contained in a specified record field and the latter updates this value.
Consider simple Use Cases: the user launches the vertical grid, browses and edits the data in the grid and then closes the application.
Let’s classify these Use Cases into two activities sequences:
The first sequence:
When the user launches the application:
Load data from the flat file and cache it in a buffer;
Create rows in the vertical grid related to the underlying data structure;
Populate rows in the grid with data cached in the buffer.
Now the user can browse and edit data in the grid.
The second sequence:
When the user exits the application:
Update any changed data when the user closes the application;
Clean up after the application.
Let’s define classes that represent the buffer’s structure:
The TCustomer class represents the composite Value Object that corresponds to the record in the flat file and
The TCustomerList class represents the composite Value Object that corresponds to the flat file itself.
unit ProviderModeDemoLibrary;
interface
{$I Demos.inc}
uses
Classes, cxCustomData{$IFDEF DELPHI6}, Variants{$ENDIF};
const
IndexOfID = 0;
IndexOfName = 1;
IndexOfCompany = 2;
type
TCustomer = class
private
FID: Integer;
FName: string;
FCompany: string;
public
constructor Create(AID: Integer);
// define a key that will be an identifier for every record
property ID: Integer read FID write SetID;
// create properties that map to the data sourse fields
property Company: string read FCompany write FCompany;
property Name: string read FName write FName;
end;
TCustomerList = class
private
// define a field to represent the TList object where all data will be stored
FList: TList;
FNextID: Integer;
// define cleaner
procedure ReleaseAllCustomers;
// read a record from the buffer
function GetCustomer(AIndex: Integer): TCustomer;
function GetCount: Integer;
public
constructor Create;
destructor Destroy; override;
// add a record to the end of the buffer collection
function Add(Customer: TCustomer): Integer;
// define a property to access the 2-dimensional array
property Customers[Index: Integer]: TCustomer read GetCustomer;
default;
// define a property to know how many records there are in the buffer
property Count: Integer read GetCount;
// define a counter that will be used to assign an ID for every record
property NextID: Integer read FNextID;
end;
Now define a class that will work with a custom data source:
// derive a class from the TcxCustomDataSource and define members that enable at the least reading from and writing to the underlying data
TCustomerDataSource = class(TcxCustomDataSource)
private
FCustomers: TCustomerList;
FModified: boolean;
protected
// define how many records in a data source
function GetRecordCount: Integer; override;
// read the value from the record
function GetValue(ARecordHandle: TcxDataRecordHandle;
AItemHandle: TcxDataItemHandle): Variant; override;
// write the value to the record
procedure SetValue(ARecordHandle: TcxDataRecordHandle;
AItemHandle: TcxDataItemHandle; const AValue: Variant);
override;
public
constructor Create(ACustomerList: TCustomerList);
// define a flag to check if the user has changed data when the application is about to be closed
property Modified: boolean read FModified;
end;
Provide implementation for the interface defined:
implementation
{ TCustomer }
constructor TCustomer.Create(AID: Integer);
begin
inherited Create;
FID := AID;
end;
{ TCustomerList }
function TCustomerList.Add(Customer: TCustomer): Integer;
begin
Result := FList.Add(Customer);
Inc(FNextID);
end;
constructor TCustomerList.Create;
begin
inherited Create;
FList := TList.Create;
FNextID := 1;
end;
destructor TCustomerList.Destroy;
begin
ReleaseAllCustomers;
FList.Free;
inherited Destroy;
end;
function TCustomerList.GetCount: Integer;
begin
Result := FList.Count;
end;
function TCustomerList.GetCustomer(AIndex: Integer): TCustomer;
begin
Result := TCustomer(FList[AIndex]);
end;
procedure TCustomerList.ReleaseAllCustomers;
var
I : Integer;
begin
for I := 0 to Count -1 do
TCustomer(FList[I]).Free;
FList.Clear;
end;
{ TCustomerDataSource }
constructor TCustomerDataSource.Create(ACustomerList: TCustomerList);
begin
inherited Create;
FCustomers := ACustomerList;
end;
function TCustomerDataSource.GetRecordCount: Integer;
begin
Result := FCustomers.Count;
end;
function TCustomerDataSource.GetValue(ARecordHandle:
TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant;
var
AColumnId: Integer;
ACustomer: TCustomer;
begin
ACustomer := FCustomers[Integer(ARecordHandle)];
AColumnId := GetDefaultItemID(Integer(AItemHandle));
case AColumnId of
IndexOfID:
Result := ACustomer.ID;
IndexOfName:
Result := ACustomer.Name;
IndexOfCompany:
Result := ACustomer.Company;
end;
end;
procedure TCustomerDataSource.SetValue(ARecordHandle:
TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue:
Variant);
var
ACustomer: TCustomer;
AColumnId: Integer;
begin
AColumnId := GetDefaultItemID(Integer(AItemHandle));
ACustomer := FCustomers[Integer(ARecordHandle)];
case AColumnId of
IndexOfID:
if VarIsNull(AValue) then
ACustomer.ID := 0
else
ACustomer.ID := AValue;
IndexOfName:
ACustomer.Name := VarToStr(AValue) ;
IndexOfCompany:
ACustomer.Company := VarToStr(AValue);
end;
if not Modified then
FModified := True;
end;
end.
Now let’s define the main unit where the activities mentioned above will take place:
unit ProviderModeDemoMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, cxStyles, cxGraphics, cxEdit, cxClasses, cxVGrid, cxControls, cxInplaceContainer, cxDataStorage;
type
TProviderModeDemoForm = class(TForm)
VirtualVerticalGrid: TcxVirtualVerticalGrid;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
procedure GenerateRows;
procedure LoadData;
procedure SaveData;
public
{ Public declarations }
end;
var
ProviderModeDemoForm: TProviderModeDemoForm;
implementation
uses ProviderModeDemoLibrary;
{$R *.dfm}
const
TabChar = #9;
var
CustomerList : TCustomerList;
procedure TProviderModeDemoForm.GenerateRows;
begin
with VirtualVerticalGrid do
begin
ClearRows;
with AddChild(nil, TcxEditorRow) as TcxEditorRow do
begin
Properties.Caption := 'ID';
Properties.DataBinding.ValueTypeClass := TcxIntegerValueType;
end;
with AddChild(nil, TcxEditorRow) as TcxEditorRow do
begin
Properties.Caption := 'Name';
Properties.DataBinding.ValueTypeClass := TcxStringValueType;
end;
with AddChild(nil, TcxEditorRow) as TcxEditorRow do
begin
Properties.Caption := 'Company';
Properties.DataBinding.ValueTypeClass := TcxStringValueType;
end;
end;
end;
procedure TProviderModeDemoForm.LoadData;
var
ACustomer: TCustomer;
I: Integer;
s: string;
begin
CustomerList := TCustomerList.Create;
with TStringList.Create do
try
LoadFromFile('contacts.txt'); // make sure the path is correct and the file is in the directory specified
for I := 0 to Count - 1 do
begin
ACustomer := TCustomer.Create(CustomerList.NextID);
s := Strings[I];
ACustomer.Name := Copy(s, 1, Pos(TabChar, s) - 1);
ACustomer.Company := Copy(s, Pos(TabChar, s) + 1, Length(s));
CustomerList.Add(ACustomer);
end;
finally
Free;
end;
end;
procedure TProviderModeDemoForm.SaveData;
var
ACustomer: TCustomer;
I: Integer;
begin
with TStringList.Create do
try
for I := 0 to CustomerList.Count - 1 do
begin
ACustomer := CustomerList.Customers[I];
Add(ACustomer.Name + TabChar + ACustomer.Company);
end;
SaveToFile('contacts.txt'); // make sure the path is correct
finally
Free;
end;
end;
procedure TProviderModeDemoForm.FormCreate(Sender: TObject);
begin
LoadData;
GenerateRows;
// aggregate the buffer with the TCustomerDataSource object
VirtualVerticalGrid.DataController.CustomDataSource := TCustomerDataSource.Create(CustomerList);
VirtualVerticalGrid.LayoutStyle := lsMultiRecordView;
end;
// don't waste memory, destroy all associated objects when closing.
procedure TProviderModeDemoForm.FormDestroy(Sender: TObject);
begin
VirtualVerticalGrid.DataController.CustomDataSource.Free;
CustomerList.Free;
end;
//bring up a message box and ask the user whether to update the data changed
procedure TProviderModeDemoForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
var
I: Integer;
begin
I := -1;
if TCustomerDataSource(cxVirtualVerticalGrid1.DataController.
CustomDataSource).Modified then
I := MessageDlg('Do you want to save changes ?', mtConfirmation, [mbYes, mbNo, mbCancel], 0);
case I of
mrYes: SaveData;
mrCancel: CanClose := False;
end;
end;
end.
Now using the Object Inspector assign the last three methods as event handlers to the ProviderModeDemoForm events: TProviderModeDemoForm.FormCreate to the OnCreate event to allow the Delphi container to call this method when the user launches the application, TProviderModeDemoForm.FormDestroy to the OnDestroy event and TProviderModeDemoForm.FormCloseQuery to the OnCloseQuery event to call these methods when the user exits the application.
To feel this code in action play with our demo shipped with this product. If everything is working correctly the output will look like this:
Multi-Threading Support
To enable support for multi-threaded data processing in your thread-safe TcxCustomDataSource descendant, override its IsMultiThreadingSupported function to return True
.