Skip to main content

Example: TreeList Provider Mode

  • 5 minutes to read

This example shows how to populate a tree list with the data in provider mode when the smart load feature is disabled (the TreeList control’s OptionsData.SmartLoad property is False).

It uses a file named DEPARTMENTS.TXT as the data store. This file was created by exporting data from the self-referencing DEPARTMENTS table that is shipped with the product. The data in the file is tabulated, and it contains no qualifiers. The first record’s values are field names.

The following image demonstrates the code execution result:

unit TLProviderModeUnit;
interface
uses
  Windows, Messages, SysUtils, StrUtils, Variants, Classes, Controls, Forms, Dialogs, cxCustomData, cxTL, cxControls, cxTLData, cxClasses, cxCalendar, cxTextEdit, cxGraphics, cxInplaceContainer, cxStyles, StdCtrls, Math, cxTLdxBarBuiltInMenu;
type
  TTLProviderModeForm = class(TForm)
    VirtualTreeList: TcxVirtualTreeList;
    procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  end;
    { TcxProviderRecordHandle }
  TcxProviderRecordHandle = class
  private
    FParent: TcxProviderRecordHandle;
    FValues: TStringList;
  protected
    property Parent: TcxProviderRecordHandle read FParent;
  public
    constructor Create(AKey: Integer; AParent: TcxProviderRecordHandle);
    function AddChild(AKey: string): TcxProviderRecordHandle;
    property Values: TStringList read FValues write FValues;
  end;
 { TcxCustomDemoDataSource }
  TcxCustomDemoDataSource = class(TcxTreeListCustomDataSource)
  private
    FModified: boolean;
    FRecordsList: TList;
    FRootHandle: TcxProviderRecordHandle;
  protected
    function GetParentRecordHandle(ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle; override;
    function GetRecordCount: Integer; override;
    function GetRecordHandle(ARecordIndex: Integer): TcxDataRecordHandle; override;
    function GetChildRecordHandle(AParentHandle: TcxDataRecordHandle; AChildIndex: Integer): TcxDataRecordHandle; override;
    function GetRootRecordHandle: TcxDataRecordHandle; override;
    function GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant; override;
    procedure SetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle; const AValue: Variant); override;
    property RootHandle: TcxProviderRecordHandle read FRootHandle;
  public
    constructor Create; virtual;
    destructor Destroy; override;
    procedure LoadData;
    procedure SaveData;
    // define a flag to check if an end-user has changed data when the application is about to be closed
    property Modified: boolean read FModified;
    property RecordsList: TList read FRecordsList;
  end;
const
  AFileName = 'DEPARTMENTS.TXT';
  ATabChar = #9;
var
  TLProviderModeForm: TTLProviderModeForm;
implementation
{$R *.dfm}
{ TcxProviderRecordHandle }
constructor TcxProviderRecordHandle.Create(
  AKey: Integer; AParent: TcxProviderRecordHandle);
begin
  FParent := AParent;
  Values := TStringList.Create;
end;
function TcxProviderRecordHandle.AddChild(AKey: string): TcxProviderRecordHandle;
begin
  Result := TcxProviderRecordHandle.Create(StrToInt(AKey), Self);
end;
{ TcxCustomDemoDataSource }
constructor TcxCustomDemoDataSource.Create;
begin
  FRecordsList := TList.Create;
  FRootHandle := TcxProviderRecordHandle.Create(-1, nil);
  LoadData;
end;
destructor TcxCustomDemoDataSource.Destroy;
begin
  FRootHandle.Free;
  FRecordsList.Free;
  inherited Destroy;
end;
function TcxCustomDemoDataSource.GetParentRecordHandle(
  ARecordHandle: TcxDataRecordHandle): TcxDataRecordHandle;
begin
  Result := TcxProviderRecordHandle(ARecordHandle).Parent;
end;
function TcxCustomDemoDataSource.GetRecordCount: Integer;
begin
  Result := FRecordsList.Count;
end;
function TcxCustomDemoDataSource.GetRecordHandle(
  ARecordIndex: Integer): TcxDataRecordHandle;
begin
  Result := FRecordsList[ARecordIndex];
end;
function TcxCustomDemoDataSource.GetRootRecordHandle: TcxDataRecordHandle;
begin
  Result := RootHandle;
end;
function TcxCustomDemoDataSource.GetValue(ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle): Variant;
var
  I: Integer;
begin
  with TcxProviderRecordHandle(ARecordHandle) do
  begin
    I := Integer(AItemHandle);
    Result := Values[I];
  end;
end;
procedure TcxCustomDemoDataSource.SetValue(
  ARecordHandle: TcxDataRecordHandle; AItemHandle: TcxDataItemHandle;
  const AValue: Variant);
var
  I: Integer;
begin
  with TcxProviderRecordHandle(ARecordHandle) do
  begin
    I := Integer(AItemHandle);
    if VarIsNull(AValue) then
      Values[I] := ''
    else
      Values[I] := AValue;
  end;
  FModified := True;
end;
procedure TcxCustomDemoDataSource.LoadData;
const
  AFieldNamesRecordNumber = 0;
  AKeyField = 0;
  AParentKeyField = 1;
