[SOLVED] TDrawGrid - multiselection with CTRL? lazarus / 31-LCL ---------------------------------------------------------------------- Dibo · 2015-01-02 22:18 Hi, I'm playing with draw grid options. There is range select with SHIFT but can I select custom rows with CTRL like in TListView multi select property? Regards ---------------------------------------------------------------------- engkin · 2015-01-03 03:21 Based on a quick search for ssCtrl in lcl\grids.pas, the answer is no. ---------------------------------------------------------------------- wp · 2015-01-03 10:42 Yes you can... In trunk, we recently added a RangeSelectMode property which, if set to rsmMulti, allows to select several cell blocks by by means of the ctrl key - see the screenshot in http://bugs.freepascal.org/view.php?id=27000. If option goRowSelect is set this works also with entire rows. ---------------------------------------------------------------------- Dibo · 2015-01-03 11:51 Great news wp . Do you know when it will be in stable version? ---------------------------------------------------------------------- wp · 2015-01-03 12:12 Target version in bugtracker is 1.2.8. Since 1.4 is scheduled for this year I guess 1.28 will be out pretty soon. ---------------------------------------------------------------------- Dibo · 2015-01-03 13:16 Indeed, tried lazarus from SVN and grid has multi selection. Great. Problem solved ---------------------------------------------------------------------- mdalacu · 2015-09-25 12:05 Hi, range selctmode rsmulti is not working as expected by me. Code: [Select] if StringGrid1.SelectedRangeCount >= 1 then         begin           for i:=1 to StringGrid1.SelectedRangeCount do            begin              ShowMessage(IntToStr(StringGrid1.SelectedRange[i].TopLeft .X)+':'+IntToStr(StringGrid1.SelectedRange[i].TopLeft.Y)+' - '+IntToSt r(StringGrid1.SelectedRange[i].BottomRight.X)+':'+IntToStr(StringGrid1 .SelectedRange[i].BottomRight.Y));            end;         end;It shows the same range for every iteration. Is the code wrong? Thx. ---------------------------------------------------------------------- wp · 2015-09-25 13:23 QuoteIs the code wrong?No idea, the code you posted seems to be ok, but I don't know what else you are doing. I just rechecked with Win 7, Laz 1.5, fpc 2.6.4, and the multiselection is doing fine -- see attached demo: Run, click any cell to create the first selection as shown in the memo at the bottom, CTRL-click at another cell to create the second selection etc. SHIFT-click extends the current selection. The selection boundaries are displayed in the memo at the bottom. What is your operating system? ---------------------------------------------------------------------- mdalacu · 2015-09-28 08:49 Thank you very much for this example. My problem is that if you click multiple times the same cell it will add a new selection and does not merge selections. Is this the intended mode? Please see the attached screenshot. Thank you. ---------------------------------------------------------------------- wp · 2015-09-28 09:02 I think this is related to the bug that I reported some time ago (http://bugs.freepascal.org/view.php?id=27437). ---------------------------------------------------------------------- mdalacu · 2015-09-28 09:31 Quote from: wp on September 28, 2015, 11:02:49 amI think this is related to the bug that I reported some time ago (http://bugs.freepascal.org/view.php?id=27437). Thank you. ---------------------------------------------------------------------- wp · 2015-09-28 20:49 The bug that I mentioned above is fixed in trunk now. But as I see it does not fix the behavior that you mentioned earlier today. Thinking about your observation that multiple clicks onto the same cells are added as new selections I am a bit uncertain if this a bug or if this could be used as a feature in some cases. What exactly is disturbing you (except for the few bytes of wasted memory)? ---------------------------------------------------------------------- mdalacu · 2015-09-29 08:13 Quote from: wp on September 28, 2015, 10:49:15 pmThe bug that I mentioned above is fixed in trunk now. But as I see it does not fix the behavior that you mentioned earlier today. Thinking about your observation that multiple clicks onto the same cells are added as new selections I am a bit uncertain if this a bug or if this could be used as a feature in some cases. What exactly is disturbing you (except for the few bytes of wasted memory)? I want to add a copy (CTRL-C) function to the string grid which add to clipboard all (not only the last one as implemented) selections...but only once. ---------------------------------------------------------------------- wp · 2015-09-29 09:54 But again: what is the problem (except for waising a few bytes of memory) that the same cell is in the clipboard multiple times? ---------------------------------------------------------------------- mdalacu · 2015-09-29 10:03 Quote from: wp on September 29, 2015, 11:54:38 amBut 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.... ---------------------------------------------------------------------- derek.john.evans · 2015-09-29 10:08 Quote from: mdalacu on September 29, 2015, 12:03:23 pmQuote from: wp on September 29, 2015, 11:54:38 amBut 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 · 2015-09-29 10:43 QuoteThe 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. Quotethe documentation  is sketchy on rsmmultipleNothing 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; ---------------------------------------------------------------------- derek.john.evans · 2015-09-29 12:30 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function StringGridSelection(const AStringGrid: TStringGrid): TRect;var  LIndex: Integer;begin  Result := AStringGrid.Selection;  for LIndex := 0 to AStringGrid.SelectedRangeCount - 1 do begin    with AStringGrid.SelectedRange[LIndex] do begin      Result.Left := Min(Result.Left, Left);      Result.Top := Min(Result.Top, Top);      Result.Right := Max(Result.Right, Right);      Result.Bottom := Max(Result.Bottom, Bottom);    end;  end;end; 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);begin  if (AKey = VK_C) and (AShift = [ssModifier]) then begin    (ASender as TStringGrid).Selection := StringGridSelection(ASender as TStringGrid);  end;end; 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function StringGridSelectionAsCSV(const AStringGrid: TStringGrid): String;var  LCol, LRow: Integer;  LRect: TRect;  LString: String;begin  Result := EmptyStr;  LRect := StringGridSelection(AStringGrid);  for LRow := LRect.Top to LRect.Bottom do begin    if Result <> EmptyStr then begin      Result += LineEnding;    end;    LString := EmptyStr;    for LCol := LRect.Left to LRect.Right do begin      if LString <> EmptyStr then begin        LString += #9;      end;      if AStringGrid.IsCellSelected[LCol, LRow] then begin        LString += AStringGrid.Cells[LCol, LRow];      end else begin        LString += '#';      end;    end;    if LString <> EmptyStr then begin      Result += LString;    end;  end;end; procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AStrings: TStrings);var  LCol, LRow: Integer;  LSelection: TRect;begin  LSelection := StringGridSelection(AStringGrid);  LSelection.BottomRight := LSelection.TopLeft;  with TStringList.Create do begin    try      for LRow := 0 to AStrings.Count - 1 do begin      Text := AnsiReplaceStr(AStrings[LRow], #9, LineEnding);        for LCol := 0 to Count - 1 do begin          if Strings[LCol]<> '#' then begin            AStringGrid.Cells[LSelection.Left + LCol, LSelection.Top + LRow] := Strings[LCol];          end;          LSelection.Right := Max(LSelection.Right, LSelection.Left + LCol);        LSelection.Bottom := Max(LSelection.Bottom, LSelection.Top + LRow);        end;      end;    finally      Free;    end;  end;  AStringGrid.Selection := LSelection;end; procedure StringGridLoadSelected(const AStringGrid: TStringGrid; const AText: String);var  LStrings: TStrings;begin  LStrings := TStringList.Create;  try    LStrings.Text := AText;    StringGridLoadSelected(AStringGrid, LStrings);  finally    FreeAndNil(LStrings);  end;end; Note: the '#' used for cells that are not selected in the union selection rectangle. And then intercept Copy/Paste using: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i procedure TForm1.StringGrid1KeyDown(ASender: TObject; var AKey: word; AShift: TShiftState);begin  if (AKey = VK_C) and (AShift = [ssModifier]) then begin    ClipBoard.AsText := StringGridSelectionAsCSV(ASender as TStringGrid);    AKey := 0;  end;  if (AKey = VK_V) and (AShift = [ssModifier]) then begin    StringGridLoadSelected( ASender as TStringGrid, ClipBoard.AsText);    AKey := 0;  end;end; 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. ---------------------------------------------------------------------- wp · 2015-09-29 20:30 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.