Parametrisieren von Komponenten



  • Hi,

    vielen Dank für eure Tips.

    akari schrieb:

    Die Ereignisse OnCreate und OnDestroy sollten in C++ nicht verwendet werden, sondern stattdessen der Konstruktor und der Destruktor.

    Das mit OnCreate war mir nicht bekannt. Wenn ich auf eine Komponente zum Initialisieren zugreifen will, die zur Zeit des Konstruktor-Aufrufes aber noch nicht existiert, ist dann das OnShow Ereignis was ich zum Initialisieren nehmen muss ?

    Th69 schrieb:

    Ich selber erstelle fast alle meine Komponenten dynamisch und habe mir für die gängigen Controls Hilfsfunktionen geschrieben à la

    TControl* Create<TControl>(TControl* parent, int left, int top, int width, int height);
    // bzw. davon spezialisierte, z.B.
    TLabel* CreateLabel(TControl* parent, int left, int top, int width, int height, String caption);
    

    P.S. Anstatt Left, Top, Width, Heigth einzeln zuzuweisen solltest du besser die Methode SetBounds(...) benutzen.

    Super, danke auch für den Hinweis mit SetBounds() !



  • Hallo

    Ronco11 schrieb:

    Das mit OnCreate war mir nicht bekannt. Wenn ich auf eine Komponente zum Initialisieren zugreifen will, die zur Zeit des Konstruktor-Aufrufes aber noch nicht existiert, ist dann das OnShow Ereignis was ich zum Initialisieren nehmen muss ?

    Wenn eine Komponente beim Initialisieren im Konstruktor wichtig ist, dann sollte sie dann auch zu diesem Zeitpunkt existieren. ::OnShow ist nicht zum einmaligen Initialisieren gedacht, es ist möglich das es in der Lebenszeit einer Instanz mehrmals aufgerufen wird. Es ist ein klarer Konzeptfehler, wenn du solche Verbiegungen machst.

    bis bald
    akari



  • akari schrieb:

    Wenn eine Komponente beim Initialisieren im Konstruktor wichtig ist, dann sollte sie dann auch zu diesem Zeitpunkt existieren. ::OnShow ist nicht zum einmaligen Initialisieren gedacht, es ist möglich das es in der Lebenszeit einer Instanz mehrmals aufgerufen wird. Es ist ein klarer Konzeptfehler, wenn du solche Verbiegungen machst.

    Hmm, vielleicht hab ich mich falsch ausgedrückt. Die Komponente ist auf dem Form schon vorhanden, aber im C'tor kann ich noch nicht darauf zugreifen, wenn ich dieses Beispiel aus der FAQ (mit Labels und ComboBoxen) zum Initialisieren beim Programmstart (und nicht mit einem Button) ausführen will:

    void __fastcall TForm1::Button1Click(TObject *Sender) 
    { 
        for(int i=0; i < Form1->ComponentCount; i++) 
        { 
            // Alle Labels verändern 
            if (Form1->Components[i]->ClassNameIs("TLabel")) 
            { 
                dynamic_cast<TLabel*>(Form1->Components[i])->Caption = "Alle Labels auf gleichen Wert setzen"; 
            } 
    
            // oder eine spezielle Behandlung 
            if (Form1->Components[i]->ClassNameIs("TLabel")) 
            { 
                if (dynamic_cast<TLabel*>(Form1->Components[i])->Name == "Label1") 
                    dynamic_cast<TLabel*>(Form1->Components[i])->Caption = "Ausnahme Label1"; 
            } 
    
        } 
    }
    


  • Mal ohne Kontext etwas Stilkritik, wenn du erlaubst:

    Ronco11 schrieb:

    if (Form1->Components[i]->ClassNameIs("TLabel"))
    

    Besser:

    if (dynamic_cast<TLabel*> (Components[i]) != 0)
    

    Das achtet auch auf abgeleitete Klassen und ist außerdem schneller und genauer.

    Und innerhalb von Formularmethoden solltest du das globale Form1 -Objekt nicht verwenden.

    Ronco11 schrieb:

    dynamic_cast<TLabel*>(Form1->Components[i])->Caption
    

    Und wenn der dynamic_cast<> nun 0 zurückgibt, z.B., weil es noch ein anderes TLabel gibt, das aber nicht mit deinem TLabel identisch ist? Dann bekommst du irgendwo eine Zugriffsverletzung und weißt nicht wieso.
    Immer das Ergebnis von dynamic_cast<> überprüfen; oder den dynamic_cast<> mit Referenzsemantik verwenden, der wirft im Zweifelsfall eine Exception. Und wenn du dir absolut sicher bist, daß der Cast erfolgreich sein wird (etwa wenn du die oben vorgeschlagene Variante der Typprüfung via dynamic_cast<> verwendest), dann kannst du auch static_cast<> nehmen.

    Wenn das so in den FAQ steht, dann müssen die dringend geändert werden.

    Ansonsten ist mir nicht klar, wieso derartige Initialisierungen im Konstruktor nicht möglich sein sollen.



  • Man kann das Ergebnis des dynamic_cast auch zwischenspeichern. Dann wird es noch einfacher.

    void __fastcall TForm1::Button1Click(TObject *Sender)
    {
        for(int i=0; i < ComponentCount; i++)
        {
            TLabel* label = dynamic_cast<TLabel*>(Components[i]);
            // Alle Labels verändern
            if (label)
            {
                label->Caption = "Alle Labels auf gleichen Wert setzen";
            // oder eine spezielle Behandlung
                if (label->Name == "Label1")
                    label->Caption = "Ausnahme Label1";
            }
        }
    }
    


  • audacia schrieb:

    Und innerhalb von Formularmethoden solltest du das globale Form1 -Objekt nicht verwenden.

    Danke für den Tip. Genau das war das Problem. Nachdem ich Form1 rausgenommen habe, gab es keine Zugriffsverletzung mehr.

    Verstanden habe ich es allerdings noch nicht. Warum funktioniert der gleiche Code

    for(int i=0; i < Form1->ComponentCount; i++)
    // ..
    

    (mit Form1) im OnShow ohne Zugriffsverletzung, aber im C'tor ausgeführt gibt es eine EAccessViolation Zugriffsverletzung 😕 .



  • Hallo

    Weil im Konstruktor von TForm1 der globale Zeiger Form1 noch gar nicht auf die richtige Speicheradresse gesetzt ist, das passiert erst nach dem Konstruktor.
    Ich rate sogar generell von der Verwendung der globalen Zeiger ab, die sind nur für die Bequemlichkeit von Anfängern.

    bis bald
    akari



  • OK, danke hab ich jetzt verstanden. Ist das so richtig umgesetzt, die FAQ Version und die von Braunstein ?

    void __fastcall TForm1::Button2Click(TObject *Sender)
    {
     for ( int i=0; i < ComponentCount; ++i )
    	{
    		// Alle Labels verändern
    		if ( dynamic_cast<TLabel*>(Components[i]) != 0 )
    		  {
    		   if ( dynamic_cast<TLabel*>(Components[i])->Caption != 0 )
    			  dynamic_cast<TLabel*>(Components[i])->Caption = "Alle Labels auf gleichen Wert setzen";
    		  }
    
    		// oder eine spezielle Behandlung
    		if ( dynamic_cast<TLabel*>(Components[i]) != 0 )
    		  {
    			if ( dynamic_cast<TLabel*>(Components[i])->Name == "Label1" )
    			   dynamic_cast<TLabel*>(Components[i])->Caption = "Ausnahme Label1";
    		  }
    	}
    }
    //---------------------------------------------------------------------------
    
    void __fastcall TForm1::Button3Click(TObject *Sender)
    {
     for ( int i=0; i < ComponentCount; ++i )
    	{
    	 if ( dynamic_cast<TLabel*>(Components[i]) != 0 )
    	   {
    		TLabel* label = dynamic_cast<TLabel*>(Components[i]);
    		// Alle Labels verändern
    		if ( label )
    		  {
    		   label->Caption = "Alle Labels auf einen anderen Wert setzen";
    		   // oder eine spezielle Behandlung
    		   if ( label->Name == "Label2" )
    			  label->Caption = "Ausnahme Label2";
    		  }
    	   }
    	}
    }
    //---------------------------------------------------------------------------
    

    Die Version von Braunstein finde ich da übersichtlicher.



  • Ja, fast 😉

    Bei der 2. Version kannst du die Zeile

    if ( dynamic_cast<TLabel*>(Components[i]) != 0 )
    

    komplett löschen (da du das ja in der folgenden Zeile nochmals abfragst!)

    Braunstein hat diese Abfrage ja auch nicht mehrfach in seinem Code ausgeführt.

    Und ja, die 2. Variante sollte man auf jeden Fall benutzen (also nicht mehrfach den dynamic_cast<> für denselben Typen aufrufen. Bei Abfrage von mehreren Typen müssen die dynamic_cast<> natürlich entsprechend oft ausgeführt werden).



  • audacia schrieb:

    Ronco11 schrieb:

    dynamic_cast<TLabel*>(Form1->Components[i])->Caption
    

    Immer das Ergebnis von dynamic_cast<> überprüfen;

    Hm, dann hab ich den Hinweis von audacia nicht richtig verstanden 😕



  • audacia meinte sicherlich, dass du bei jedem neuen dynamic_cast abfragen solltest. Wenn du immer wieder die gleiche Variable castest machen weitere Tests keinen Sinn.
    Allerdings sollte man nur einmal casten und dann mit dem erhaltenen Wert weiterarbeiten. So ganz billig ist ein dynamic_cast nämlich nicht.
    Lies am besten mal was darüber.



  • akari schrieb:

    Weil im Konstruktor von TForm1 der globale Zeiger Form1 noch gar nicht auf die richtige Speicheradresse gesetzt ist, das passiert erst nach dem Konstruktor.

    Genaugenommen passiert es tatsächlich vor dem Konstruktoraufruf. In Application.CreateForm() wird zu einem sehr ungewöhnlichen Trick gegriffen, um das zu ermöglichen:

    procedure TApplication.CreateForm(InstanceClass: TComponentClass; var Reference);
    var
      Instance: TComponent;
    begin
      ...
      Instance := nil;
      try
        ...
        Instance := TComponent(InstanceClass.NewInstance); // <-- Allokation
        TComponent(Reference) := Instance; // <-- Zuweisung der globalen Variable
        try
          Instance.Create(Self); // <-- Konstruktoraufruf
        except
          TComponent(Reference) := nil;
          raise;
        end;
        ...
    end;
    

    Deswegen hatte ich die Fehlerursache dort eigentlich auch nicht vermutet. Bei einem einfachen Test dieser Art

    __fastcall TForm1::TForm1(TComponent* Owner)
    	: TForm(Owner)
    {
    	Form1->Caption = "FooBar";
    }
    

    funktionierte bei mir auch alles wie erwartet.

    Wenn das Formular natürlich nicht durch Application.CreateForm(), sondern mit new() erstellt wird, ist das anders.

    Braunstein schrieb:

    So ganz billig ist ein dynamic_cast nämlich nicht.

    Im Prinzip ist das richtig, aber für Delphi-Klassen (d.h., alles, was von TObject erbt) ist es tatsächlich sehr billig; im Idealfall ist es nur ein einziger Zeigervergleich. Dies ist der Fall, weil für Delphi-Klassen keine Mehrfachvererbung unterstützt wird; daher muß der Objektzeiger nicht justiert werden, und die Liste der Vorfahren ist tatsächlich nur eine Liste und kein Graph.



  • Bezüglich Delphi hast du natürlich recht. Ist der dynamic_cast dann hier so implementiert, dass er zwischen Delphi-Klassen und C++ Klassen unterscheidet?
    Gibt es irgendwo Informationen wie genau der dynamic_cast beim BCB implemetiert ist?



  • Braunstein schrieb:

    Bezüglich Delphi hast du natürlich recht. Ist der dynamic_cast dann hier so implementiert, dass er zwischen Delphi-Klassen und C++ Klassen unterscheidet?

    Ja. dynamic_cast<> benötigt RTTI, um Klassen zur Laufzeit identifizieren zu können, und die ist für Delphi- und C++-Klassen völlig unterschiedlich.

    Braunstein schrieb:

    Gibt es irgendwo Informationen wie genau der dynamic_cast beim BCB implemetiert ist?

    Du kannst es selbst nachlesen in $(BDS)\source\cpprtl\Source\except\xxtype.c .



  • Danke.


Anmelden zum Antworten