var
  ARecords, AValues: TStringList;
  procedure CreateColumns();
  var
    I: Integer;
  begin
    AValues.DelimitedText := ARecords[AFieldNamesRecordNumber];
    for I := 0 to AValues.Count - 1 do
      with TLProviderModeForm.VirtualTreeList.CreateColumn() do
        begin
          Caption.Text := AValues[I];
          Caption.AlignHorz := taCenter;
        end;
    ARecords.Delete(AFieldNamesRecordNumber);
  end;
  function AddRecordHandle(AParentHandle: TcxProviderRecordHandle;
    const ARecord: string): TcxProviderRecordHandle;
  var
    ADelimiterPos, AOffset, AStrLength: Integer;
    AValue: string;
  begin
    AValues.DelimitedText := ARecord;
    Result := AParentHandle.AddChild(AValues[AKeyField]);
    AStrLength := Length(ARecord);
    AOffset := 1;
    // decompose the record string (recall that record values are delimited by a tabulator)
    repeat
      ADelimiterPos := PosEx(ATabChar, ARecord, AOffset);
      if ADelimiterPos = 0 then
        AValue := Copy(ARecord, AOffset, AStrLength - (AOffset - 1))
      else
      begin
        AValue := Copy(ARecord, AOffset, ADelimiterPos - AOffset);
        AOffset := ADelimiterPos + 1;
      end;
      Result.Values.Add(AValue);
    until ADelimiterPos = 0;
  end;
  procedure AddRecordHandles(AParentHandle: TcxProviderRecordHandle;
     const AParentKeyValue: string);
    function GetFieldValue(ARecord: string; AFieldIndex: Integer): string;
    begin
      AValues.DelimitedText := ARecord;
      Result := AValues[AFieldIndex];
    end;
  var
    I: Integer;
    ARecordHandle: TcxProviderRecordHandle;
  begin
    // build up the tree structure
    for I := 0 to ARecords.Count - 1 do
      if GetFieldValue(ARecords[I], AParentKeyField) = AParentKeyValue then
      begin
        ARecordHandle := AddRecordHandle(AParentHandle, ARecords[I]);
        FRecordsList.Add(ARecordHandle);
        AddRecordHandles(ARecordHandle, GetFieldValue(ARecords[I], AKeyField));
      end;
  end;
begin
  if not FileExists(AFileName) then
    raise Exception.Create('No data file was found');
  ARecords := TStringList.Create;
  AValues := TStringList.Create;
  TLProviderModeForm.VirtualTreeList.BeginUpdate;
    try
      ARecords.LoadFromFile(AFileName);
      CreateColumns;
      AddRecordHandles(RootHandle, '0');
    finally
      TLProviderModeForm.VirtualTreeList.EndUpdate;
      ARecords.Free;
      AValues.Free;
    end;
end;
procedure TcxCustomDemoDataSource.SaveData;
var
  ARecords: TStringList;
  AValue, AValues: string;
  procedure AddColumnCaptions();
  var
    I: Integer;
  begin
    with TLProviderModeForm.VirtualTreeList do
    begin
      for I := 0 to ColumnCount - 1 do
      begin
        AValue := Columns[I].Caption.Text;
        if I <> (ColumnCount - 1) then
          AValues := AValues + AValue + ATabChar
        else
          AValues := AValues + AValue;
      end;
      ARecords.Add(AValues);
    end;
  end;
  procedure AddRecords();
  const
    AKeyField = 0;
  var
    I, J: Integer;
    function CompareKeyValues(Item1, Item2: Pointer): Integer;
    var
      AKeyValue1, AKeyValue2: Integer;
    begin
      AKeyValue1 := StrToInt(TcxProviderRecordHandle(Item1).Values[AKeyField]);
      AKeyValue2 := StrToInt(TcxProviderRecordHandle(Item2).Values[AKeyField]);
      Result := CompareValue(AKeyValue1, AKeyValue2);
    end;
  begin
    // originate the order of records as it was in the file store before loading data into the buffer
    FRecordsList.Sort(@CompareKeyValues);
    for I := 0 to FRecordsList.Count - 1 do
    begin
      AValue := '';
      AValues := '';
      with TcxProviderRecordHandle(FRecordsList[I]) do
      begin
        for J := 0 to Values.Count - 1 do
        begin
          AValue := Values[J] ;
          if J <> (Values.Count - 1) then
            AValues := AValues + AValue + ATabChar
          else
            AValues := AValues + AValue;
        end;
      end;
      ARecords.Add(AValues);
    end;
  end;
begin
  ARecords := TStringList.Create;
  AddColumnCaptions;
  AddRecords;
  try
    ARecords.SaveToFile(AFileName);
  finally
    ARecords.Free;
  end;
end;
procedure TTLProviderModeForm.FormCreate(Sender: TObject);
begin
  VirtualTreeList.CustomDataSource := TcxCustomDemoDataSource.Create;
  VirtualTreeList.FullExpand;
end;
procedure TTLProviderModeForm.FormDestroy(Sender: TObject);
begin
  VirtualTreeList.DataController.CustomDataSource.Free;
  VirtualTreeList.DataController.CustomDataSource := nil;
end;
procedure TTLProviderModeForm.FormCloseQuery(Sender: TObject;
  var CanClose: Boolean);
var
  I: Integer;
begin
  with VirtualTreeList.DataController.CustomDataSource as TcxCustomDemoDataSource do
  begin
    if Modified then
      I := MessageDlg('Save changes to the ' + AFileName + ' file?', mtConfirmation, [mbYes, mbNo, mbCancel], 0);
    case I of
      mrYes: SaveData;
      mrCancel: CanClose := False;
    end;
  end;
end;
end.
See Also