Recent

Author Topic: How to install a new property on a TGridColumn component?  (Read 700 times)

Al3

  • New Member
  • *
  • Posts: 18
How to install a new property on a TGridColumn component?
« on: June 28, 2022, 05:41:10 am »
How do I add new properties to the columns of a TStringGrid, which are created dynamically?

I want to create a "datatype" property to each column object so that each column has its own virtual type instead of just displaying strings, but I am new to the languages and I only found how to install properties on a TForm.

Handoko

  • Hero Member
  • *****
  • Posts: 4712
  • My goal: build my own game engine using Lazarus
Re: How to install a new property on a TGridColumn component?
« Reply #1 on: June 28, 2022, 10:05:21 pm »
That will require too much work. Basically, you can add a new property to a class but in this case you're trying add a property to a class that used by TStringGrid. Simply adding some code for TGridColumn, which is the class that handle column in the grid is not enough. You need to modify the code for TGridColumns and then TStringGrid.

So I think you need to copy/paste to code of TStringGrid, TGridColumns and TGridColumn to a new module or file and make necessary changes by inheriting them to newer classes. And they should be named differently, maybe TStringGridEx, TGridColumnsEx and TGridColumnEx.

Perhaps the easiest is to write a child class inherited from TStringGrid, add a new ColumnEx property and write necessary code on how ColumnEx will affect the data to show on the Paint and/or PrepareCanvas calls. This solution is less integrated into all the string grid internal functions.

wp

  • Hero Member
  • *****
  • Posts: 10030
