Skip to main content

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

In order to enable support for multi-threaded data processing in your thread-safe TcxCustomDataSource descendant, override its IsMultiThreadingSupported function to return True.

See Also