Skip to main content

Unbound Mode: Master-Detail

  • 9 minutes to read

ExpressQuantumGrid provides unbound mode to display custom data (from non data-aware sources) within grid Views. In unbound mode, the data controller of a View is not connected to any data source and therefore must be populated with values manually: record-by-record and item-by-item. First, you should set the number of records to display and then set the field values for each record.

Unbound mode can be useful, for instance, when you need to represent runtime created data within a grid control. In this mode, you are notified of editing data via the DataController.OnDataChanged event. This event is fired when the end-user modifies cell values, deletes a specific record or inserts a new one. You may find that use of the DataController.OnDataChanged event is not suitable for responding to user manipulation such as updating your external data source, for instance. In this case, you should consider use of provider mode. This also allows you to display custom data (from non data-aware sources). However, provider mode gives you more functionality while editing data within Views. See the Provider Mode and Provider Mode: Master-Detail documents for more information.

The Unbound Mode section provides an example of implementing unbound mode to fill a single View with data. This document provides a simple example of how to create a master-detail relationship in unbound mode (both master and detail Views will work in unbound mode).

Unbound mode is supported by non data-aware Views (TcxGridTableView, TcxGridBandedTableView, TcxGridCardView and TcxGridChartView). Note that neither a Card View nor a Chart View can become a master View.

A master-detail relationship associates every single record from a master data source with zero or more records from a detail data source. To set up a master-detail relationship in unbound mode, you need to:

  1. Create a hierarchical structure of grid levels and Views similar to the one shown on the following image:

MasterTableView will represent a master data source. DetailTableView will represent a detail data source.

Refer to the Working with Levels section to learn how to create such a structure at design time and via code. Note that in unbound and provider modes, you should use non data-aware versions of Views (TcxGridTableView, TcxGridBandedTableView, TcxGridCardView and TcxGridChartView).

  1. Populate the master View with data.

  2. Populate the detail View with data. Doing this, you should bind data to a specific master row.

The last two steps can be performed via code only.

Detail Views in ExpressQuantumGrid are represented by their “clones“. A clone is an instance of a detail View displaying records corresponding to a specific master row. For every detail clone you can obtain a reference to the DataController object using which, you can populate this particular View with data in unbound mode. To access the DataController of a specific detail, use the DataController.GetDetailDataController function of a master View.

This function requires two parameters: record index and relation index. The record index indicates the zero-based index of the master row for which to get the detail data controller. Record indexes correspond to the natural order of records (i.e. with no sorting applied). Record indexes always refer to the same data regardless of the position of a record within the View.

The relation index indicates the zero-based index of a master-detail relationship for a master View (if there are several detail Views). For the DetailTableView1 View (see the image below), the relation index is 0. For DetailTableView2, the relation index is 1.

Once you obtain the View’s DataController object, you can specify the number of records to display and populate the data controller with values. To set the number of records, use the data controller’s RecordCount property. The Values property allows you to specify the value for each cell. The first parameter of the Values property specifies the record index. The second parameter specifies the index of the item for which to set a value.

Let’s consider a simple example implementing a master-detail relationship in unbound mode (both master and detail Views will work in unbound mode).

The master Table View will represent information on planets while the detail View will display their satellites.

Assume that the master View (FMasterTableView) contains only two rows. Therefore, only two detail clones of the FDetailTableView View corresponding to these rows exist. The following code shows how to populate both clones with data. The first detail clone will contain two records (the RecordCount property is set to 2), the second one will contain only one record.

var
  I: Integer;
  ADetDataController: TcxCustomDataController;
//...
  with FMasterTableView.DataController do
  begin
    BeginUpdate;
    try
      for I := 0 to RecordCount - 1 do
      begin
        ADetDataController := GetDetailDataController(I, 0);
        with ADetDataController do
        begin
          BeginUpdate;
          try
            case I of
              0:
              begin
                RecordCount := 2;
                Values[0, 0] := 'Io';
                Values[0, 1] := 422;
                Values[0, 2] := 1.77;
                Values[1, 0] := 'Europa';
                Values[1, 1] := 671;
                Values[1, 2] := 3.55;
              end;
              1:
              begin
                RecordCount := 1;
                Values[0, 0] := 'Moon';
                Values[0, 1] := 384;
                Values[0, 2] := 27.32;
              end;
            end;
          finally
            EndUpdate;
          end;
        end;
      end;
    finally
      EndUpdate();
    end;
  end;

The full code of the example is listed below. It shows how to create a grid control, customize it for representing a master-detail relationship and populate the master and detail Views. This is performed in the following procedures:

  • CreateGrid

Creates a grid control and the structure of levels and Views.

  • CreateColumns

