Datenmodel Design für Objekthierarchie

Alles rund um die Programmierung mit Qt
Antworten
Sourcerer
Beiträge: 5
Registriert: 29. August 2014 13:34

Datenmodel Design für Objekthierarchie

Beitrag von Sourcerer »

Hallo Qt-Gemeinde,

erstmal ein freudiges "Hallo allerseits", da dies mein erster Beitrag ist.

Zu meiner Frage:

Ich implementiere gerade ein Datenprotokoll, welches aus verschiedenen fixen Datenstrukturen besteht.
Hier ein beispielhafter Aufbau:

Code: Alles auswählen

class value {};

class specialvalue1 : public value {};
class specialvalue2 : public value {};
class specialvalue3 : public value {};
class specialvalue4 : public value {};

class inner_structure {};

class special_inner_structure1 : public inner_structure {};
class special_inner_structure2 : public inner_structure {};
class special_inner_structure3 : public inner_structure {};

class protocol {};

--------------------------------------------

protocol
|- special_inner_structure1
|  |- specialvalue1
|  |- special_inner_structure2
|  |  |- specialvalue3
|  |- specialvalue2
|
|- special_inner_structure3
   |- specialvalue1
   |- specialvalue4
Die erste ebene in protocol ist dynamisch. Es können verschiedene und unbestimmt viele Einträge kommen. Ab der zweiten Ebene sind die Strukturen pro Nachrichtentyp fix. Innerhalb der Klassen werden die Member natürlich über die jeweiligen Basisklassen-Pointer verwaltet.
Nun will ich das Protokoll bei eingehenden und erstellten Nachrichten visualisieren.
Dazu habe ich vor von QAbstractItemModel abzuleiten. Nun meine Verständnis-Schwierigkeit: Soll ich hier nur die übergeordnete protocol Klasse von QAbstractItemModel ableiten und die Daten von dort aus aus den Objekten holen? Das ist programmtechnisch jedoch sehr aufwändig, da es viele verschiedene Unterstrukturen gibt, die zwar die gleiche Basisklasse (Parse() Serialize()) haben aber sehr verschiedene Aufbaustrukturen. Oder ist es möglich/besser jede der Basisklassen von QAbstractItemModel abzuleiten und nur das protocol Objekt an das View zu übergeben und die QModelIndex Objekte aus den untergeordneten QAbstractItemModel über dessen Interface zu holen? Dadurch wären die Strukturen der untergeordneten Objekte gekapselt und die Implementierung würde sich vereinfachen, da ja jede Klasse selbst weiß wie sie aufgebaut ist.
Wie kann man so etwas vernünftig mit Qt Mitteln umsetzen?

Ich hoffe die Fragestellung ist verständlich. Es gibt noch keinen aussagekräftigen Code, da ich mich erst um das Design kümmern will.
Schonmal Danke für die Hilfe.

Gruß Sourcerer.
nebler
Beiträge: 51
Registriert: 9. Juli 2009 15:26

Re: Datenmodel Design für Objekthierarchie

Beitrag von nebler »

Hallo Sourcerer,

ich für meinen Teil würde vorschlagen, die Datenhaltung und die UI Sachen getrennt zu halten.

Code: Alles auswählen

class MyProtocolModel : public QAbstractItemModel { 
public:
  MyProtocolModel(ProtocolBase*);
/*...*/ 
private:
  ProtocolBase* _protocol;
};
In den einzelne implementierten Views von Qt gibt es auch immer eine Methode "setRootIndex(QModelIndex)", um nur ein Sub-Baum/-View auf das Model zu erhalten.
Sprich Dein Model kann die Ganze Welt beinhalten und über "setRootIndex(QModelIndex)" nur Dein Protokoll-Subtree darstellen ;)

Wenn keine Notwendigkeit besteht ein eigenes Model zu implementieren, könntest Du auch einfach mit einem QStandardItemModel oder einem QTreeWidget Dein Baum aufbauen.
(Empfiehlt sich allerdings nur, wenn es nur eine View sein soll und keine Datensychronisation stattfinden muss zw. UI und Protokoll-Objekte.)

Hilft Dir das etwas weiter?

Viele Grüße
Sourcerer
Beiträge: 5
Registriert: 29. August 2014 13:34

Re: Datenmodel Design für Objekthierarchie

Beitrag von Sourcerer »

Hallo nebler,

