Formulare als template Klassen



  • Morgen zusammen,

    ich hab ein wenig rumprobiert und bin der Meinung, dass das, was ich vorhabe, nicht funktioniert. Kann ich Formularklassen (von TForm abgeleitet) als template Klassen implementieren? Also in der Form

    template<typename T>
    class TMyForm : public TForm
    {
       ...
    };
    

    Der Compiler und Linker laufen durch, aber zur Laufzeit bekomme ich eine Fehlermeldung "Ressource %TMyForm$i%" wurde nicht gefunden, wenn ich TMyForm<int> erzeuge.



  • DocShoe schrieb:

    Kann ich Formularklassen (von TForm abgeleitet) als template Klassen implementieren? Also in der Form

    template<typename T>
    class TMyForm : public TForm
    {
       ...
    };
    

    Der Compiler und Linker laufen durch, aber zur Laufzeit bekomme ich eine Fehlermeldung "Ressource %TMyForm$i%" wurde nicht gefunden, wenn ich TMyForm<int> erzeuge.

    Ich bin nicht sicher, ob eine Ressource unter Windows überhaupt einen derartigen Namen tragen darf.

    Mit einem etwas low-leveligen Eingriff (z.B. Ändern der RTTI zur Laufzeit, Aufrufen von TCustomForm.CreateNew anstelle von TCustomForm.Create und angepaßter Nachbau von InitInheritedComponent() etc. pp.) läßt sich das sicherlich dennoch hinbiegen. Aber vorher wäre es gut, wenn du sagst, was du damit bezweckst 😉 Weshalb muß gerade die Formularklasse selbst ein Template sein, zumal die IDE das ohnehin nicht direkt unterstützt (wie du sicherlich festgestellt haben wirst)?



  • Ich habe eine abstrakte templated Basisklasse, die aus einem bestimmten Datensatztypen einen Wert extrahiert. Von dieser Klasse gibt es 3 konkrete Implementationen, ich nenn´ sie mal TypA, TypB und TypC. Diese DataSources sind in einem Vektor abgelegt, den ich in einem Formular bearbeiten möchte. Aufgaben des Formulars sind a) Neue Datenquelle erzeugen, b) bestehende Datenquelle bearbeiten und c) bestehende Datenquelle löschen. Das Formular muss natürlich wissen, welchen Typ DataSource es bearbeitet, und weil dieser Typ templated ist muss auch die Formularklasse templated sein.

    template<typename DataType>
    struct IDataSource
    {
       Settings Settings_;
    
       virtual ~IDataSource()
       {
       }
    
       virtual double operator()( const DataType& op ) const = 0;
    };
    
    typedef boost::shared_ptr<IDataSource<TypeA> > DataSourceTypeAPtr;
    typedef boost::shared_ptr<IDataSource<TypeB> > DataSourceTypeBPtr;
    typedef boost::shared_ptr<IDataSource<TypeC> > DataSourceTypeCPtr;
    
    typedef std::vector<DataSourceTypeAPtr> DataSourceTypeAVector;
    typedef std::vector<DataSourceTypeBPtr> DataSourceTypeBVector;
    typedef std::vector<DataSourceTypeCPtr> DataSourceTypeCVector;
    
    template<typename DataType>
    class TFormEditDataSources : public TForm
    {
    public:
       typedef IDataSource<DataType> DataSourceType;
       typedef boost::shared_ptr<DataSourceType> DataSourceTypePtr;
       typedef std::vector<DataSourceTypePtr> DataSourcePtrVector;
    
    private:
       DataSourcePtrVector DataSources_;
    
    public:
       TFormEditDataSources( TComponent* Owner, const DataSourcePtrVector& ds ) :
          TForm( Owner ),
          DataSource_( ds )
       {
       }
    };
    

    Das ist der vereinfachte Code, der ganz gut demonstriert, warum ich ein Formular als template haben möchte. Wenn ich die Klasse IDataSource als nicht-template realisieren komme ich mit dem Formular zwar gut hin, aber dafür müsste ich an vielen anderen Stellen redundanten Code schreiben, der verschiedene abgeleitete Klassen separat behandelt. Also entweder drei Formulare (für Typ A, B und C) oder einige Klassen, die jeweils Typ A, B oder C behandeln. Beides zusammen geht wohl nicht.



  • Kannst du generell die Logik von dem Formular trennen? Warum muß das Formular die Implementierungsdetails (z.B. welchen Typ die Elemente des Vektors besitzen) wissen? Weil es den Inhalt des Formulars bestimmt?



  • Weil das Formular eine lokale Kopie der Datenquellen besitzt muss es die Implementierungsdetails kennen. Eine Möglichkeit wäre allerdings, eine weitere Schicht in der Abstraktionshierachie einzufügen, sodass IDataSource ohne template Parameter auskäme. Ich glaube, das gucke ich mir mal in Ruhe an, vielleicht erschlägt das beide Probleme.
    Der Nachteil ist allerdings, dass ich keinen heterogenen Container von Datenquellen bezüglich des Eingabedatentyps mehr habe, da die neue (oberste) Schicht keine Unterscheidung mehr zulässt:

    struct IDataSource
    {
       ...
    };
    
    typedef boost::shared_ptr<IDataSource> DataSourcePtr;
    typedef vector<DataSourcePtr> DataSourcePtrVector;
    
    struct IDataSourceTypeA : public IDataSource
    {
       virtual double operator( const TypeA& op ) const = 0;
    };
    
    struct IDataSourceTypeB : public IDataSource
    {
       virtual double operator( const TypeB& op ) const = 0;
    };
    

    Ich könnte dem Formular jetzt einen Vektor vom Typ DataSourcePtrVector übergeben, müsste aber selbst sicherstellen, dass er nur Objekte vom Typ IDataSourceTypeA oder IDataSourceTypeB enthält. Vorher hat mir der Compiler diese Aufgabe abgenommen, da IDataSource<TypeA> und IDataSourcee<TypeB> in keinem Bezug zueinander stehen und sich nicht in einem Container unterbringen lassen.



  • Ich habe jetzt eher daran gedacht dass Formular von dem Wissen der Daten völlig zu trennen. Man könnte doch die konkreten Daten und deren Verhalten in einer Klasse kapseln und nach außen hin nur eine Fassade dem Formular zeigen, z.B. IDataProvider mit Load(), Save(), Add(), Delete>(). Das Formular besitzt dann nur ein Member IDataProvider. Wie das Teil implementiert ist muß das Form doch nicht wissen oder? Oder muß das Form anhand des Elementtypen entscheiden welches TFrame es einblenden soll?



  • Hallo

    Wenn du den DataProvider ordentlich polymorph machst, brauchst das Form nichts über die Implementation zu wissen. Auch das Frame kann ja durch eine Factory-Methode des DataProviders erzeugt werden.

    bis bald
    akari



  • Interessanter Gedanke 👍

    Ich muss aber mal sehen, wie komplex das Ganze wird, mein oben geposteter Code ist schon stark vereinfacht. Leider ist die Add() Funktionalität recht vielfältig, da die konkrete DataSource Klasse über zwei weitere template Parameter verfügt (8 Adapter, 23 Extraktoren), womit die IDataProvider Klasse auch noch wissen müsste, welche Kombinationen möglich sind und diese dann auch erzeugen. Ich überlege mal laut:

    Der DataPrivoder muss folgende Funktionalität bieten:

    (1) eine Liste von IDs liefern, aufgrund derer konkrete DataSource Objekte erzeugt werden
    (2) eine Methode, die zu einer ID eine Datenquellentyp Bezeichnung zurückgibt
    (3) eine Methode, die die aktuelle Anzahl der existierenden Datenquellen zurückgibt
    (4) eine Methode, die den Namen einer Datenquelle zu einem Index zurückgibt
    (5) eine Methode, die eine Datenquelle anhand einer Datenquellen ID erzeugt
    (6) eine Methode, die eine Datenquelle an einem bestimmten Index bearbeitet
    (7) eine Methode, die eine Datenquelle an einem bestimmten Index löscht

    Methoden 1) und 2) werden benötigt, um dem Benutzer eine Auswahl der zur Verfügung stehenden Datenquellen anzubieten

    Methoden 3) und 4) werden benötigt, um dem Benutzer die von ihm ausgewählten Datenquellen anzuzeigen
    Methoden 5), 6) und 7) werden benötigt, um Datenquellen auszuwählen, zu bearbeiten oder zu löschen

    Danke für den exzellenten Tipp, das werde ich mal implementieren und sehen, ob´s klappt.


Anmelden zum Antworten