Search code examples
c++c++buildervcl

Passing a TForm as an argument to a function


I have an application with several Forms. Two of them are quite similar, they have features in the form of VCL objects (labels, images, etc...) in common, which I named the same. I want to have a function in a specific class which can accept one of these two Form as a parameter in order to modify the parameters that they have in common. The solution I came around does not seem to work.

As my application is quite big and complicated, I replicated the problem using a small example. First, below is an example of my MainForm :

enter image description here

And an example of one subForm (they are all arranged in a similar way)

enter image description here

I have an additionnal class which is used to fill in the Edits on the subForms. The code for this class is the following:

#pragma hdrstop

#include "master_class.h"
#include "sub_Form2.h"
#include "sub_Form3.h"
#include "sub_Form4.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)

Master::Master(void)
{

}

Master::~Master(void)
{

 }

 void Master::WriteToForm(TForm* Form)
{
TForm2* curForm = static_cast<TForm2*>(Form);

TForm3* self = dynamic_cast<TForm3*>(Form);
TForm2* self2 = dynamic_cast<TForm2*>(Form);

if (self != NULL && self2 == NULL) {
    TForm3* curForm = static_cast<TForm3*>(Form);
}

else if (self == NULL && self2 != NULL) {
    TForm2* curForm = static_cast<TForm2*>(Form);
}

curForm -> Edit1 -> Text = "blablabla_1";
curForm -> Edit2 -> Text = "blablabla_2";
}

And in the MainForm, the code for the "Fill Form2" button is the following:

Master1 -> WriteToForm(Form2);

where Master1 is just an object of the Master class. This works very well for Form2 :

enter image description here

But for Form3, which is filled up using Master1 -> WriteToForm(Form3), here is what I get, which the same pb than in my real application:

enter image description here

So what should go to the Edit, is misplaced. I think the main pb comes from the fact that I did not create every label, edit, etc... on the same order. I did that on purpose to mimic my real application. To verify this, I created a 3rd subForm, where this time the VCL objects were created in the same order as my first subForm, and this works:

enter image description here

So I would suspect that this comes from the initial cast

TForm2* curForm = static_cast<TForm2*>(Form);

When I pass Form3 as an argument, Form3 is somewhat casted into the "shape" of Form2, which is not defined in the same order. Maybe this could be corrected by modifying directly the DFM file, but it is not a realistic approach for my main app.

I do this initial cast otherwise I get a compilation error saying that curForm is not known at the first line

curForm -> Edit1 -> Text = "blablabla_1";

So, is there a better way to pass the Form as an argument to the WriteToForm function?


