0

I created a program in C++Builder 6 and I have a problem now.

I have 6 files: Unit1.cpp, Unit1.h, Unit2.cpp, Unit2.h, Unit3.cpp, Unit3.h.

Unit1.cpp is file for main form.

Problem : I want to create in Function void __fastcall TForm3::Button1Click(TObject *Sender) a TStringGrid which will be visible in Unit1.cpp and Unit2.cpp. Next click should create new TStringGrid with new name(previous exist)

I tried to fix my problem, I wrote some code, but it is not enough for me.
In Unit1.h I added:

void __fastcall MyFunction(TStringGrid *Grid1);  

In Unit1.cpp I added:

void __fastcall TForm1::MyFunction(TStringGrid *Grid1)
{
        Grid1 = new TStringGrid(Form2);
        Grid1->Parent = Form2;
}

In Unit3.cpp I added:

#include "Unit1.h"

and the Button click function is:

 void __fastcall TForm3::Button1Click(TObject *Sender)
     {
         Form1->MyFunction(Form1->Grid);  //Grid was declarated previous in Unit1.h
     }

Now when I used this method I dynamically create a TStringGrid, but only one. How do I create as many TStringGrids (with unique names) as the number of times the button is pressed? (Now i must declarate TStringGrid in Unit1.h).

caps
  • 1,225
  • 14
  • 24
john
  • 1
  • 1
  • 2
  • 1
    I suggest you name all of your forms, components, objects, and functions. Code where everything is "ClassName#" is not easy to maintain. I speak from experience with my company's codebase. – caps Dec 15 '14 at 17:16
  • Which `unit.h` are you declaring `StringGrid` in, as you say in "Now i must declarate StringGrind in Unit.h"? – caps Dec 15 '14 at 17:25
  • i declared in Unit 1.h – john Dec 15 '14 at 17:29
  • Okay, so do you want to have a different `TStringGrid` pointer for each one you create? Or do you want to re-use the same `TStringGrid` pointer from `Unit1.h` every time? – caps Dec 15 '14 at 17:34
  • it may be difficult to understand what I wrote, but trying to do so that I can create any number stringgrid with different names, which will be available for others file. e.g Stringgrid created in unit3.cpp, can be modify in Unit1.cpp -> in Unit1.cpp I can write data to rows StringGrid. Every created stringgrid should have diffrent name. – john Dec 15 '14 at 17:39
  • The exact thing you seem to have in mind is not possible, but I will post an answer soon proposing a couple of different options for you. – caps Dec 15 '14 at 17:54
  • As another note, don't put `__fastcall` in a function signatures unless it is required. See this article: http://www.nynaeve.net/?p=63 – caps Dec 15 '14 at 18:00
  • You can set the name by doing `Grid1->Name = "hello";` after creating it – M.M Dec 19 '14 at 05:01
  • It's not clear what `Form1->Grid` is in this code, you never use it. (`MyFunction` does not update `Form1->Grid` since C++ is pass-by-value). – M.M Dec 19 '14 at 05:02
  • C++ is pass-by-value, but since it is a pointer being passed, the pointer can be used to modify the original object. – caps Dec 19 '14 at 07:36

1 Answers1

0

First, note that your code is creating multiple TStringGrids. It is just creating them all with the same dimensions in the same place on the form, so you only see the one on top.

--

What you want to be able to do (Form1->dynamically_created_TStringGrid) is not possible, but there are a couple of methods available to you to get similar behavior.

The std::vector method

Unit1.h

#ifndef Unit1H
#define Unit1H
#include <vector>
//your other includes here

class PACKAGE Form1 : public TForm
{
__published:    //IDE-managed Components
    //your components here
private:
    /.../
    std::vector<TStringGrid *> SGridVec;
public:
    /.../
    AnsiString AddStringGrid(); 
    TStringGrid * GetStringGridByName(const AnsiString &Name);
    TStringGrid * GetStringGridByIndex(const unsigned int Index);
}

Unit1.cpp

AnsiString TForm1::AddStringGrid()
{
    SGridVec.push_back(new TStringGrid(Form2));    //Form2 is owner and handles memory management
    if (SGridVec.back())
    {
        SGridVec.back()->Parent = Form2;
        SGridVec.back()->Name = "some uniquely generated name";
        return SGridVec.back()->Name;
    }
    return "";  //add was unsuccessful
}

TStringGrid * TForm1::GetStringGridByName(const AnsiString &Name)
{
    for(std::vector<TStringGrid *>::iterator sgItr = SGridVec.begin();
        sgItr != SGridVec.end();
        ++sgItr)
    {
        if (*sgItr && (*sgItr)->Name == Name)
        {
            return *sgItr;
        }
    }
    return NULL;  //StringGrid with Name was not found
}

TStringGrid * TForm1::GetStringGridByIndex(const unsigned int Index)
{
    if (Index < SGridVec.size() && SGridVec.at(Index) != NULL)
        return SGridVec.at(Index);
    return NULL;  //StringGrid at Index was not found
}

