0

So I have a string grid with columns. But each column can be deleted and if a column is deleted indexes are rearranged. I can use the value of my index before deletion, but when it comes to delete several columns the indexes aren't the same at all. For example if i delete the column at index 1 and 2, the one that was at index 3 get a new index, index 1.

So what I want to do is to add new methods to my columns where i will set and get the real index, as it was never deleted. I have found a tutorial on how to add new methods to delphi classes and this is how it looks:

unit columnInterceptor

interface

uses stdCtrls, sysUtils, Classes, dialogs, grids;

type
   TStrings = class(Classes.TStrings)
   private
   public
     procedure hello;
   end;

implementation

procedure TStrings.Hello;
begin
    ShowMessage('hello');
end;
end.

This works if i use it to add methods on a StringGrid. But i want to use this on a Column of a stringGrid. I've seen that some methods are coming from the class TStrings or TObject, and i tried them both but the procedure hello doesn't show.

EDIT

Using class helper i managed to have access to my own method and after changing this is how it looks:

unit columnInterceptor

interface
uses stdCtrls, sysUtils, Classes, dialogs, grids;

type
   colIntercept= class helper for TStrings
   public
     procedure setValue(val: integer);
     function getValue: integer;
   end;

implementation
var 
  value : integer;


procedure colIntercept.setValue(val: integer);
begin
    value := integer;;
end;
function colIntercept.getValue: integer;
begin
    Result := value;
end;
end.

Thing is that if i add a private statement i can't use my methods anymore which are declared in public statement. And when i set a value it's actually the same for all columns. This is how i uses this class:

//somewhere in the unit where  create all the columns
grid.Cols[aCol].setValue(aCol);

//somewhere in the unit
grid.Cols[aCol].getValue

And then all the value for any column is always the same. When i'm setting my values they are different each time. But getting them, returns me always the last value i inserted using setValue method.

Cœur
  • 37,241
  • 25
  • 195
  • 267
user28470
  • 419
  • 5
  • 17
  • 1
    [What are good uses for class helpers](http://stackoverflow.com/questions/253399/what-are-good-uses-for-class-helpers) – bummi Sep 24 '14 at 07:13
  • my unit is now defined as class helper for TStrings. And for accessing it i'm using grid.Cols[0].hello which doesn't work but it's how i want to use it; – user28470 Sep 24 '14 at 07:27
  • 1
    Rather then deleting columns, why not just hide them instead? Then the indexes don't change. Look at the `ColWidths[]` property. – Remy Lebeau Sep 24 '14 at 08:21
  • 1
    Would you accept an answer which would show that storing an index with the Cols is possible, but won't help since "the content" is moving through the cols? – bummi Sep 24 '14 at 09:55
  • Use a TListView in virtual mode. This will helop you separate your data and your GUI control and allow you to make the GUI control be simply a view of the data. – David Heffernan Sep 24 '14 at 10:45

2 Answers2

0

The problem is that StringGrid.Cols isn't a TStrings but a TStrings descendants.

You could Try the following code:

ShowMessage(StringGrid1.Cols[0].Classname);

Then you'll find out the real class is a TStringGridStrings

So this leads us to the solution:

write a classhelper for TStringGridStrings

type
  TStringGridStringsHelper = class helper for TStringGridStrings
    procedure SayHello;
  end;

{ TStringGridStringsHelper }

procedure TStringGridStringsHelper.SayHello;
begin
  ShowMessage('Hello from TStringGridStringsHelper');
end;

But STILL you can't write

StringGrid1.Cols[0].SayHello;

You have to typecast it:

TStringGridStrings(StringGrid1.Cols[0]).SayHello;

The problem are that StringGrid only exposes it's columns as TStrings and not the correct datatype TStringGridStrings so that's why you have to type cast it

=====================

An other solution could be using an inspector class (http://delphi.about.com/od/delphitips2009/qt/interceptor.htm) but some say that's bad code:

Any how here is the example:

unit Unit6;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, Grids;

type
  TStringGridStringsHelper = class helper for TStringGridStrings
    procedure SayHello;
  end;

  TStringGrid = class(Grids.TStringGrid)
  strict private
    function GetCols(Index: Integer): TStringGridStrings;
    function GetRows(Index: Integer): TStringGridStrings;
    procedure SetCols(Index: Integer; const Value: TStringGridStrings);
    procedure SetRows(Index: Integer; const Value: TStringGridStrings);
  public
    property Cols[Index: Integer]: TStringGridStrings read GetCols write SetCols;
    property Rows[Index: Integer]: TStringGridStrings read GetRows write SetRows;
  end;

  TForm6 = class(TForm)
    StringGrid1: TStringGrid;
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form6: TForm6;

implementation

{$R *.dfm}

uses
  Math;

{ TStringGridStringsHelper }

procedure TStringGridStringsHelper.SayHello;
begin
  ShowMessage('Hello from TStringGridStringsHelper');
end;

procedure TForm6.FormCreate(Sender: TObject);
begin
  StringGrid1.Rows[0].SayHello;
end;

{ TMyStringGrid }

function TStringGrid.GetCols(Index: Integer): TStringGridStrings;
begin
  Result := inherited Cols[Index] as TStringGridStrings;
end;

function TStringGrid.GetRows(Index: Integer): TStringGridStrings;
begin
  Result := inherited Rows[Index] as TStringGridStrings;
end;

procedure TStringGrid.SetCols(Index: Integer; const Value: TStringGridStrings);
begin
  inherited Cols[Index] := Value;
end;

procedure TStringGrid.SetRows(Index: Integer; const Value: TStringGridStrings);
begin
  inherited Rows[Index] := Value;
end;

end.
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Jens Borrisholt
  • 6,174
  • 1
  • 33
  • 67
  • This works but it returns me the same value for all columns. Check my Edit in my question pls. I think all the columns uses the exast same instance of my TStringGridStringsHelper and idk how i can do it so they can use their own instance of TStringGridStringsHelper :/ – user28470 Sep 24 '14 at 08:06
  • So you want a diffent result depending on the value of the Row or Cell? – Jens Borrisholt Sep 24 '14 at 08:11
  • Yes exactly. Sorry for my first explanation it wasn't clear enough. In fact when i'm creating my columns i want to set the index at this time so when i delete it i can do grid.cell[aCol].getIndex and this should return the index of my column that was before any other deletion. – user28470 Sep 24 '14 at 08:15
  • I still dont follow you. Culyd you add some pseudocode? When you are inside the implementation of TStringGridStringsHelper.SayHello; you have access to it's content – Jens Borrisholt Sep 24 '14 at 08:17
  • I want to do this cause i'm working with those indexes in an array which keeps the data even if you delete a column so i can make an undo button and undo any actions. But when i'm checking an index of a column next to the deleted one, the index changes cause it's rearanged. Idk if i explain clearly what i'm trying to do :/ – user28470 Sep 24 '14 at 08:18
  • 1
    You could save the original index in the object Strings.Objects[i] – Jens Borrisholt Sep 24 '14 at 08:24
  • huh, sometimes i'm searching way too far. Thank you ^^ – user28470 Sep 24 '14 at 08:28
0

Regarding to your edit, you have got one variable so any access to your helper will read/write on this.

Although it is possible to save the index of a Col with a helper class , it does not really help because the "content" of a col is "moved" to another one.
Internal cols is a casted Pointer to an entry in an TSparsePointerArray of a TSparseList

Below standing code is not meant to build such a helper as a recommendation, but for illustrative purposes only.

implementation
uses system.generics.collections;

{$R *.dfm}
type
   THackGrid=Class(TCustomGrid);



  TSLDict=Class(TDictionary<TStrings,Integer>)
  End;

  TStringsHelper = class helper for TStrings
   private
    class var Dict:TSLDict;
    function GetIndex: Integer;
    procedure SetIndex(const Value: Integer);
   public
    CLASS Procedure FreeDict;
    Property Index:Integer Read GetIndex write SetIndex;

  end;



{ TStringsHelper }

CLASS procedure TStringsHelper.FreeDict;
begin
  FreeAndNil(Dict);
end;

function TStringsHelper.GetIndex: Integer;
begin
   if not assigned(Dict) then Dict:=TSLDict.Create;
   if not Dict.TryGetValue(self,Result) then Result := -1;

end;

procedure TStringsHelper.SetIndex(const Value: Integer);
begin
   if not assigned(Dict) then Dict:=TSLDict.Create;
   Dict.AddOrSetValue(self,Value);
end;


procedure TForm7.DeleteAColClick(Sender: TObject);
var
 I:Integer;
begin
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,0] := IntToStr(i);
      StringGrid2.Cells[i,0] := IntToStr(i);
      StringGrid1.Cols[i].Index := i;

  end;

  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,1] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid1.Cells[i,2] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8);
      StringGrid2.Cells[i,1] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid2.Cells[i,2] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8);

  end;

  THackGrid(StringGrid1).DeleteColumn(2);
  THackGrid(StringGrid1).DeleteColumn(1);
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,3] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid1.Cells[i,4] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8)
  end;
