views:

570

answers:

4

Dear all, I am starting to learn Delphi. So I decided to write an application like MS Excel from scratch. In a new Form1, I did put a TPageControl component containing only 1 page. In that page, I did put a TAdvStringGrid and a TPanel with some buttons (button1, button2) and a Popup1 menu for defining some actions on the grid, like copy cell, copy row, delete row, etc. For that StringGrid, also, I have defined some properties, like colors, fonts, etc. I added a toolbar with a button to the main form, in order to add more pages to the PageControl. The OnClick method of that button defines two actions:
1) to add a new Page2 to the PageControl, and 2) to add a new StringGrid In the new created Page2 .

That new (runtime defined) StringGrid created in a new Page of a Tpagecontrol should inherite (get, copy, clone, duplicate) the properties and methods of the StringGrid parent already defined in the first page at design time, and should be able to call the PopUp1 menu just like the StringGrid parent. How we do this?

At the beginning, I thought I just should copy the StringGrid properties using assing(), but when using this approach, the popup menu does not pop up when right mouse clicking on the new StringGrid... and the buttons (button1 and button2) of Form1 only work with the first StringGrid but not with the new added one. I did read somewhere that in order to solve this problem, I could duplicate the StringGrid component by using the write and read TMemoryStream (save the parent stringgrid into a memorystream, create a new stringgrid and then read that memorystream into the new created stringgrid), so I did, but when the program executes this component cloning method, I get a runtime error. :-(

I did carefully chech the Help. Nothing found on that topic. Seems there is not any Delphi component library or third party components that cope with this kind of task. Can anybody help, please? :o)

A: 

Dear all, I am trying to learn Delphi

The Delphi style is to find/create/buy a component that does the job and use them in the design-time. You could try making a custom component based on a grid or use TFrame. See links from Custom Component Development and help files that comes with Delphi.

If you really need to clone the control dynamically, here's an example I found that uses stream.ReadComponent.

eed3si9n
Dear eed3si9n,I am so sorry for my ignorance, but what does it mean "subclassing a grid"? I did check the Help in Delphi, I cannot see the link.The method for TMemoryStream in your link is exactly what I did try before asking here in StackOverflow... I use TAdvStringGrid (as suggested by Vegar) but the TMemoryStream does not work with it.
Biolyzer
@Biolyzer, according to Wikipedia "a subclass is a class that inherits some properties from its superclass. [...] Subclasses and superclasses are often referred to as derived and base classes, respectively, terms coined by C++ creator Bjarne Stroustrup."
eed3si9n
+2  A: 

I would use an tabcontrol instead of an pagecontrol. That way, you would end up with multiple tabs but only one page and grid. I would then make some kind of data structure to keep all my cell information in, and render this structure to the grid. This way, I can have multiple structures, and let the active tab decide which structure to render. You will also end up with a looser coupling between your gui and your logic, making it easier change things later. E.g. if you need to bring in some values form a different spreadsheet into a cell in the current spreadsheet, you can load up a structure and pick out the wanted values. No need to make any gui for the second spreadsheet at all.

For a 3.party component, I will recommend TMS FlexCell and TAdvSpreadGrid. That will give you most of what you need.

Vegar
I see. Sounds really an interesting solution. I have to check if the DataStructure rendering does the job for a small grid and for a big grid (lot of columns and rows with data). At RunTime, if we change something in any grid, that change must be mirrored in the DataStructure. That means writing a routine to loop into the DataStructure everytime I make a minor change in the grid? For example, I type something in cell [25,42], so the onclick method of the grid calls the DataStructure, goes findidng the right grid, finding the right cell and writing the value, is that?
Biolyzer
Yup, something like that. It makes it important to have a rather efficient structure underneath, but as long as the sheets ain't to large, there shouldn't be too much trouble. A list of [row-reference, list]-pairs, where each list contains a [column-reference, value] pair should do. If you keep the lists sorted, you will have a fairly fast lookup.A simpler solution would be to convert the cellreference [row, column] to some kind of hash value, and store [hashvalue, cellvalue] pairs in some kind of dictionary.
Vegar
A: 

"...and the buttons (button1 and button2) of Form1 only work with the first StringGrid but not with the new added one. I did read somewhere that in order to solve this problem..."

There is no generic method for solving this. Delphi offers different tools to solve it.

  1. You can create a list of Objects (TObjectList) that containts all the StringGrid; At Button1 Click event you must search what is the grid that you are using at this moment. For example (BIS for the other buttons):


var  
  index:integer;  
  sg:TStringGrid;  
begin  
  ...  
  // search the active page   
  index := pageControl.ActivePageIndex;    //0, 1, 2,...  
  // USe this for search the StringGrid  
  sg := TStringGrid(OList.Objects[index]);  
  // the code that you have at the event bus woking with sg 
  // not stringgrid1, stringgrid2,...
  ...
  sg.Color :=      
  ...


If you don't want use ObjectList, there are alternatives. You can use Tag property for all StringGrids. Assign 0, 1, 2, 3,...
Extract the index (active page) and search the TStringGrid that have the property Tag with the same value. You can do this with FindComponent. The first methos is better. ;-)

Regards. P.D: Excuse for my bad english.

Neftalí
The ObjectList idea and your English are fine :)
Biolyzer
+1  A: 

A tricky choice for a learner :) however you do not need to start streaming things.

Look up the assign() procedure for TPersistent this is the routine you need to copy parts of the grid easily. For example

for i := 0 to StringGrid1.RowCount - 1 do
  StringGrid2.Rows[i].Assign(StringGrid1.Rows[i]);

for an easy start differentiate your grids with the Tag property(StringGrid1.Tag := 1, StringGrid2.Tag := 2 Etc.

The popup menu is pretty simple too:

StringGrid2.popupmenu := stringGrid1.popupMenu But then then you must decide in the Popup Routine Which Grid is "Active" some thing l like

Tform1.popupMenuItem1Click(Sender: TObject)
  if Sender is TStringGrid then
    Case TStrigngGrid(Sender).Tag of
     1: // Grid 1
     2: // Grid 2

You can use the same simple logic with the buttons.

As neftali mentioned the best thing would be to put the created grids in an ObjectList. You would then end up with the slightly more complex but expandabe:

Tform1.popupMenuItem1Click(Sender: TObject)
 var AGrid: TStringGrid;

  if Sender is TStringGrid then
     AGrid := MyListOfStringGrids[MyListOfStringGrids.IndexOf(Sender)];
       DoMenuItem1Stuff(AGrid);

Have fun

Despatcher
This PopupMenuItemClick seems promising and looks like helping to solve part of the problem! Thanks. I will check it right now. About the grids, well, I dont need to copy the grid content. I need to copy some properties and methods of the first grid into a new fresh runtime created grid. If I use the TadvStringGrid, as suggested by Vegar, things goes even worse...
Biolyzer