View Issue Details

IDProjectCategoryView StatusLast Update
0027000LazarusLCLpublic2014-11-07 16:45
ReporterwpAssigned ToJesus Reyes 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version1.3 (SVN)Product Build 
Target Version1.2.8Fixed in Version1.3 (SVN) 
Summary0027000: [Patch] Multiple range select in grids
DescriptionUsing TCustomGrid and its descendants, TDrawGrid and TStringGrid, it is possible to select only a single cell range. Standard behavior, particularly of spreadsheet applications, is selection of multiple ranges which is achieve by holding the CTRL key down while the next ranges are selected by the mouse.

In the attached patch this behavior implemented also for TCustomGrid. There is a new property RangeSelectMode: TRangeSelectMode = (rsmSingle, rsmMulti). Multi-selection is achieved by setting this property to rsmMulti and having the grid option goRangeSelect set. The default behavior is rsmSingle and does not break existing programs.

After application of the patch, the new behavior can be tested on the attached demo (see also attached screenshot).
TagsNo tags attached.
Fixed in Revision46767
LazTarget1.4
Widgetset
Attached Files
  • Grid_MultiSelect.zip (2,320 bytes)
  • grid_multiselect.png (49,243 bytes)
    grid_multiselect.png (49,243 bytes)
  • grids.pas.patch (7,960 bytes)
    Index: lcl/grids.pas
    ===================================================================
    --- lcl/grids.pas	(revision 46762)
    +++ lcl/grids.pas	(working copy)
    @@ -126,6 +126,9 @@
       TAutoAdvance = (aaNone,aaDown,aaRight,aaLeft, aaRightDown, aaLeftDown,
         aaRightUp, aaLeftUp);
     
    +  { Option goRangeSelect: --> select a single range only, or multiple ranges }
    +  TRangeSelectMode = (rsmSingle, rsmMulti);
    +
       TItemType = (itNormal,itCell,itColumn,itRow,itFixed,itFixedColumn,itFixedRow,itSelected);
     
       TColumnButtonStyle = (
    @@ -611,6 +614,7 @@
       type
         TGridCoord = TPoint;
         TGridRect  = TRect;
    +    TGridRectArray = array of TGridRect;
     
         TSizingRec = record
           Index: Integer;
    @@ -675,6 +679,8 @@
         FFastEditing: boolean;
         FAltColorStartNormal: boolean;
         FFlat: Boolean;
    +    FRangeSelectMode: TRangeSelectMode;
    +    FSelections: TGridRectArray;
         FOnUserCheckboxBitmap: TUserCheckboxBitmapEvent;
         FSortOrder: TSortOrder;
         FSortColumn: Integer;
    @@ -793,6 +799,8 @@
         function  GetBorderWidth: Integer;
         function  GetRowCount: Integer;
         function  GetRowHeights(Arow: Integer): Integer;
    +    function  GetSelectedRange(AIndex: Integer): TGridRect;
    +    function  GetSelectedRangeCount: Integer;
         function  GetSelection: TGridRect;
         function  GetTopRow: Longint;
         function  GetVisibleColCount: Integer;
    @@ -833,6 +841,7 @@
         procedure SetGridLineWidth(const AValue: Integer);
         procedure SetLeftCol(const AValue: Integer);
         procedure SetOptions(const AValue: TGridOptions);
    +    procedure SetRangeSelectMode(const AValue: TRangeSelectMode);
         procedure SetRow(AValue: Integer);
         procedure SetRowCount(AValue: Integer);
         procedure SetRowHeights(Arow: Integer; Avalue: Integer);
    @@ -854,6 +863,7 @@
       protected
         fGridState: TGridState;
         class procedure WSRegisterClass; override;
    +    procedure AddSelectedRange;
         procedure AdjustClientRect(var ARect: TRect); override;
         procedure AdjustEditorBounds(NewCol,NewRow:Integer); virtual;
         procedure AssignTo(Dest: TPersistent); override;
    @@ -871,6 +881,7 @@
         procedure CellClick(const aCol,aRow: Integer; const Button:TMouseButton); virtual;
         procedure CheckLimits(var aCol,aRow: Integer);
         procedure CheckLimitsWithError(const aCol, aRow: Integer);
    +    procedure ClearSelections;
         procedure CMBiDiModeChanged(var Message: TLMessage); message CM_BIDIMODECHANGED;
         procedure CMMouseEnter(var Message: TLMessage); message CM_MOUSEENTER;
         procedure CMMouseLeave(var Message :TLMessage); message CM_MouseLeave;
    @@ -1112,6 +1123,7 @@
         property LeftCol:Integer read GetLeftCol write SetLeftCol;
         property MouseWheelOption: TMouseWheelOption read FMouseWheelOption write FMouseWheelOption default mwCursor;
         property Options: TGridOptions read FOptions write SetOptions default DefaultGridOptions;
    +    property RangeSelectMode: TRangeSelectMode read FRangeSelectMode write SetRangeSelectMode default rsmSingle;
         property Row: Integer read FRow write SetRow;
         property RowCount: Integer read GetRowCount write SetRowCount default 5;
         property RowHeights[aRow: Integer]: Integer read GetRowHeights write SetRowHeights;
    @@ -1171,7 +1183,7 @@
         procedure EndUpdate(aRefresh: boolean = true);
         procedure EraseBackground(DC: HDC); override;
         function  Focused: Boolean; override;
    -
    +    function  HasMultiSelection: Boolean;
         procedure InvalidateCell(aCol, aRow: Integer); overload;
         procedure InvalidateCol(ACol: Integer);
         procedure InvalidateRange(const aRange: TRect);
    @@ -1189,6 +1201,8 @@
         procedure SaveToStream(AStream: TStream);
         procedure SetFocus; override;
     
    +    property SelectedRange[AIndex: Integer]: TGridRect read GetSelectedRange;
    +    property SelectedRangeCount: Integer read GetSelectedRangeCount;
         property SortOrder: TSortOrder read FSortOrder write FSortOrder;
         property SortColumn: Integer read FSortColumn;
         property TabStop default true;
    @@ -1428,6 +1442,7 @@
         property ParentFont;
         property ParentShowHint;
         property PopupMenu;
    +    property RangeSelectMode;
         property RowCount;
         property ScrollBars;
         property ShowHint;
    @@ -1644,6 +1659,7 @@
         property ParentFont;
         property ParentShowHint;
         property PopupMenu;
    +    property RangeSelectMode;
         property RowCount;
         property ScrollBars;
         property ShowHint;
    @@ -2908,6 +2924,13 @@
       Click;
     end;
     
    +procedure TCustomGrid.SetRangeSelectMode(const AValue: TRangeSelectMode);
    +begin
    +  if FRangeSelectMode=AValue then exit;
    +  FRangeSelectMode := AValue;
    +  ClearSelections;
    +end;
    +
     procedure TCustomGrid.SetRow(AValue: Integer);
     begin
       if AValue=FRow then Exit;
    @@ -4590,6 +4613,17 @@
       RegisterCustomGrid;
     end;
     
    +procedure TCustomGrid.AddSelectedRange;
    +var
    +  n: Integer;
    +begin
    +  if (goRangeSelect in Options) and (FRangeSelectMode = rsmMulti) then begin
    +    n := Length(FSelections);
    +    SetLength(FSelections, n+1);
    +    FSelections[n] := FRange;
    +  end;
    +end;
    +
     procedure TCustomGrid.AdjustClientRect(var ARect: TRect);
     begin
       inherited AdjustClientRect(ARect);
    @@ -4955,11 +4989,25 @@
     end;
     
     function TCustomGrid.GetIsCellSelected(aCol, aRow: Integer): boolean;
    +var
    +  i: Integer;
     begin
       Result:=  (FRange.Left<=aCol)   and
                 (aCol<=FRange.Right)  and
                 (FRange.Top<=aRow)    and
                 (aRow<=FRange.Bottom);
    +
    +  if not Result and (goRangeSelect in FOptions) and (RangeSelectMode = rsmMulti)
    +  then
    +    for i:=0 to High(FSelections) do
    +      if (FSelections[i].Left <= aCol)  and
    +         (ACol <= FSelections[i].Right) and
    +         (FSelections[i].Top <= ARow)   and
    +         (ARow <= FSelections[i].Bottom)
    +      then begin
    +        Result := true;
    +        exit;
    +      end;
     end;
     
     function TCustomGrid.GetSelectedColumn: TGridColumn;
    @@ -5218,6 +5266,20 @@
       SelectActive := False;
     end;
     
    +function TCustomGrid.GetSelectedRange(AIndex: Integer): TGridRect;
    +begin
    +  if AIndex >= Length(FSelections) then
    +    Result := FRange
    +  else
    +    Result := FSelections[AIndex];
    +end;
    +
    +function TCustomGrid.GetSelectedRangeCount: Integer;
    +begin
    +  Result := Length(FSelections) + 1;
    +    // add 1 because the current selection (FRange) is not stored in the array
    +end;
    +
     function TCustomGrid.GetSelection: TGridRect;
     begin
       Result:=FRange;
    @@ -6096,6 +6158,16 @@
                 if ssShift in Shift then
                   SelectActive:=(goRangeSelect in Options)
                 else begin
    +              if (goRangeSelect in Options) and (FRangeSelectMode = rsmMulti)
    +              then begin
    +                if (ssCtrl in Shift) then
    +                  AddSelectedRange
    +                else begin
    +                  ClearSelections;
    +                  Invalidate;
    +                end;
    +              end;
    +
                   // shift is not pressed any more cancel SelectActive if necessary
                   if SelectActive then
                     CancelSelection;
    @@ -7336,6 +7408,11 @@
         raise EGridException.Create(rsGridIndexOutOfRange);
     end;
     
    +procedure TCustomGrid.ClearSelections;
    +begin
    +  SetLength(FSelections, 0);
    +end;
    +
     procedure TCustomGrid.CMBiDiModeChanged(var Message: TLMessage);
     begin
       VisualChange;
    @@ -7457,6 +7534,12 @@
       InvalidateCell(ACol,ARow, False);
     end;
     
    +function TCustomGrid.HasMultiSelection: Boolean;
    +begin
    +  Result := (goRangeSelect in Options) and
    +    (FRangeSelectMode = rsmMulti) and (Length(FSelections) > 0);
    +end;
    +
     procedure TCustomGrid.InvalidateCell(aCol, aRow: Integer; Redraw: Boolean);
     var
       R: TRect;
    @@ -10345,6 +10428,11 @@
     
     procedure TCustomStringGrid.DoPasteFromClipboard;
     begin
    +  // Unpredictable results when a multiple selection is pasted back in.
    +  // Therefore we inhibit this here.
    +  if HasMultiSelection then
    +    exit;
    +
       if EditingAllowed(Col) and Clipboard.HasFormat(CF_TEXT) then begin
         SelectionSetText(Clipboard.AsText);
       end;
    
    grids.pas.patch (7,960 bytes)

Activities

wp

2014-11-05 22:19

developer  

Grid_MultiSelect.zip (2,320 bytes)

wp

2014-11-05 22:20

developer  

grid_multiselect.png (49,243 bytes)
grid_multiselect.png (49,243 bytes)

wp

2014-11-05 23:59

developer  

grids.pas.patch (7,960 bytes)
Index: lcl/grids.pas
===================================================================
--- lcl/grids.pas	(revision 46762)
+++ lcl/grids.pas	(working copy)
@@ -126,6 +126,9 @@
   TAutoAdvance = (aaNone,aaDown,aaRight,aaLeft, aaRightDown, aaLeftDown,
     aaRightUp, aaLeftUp);
 
+  { Option goRangeSelect: --> select a single range only, or multiple ranges }
+  TRangeSelectMode = (rsmSingle, rsmMulti);
+
   TItemType = (itNormal,itCell,itColumn,itRow,itFixed,itFixedColumn,itFixedRow,itSelected);
 
   TColumnButtonStyle = (
@@ -611,6 +614,7 @@
   type
     TGridCoord = TPoint;
     TGridRect  = TRect;
+    TGridRectArray = array of TGridRect;
 
     TSizingRec = record
       Index: Integer;
@@ -675,6 +679,8 @@
     FFastEditing: boolean;
     FAltColorStartNormal: boolean;
     FFlat: Boolean;
+    FRangeSelectMode: TRangeSelectMode;
+    FSelections: TGridRectArray;
     FOnUserCheckboxBitmap: TUserCheckboxBitmapEvent;
     FSortOrder: TSortOrder;
     FSortColumn: Integer;
@@ -793,6 +799,8 @@
     function  GetBorderWidth: Integer;
     function  GetRowCount: Integer;
     function  GetRowHeights(Arow: Integer): Integer;
+    function  GetSelectedRange(AIndex: Integer): TGridRect;
+    function  GetSelectedRangeCount: Integer;
     function  GetSelection: TGridRect;
     function  GetTopRow: Longint;
     function  GetVisibleColCount: Integer;
@@ -833,6 +841,7 @@
     procedure SetGridLineWidth(const AValue: Integer);
     procedure SetLeftCol(const AValue: Integer);
     procedure SetOptions(const AValue: TGridOptions);
+    procedure SetRangeSelectMode(const AValue: TRangeSelectMode);
     procedure SetRow(AValue: Integer);
     procedure SetRowCount(AValue: Integer);
     procedure SetRowHeights(Arow: Integer; Avalue: Integer);
@@ -854,6 +863,7 @@
   protected
     fGridState: TGridState;
     class procedure WSRegisterClass; override;
+    procedure AddSelectedRange;
     procedure AdjustClientRect(var ARect: TRect); override;
     procedure AdjustEditorBounds(NewCol,NewRow:Integer); virtual;
     procedure AssignTo(Dest: TPersistent); override;
@@ -871,6 +881,7 @@
     procedure CellClick(const aCol,aRow: Integer; const Button:TMouseButton); virtual;
     procedure CheckLimits(var aCol,aRow: Integer);
     procedure CheckLimitsWithError(const aCol, aRow: Integer);
+    procedure ClearSelections;
     procedure CMBiDiModeChanged(var Message: TLMessage); message CM_BIDIMODECHANGED;
     procedure CMMouseEnter(var Message: TLMessage); message CM_MOUSEENTER;
     procedure CMMouseLeave(var Message :TLMessage); message CM_MouseLeave;
@@ -1112,6 +1123,7 @@
     property LeftCol:Integer read GetLeftCol write SetLeftCol;
     property MouseWheelOption: TMouseWheelOption read FMouseWheelOption write FMouseWheelOption default mwCursor;
     property Options: TGridOptions read FOptions write SetOptions default DefaultGridOptions;
+    property RangeSelectMode: TRangeSelectMode read FRangeSelectMode write SetRangeSelectMode default rsmSingle;
     property Row: Integer read FRow write SetRow;
     property RowCount: Integer read GetRowCount write SetRowCount default 5;
     property RowHeights[aRow: Integer]: Integer read GetRowHeights write SetRowHeights;
@@ -1171,7 +1183,7 @@
     procedure EndUpdate(aRefresh: boolean = true);
     procedure EraseBackground(DC: HDC); override;
     function  Focused: Boolean; override;
-
+    function  HasMultiSelection: Boolean;
     procedure InvalidateCell(aCol, aRow: Integer); overload;
     procedure InvalidateCol(ACol: Integer);
     procedure InvalidateRange(const aRange: TRect);
@@ -1189,6 +1201,8 @@
     procedure SaveToStream(AStream: TStream);
     procedure SetFocus; override;
 
+    property SelectedRange[AIndex: Integer]: TGridRect read GetSelectedRange;
+    property SelectedRangeCount: Integer read GetSelectedRangeCount;
     property SortOrder: TSortOrder read FSortOrder write FSortOrder;
     property SortColumn: Integer read FSortColumn;
     property TabStop default true;
@@ -1428,6 +1442,7 @@
     property ParentFont;
     property ParentShowHint;
     property PopupMenu;
+    property RangeSelectMode;
     property RowCount;
     property ScrollBars;
     property ShowHint;
@@ -1644,6 +1659,7 @@
     property ParentFont;
     property ParentShowHint;
     property PopupMenu;
+    property RangeSelectMode;
     property RowCount;
     property ScrollBars;
     property ShowHint;
@@ -2908,6 +2924,13 @@
   Click;
 end;
 
+procedure TCustomGrid.SetRangeSelectMode(const AValue: TRangeSelectMode);
+begin
+  if FRangeSelectMode=AValue then exit;
+  FRangeSelectMode := AValue;
+  ClearSelections;
+end;
+
 procedure TCustomGrid.SetRow(AValue: Integer);
 begin
   if AValue=FRow then Exit;
@@ -4590,6 +4613,17 @@
   RegisterCustomGrid;
 end;
 
+procedure TCustomGrid.AddSelectedRange;
+var
+  n: Integer;
+begin
+  if (goRangeSelect in Options) and (FRangeSelectMode = rsmMulti) then begin
+    n := Length(FSelections);
+    SetLength(FSelections, n+1);
+    FSelections[n] := FRange;
+  end;
+end;
+
 procedure TCustomGrid.AdjustClientRect(var ARect: TRect);
 begin
   inherited AdjustClientRect(ARect);
@@ -4955,11 +4989,25 @@
 end;
 
 function TCustomGrid.GetIsCellSelected(aCol, aRow: Integer): boolean;
+var
+  i: Integer;
 begin
   Result:=  (FRange.Left<=aCol)   and
             (aCol<=FRange.Right)  and
             (FRange.Top<=aRow)    and
             (aRow<=FRange.Bottom);
+
+  if not Result and (goRangeSelect in FOptions) and (RangeSelectMode = rsmMulti)
+  then
+    for i:=0 to High(FSelections) do
+      if (FSelections[i].Left <= aCol)  and
+         (ACol <= FSelections[i].Right) and
+         (FSelections[i].Top <= ARow)   and
+         (ARow <= FSelections[i].Bottom)
+      then begin
+        Result := true;
+        exit;
+      end;
 end;
 
 function TCustomGrid.GetSelectedColumn: TGridColumn;
@@ -5218,6 +5266,20 @@
   SelectActive := False;
 end;
 
+function TCustomGrid.GetSelectedRange(AIndex: Integer): TGridRect;
+begin
+  if AIndex >= Length(FSelections) then
+    Result := FRange
+  else
+    Result := FSelections[AIndex];
+end;
+
+function TCustomGrid.GetSelectedRangeCount: Integer;
+begin
+  Result := Length(FSelections) + 1;
+    // add 1 because the current selection (FRange) is not stored in the array
+end;
+
 function TCustomGrid.GetSelection: TGridRect;
 begin
   Result:=FRange;
@@ -6096,6 +6158,16 @@
             if ssShift in Shift then
               SelectActive:=(goRangeSelect in Options)
             else begin
+              if (goRangeSelect in Options) and (FRangeSelectMode = rsmMulti)
+              then begin
+                if (ssCtrl in Shift) then
+                  AddSelectedRange
+                else begin
+                  ClearSelections;
+                  Invalidate;
+                end;
+              end;
+
               // shift is not pressed any more cancel SelectActive if necessary
               if SelectActive then
                 CancelSelection;
@@ -7336,6 +7408,11 @@
     raise EGridException.Create(rsGridIndexOutOfRange);
 end;
 
+procedure TCustomGrid.ClearSelections;
+begin
+  SetLength(FSelections, 0);
+end;
+
 procedure TCustomGrid.CMBiDiModeChanged(var Message: TLMessage);
 begin
   VisualChange;
@@ -7457,6 +7534,12 @@
   InvalidateCell(ACol,ARow, False);
 end;
 
+function TCustomGrid.HasMultiSelection: Boolean;
+begin
+  Result := (goRangeSelect in Options) and
+    (FRangeSelectMode = rsmMulti) and (Length(FSelections) > 0);
+end;
+
 procedure TCustomGrid.InvalidateCell(aCol, aRow: Integer; Redraw: Boolean);
 var
   R: TRect;
@@ -10345,6 +10428,11 @@
 
 procedure TCustomStringGrid.DoPasteFromClipboard;
 begin
+  // Unpredictable results when a multiple selection is pasted back in.
+  // Therefore we inhibit this here.
+  if HasMultiSelection then
+    exit;
+
   if EditingAllowed(Col) and Clipboard.HasFormat(CF_TEXT) then begin
     SelectionSetText(Clipboard.AsText);
   end;
grids.pas.patch (7,960 bytes)

Jesus Reyes

2014-11-06 21:57

developer   ~0078968

Thanks, applied with a change: use ssMeta in Mac OS X and ssCtrl elsewhere

wp

2014-11-07 16:45

developer   ~0078986

Thank you

Issue History

Date Modified Username Field Change
2014-11-05 22:18 wp New Issue
2014-11-05 22:18 wp File Added: grids.pas.patch
2014-11-05 22:19 wp File Added: Grid_MultiSelect.zip
2014-11-05 22:20 wp File Added: grid_multiselect.png
2014-11-05 22:21 wp Description Updated View Revisions
2014-11-05 23:44 wp Target Version => 1.4
2014-11-05 23:44 wp Summary Multiple range select in grids => [Patch] Multiple range select in grids
2014-11-05 23:58 wp File Deleted: grids.pas.patch
2014-11-05 23:59 wp File Added: grids.pas.patch
2014-11-06 07:49 Jesus Reyes Assigned To => Jesus Reyes
2014-11-06 07:49 Jesus Reyes Status new => assigned
2014-11-06 21:57 Jesus Reyes Fixed in Revision => 46767
2014-11-06 21:57 Jesus Reyes LazTarget - => 1.4
2014-11-06 21:57 Jesus Reyes Note Added: 0078968
2014-11-06 21:57 Jesus Reyes Status assigned => resolved
2014-11-06 21:57 Jesus Reyes Fixed in Version => 1.3 (SVN)
2014-11-06 21:57 Jesus Reyes Resolution open => fixed
2014-11-07 16:45 wp Note Added: 0078986
2014-11-07 16:45 wp Status resolved => closed
2014-11-07 16:45 wp Target Version 1.4 => 1.2.8