Skip to main content

Implementing Drag-And-Drop Functionality

  • 13 minutes to read

Drag-and-drop allows end-users to drag one control (or its items) onto another control within the same application. EQGrid supports drag-and-drop for grid records and root level tabs. You can implement drag-and-drop to drag:

  • objects (list boxes, tree views, grid controls, etc.) and their items onto a grid View or a root level tab

  • grid records onto another control or within the same View

  • root level tabs onto another control

Root level tabs enable you to switch between root levels used to display data from non-related datasets. Refer to the Data Representation section for more information. The following image shows three root level tabs (Items, Projects and Users).

Options affecting drag-and-drop operations involving root level tabs are published in the TcxGrid class. For instance, when you need to respond to dragging over a root level tab or dropping onto it, use the grid’s OnDragOver and OnDragDrop events respectively. Conversely, options affecting drag-and-drop within a View are declared in the base View class (TcxCustomGridView).

To implement drag-and-drop from a source control to a target control, you need to:

  • Enable a user to start the operation within the source control

The manner in which drag & drop operations are initiated depends on the DragMode property value of the control to be dragged. If this property value is dmAutomatic, dragging in initiated automatically as soon as the end-user presses the mouse button. If the DragMode property is set to dmManual, you have to call the control’s BeginDrag method to start dragging.

  • Handle the target control’s OnDragOver event to accept or reject dragged items

  • Handle the target control’s OnDragDrop event to respond to the dropping of items within the control

A Dragged Object

A dragged object is the currently focused object at the time a drag-and-drop operation is initiated. The dragged object is passed as a parameter to the OnDragOver and OnDragDrop events of the target control (the control hovered over and where the object is dropped).

When a drag-and-drop operation starts within EQGrid, an object of the TcxDragControlObject class is created. Its Control property refers to the control about to be dragged.

If you start drag-and-drop by clicking on a root level tab of your grid control, the drag object will refer to the grid itself.

If drag-and-drop is initiated within cells of a grid View, the drag object will refer to the grid site containing this View. Grid Views are not controls; therefore they cannot start drag-and-drop. When a user clicks within a View, the click on the grid is handled by the site control (a TcxGridSite object), containing the View. You can obtain the View displayed by the site via its GridView property.

Starting Drag-And-Drop

The DragMode property published in the TcxGrid class affects the start of drag-and-drop within root level tabs. The DragMode property declared in the TcxCustomGridView class affects the start of drag-and-drop within grid Views.

Automatic start of drag-and-drop does not require any code. Set the DragMode property to dmAutomatic to initiate the operation when a user clicks within the control and moves the mouse holding the key down. However, you cannot control the initial point of the drag-and-drop operation in this mode.

If DragMode is set to dmManual, you need to start the drag-and-drop operation manually by handling mouse-down events. A manual start of the operation allows you to control the initial point of drag-and-drop (for instance, when clicking within specific items, records, etc). To drag a control manually, call the control’s BeginDrag method. BeginDrag has a Boolean parameter called Immediate and, optionally, an integer parameter called Threshold. If the Immediate parameter is set to True, the BeginDrag method initiates drag-and-drop from the current mouse position. If it is set to False, dragging begins after the user moves the mouse the number of pixels specified by the Threshold parameter. If Threshold is set to a value less than 0 (such as the default value for this parameter), BeginDrag uses the DragThreshold property of the global Mouse variable.

In order to start drag-and-drop manually for a grid View, you need to call the BeginDrag method of its grid site.

Accepting/Rejecting the Dragged Object

To accept or reject the dragged object, you should use the OnDragOver event of the target control. When you intend to allow dragging over a grid View and dropping onto it, use its OnDragOver event. The object being dragged is specified by the Source parameter. If the source of the operation is a grid View, the Source parameter represents a TcxDragControlObject instance, whose Control property refers to the grid site containing the View. You can test it to decide whether subsequent dropping is allowed.

Dropping items

Use the OnDragDrop event of the target control (or the target grid View) to respond to dropping the dragged object within the control. This event is generated only if the dragged object was accepted in your OnDragOver event handler. In the OnDragDrop event handler, you need to get the source control, which initiated the drag-and-drop operation and then perform specific actions using data from this source control.

Properties relating to drag-and-drop

EQGrid provides a number of options relating to drag-and-drop operations when dragging over grid Views. These options can be accessed via a View’s OptionsBehavior object:

  • DragFocusing specifies whether focus is moved to the target record when dragging/dropping over the current View

  • DragHighlighting specifies whether the target record is highlighted when you perform drag-and-drop over the View

  • DragOpening forces master and group rows and detail level tabs to expand during drag-and-drop. To expand root level tabs during drag-and-drop, use the grid’s DragOpening property.

  • DragScrolling specifies whether the View is scrolled automatically when dragging an object to the top, left, bottom or right View edges

Example

The following is an example of implementing drag-and-drop from a grid control onto a TMemo component.

When the end-user drags root grid level tabs onto the memo, it displays the hierarchical structure of child levels and Views owned by the clicked root level in the following form:

<RootGridLevel Caption=”Projects” View=”tvProjects”>

<GridLevel Caption=”” View=”tvTeam”/>

</RootGridLevel>

When dragging grid records onto the memo, the latter displays information on the currently selected records (if the View’s OptionsSelection.MultiSelect property is set to True) or the focused record (if multi-selection is disabled).

For instance, the following text describes the values of three records from the tvItems View. The values are quoted and preceded by the corresponding field names:

<GridLevel Caption=”Items” View=”tvItems”>

<Record NAME=”Screen Redraw” TYPE=”Bug” PROJECTID=”Small-Business Accounting System” PRIORITY=”Normal” STATUS=”New” CREATORID=”Dave Murrel” />

<Record NAME=”Faxing” TYPE=”Bug” PROJECTID=”Small-Business Accounting System” PRIORITY=”Low” STATUS=”New” CREATORID=”Dave Murrel” />

<Record NAME=”Data Export” TYPE=”Bug” PROJECTID=”Small-Business Accounting System” PRIORITY=”Normal” STATUS=”Fixed” CREATORID=”Ray Shipman” />

</GridLevel>

The following steps describe how to apply drag-and-drop functionality to the ColumnsShareDemo shipped with EQGrid.

  1. Open the ColumnsShareDemo and place a TMemo component onto the main form.

  2. Set the properties of the memo control to the following values:

This keeps the memo on the right edge of the form. You are now able to specify its width by dragging the control’s left margin.

  • WordWrap to False to prevent text wrapping at the right edge of the memo.

  • ScrollBars to ssBoth to enable horizontal and vertical scrollbars.

  1. For the grid control in the main form, set its DragMode property value to dmAutomatic.

  2. For the tvProjects, tvTeam and cvUsers Views, enable automatic start of drag-and-drop by setting their DragMode properties to dmAutomatic.

  3. For the tvItems View, we will implement manual start of drag-and-drop for demonstrative purposes. The DragMode property of the View must be set to dmManual (the default value).

Write an OnMouseDown event handler for the View as follows. This code initiates drag-and-drop after a user clicks any record within a View and drags the mouse pointer a particular number of pixels (by default, 5):