end;


procedure TForm7.MoveAColClick(Sender: TObject);
var
 I:Integer;
begin
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,0] := IntToStr(i);
      StringGrid2.Cells[i,0] := IntToStr(i);
      StringGrid1.Cols[i].Index := i;
  end;

  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,1] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid1.Cells[i,2] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8);
      StringGrid2.Cells[i,1] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid2.Cells[i,2] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8);
  end;

 THackGrid(StringGrid1).MoveColumn(0,3);
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,3] := IntToStr(StringGrid1.Cols[i].Index);
      StringGrid1.Cells[i,4] := '$'+IntToHex(Integer(StringGrid1.Cols[i]),8)
  end;
end;


procedure TForm7.MoveFirstAndDeleteThenClick(Sender: TObject);
var
 I:Integer;
 // we want to delete Col 1
begin
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,0] := IntToStr(i);
      StringGrid1.Cols[i].Index := i;
  end;

  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,1] := IntToStr(StringGrid1.Cols[i].Index);
  end;

  THackGrid(StringGrid1).MoveColumn(1,StringGrid1.ColCount-1);
  THackGrid(StringGrid1).DeleteColumn(StringGrid1.ColCount-1);
  for I := 0 to StringGrid1.ColCount - 1 do
  begin
      StringGrid1.Cells[i,2] := IntToStr(StringGrid1.Cols[i].Index);
  end;
end;

initialization
finalization
 TStrings.FreeDict;

The result if we delete Col 1 and Col 2:

enter image description here

bummi
  • 27,123
  • 14
  • 62
  • 101
  • It seems it answers the question. The only thing is that in my case i have a huge string grid and each time i would have to move more than 1000 lines from one col to another and then if the user clicks undo i will have to move it back and the undeleted column will have to be repopulated. Right now i used the trick from on of the comentators. I'm hiding the delted collumn so indexes doesn't change. And when an user undo i just show back the hidden column. Anyway, thank you for the time token to answer, i will mark this as an answer cause it answers the question. Thank you :) – user28470 Sep 24 '14 at 12:06