ich stimme zu, dass Datenhaltung und UI getrennt werden sollen. Darum auch Model/View.
Eine Datensynchronisation zwischen Model und View brauche ich, da über das UI auch Protokoll-Frames erstellt werden sollen.

Wie bereits beschrieben ist der Aufbau des Protokolls folgendermaßen:
Es gibt ein protocol Objekt. Dieses stellt einen Übertragungsframe dar (Name vielleicht etwas ungünstig gewählt :roll: ). Dieses protocol Objekt enthält mehrere message Objekte (inner_structure). Diese wiederum enthalten mehrere Werte (int, bool, ...) und auch ganze Strukturen.
Es handelt sich um das SML Protokoll, wenn das bekannt ist. Kommt aus dem Stromzähler Bereich.

Das Problem ist nur, das protocol Objekt kennt seine untergeordneten message Objekte nicht, nur die Basisklasse. Die message Objekte haben für die internen Strukturen auch nur die Basisklasse.
Wenn ich jetzt nur die protocol Klasse von QAbstractItemModel ableite, müsste ich durch RTTI oder ähnliches die gesamten Bäume auswerten und die Werte herausholen.
Mein Gedanke ist: Wenn ich alle Klassen von QAbstractItemModel ableite, müsste ich doch über dieses Interface die Daten aus den Objekten bekommen ohne sie kennen zu müssen.

Eine View kommt ja mal mit einer QModelIndex Instanz zum Model und will Daten oder Kindelemente. Nachdem der Pointer in der QModelIndex-Instanz ein void* ist, weiß ich ja nicht welche Klasse wirklich dahinter steckt. Kann eine message sein oder eine Struktur in einer message.

Da ich mit Qt sowas noch nicht aufgebaut habe, weis ich nicht ob eine View mit quasi verschachtelten QAbstractItemModel Instanzen umgehen kann.

Gruß Sourcerer
nebler
Beiträge: 51
Registriert: 9. Juli 2009 15:26

Re: Datenmodel Design für Objekthierarchie

Beitrag von nebler »

Hey,

ich kenne das Protokoll nicht.
Also die Protokoll-Klassen hast Du erstellt?

Bzgl. einer Multi-View, nein das gibt es bei Qt nicht.
Ich bin noch auf der Suche nach so etwas, siehe: http://www.qtforum.de/forum/viewtopic.php?f=1&t=16547

Du kannst es natürlich so erstellen, dass Du eine Master "QAbstractItemView" hast, die einfach die Abfragen an die richtigen "QAbstractItemView" weiter leitet.
Bzgl. dem void* im QModelIndex, wenn Du den Index erstellst, weißt Du ja welche Klasse drin steckt.
Empfehlenswert wäre natürlich eine Basis-Klasse zu verwenden mit einem geeigneten Interface.
Oder halt "QAbstractItemView", dann einfach "static_cast< QAbstractItemView* >( index.internalPointer() )".

Statt ein RTTI Mechanismus zu verwenden, bekommst Du es vllt. auch durch eine geschickte Ableitung und Interface-Klassen hin.
Im Zweifel (was ich allerdings nicht schön finde):

Code: Alles auswählen

if( dynamic_cast< SpecificA* >( baseObject ) != NULL ) {
specifcAFunction();
} else if( dynamic_cast< SpecificB* >( baseObject ) != NULL ) {
specificBFunction();
}
Viele Grüße
Sourcerer
Beiträge: 5
Registriert: 29. August 2014 13:34

Re: Datenmodel Design für Objekthierarchie

Beitrag von Sourcerer »

Hallo,

ja, die Protokoll Klassen habe ich erstellt, diese kommen aber aus einem Code für Microcontroller. Ich habe den Code soweit angepasst, dass er mit Qt funktioniert.

Es geht mir nicht um eine Multi-View oder so. Als View werden die Qt vorgefertigten verwendet.

Mein Problem liegt im Model. Eine gemeinsame Basisklasse wäre natürlich gut, dafür unterscheiden sich die einzelnen Ableitung aber zu stark. Es gibt ja für die Elemente bereits Basisklassen. Diese beschränken sich aber auf Serialize() und Parse(). Für Zugriff auf die Daten ist eine Basisklasse in diesem Fall ungeeignet.

Daher hätte ich QAbstractItemModel als gemeinsame Basisklasse für alle Klassen in dem Protokoll verwendet.
Das ganze sieht in etwa so aus (bin schon etwas weiter gekommen):

Code: Alles auswählen