procedure TColumnsShareDemoMainForm.tvItemsMouseDown(Sender: TObject;
  Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
//Start drag-and-drop only if the user clicks a grid record
  with TcxGridSite(Sender) do
  begin
    if ViewInfo.GetHitTest(X, Y).HitTestCode = htRecord then
      BeginDrag(False);
  end;
end;
  1. Enable the multi-selection feature for the tvItems View by setting its OptionsSelection.MultiSelect property to True.

  2. Write an OnDragOver event handler for the memo as follows:

procedure TColumnsShareDemoMainForm.Memo1DragOver(Sender, Source: TObject;
  X, Y: Integer; State: TDragState; var Accept: Boolean);
begin
  if Source is TcxDragControlObject then
    with TcxDragControlObject(Source) do
      if (Control is TcxGridSite) or (Control is TcxGrid) then
          Accept := True;
end;

This accepts the dragged object if the source of the operation is a grid control or a grid site. If the source of the drag-and-drop operation is another control, the dragged object is rejected (the Accept parameter is False by default).

  1. Handle the OnDragDrop event for the memo and write it as follows:

The code ensures that the dragged object is a grid site or a grid control. Then it performs different actions depending on the object type. The GetSelectedRecordsInfo and GetGridInfo procedures used in the event handler will be defined in the next step.

procedure TColumnsShareDemoMainForm.Memo1DragDrop(Sender, Source: TObject;
  X, Y: Integer);
var
  AControl: TControl;
begin
  AControl := TDragControlObject(Source).Control;
  if AControl is TcxGridSite then
    GetSelectedRecordsInfo(TcxCustomGridTableView
      (TcxGridSite(AControl).GridView))
  else
    if AControl is TcxGrid then
      GetGridInfo(TcxGrid(AControl));
end;
  1. Add four methods to the declaration of your TColumnsShareDemoMainForm class to obtain the information from the dropped grid and represent it in the desired form as described above:
TColumnsShareDemoMainForm = class(TForm)
  //...
  public
    procedure GetSelectedRecordsInfo(ASourceView: TcxCustomGridTableView);
    procedure GetGridInfo(ASourceGrid: TcxGrid);
    function GetRecordInfo(ARecord: TcxCustomGridRecord): string;
    function GetLevelInfo(ALevel: TcxGridLevel; APrefix, APostFix: string): string;
  end;

GetSelectedRecordsInfo obtains data from selected or focused records from the source View. This View is passed as the ASourceView parameter. This code assumes that Grid Mode is not applied. In this case, selected records can be obtained via the View’s Controller.SelectedRecords property. The focused record is specified by the View’s Controller.FocusedRecord property. Refer to the Focused/Selected Records section for more details.

procedure TColumnsShareDemoMainForm.GetSelectedRecordsInfo(
  ASourceView: TcxCustomGridTableView);
var
  AList: TStringList;
  I: Integer;
begin
  Memo1.Clear;
  AList := TStringList.Create;
  with AList do
  begin
    Add(GetLevelInfo(TcxGridLevel(ASourceView.Level), '<GridLevel', '>'));
    if ASourceView.OptionsSelection.MultiSelect then
      for I := 0 to ASourceView.Controller.SelectedRecordCount - 1 do
        Add('  ' + GetRecordInfo(
          ASourceView.Controller.SelectedRecords[I]))
    else
      Add('  ' + GetRecordInfo(
        ASourceView.Controller.FocusedRecord));
    Add('</GridLevel>');
  end;
  Memo1.Lines.Add(AList.Text);
  AList.Free;
end;

GetRecordInfo retrieves values from a record as a formatted string in the following format:

<Record NAME=”Small-Business Accounting System” MANAGERID=”Dave Murrel” />

where NAME and MANAGERID are item names (or field names for data-aware items).

Item values are obtained from the record’s DisplayTexts property and are presented as quoted strings.

function TColumnsShareDemoMainForm.GetRecordInfo(
  ARecord: TcxCustomGridRecord): string;
var
  I: Integer;
  AItem: TcxCustomGridTableItem;
  AItemName: string;
begin
    if ARecord is TcxGridGroupRow then
    begin
      Result := '';
      Exit;
    end;
    Result := '<Record ';
    for I := 0 to ARecord.ValueCount - 1 do
    begin
      AItem := ARecord.GridView.Items[I];
      if AItem.DataBinding is TcxGridItemDBDataBinding then
        AItemName := TcxGridItemDBDataBinding(AItem.DataBinding).FieldName
      else
        AItemName := AItem.Name;
      Result := Result + AItemName +  '="' + ARecord.DisplayTexts[I] + '" ';
    end;
    Result := Result + '/>';
end;

GetGridInfo displays information on child levels and Views of the active root grid level within the memo. It navigates through all child levels and every level found is processed by the GetLevelInfo function.

The GetGridInfo function has the ASourceGrid parameter providing access to the source grid control. The grid’s ActiveLevel property determines the root level the end-user has clicked.

procedure TColumnsShareDemoMainForm.GetGridInfo(ASourceGrid: TcxGrid);
var
  AList: TStringList;
  ACurrentLevel, ARootLevel: TcxGridLevel;
  AIndent : string;
  AChildIndex: Integer;
begin
  AList := TStringList.Create;
  AIndent := '';
  ARootLevel := ASourceGrid.ActiveLevel;
  ACurrentLevel := ARootLevel;
  AList.Add(AIndent + GetLevelInfo(ACurrentLevel, '<RootGridLevel', '>'));
  AChildIndex := -1;
  if ACurrentLevel.Count > 0 then
    repeat
      if AChildIndex < ACurrentLevel.Count - 1 then
      begin
        ACurrentLevel := ACurrentLevel.Items[AChildIndex + 1];
        AIndent := AIndent + '  ';
        AList.Add(AIndent + GetLevelInfo(ACurrentLevel, '<GridLevel', '/>'));
        AChildIndex := -1;
      end
      else
       begin
        AChildIndex := ACurrentLevel.Index;
        ACurrentLevel := ACurrentLevel.Parent;
        SetLength(AIndent, Length(AIndent) - 2);
       end;
    until (ACurrentLevel =ARootLevel) and (AChildIndex = ACurrentLevel.Count - 1);
  AList.Add(AIndent + '</RootGridLevel>');
  Memo1.Clear;
  Memo1.Lines.Add(AList.Text);
  AList.Free;
end;

The GetLevelInfo method is used to retrieve information for a given level. It returns the Caption of the level and the View assigned, if any, in the following format:

<RootGridLevel Caption=”Projects” View=”tvProjects”>

or

<GridLevel Caption=”” View=”tvTeam”/>

where ‘<RootGridLevel and ‘<GridLevel’ are passed as function parameters.

function TColumnsShareDemoMainForm.GetLevelInfo(ALevel: TcxGridLevel;
  APrefix, APostFix: string): string;
begin
  Result := APrefix + ' ';
  Result := Result + 'Caption="' + ALevel.Caption + '" ';
  if Assigned(ALevel.GridView) then
    Result := Result + 'View="' + ALevel.GridView.PatternGridView.Name + '"'
  else
    Result := Result + 'View="NIL"';
  Result := Result + APostfix;
end;
  1. Add the cxGridDBDataDefinitions and cxGridViewData modules to the uses section of the unit.

  2. Run the program.

Multi-select functionality is enabled for the tvItems View. Select several records within the View and drag them onto the memo control.

After dropping the records, the memo will display record values as a list:

Dragging the Projects tab onto the memo displays child levels and the Views associated with the corresponding root View (the tvProjects View).

There is only one detail (tvTeam) bound to the tvProjects View, which is displayed in the following image:

You can drag other root tabs (Items and Users) and records from other Views onto the memo control. This will display the appropriate information on details and records in a similar manner.