Recent

Author Topic: TStringGrid and cut to clipboard  (Read 4419 times)

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 882
TStringGrid and cut to clipboard
« on: October 26, 2018, 05:36:41 pm »
Today I wanted to add Copy/Cut/Paste via context menu functionality to my program and stumbled over some problems with clipboard implementation in TStringGrid. My program uses TStringGrid to represent some string data. Data is stored in separate data structures, so it should be synced with TStringGrid. And currently I have three problems with cutting data to clipboard:
1) Wrong shortcut. Shift+X instead of usual Ctrl+X. I think, I don't need to explain, why using Shift+X as shortcut is so bad decision? Because I use inplace editor, of course Shift+X triggers it and user can also just erase his data by accident. I think, that it's just bug, so I simply fixed it in sources.
2) There is no way to just copy or cut contents of any cell to clipboard. I.e. call something like CutCell(X, Y). Due to some reasons TStringGrid has only CopyToClipboard method available, that requires selection change, before it's performed. There is no CutToClipboard and PasteFromClipboard methods. So I needed to use Clipboard object from Clipbrd unit directly.
3) TStringGrid doesn't have some way to notify me, when data is cut via shortcut. Yeah, there is OnSetEditorText, that triggers, when data is edited, but it doesn't work for shortcuts. There is OnCellProcess, I use to update data, when paste from clipboard via Ctrl+V happens. But it doesn't make any difference between copy and cut. I would update my internal data on both copy and paste, despite it being useless in waste of CPU cycles in case of copy, but I just can't do it, as data is erased from cell AFTER this event, not before, so it's still there, when OnCellProcess fires and, as I've already said, I can't determine, whether it's copy or cut.

So, I guess, only solution - is to make new control, inherited from TCustomStringGrid, and override some virtual methods, like DoCutToClipboard. Or there is some better solution?
« Last Edit: October 26, 2018, 05:51:33 pm by Mr.Madguy »
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

jamie

  • Hero Member
  • *****
  • Posts: 7764
Re: TStringGrid and cut to clipboard
« Reply #1 on: October 26, 2018, 06:11:50 pm »
I believe you can use a hack and extend the StringGrid class within the same unit.

 Its been a while since I've done this but iirc you create a class based form the TstringGrid class and assign the
already made instance to it or cast over the ready made grid..
 
 Like I said, its been awhile.

The only true wisdom is knowing you know nothing

rvk

  • Hero Member
  • *****
  • Posts: 7045
Re: TStringGrid and cut to clipboard
« Reply #2 on: October 26, 2018, 09:04:05 pm »
I believe you can use a hack and extend the StringGrid class within the same unit.
Yes. I haven't looked at other possible solutions but jamie is talking about what I've shown here:
https://forum.lazarus.freepascal.org/index.php/topic,42850.msg299362.html#msg299362

K155LA3

  • New Member
  • *
  • Posts: 17
Re: TStringGrid and cut to clipboard
« Reply #3 on: October 26, 2018, 11:50:19 pm »
So, I guess, only solution - is to make new control, inherited from TCustomStringGrid, and override some virtual methods, like DoCutToClipboard. Or there is some better solution?

I'm made ExtendedStdCtrls.pas unit (this must added after all units in unit section):

