Recent

Author Topic: [SOLVED] TDrawGrid - multiselection with CTRL?  (Read 12185 times)

derek.john.evans

  • Guest
Re: [SOLVED] TDrawGrid - multiselection with CTRL?
« Reply #15 on: September 29, 2015, 12:08:48 pm »
But again: what is the problem (except for waising a few bytes of memory) that the same cell is in the clipboard multiple times?
It is harder for me to implement an algorithm to merge selections (regions) and then copy them to clipboard. The current implementation of TStringGrid.CopyToClipboard(True) puts in the clipboard only the first region selected, disregarding that you have multiple regions selected in the grid. Maybe the is a bug too and i dont't have to do anything just to wait for it to be solved....the documentation  is sketchy on rsmmultiple....

I think I know what you are saying.

Instead of iterating the selected ranges to get selected cells, try, iterating the entire TStringGrid and use IsCellSelected to check if a cell is selected.

That works with the current duplicate range selection issue.

wp

  • Hero Member
  • *****
  • Posts: 13270
Re: [SOLVED] TDrawGrid - multiselection with CTRL?
« Reply #16 on: September 29, 2015, 12:43:32 pm »
Quote
The current implementation of TStringGrid.CopyToClipboard(True) puts in the clipboard only the first region selected, disregarding that you have multiple regions selected in the grid.
Oh, not good... Let me take care of it, but it may take some time because I just uploaded another patch for grids.pas which I want to be included first.

Quote
the documentation  is sketchy on rsmmultiple
Nothing special about the multi-selection: All selections are collected in an array (internaly called FSelections), you can query the individual selection blocks by means of the property SelectedRange[index], and their count by SelectedRangeCount. AddSelectedRange adds a new selected block, and ClearSelection removes all selections.

In order to remove duplicate ranges you could do this (not tested...):
Code: [Select]
var
  selArr: TGridRectArray;
  i: Integer;

  function IsDuplicate(R: TGridRect): Boolean;
  var
    j: Integer:
  begin
    for j:=0 to High(selArr) do
      if (selArr[j].Left <> R.Left) or (selArr[j].Right <> R.Right) or
         (selArr[j].Top <> R.Top) or (selArr[j].Bottom <> R.Bottom) then exit(false);
    Result := true;
  end;
   
begin
  SetLength(selArr, 0);
  for i:=0 to StringGrid1.SelectedRangeCount-1 do begin
    sel := StringGrid1.SelectedRange[i];
    if not IsDuplicate(sel) then
    begin
      SetLength(selArr, Length(selArr)+1);
      selArr[High(selArr)] := sel;
    end;
  end;
  StringGrid1.ClearSelections;
  for i:=0 to High(selArr) do
    StringGrid1.AddSelectedRange(selArr[i]);
end;
« Last Edit: September 29, 2015, 01:25:38 pm by wp »

derek.john.evans

  • Guest
Re: [SOLVED] TDrawGrid - multiselection with CTRL?
« Reply #17 on: September 29, 2015, 02:30:33 pm »
O, I didn't realize TStringGrid supported copy/paste. After playing with the multi-selection, it looks like the issue will be "how to handle sparse selections using CSV tables?". And then, what happens when you paste sparse selections back?

Anyway, here is a function to return the union of the current multi-selection.
Code: Pascal  [Select][+][-]
  1. function StringGridSelection(const AStringGrid: TStringGrid): TRect;
  2. var
  3.   LIndex: Integer;
  4. begin
  5.   Result := AStringGrid.Selection;
  6.   for LIndex := 0 to AStringGrid.SelectedRangeCount - 1 do begin
  7.     with AStringGrid.SelectedRange[LIndex] do begin
  8.       Result.Left := Min(Result.Left, Left);
  9.       Result.Top := Min(Result.Top, Top);
  10.       Result.Right := Max(Result.Right, Right);
  11.       Result.Bottom := Max(Result.Bottom, Bottom);
  12.     end;
  13.   end;
  14. end;
  15.  

And, if you intercept CTRL-C in OnKeyDown, then the union of the selections will be copied to the clipboard. Plus, the visible selection will be grown to the union of the multi-selections, so, there is a visible indicator to the user, that selection has changed.
Code: Pascal  [Select][+][-]
  1. procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);
  2. begin
  3.   if (AKey = VK_C) and (AShift = [ssModifier]) then begin
  4.     (ASender as TStringGrid).Selection := StringGridSelection(ASender as TStringGrid);
  5.   end;
  6. end;
  7.  

That would be the "simple" solution, since it works with the current copy/paste system.