Re: How to install a new property on a TGridColumn component?
« Reply #2 on: June 28, 2022, 10:55:53 pm »
I would not use a TStringGrid, but a TDrawGrid. The difference between them is that the latter does not store any data. This way you can freely choose the data type per column (in TStringGrid, there's no way to get around the strings...).

The first task is how to extend the DrawGrid such that its columns have a new property DataType. Define a new class, e.g. TDataGrid:
Code: Pascal  [Select][+][-]
  1. type
  2.   TDataGrid = class(TDrawGrid)
  3.   ...

Its basic ancestor, TCustomGrid, has a method CreateColumns which creates the columns of the grid as type TGridColumns. Being a TCollection, the TGridColumns can specify the type of the items in the collection in the constructor:
Code: Pascal  [Select][+][-]
  1. function TDataGrid.CreateColumns: TGridColumns;
  2. begin
  3.   Result := TGridColumns.Create(self, TDataGridColumn);
  4. end;

The original code of TCustomGrid has TGridColumn here as last parameter, we replace it by a descendant TDataGridColumn and thus are able to create the column items as instances of a new class with a new property, DataType:
Code: Pascal  [Select][+][-]
  1. type
  2.   TDataType = (dtString, dtInteger, dtFloat, dtDate);  // change as needed
  3.  
  4.   TDataGridColum = class(TGridColumn)
  5.   private
  6.     FDataType: TDataType;
  7.   published
  8.     property DataType: TDataType read FDataType write FDataType;
  9.   end;

Now, the new TDataGrid contains columns with a DataType property.

But that's only the easiest part of the story...

Inherited from TDrawGrid, we must enable the new grid to draw its cell content. The pre-requisite for this is that the grid "knows" how to find the data. Are they in a 2D-Array of variant? Or are they an array of records in which each record contains the data types defined by the new columns? Is the data storage independed of the grid, or part of it? Then you must override the DefaultDrawCell method which which must extract the cell value from the data storage for the col/row index provided, and draw it. You must override the SetEditText and GetEditText methods to invoke the cell editor for entering or editing the cell values. And probably a lot more...

If you look at the code of the TsWorksheetGrid of the fpspreadsheet package (https://wiki.lazarus.freepascal.org/FPSpreadsheet, https://wiki.lazarus.freepascal.org/FPSpreadsheet) you can find an example how this could be done. BTW: it started small like in above basic description, but now has grown to a monster of more than 7000 lines...

It's a good exercise in component writing, but definitely not the best starting point for a novice.
« Last Edit: June 28, 2022, 11:06:08 pm by wp »

Al3

  • New Member
  • *
  • Posts: 18
Re: How to install a new property on a TGridColumn component?
« Reply #3 on: June 29, 2022, 01:47:00 am »
I figured it has to be something harder than usual.
It of course wouldn't worth the effort and I should probably find a different way to do that such as associating the column object with a set or something?
In CGTK component objects can be associated with arbitrary data which internally uses a hash table.
I wouldn't learn how to write components just when I started using OP again  :D
Also while TStringGrid displays string cells, I should be able to easily (re)format them based on the column data type once I find out how to associate it.
Another probably simpler, but less convenient way is to just grow an array dynamically depending on the number of columns and store data there.
Of course I have 0% confidence in coding as of now, but I think that's what I would've done in C.

Handoko

  • Hero Member
  • *****
  • Posts: 4712
  • My goal: build my own game engine using Lazarus
Re: How to install a new property on a TGridColumn component?
« Reply #4 on: June 29, 2022, 07:53:07 am »
There are many possible solutions, writing a child class inherited from TStringGrid, or modify all the column-related classes of TStringGrid, use a TDrawGrid as suggested by wp, even writing a new component from scratch.

Maybe you can draw some sketches showing what you want it to look like and explaining what features you need. So we can suggest you which solution suits your case. I may write it for you if that is not too complicated.

dje

  • Full Member
  • ***
  • Posts: 107
Re: How to install a new property on a TGridColumn component?
« Reply #5 on: June 29, 2022, 07:59:37 am »
Have you looked into TTIGrid? Its a RTTI enabled control derived from TCustomGrid. It seems to fit your requirement of "component objects can be associated with arbitrary data".

edit: It is located in the RTTI tab.

y.ivanov

  • Sr. Member
  • ****
  • Posts: 497
Re: How to install a new property on a TGridColumn component?
« Reply #6 on: June 29, 2022, 08:28:38 am »
I figured it has to be something harder than usual.
It of course wouldn't worth the effort and I should probably find a different way to do that such as associating the column object with a set or something?
It depends on what you're aiming for. If you want to work with the added properties into the form designer it is a bit harder - you should create new component based on TCustomStringGrid or TCustomDrawGrid.

In CGTK component objects can be associated with arbitrary data which internally uses a hash table.
I wouldn't learn how to write components just when I started using OP again  :D
Also while TStringGrid displays string cells, I should be able to easily (re)format them based on the column data type once I find out how to associate it.

FYI, the TStringGrid have a Objects property along with the Cells:

Code: Pascal  [Select][+][-]
  1.     property Cells[ACol, ARow: Integer]: string read GetCells write SetCells;
  2.     property Objects[ACol, ARow: Integer]: TObject read GetObjects write SetObjects;
and you can freely assign them to whatever descendant of TObject or whatever data fits in SizeOf(Pointer) you'd like. Just don't forget to Free them (if they are dynamic) when appropriate, on OnColRowDeleted and when exiting the form.

Another probably simpler, but less convenient way is to just grow an array dynamically depending on the number of columns and store data there.

TGridColumn has a Tag (most classes in LCL have it) and you can use it for encoding the data type:

Code: Pascal  [Select][+][-]
  1. type
  2.   TDataType = (dtString, dtInteger, dtFloat, dtDate);  // change as needed
  3.  
  4.   ...
  5.   Grid.Columns[C].Tag := Ord(dtFloat);
  6.   ...
  7.   if TDataType(Grid.Columns[C].Tag) = dtFloat then ...;

And then you can hook on OnGetEditText/OnSetEditText to convert to/from text in Object[C,R]. Of course you must assign both Objects[C,R] and the text representation of Objects[C,R] in Cells[C,R] initially.

"I'm sorry Dave, I'm afraid I can't do that."
—HAL 9000

Al3

  • New Member
  • *
  • Posts: 18
Re: How to install a new property on a TGridColumn component?
« Reply #7 on: June 29, 2022, 09:49:23 am »
So plenty of options. I wouldn't be able to know which one is optimal. What I do is focus on one and if it works - it works.
Long road to go to be able to review code in terms of performance, readability, room for bugs etc.

RTTI
I haven't checked it, because I wasn't familiar with the RTTI abbreviation. It did not sound like something that I could use.
A quick search reveals that people reported crashes when using it, I don't know what's the case particularly (maybe not self-explanatory to use?), but I also cannot find a documentation about it.
Edit: This looks useful to read: https://wiki.freepascal.org/RTTI_controls

At least I am sure that I won't subclass TStringGrid and I am starting to think this is not even needed.
I don't need that much of an extended functionality.
What I need is columns displaying cells in a certain format, depending on what the user wants them to be.
Hooking cell changes should let me reformat them, but there's one more potentially problematic scenario.
I am 100% sure I want to use OP for this project, but with GTK I can have checkboxes and images in cells.
So I am aiming to have these column types:
- bool (checkbox)
- integer
- real
- string
- color
- image
These will suffice, but I am more inclined to think that I will actually have to use TCustom or TDraw Grids for those. It sounds as if that's the case, but I haven't looked into them yet.

So I think the question remains - what control fits those requirements the most.
It'd be vrry cool to use one that is capable of doing all that for me
« Last Edit: June 29, 2022, 09:56:46 am by Al3 »

Al3

  • New Member
  • *
  • Posts: 18
Re: How to install a new property on a TGridColumn component?
« Reply #8 on: June 29, 2022, 10:00:49 am »
There are many possible solutions, writing a child class inherited from TStringGrid, or modify all the column-related classes of TStringGrid, use a TDrawGrid as suggested by wp, even writing a new component from scratch.

Maybe you can draw some sketches showing what you want it to look like and explaining what features you need. So we can suggest you which solution suits your case. I may write it for you if that is not too complicated.

:)
I never actually offered something like that, but I could tell you what's my idea and if you like it, maybe we could write some code on it together in our spare time?
It's merely something that I want to do for sure, but have no specific time restriction, neither I am in a hurry. So maybe if you like the idea we can share credits on it and write some code that I can also learn from in the meantime. When the program is complete, I will use it a lot in a variety of different applications. Including in games and in gE2.
« Last Edit: June 29, 2022, 10:03:48 am by Al3 »

Handoko

  • Hero Member
  • *****
  • Posts: 4712
  • My goal: build my own game engine using Lazarus
Re: How to install a new property on a TGridColumn component?
« Reply #9 on: June 29, 2022, 05:05:09 pm »
Writing components is a fun thing I like in programming. Tell me what already in your head and it will be more helpful if you can provide some pictures. You can post it here, or if you want you can PM me.

Al3

  • New Member
  • *
  • Posts: 18
Re: How to install a new property on a TGridColumn component?
« Reply #10 on: June 30, 2022, 08:10:02 pm »
I did the latter :)
« Last Edit: June 30, 2022, 08:33:20 pm by Al3 »

 

TinyPortal © 2005-2018