Code: Pascal  [Select][+][-]
  1. unit ExtendedStdCtrls;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. interface
  6.  
  7. uses
  8.   Classes, Windows, SysUtils, Forms, Graphics, LCLType,  Controls, Clipbrd, Grids,  StdCtrls;
  9.  
  10. type
  11.  
  12.   { TStringGrid }
  13.  
  14.   TStringGrid = class(Grids.TStringGrid)
  15.   private
  16.     type
  17.       UnDoRec = record
  18.         R: TRect;
  19.         D: String;
  20.       end;
  21.   private
  22.     var
  23.       UnDoStack: array of UnDoRec;
  24.       UnDoPosition: Integer;
  25.  
  26.   private
  27.     function GetData(const R: TRect): String;
  28.     procedure AddExistState;
  29.   protected
  30.     procedure DoDeleteSelection;
  31.     procedure KeyDown(var Key: Word; Shift: TShiftState); override;
  32.     function GetEditText(ACol, ARow: Longint): string; override;
  33.  
  34.   public
  35.     constructor Create(AOwner: TComponent); override;
  36.     destructor Destroy; override;
  37.     procedure ReDoState;
  38.     procedure UnDoState;
  39.     procedure ClearUnDoStack;
  40.   end;
  41.  
  42.   { TStringGrid }
  43.  
  44.  
  45. implementation
  46.  
  47. { TStringGrid }
  48.  
  49. function TStringGrid.GetData(const R: TRect): String;
  50. var
  51.   i, j, k: LongInt;
  52. begin
  53.   Result := '';
  54.   for i:=R.Top to R.Bottom do
  55.   begin
  56.     for j:=R.Left to R.Right do
  57.     begin
  58.       if Columns.Enabled and (j>=FirstGridColumn) then
  59.       begin
  60.         k := ColumnIndexFromGridColumn(j);
  61.         if not Columns[k].Visible then continue;
  62.         Result := Result + Cells[j,i];
  63.       end
  64.       else
  65.         Result := Result + Cells[j,i];
  66.       if j<>R.Right then Result := Result + #9;
  67.     end;
  68.     if (R.Top <> R.Bottom) or (R.Left <> R.Right) then
  69.       Result := Result + sLineBreak;
  70.   end;
  71. end;
  72.  
  73. procedure TStringGrid.AddExistState;
  74. var
  75.   u: Integer;
  76. begin
  77.   if UnDoPosition < -1 then
  78.   begin
  79.     UnDoPosition := -1;
  80.     Exit;
  81.   end;
  82.  
  83.   u := Length(UnDoStack);
  84.  
  85.   if UnDoPosition >= u then UnDoPosition := u - 1;
  86.  
  87.   Inc(UnDoPosition);
  88.  
  89.   if (UnDoPosition > -1) and (UnDoPosition <= u) then
  90.   begin
  91.     u := UnDoPosition + 1;
  92.   end;
  93.  
  94.   SetLength(UnDoStack, u);
  95.   UnDoStack[UnDoPosition].R := Selection;
  96.   UnDoStack[UnDoPosition].D := GetData(Selection);
  97.  
  98. end;
  99.  
  100. procedure TStringGrid.ReDoState;
  101. var
  102.   TmpStr: String;
  103.   i: Integer;
  104.   R: TRect;
  105. begin
  106.   i := Length(UnDoStack);
  107.   if i < 1 then Exit;
  108.   if UnDoPosition > (i - 2) then Exit;
  109.   Inc(UnDoPosition);
  110.   Selection := UnDoStack[UnDoPosition].R;
  111.   TmpStr := GetData(UnDoStack[UnDoPosition].R);
  112.   if (UnDoStack[UnDoPosition].R.Left = UnDoStack[UnDoPosition].R.Right) and
  113.      (UnDoStack[UnDoPosition].R.Top = UnDoStack[UnDoPosition].R.Bottom) then
  114.     Cells[UnDoStack[UnDoPosition].R.Left, UnDoStack[UnDoPosition].R.Top] := UnDoStack[UnDoPosition].D
  115.   else
  116.     SelectionSetText(UnDoStack[UnDoPosition].D);
  117.   UnDoStack[UnDoPosition].D := TmpStr;
  118.   R.Top := -1;
  119.   R.Bottom := -1;
  120.   R.Left := -1;
  121.   R.Right := -1;
  122.   Selection := R;
  123. end;
  124.  
  125. procedure TStringGrid.UnDoState;
  126. var
  127.   TmpStr: String;
  128.   i: Integer;
  129.   R: TRect;
  130. begin
  131.   i := Length(UnDoStack);
  132.   if i < 1 then Exit;
  133.   if UnDoPosition < 0 then Exit;
  134.   Selection := UnDoStack[UnDoPosition].R;
  135.   TmpStr := GetData(UnDoStack[UnDoPosition].R);
  136.   if (UnDoStack[UnDoPosition].R.Left = UnDoStack[UnDoPosition].R.Right) and
  137.      (UnDoStack[UnDoPosition].R.Top = UnDoStack[UnDoPosition].R.Bottom) then
  138.     Cells[UnDoStack[UnDoPosition].R.Left, UnDoStack[UnDoPosition].R.Top] := UnDoStack[UnDoPosition].D
  139.   else
  140.     SelectionSetText(UnDoStack[UnDoPosition].D);
  141.   UnDoStack[UnDoPosition].D := TmpStr;
  142.   Dec(UnDoPosition);
  143.   R.Top := -1;
  144.   R.Bottom := -1;
  145.   R.Left := -1;
  146.   R.Right := -1;
  147.   Selection := R;
  148. end;
  149.  
  150. procedure TStringGrid.ClearUnDoStack;
  151. begin
  152.   SetLength(UnDoStack, 0);
  153.   UnDoPosition := -1;
  154. end;
  155.  
  156. procedure TStringGrid.DoDeleteSelection;
  157. begin
  158.   if EditingAllowed(Col) then Clean(Selection, []);
  159. end;
  160.  
  161. procedure TStringGrid.KeyDown(var Key: Word; Shift: TShiftState);
  162. var
  163.   R: TRect;
  164.  
  165.   procedure SetSelectionFromClipboard;
  166.   var
  167.     L: TStringList;
  168.     P: PChar;
  169.   begin
  170.     if HasMultiSelection then Exit;
  171.     if EditingAllowed(Col) and Clipboard.HasFormat(CF_TEXT) then
  172.     begin
  173.       L := TStringList.Create;
  174.       try
  175.         L.Clear;
  176.         L.Text := Clipboard.AsText;
  177.         if L.Count > 0 then
  178.         begin
  179.           R := Selection;
  180.           R.Bottom := R.Bottom + L.Count - 1;
  181.           P := Pchar(L[0]);
  182.           if P<>nil then
  183.             while P^<>#0 do
  184.             begin
  185.               if P^ = #9 then Inc(R.Right);
  186.               Inc(P);
  187.             end;
  188.           if R.Bottom >= RowCount then R.Bottom := RowCount - 1;
  189.           if R.Right >= ColCount then R.Right := ColCount - 1;
  190.           Selection := R;
  191.         end;
  192.       finally
  193.         L.Free;
  194.       end;
  195.     end;
  196.   end;
  197.  
  198. begin
  199.   case Key of
  200.     VK_A: //Select all
  201.       if Shift = [ssModifier] then
  202.       begin
  203.         Key := 0;
  204.         R.Top := FixedRows;
  205.         R.Left := FixedCols;
  206.         R.Right := ColCount - 1;
  207.         R.Bottom := RowCount - 1;
  208.         Selection := R;
  209.       end;
  210.     VK_V: //Insert
  211.       if Shift = [ssModifier] then
  212.       begin
  213.         Key := 0;
  214.         SetSelectionFromClipboard;
  215.         AddExistState;
  216.         doPasteFromClipboard;
  217.       end;
  218.     VK_X: //Cut to clipboard
  219.       begin
  220.         if Shift = [ssModifier] then
  221.         begin
  222.           Key := 0;
  223.           AddExistState;
  224.           doCutToClipboard;
  225.         end;
  226.       end;
  227.     VK_Z: //Undo / Redo
  228.       begin
  229.         Key := 0;
  230.         if Shift = [ssModifier] then UnDoState;
  231.         if Shift = [ssAlt] then ReDoState;
  232.       end;
  233.     VK_INSERT: //Copy to clipboard / paste from clipboard
  234.       begin
  235.         Key := 0;
  236.         if Shift = [ssModifier] then DoCopyToClipboard;
  237.         if Shift = [ssShift] then
  238.         begin
  239.           SetSelectionFromClipboard;
  240.           AddExistState;
  241.           doPasteFromClipboard;
  242.         end;
  243.       end;
  244.      VK_DELETE: //Delete / Cut to clipboard
  245.      begin
  246.        Key := 0;
  247.        if Shift = [ssShift] then
  248.        begin
  249.          AddExistState;
  250.          doCutToClipboard;
  251.        end
  252.        else
  253.        begin
  254.          AddExistState;
  255.          DoDeleteSelection;
  256.        end;
  257.      end;
  258.   end;
  259.  
  260.   inherited KeyDown(Key, Shift);
  261. end;
  262.  
  263. function TStringGrid.GetEditText(ACol, ARow: Longint): string;
  264. begin
  265.   AddExistState;
  266.   Result := inherited GetEditText(ACol, ARow);
  267. end;
  268.  
  269. constructor TStringGrid.Create(AOwner: TComponent);
  270. begin
  271.   inherited Create(AOwner);
  272.   ClearUnDoStack;
  273. end;
  274.  
  275. destructor TStringGrid.Destroy;
  276. begin
  277.   ClearUnDoStack;
  278.   inherited Destroy;
  279. end;
  280.  
  281. { TStringGrid }
  282.  
  283. end.