But, if you wanted to only copy the selected cells using CSV, then you would need something like:
Code: Pascal  [Select][+][-]
  1. function StringGridSelectionAsCSV(const AStringGrid: TStringGrid): String;
  2. var
  3.   LCol, LRow: Integer;
  4.   LRect: TRect;
  5.   LString: String;
  6. begin
  7.   Result := EmptyStr;
  8.   LRect := StringGridSelection(AStringGrid);
  9.   for LRow := LRect.Top to LRect.Bottom do begin
  10.     if Result <> EmptyStr then begin
  11.       Result += LineEnding;
  12.     end;
  13.     LString := EmptyStr;
  14.     for LCol := LRect.Left to LRect.Right do begin
  15.       if LString <> EmptyStr then begin
  16.         LString += #9;
  17.       end;
  18.       if AStringGrid.IsCellSelected[LCol, LRow] then begin
  19.         LString += AStringGrid.Cells[LCol, LRow];
  20.       end else begin
  21.         LString += '#';
  22.       end;
  23.     end;
  24.     if LString <> EmptyStr then begin
  25.       Result += LString;
  26.     end;
  27.   end;
  28. end;
  29.  
  30. procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AStrings: TStrings);
  31. var
  32.   LCol, LRow: Integer;
  33.   LSelection: TRect;
  34. begin
  35.   LSelection := StringGridSelection(AStringGrid);
  36.   LSelection.BottomRight := LSelection.TopLeft;
  37.   with TStringList.Create do begin
  38.     try
  39.       for LRow := 0 to AStrings.Count - 1 do begin
  40.         Text := AnsiReplaceStr(AStrings[LRow], #9, LineEnding);
  41.         for LCol := 0 to Count - 1 do begin
  42.           if Strings[LCol]<> '#' then begin
  43.             AStringGrid.Cells[LSelection.Left + LCol, LSelection.Top + LRow] := Strings[LCol];
  44.           end;
  45.           LSelection.Right := Max(LSelection.Right, LSelection.Left + LCol);
  46.           LSelection.Bottom := Max(LSelection.Bottom, LSelection.Top + LRow);
  47.         end;
  48.       end;
  49.     finally
  50.       Free;
  51.     end;
  52.   end;
  53.   AStringGrid.Selection := LSelection;
  54. end;
  55.  
  56. procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AText: String);
  57. var
  58.   LStrings: TStrings;
  59. begin
  60.   LStrings := TStringList.Create;
  61.   try
  62.     LStrings.Text := AText;
  63.     StringGridLoadSelected(AStringGrid, LStrings);
  64.   finally
  65.     FreeAndNil(LStrings);
  66.   end;
  67. end;
  68.  

Note: the '#' used for cells that are not selected in the union selection rectangle.

And then intercept Copy/Paste using:
Code: Pascal  [Select][+][-]
  1. procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);
  2. begin
  3.   if (AKey = VK_C) and (AShift = [ssModifier]) then begin
  4.     ClipBoard.AsText := StringGridSelectionAsCSV(ASender as TStringGrid);
  5.     AKey := 0;
  6.   end;
  7.   if (AKey = VK_V) and (AShift = [ssModifier]) then begin
  8.     StringGridLoadSelected( ASender as TStringGrid, ClipBoard.AsText);
  9.     AKey := 0;
  10.   end;
  11. end;  
  12.  

That code works for sparse selections, but, doesn't handle pasting outside the string grid range. Which means, you have to decide if you disallow, trim, or expand the string grid.

All are application specific choices.

I'd go with the union rectangle selection option.
« Last Edit: October 02, 2015, 02:57:09 am by Geepster »

wp

  • Hero Member
  • *****
  • Posts: 13270
Re: [SOLVED] TDrawGrid - multiselection with CTRL?
« Reply #18 on: September 29, 2015, 10:30:50 pm »
Geepster, thank you for your code. I took the basic idea, the union of the individual selections, as the basis of a patch which I just uploaded to bugtracker (http://bugs.freepascal.org/view.php?id=28755). However, I did not create a new selection from the union rectangle because this new selection may contain previously unselected cells. If the "copy" operation belongs to a "cut" operation then these unselected cells will be deleted. I also did not use your idea to mark unselected cells by a label like "#" because the clipboard can be used in other applications, and it is certainly not good to have to delete these labels afterwards.

Once the patch is applied it will be possible to copy/cut/paste multiple selections in the StringGrid by using CTRL+C/CTRL+X/CTRL+V, or by calling CopyToClipboard, CutToClipboard, PasteFromClipboard. When pasting into the grid, I should mention that empty cells are not pasted by default, i.e. an empty cell in the clipboard does not erase an existing grid cell. If this is not desired the option coPasteEmptyCells must be added to the new ClipboardOptions of the grid.

I am not 100% happy with the pasting operation because of the empty cells issue and because the pasted cells sometimes appear to be misplaced if the original selection was made in a weird order. But I don't so a better way without introducing a special clipboard format.

Also, I played with the possibility to extend the grid if the pasted cells exceed the current ColCount/RowCount, but postponed it because it opens another box of issues: what will be the title of the newly added columns? what will be their widths? Plus some more.

 

TinyPortal © 2005-2018