Creates columns in the master and detail Views. Note that in unbound and provider modes the column’s DataBinding.ValueTypeClass property is assigned. This indicates the data type of values to edit within columns.

  • FillMasterView and FillDetailView

Populate the master and detail Views with data.

  • FormCreate (the form’s OnCreate event handler)

Calls the aforementioned procedures.

To run the example:

  • create an empty application

  • replace the code of your Unit1 module with the following code

  • assign the form’s OnCreate event handler to the FormCreate method

The following image shows the grid control created in this example:

The full code of the example

unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, cxStyles, cxCustomData, cxGraphics, cxFilter, cxData, cxEdit, cxGridCustomTableView, cxGridTableView, cxClasses, cxControls, cxGridCustomView, cxGridLevel, cxGrid;
type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    FGrid: TcxGrid;
    FMasterTableView: TcxGridTableView;
    FDetailTableView: TcxGridTableView;
  public
    procedure CreateGrid;
    procedure CreateColumns;
    procedure FillMasterView;
    procedure FillDetailView;
  end;
var
  Form1: TForm1;
implementation
uses
  cxDataStorage, cxGridPredefinedStyles;
{$R *.dfm}
procedure TForm1.FillDetailView;
var
  I: Integer;
  ADetDataController: TcxCustomDataController;
begin
  with FMasterTableView.DataController do
  begin
    BeginUpdate;
    try
      for I := 0 to RecordCount - 1 do
      begin
        ADetDataController := GetDetailDataController(I, 0);
        with ADetDataController do
        begin
          BeginUpdate;
          try
            case I of
              0:
              begin
                RecordCount := 2;
                Values[0, 0] := 'Io';
                Values[0, 1] := 422;
                Values[0, 2] := 1.77;
                Values[1, 0] := 'Europa';
                Values[1, 1] := 671;
                Values[1, 2] := 3.55;
              end;
              1:
              begin
                RecordCount := 1;
                Values[0, 0] := 'Moon';
                Values[0, 1] := 384;
                Values[0, 2] := 27.32;
              end;
            end;
          finally
            EndUpdate;
          end;
        end;
      end;
    finally
      EndUpdate();
    end;
  end;
end;
procedure TForm1.FillMasterView;
begin
  with FMasterTableView.DataController do
  begin
    BeginUpdate;
    try
      RecordCount := 2;
      Values[0, 0] := 'Jupiter';
      Values[0, 1] := 778330;
      Values[0, 2] := 4332.71;
      Values[1, 0] := 'Earth';
      Values[1, 1] := 149600;
      Values[1, 2] := 365.26;
    finally
      EndUpdate;
    end;
  end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
  CreateGrid;
  CreateColumns;
  FillMasterView;
  FillDetailView;
end;
procedure TForm1.CreateColumns;
begin
  with FMasterTableView do
  begin
    BeginUpdate;
    try
      with CreateColumn do
      begin
        Caption := 'Planet';
        Name := 'mtPlanet';
        DataBinding.ValueTypeClass := TcxStringValueType;
        Width := 90;
      end;
      with CreateColumn do
      begin
        Caption := 'Distance(000km)';
        Name := 'mtDistance';
        DataBinding.ValueTypeClass := TcxFloatValueType;
        Width := 130;
      end;
      with CreateColumn do
      begin
        Caption := 'Period(days)';
        Name := 'mtPeriod';
        DataBinding.ValueTypeClass := TcxFloatValueType;
        Width := 150;
      end;
    finally
      EndUpdate;
    end
  end;
  with FDetailTableView do
  begin
    BeginUpdate;
    try
      with CreateColumn do
      begin
        Caption := 'Satellite';
        Name := 'dtSatellite';
        DataBinding.ValueTypeClass := TcxStringValueType;
        Width := 100;
      end;
      with CreateColumn do
      begin
        Caption := 'Distance(000km)';
        Name := 'dtDistance';
        DataBinding.ValueTypeClass := TcxFloatValueType;
        Width := 160;
      end;
      with CreateColumn do
      begin
        Caption := 'Period(days)';
        Name := 'dtPeriod';
        DataBinding.ValueTypeClass := TcxFloatValueType;
        Width := 90;
      end;
    finally
      EndUpdate;
    end;
  end;
end;
procedure TForm1.CreateGrid;
begin
  FGrid := TcxGrid.Create(Self);
  with FGrid do
  begin
    FMasterTableView := 
      CreateView(TcxGridTableView) as TcxGridTableView;
    Levels.Add.GridView := FMasterTableView;
    FDetailTableView := 
      CreateView(TcxGridTableView) as TcxGridTableView;
    Levels[0].Add.GridView := FDetailTableView;
    Align := alClient;
    Parent := Self;
  end;
end;
end.
See Also