This code add some button reaction (see procedure KeyDown) and undo / redo functional. You can place standard StringGrid component on form (and change standard property in design time) and this code change functional of StringGrid on runtime.

Mr.Madguy

  • Hero Member
  • *****
  • Posts: 882
Re: TStringGrid and cut to clipboard
« Reply #4 on: October 27, 2018, 09:09:57 am »
Yes. I haven't looked at other possible solutions but jamie is talking about what I've shown here:
https://forum.lazarus.freepascal.org/index.php/topic,42850.msg299362.html#msg299362
I just don't want to complicate things. Everything else works just perfectly. So I wanted to avoid TStringGrid modification. But if it's the only way to achieve my goal, then I'll have to do it. But I'll make some researches first. May be there is better way.
Is it healthy for project not to have regular stable releases?
Just for fun: Code::Blocks, GCC 13 and DOS - is it possible?

jamie

  • Hero Member
  • *****
  • Posts: 7764
Re: TStringGrid and cut to clipboard
« Reply #5 on: October 27, 2018, 03:24:21 pm »
what is being discussed here isn't really  changing the StringGrid, just adding to it's behavior in the current sense.

But in your case if what you need to do is alter behavior via keystrokes then way not just use the keystroke events to
set flags that can be tested elsewhere. Place the depress keys in some variable so that it can be examined and maybe even
altered if need be..
The only true wisdom is knowing you know nothing

 

TinyPortal © 2005-2018