class ISmlComplexType : public QAbstractItemModel;       // Basisklasse für komplexe SML Typen
class CSmlListEntry : public ISmlComplexType;            // SML Listeneintrag
{
   ...
}
class CSmlList : public ISmlComplexType                  // SML Liste
{
   ...
   QList<CSmlListEntry*> _listEntryList;
   ...
}

class ISmlInnerMessage : public QAbstractItemModel;      // Basisklasse für SML Messages
class CSmlOpenRequest : public ISmlInnerMessage;         // Open Message
{
   ...
}
class CSmlCloseRequest : public ISmlInnnerMessage;       // Close Message
{
   ...
}
class CSmlProfileListRequest : public ISmlInnerMessage   // ListRequest Message
{
   ...
   CSmlList *_smlList;
   ...
}

class CSmlMessage : public QAbstractItemModel            // Klasse welche eine ISmlInnerMessage beinhaltet plus einige andere Werte
{
   ...
   ISmlInnerMessage *_innerMessage;
   ...
}


class CSmlFile : public QAbstractItemModel               // Klasse, welche den Übertragungsrahmen für die Messages bildet
{
   ...
   QList<CSmlMessage> _messageList;
   ...
}
Die Klassen sind nicht vollständig, das Prinzip bleibt aber.
An ein TreeView als Beispiel wird dann die Instanz der CSmlFile Klasse übergeben. Kann die View dann mit diesem Klassenaufbau umgehen wenn ich die jeweiligen Kind Elemente als QAbstractItemModel Instanz verwende?
Andere Ableitungshierarchien sind aufgrund des Protokolls eher kontraproduktiv.

Gruß
nebler
Beiträge: 51
Registriert: 9. Juli 2009 15:26

Re: Datenmodel Design für Objekthierarchie

Beitrag von nebler »

Hey,

ja sie kann dann damit umgehen, wenn Du das Interface korrekt implementierst.
Allerdings wäre evtl. auch das "Double-Dispatcher" Pattern eine mögliche Lösung. (So heißt das glaube ich ^^)

Code: Alles auswählen

class ISmlComplexType  {
public:
...
virtual void buildTree( QTreeWidget* treeWidget, QTreeWidgetItem* currentRoot ) = 0;
...
};

class CSmlList : public ISmlComplexType {
public:
...
virtual void buildTree( QTreeWidget* treeWidget, QTreeWidgetItem* currentRoot ) {
  foreach( CSMListEntry* entry, _listEntryList ) {
    QTreeWidgetItem* itm = new QTreeWidgetItem( "List Eintrag <n>" );
    entry->buildTree( treeWidget, itm );
  }
};
..
Also ich sehe das so, entweder Du hast ordentlich Aufwand ein geeignetes "Master-"QAbstractItemModel zu implementieren,
oder Du baust den Baum einfach über so eine Interface-Methode (s.o.) auf - auf Basis von vorhandenen Klassen.

Ich würde Dir den Weg empfehlen, welcher Dir einfacher von der Hand geht. :-)

Oder schreibe ich komplett an Deine Frage vorbei?!?

Wünsche Dir einen schönen Abend.

Viele Grüße
Sourcerer
Beiträge: 5
Registriert: 29. August 2014 13:34

Re: Datenmodel Design für Objekthierarchie

Beitrag von Sourcerer »

Hallo,

ich habe ein paar Versuche gemacht. Dazu habe ich folgende Klassen gebaut:

Code: Alles auswählen

class SecondLevel : public QAbstractItemModel
{
      Q_OBJECT
   public:
      SecondLevel(QString string1, quint32 int1, quint32 int2, QAbstractItemModel *parentModel, QObject *parent = 0);
      ~SecondLevel();

      // QAbstractItemModel interface
   public:
      QModelIndex index(int row, int column, const QModelIndex &parent) const;
      QModelIndex parent(const QModelIndex &child) const;
      int rowCount(const QModelIndex &parent) const;
      int columnCount(const QModelIndex &parent) const;
      bool hasChildren(const QModelIndex &parent) const;
      QVariant data(const QModelIndex &index, int role) const;

   private:
      QAbstractItemModel *_parentModel;
      QString _string1;
      quint32 _int1;
      quint32 _int2;
};

class FirstLevel : public QAbstractItemModel
{
      Q_OBJECT
   public:
      FirstLevel(QString string1, QString string2, quint32 int1, QAbstractItemModel *parentModel, QObject *parent = 0);
      ~FirstLevel();