Solution

  • Just because two types are similar does not mean they are related. Your code does not work because your two Form classes are not related to each other in any way. You can't just cast one to the other arbitrarily.

    To solve this, you have several options:

    1. code for both Form classes separately, eg:
    void Master::WriteToForm(TForm* Form)
    {
        TForm2* curForm2 = dynamic_cast<TForm2*>(Form);
        TForm3* curForm3 = dynamic_cast<TForm3*>(Form);
    
        if (curForm2)
        {
            curForm2->Edit1->Text = _D("blablabla_1");
            curForm2->Edit2->Text = _D("blablabla_2");
        }
        else if (curForm3)
        {
            curForm3->Edit1->Text = _D("blablabla_1");
            curForm3->Edit2->Text = _D("blablabla_2");
        }
    }
    

    Or:

    void WriteToForm(TForm2* Form);
    void WriteToForm(TForm3* Form);
    
    ...
    
    void Master::WriteToForm(TForm2* Form)
    {
        Form->Edit1->Text = _D("blablabla_1");
        Form->Edit2->Text = _D("blablabla_2");
    }
    
    void Master::WriteToForm(TForm3* Form)
    {
        Form->Edit1->Text = _D("blablabla_1");
        Form->Edit2->Text = _D("blablabla_2");
    }
    
    1. Make your function use a template (however, be aware of this: Why can templates only be implemented in the header file?):
    template<typename T>
    void WriteToForm(T* Form);
    
    ...
    
    void Master::WriteToForm<T>(T* Form)
    {
        Form->Edit1->Text = _D("blablabla_1");
        Form->Edit2->Text = _D("blablabla_2");
    }
    
    1. make the two Form classes derive from a common base class or interface, eg:
    class TBaseForm : public TForm
    {
    public:
        inline __fastcall TBaseForm(TComponent *Owner) : TForm(Owner) {}
        virtual void SetEdit1(const String &Text) = 0;
        virtual void SetEdit2(const String &Text) = 0;
    };
    
    ...
    
    class TForm2 : public TBaseForm
    {
        ...
    public:
        __fastcall TForm2(TComponent *Owner);
        ...
        void SetEdit1(const String &NewText);
        void SetEdit2(const String &NewText);
    };
    
    __fastcall TForm2::TForm2(TComponent *Owner)
        : TBaseForm(Owner)
    {
        ...
    }
    
    void TForm2::SetEdit1(const String &NewText)
    {
        Edit1->Text = NewText;
    }
    
    void TForm2::SetEdit2(const String &NewText)
    {
        Edit2->Text = NewText;
    }
    
    ...
    
    repeat for TForm3...
    
    ...
    
    void Master::WriteToForm(TBaseForm* Form)
    {
        Form->SetEdit1(_D("blablabla_1"));
        Form->SetEdit2(_D("blablabla_2"));
    }
    

    Or:

    __interface INTERFACE_UUID("{E900785E-0151-480F-A33A-1F1452A431D2}")
    IMyIntf : public IInterface
    {
    public:
        virtual void SetEdit1(const String &Text) = 0;
        virtual void SetEdit2(const String &Text) = 0;
    };
    
    ...
    
    class TForm2 : public TForm, public IMyIntf
    {
        ...
    public:
        __fastcall TForm2(TComponent *Owner);
        ...
        void SetEdit1(const String &NewText);
        void SetEdit2(const String &NewText);
    };
    
    __fastcall TForm2::TForm2(TComponent *Owner)
        : TForm(Owner)
    {
        ...
    }
    
    void TForm2::SetEdit1(const String &NewText)
    {
        Edit1->Text = NewText;
    }
    
    void TForm2::SetEdit2(const String &NewText)
    {
        Edit2->Text = NewText;
    }
    
    ...
    
    repeat for TForm3...
    
    ...
    
    void Master::WriteToForm(IMyIntf* Intf)
    {
        Intf->SetEdit1(_D("blablabla_1"));
        Intf->SetEdit2(_D("blablabla_2"));
    }
    
    1. use RTTI to access the fields, eg:
    #include <System.Rtti.hpp>
    
    void Master::WriteToForm(TForm* Form)
    {
        TRttiContext Ctx;
        TRttiType *FormType = Ctx.GetType(Form->ClassType());
    
        TRttiField *Field = FormType->GetField(_D("Edit1"));
        if (Field)
        {
            TValue value = Field->GetValue(Form);
            if( (!value.Empty) && (value.IsObject()) )
            {
                TObject *Obj = value.AsObject();
    
                // Either: 
    
                static_cast<TEdit*>(Obj)->Text = _D("blablabla_1");
    
                // Or:
    
                TRttiProperty *Prop = Ctx.GetType(Obj->ClassType())->GetProperty(_D("Text"));
                if (Prop) Prop->SetValue(Obj, String(_D("blablabla_1")));
            }
        }
    
        Field = FormType->GetField(_D("Edit2"));
        if (Field)
        {
            TValue value = Field->GetValue(Form);
            if( (!value.Empty) && (value.IsObject()) )
            {
                TObject *Obj = value.AsObject();
    
                // Either: 
    
                static_cast<TEdit*>(Obj)->Text = _D("blablabla_2");
    
                // Or:
    
                TRttiProperty *Prop = Ctx.GetType(Obj->ClassType())->GetProperty(_D("Text"));
                if (Prop) Prop->SetValue(Obj, String(_D("blablabla_2")));
            }
        }
    }