Using this method you could call AddStringGrid() and store the return value. Then when you wanted to manipulate a given TStringGrid on Form1 you would call GetStringGridByName and pass in the name of the TStringGrid you want to manipulate. You could also implement something very similar with a std::map, even as a public member.

The FindChildControl method

Unit1.h

#ifndef Unit1H
#define Unit1H
#include <map>
//your other includes here

class PACKAGE Form1 : public TForm
{
__published:    //IDE-managed Components
    //your components here
public:
    /.../
    AnsiString AddStringGrid(); 
}

Unit1.cpp

AnsiString TForm1::AddStringGrid()
{
    TStringGrid *temp_ptr = new TStringGrid(Form2);    //Form2 is owner and handles memory management
    if (temp_ptr)
    {
        temp_ptr->Parent = Form2;
        temp_ptr->Name = "some uniquely generated name";
        return temp_ptr->Name;
    }
    return "";  //add was unsuccessful
}

void SomeClass::SomeFunctionThatUsesForm2sTStrinGrids(/.../)
{
    /.../  //some code
    TStrinGrid *temp_ptr = static_cast<TStringGrid *>(Form2->FindChildControl("name of control"));
    if (temp_ptr)
    {
        //do stuff to the string grid
    }
    /.../  //some other code
}

This version basically uses the Parent -> Children relationship to find the dynamically created TStringGrid instead of storing it in a std::vector. My gut says that it is slower and less safe than the std::vector method, but I don't have any proof. It also doesn't offer any simple, reliable way to get at the StringGrids if you "forget" the unique names you gave them, while the std::vector lets you access them by index, or via an iterator if you make one available. There is GetChildren, but it does not look very intuitive to use.

--

At first I thought you would get a memory leak every time you call TForm1::MyFunction, but if I'm understanding the Builder 6 documentation correctly, that is not the case:

The TComponent class also introduces the concept of ownership that is propagated throughout the VCL and CLX. Two properties support ownership: Owner and Components. Every component has an Owner property that references another component as its owner. A component may own other components. In this case, all owned components are referenced in the component’s Array property.

A component's constructor takes a single parameter that is used to specify the new component's owner. If the passed-in owner exists, the new component is added to the owner's Components list. Aside from using the Components list to reference owned components, this property also provides for the automatic destruction of owned components. As long as the component has an owner, it will be destroyed when the owner is destroyed. For example, since TForm is a descendant of TComponent, all components owned by the form are destroyed and their memory freed when the form is destroyed. This assumes that all of the components on the form clean themselves up properly when their destructors are called.

[.pdf page 53]

So even though you are assigning a newed object to Grid1 every time you pass it in to that function, those objects are still owned by Form2 and will be cleaned up when Form2 is destructed.

All that said, you should be aware that, if you stick with the implementation you have posted in the OP, you won't be able to manipulate any of your string grids except for the last one unless you access them from Form2->Array.

caps
  • 1,225
  • 14
  • 24
  • If the answer satisfies you, please accept it by clicking the check-box. You are new here, so maybe you should go through the tour of the site: http://stackoverflow.com/tour – caps Dec 15 '14 at 18:56
  • I only want to create a stringgrid's( to keep data about cars entered by Edit fields) during program is working. Cars are listed in Listbox1 and when i choose one filed and click button i see stringgrid with data for the selected car – john Dec 15 '14 at 18:57
  • Ah, then in fact you do not want to create new `TStringGrid`s at all. I think you want to reuse the same `TStringGrid` but populate it with different data depending on which car is selected. I suggest you read up on the `TStringGrid` documentation to see how its elements can be modified. – caps Dec 15 '14 at 18:59
  • @john: My answer answers the question you posted here. The question as you posted it is not going to draw the answer that your comment implies you are looking for. You will need to post a different question asking how to do that, **if and only if** you cannot figure it out for yourself from looking at the `TStringGrid` documentation. – caps Dec 15 '14 at 19:34
  • i think about only one StringGrid but, I would have to always(when call StringGrid) read from file old date. If i have one stringgrid for one field I will read date only one time when program starts. – john Dec 15 '14 at 19:58
  • You could save the date when you load the file and then load it into the StringGrid from the variable instead of reloading from the file each time. However, if you want to have a separate `TStringGrid` for each car model, I suggest using a `std::map`. The implementation would look similar to what I showed for `std::vector`, but your map would use some kind of key to differentiate the different kinds of car models and display the correct TStringGrid. You'll want to use something like `BringToFront`, `Parent` reassignment, or the Visible property to make the `TStringGrid` visible for the model. – caps Dec 15 '14 at 20:58
  • Having a function return `AnsiString&` and then doing `return NULL;` causes undefined behaviour. The reference must refer to a valid string that exists. In fact the `return temp_ptr->Name;`also causes UB as that is a Property returning by value, so you are returning reference to a temporary object. I'd suggest just returning `TStringGrid *`. – M.M Dec 19 '14 at 05:03