      // QAbstractItemModel interface
   public:
      QModelIndex index(int row, int column, const QModelIndex &parent) const;
      QModelIndex parent(const QModelIndex &child) const;
      int rowCount(const QModelIndex &parent) const;
      int columnCount(const QModelIndex &parent) const;
      bool hasChildren(const QModelIndex &parent) const;
      QVariant data(const QModelIndex &index, int role) const;

   private:
      QAbstractItemModel *_parentModel;
      QList<QAbstractItemModel*> internList;
      QString _string1;
      QString _string2;
      quint32 _int1;
};

class RootLevel : public QAbstractItemModel
{
      Q_OBJECT
   public:
      RootLevel(QObject *parent = 0);
      ~RootLevel();

      // QAbstractItemModel interface
   public:
      QModelIndex index(int row, int column, const QModelIndex &parent) const;
      QModelIndex parent(const QModelIndex &child) const;
      int rowCount(const QModelIndex &parent) const;
      int columnCount(const QModelIndex &parent) const;
      bool hasChildren(const QModelIndex &parent) const;
      QVariant data(const QModelIndex &index, int role) const;

   private:
      QList<QAbstractItemModel*> internList;
};
Nach diesem Schema hätte ich mir das vorgestellt. So dass die Klassen untereinander nur als QAbstractItemModel bekannt sind. Bei den parent Methoden bekomme ich dann Probleme, da ich den Index des des parent objekts nicht erstellen kann, weil ich nicht weiß in welcher row der parent in seinem parent ist.

Entweder geht das nicht oder ich blicks im Moment einfach nicht. Wie gesagt, ich arbeite das erste Mal mit MVC von Qt.

Wenn das so nicht umsetzbar ist, halte ich mich an das Beispiel was ich gefunden habe: http://qt-project.org/doc/qt-4.8/itemvi ... model.html.
Mit etwas umdesignen der Klassen und den Anregungen von nebler sollte das dann funktionieren.

Gruß Sourcerer
nebler
Beiträge: 51
Registriert: 9. Juli 2009 15:26

Re: Datenmodel Design für Objekthierarchie

Beitrag von nebler »

Hallo Sourcerer,

eine mögliche Lösung wäre die folgende:

Code: Alles auswählen

class MyAbstractItemModel : public QAbstractItemModel {
public:
   QModelIndex createMySpecificIndex( int row, int col, MyAbstractItemModel* m ) const {
      return _masterModel->createIndex( row, col, m );
   }

   // ermittle die Zeilennummer (vom parent)
   int getRow() const {
      for(int i=0; i < _parent->getChilds().count(); ++i) {
         if( _parent->getChilds().at( i ) == this ) {
            return i;
         }
      }
      return -1; // Fehlschlag
   }

   MyAbstractItemModel* getParent() const { return _parent; }
 
   const QList< MyAbstractItemModel * >& getChilds() const { return _childs; }

   QModelIndex index(int row, int column, QModelIndex parent) {
       return createMySpecificIndex( row, column, this );
    }

   QModelIndex parent(QModelIndex index) {
     MyAbstractItemModel* my = static_cast< MyAbstractItemModel* >( index.internalPointer() );
     return createMySpecificIndex( my->getParent()->getRow(), index.column(), my->getParent() );
   }

private:
  MyAbstractItemModel* _masterModel; // Dieses Model ist der View zugeordnet
  MyAbstractItemModel* _parent;
  QList< MyAbstractItemModel* > _childs;
};

class RootLevel : public MyAbstractItemModel { /**/ };
class FirstLevel : public MyAbstractItemModel { /**/ };
class SecondLevel : public MyAbstractItemModel { /**/ };
Also Du solltest schon OOP nutzen. In jedem Level das gleiche zu implementieren macht kein Sinn.
Copy&Paste ist auch nicht gerade schön.

Was mir gerade einfällt, es kann natürlich sein, dass das gar nicht so funktioniert.
Das "createIndex(...)" erstellt ein ModelIndex welches das QAbstractItemModel Pointer speichert.
Dieser wird bei der View und an ein paar anderen Stellen geprüft ob der übergebener Index mit dem der View übereinstimmt.

Der Code oben ist ungetestet und soll nur das Schema ungefähr klar machen, wie Du Deine Idee umsetzen könntest.

Wünsche Dir viel Erfolg.

Viele Grüße
Antworten