PDA

View Full Version : How to create a "node" using QObject



cryomicl
9th May 2017, 08:28
I have been tasked with modifying the GUI of a software, specifically I need to add a third node under "PORT-1" and "PORT-2" called "CONNECTIONS" . Now I haven't worked in Qt before and really would like some guidance.
Image: https://i.stack.imgur.com/QG0LE.jpg

From the start I was told that every node would have its own class and header, in which bespoke functionality could be added. I just need to show an empty "CONNECTIONS" node for the time being.

I went ahead and created the following header and class file:

Header: FCConnections.h

#ifndef FCCONNECTIONS_H
#define FCCONNECTIONS_H
#include <QObject>
#include "FCInterface.h"

class CFCConnections: public CResourceItem
{

Q_OBJECT
Q_PROPERTY(QString Name READ fnGetName)
public:
CFCConnections(QObject*);
~CFCConnections();


public:
CFCInterface* pParent;
int fnGetName();
};
Q_DECLARE_METATYPE(CFCConnections*);

#endif

Class: FCConnections.cpp


#include "FCConnections.h"
#include "FCInterface.h"
#include "other headers"


CFCConnections::CFCConnections(QObject* parent) : CResourceItem(parent)
{
pParent = (CFCInterface*) parent;

}
CFCConnections::~CFCConnections()
{

}

int CFCConnections:: fnGetName()
{

return 3;
}


The function ***fnGetName*** is just to demonstrate how I would get **READ** to work in **Q_PROPERTY**.

The main "**FCASM**" node is controlled by a class called "**FCInterface**". Now how would I go about making the "**CONNECTIONS**" node? Do I make a function in **FCInterface** or **FCConnections**? What function can I use?

The code block for the constructor of FCinterface is :

CFCInterface::CFCInterface(QObject* parent): CInterface(parent), CFCAbstract()
{
fnSetProperty("objectName",QString("%1").arg("FCASM"));
fnSetInterfaceType( "FCASM" );
fnSetResourceBaseType(m_enDatabase);
fnSetResourceItemType(m_enInterface);
fnLoadDependentInfo();

}

I realise this is a broad question but any suggestion would be welcome.

d_stranz
9th May 2017, 16:47
The function ***fnGetName*** is just to demonstrate how I would get **READ** to work in **Q_PROPERTY**.

Except that the property is a QString, not an int, so this won't actually work. There is no built-in C++ conversion from int to QString.


specifically I need to add a third node under "PORT-1" and "PORT-2" called "CONNECTIONS" .

"Under" meaning what, exactly? As a child node of either PORT-1 or PORT-2, or as a sibling of PORT-1 and PORT-2 (i.e. a child of "FCASM")?

Please be careful that you are not confusing the map for the terrain, in other words, do not confuse the GUI picture of what is displayed on screen (the tree view = "map", a picture of the data) with the underlying data structure that is used to build that view (the "terrain"). These are different things. You can model your data as a hierarchical set of QObject-based classes. You could do exactly the same thing using XML, read into a DOM document. Or in plain old C++.

So it looks like there are rules for what can be added at each level of your tree. An "FCASM" node can have only "ports" as children, for example. I hope that your tree structure has been defined such that every node inherits from the same C++ base class (CResource, maybe? or CFCAbstract?)

If "connections" are supposed to be children of "ports", then I would add a virtual function to this base class:




virtual bool CFCAbstract::addChild( CFCAbstract * pChild );


In each derived class, you can perform a dynamic cast (for example) to ensure that "pChild" is a pointer to one of the types you allow as a child of that node at that level of the tree. If it is, you add it and return true, otherwise you do nothing and return false.

So if "connections" are supposed to be children of "ports", then this code:



bool CFCPort::addChild( CFCAbstract * pChild )
{
CFCConnection * pConn = dynamic_cast< CFCConnection * >( pChild );
if ( pConn != nullptr )
{
// add connection child
return true;
}
return false;
}


If all of your classes are QObject-based, then you could use qobject_cast< CFCConnection * > instead of dynamic_cast<>.

cryomicl
12th May 2017, 04:57
@d_stranz
Thank you for the reply!!
"Connections" is a sibling of "PORT-1" and "PORT-2". So, if I understood the code you wrote correctly,the following should work:

bool CFCInterface::addChild( CFCAbstract * pChild )
{
CFCConnection * pConn = qobject_cast< CFCConnection * >( pChild );
if ( pConn != nullptr )
{
// add connection child
return true;
}
return false;
}

Could you explain what you meant by the "Add connection child" comment?

d_stranz
12th May 2017, 05:52
Could you explain what you meant by the "Add connection child" comment?

It means "write the code here that you need to insert the child node into whatever data structure you are using to hold the information in the tree you are building".

If your CFCAbstract hierarchy is all QObject-based (i.e. CFCAbstract inherits from QObject) then this could be as simple as:



bool CFCInterface::addChild( CFCAbstract * pChild )
{
CFCConnection * pConn = qobject_cast< CFCConnection * >( pChild );
if ( pConn != nullptr )
{
pConn->setParent( this );
return true;
}
return false;
}

But if I were doing this, I would implement a customized tree model that stored this structure independently of the view, then wrap it into a QAbstractItemModel for display in a QTreeView. See the Qt Simple Tree Model example (https://doc.qt.io/qt-5/qtwidgets-itemviews-simpletreemodel-example.html).

To translate that example to yours, "TreeItem" is replaced everywhere by "CFCAbstract", and all of the specific types derive from the CFCAbstract base class (which itself no longer derives from QObject; it's just an ordinary C++ class). The appendChild() method is what I called "addChild()" above, and would have to be made a virtual method and reimplemented in each class derived from CFCAbstract in order to implement your rules for what can be added where.

cryomicl
12th May 2017, 07:56
@d_stranz
Thank you so very much!! As you can see, my knowledge in Qt is fairly limited. But with the help of supportive experts like you, people like me will surely learn!

cryomicl
15th May 2017, 08:39
@d_stranz
I tried implementing the code, but there was no change in the GUI. By using breakpoints I found that the qobjectcast was returning a null pointer (pConn ==nullptr). Could you take a stab at why this could possibly be happening?

d_stranz
15th May 2017, 18:33
Is the CFCConnection class derived from QObject somewhere in its inheritance hierarchy? Is the Q_OBJECT macro present in the class definition in the header file? Is MOC being run on the header file?

cryomicl
16th May 2017, 04:38
Is the CFCConnection class derived from QObject somewhere in its inheritance hierarchy? Is the Q_OBJECT macro present in the class definition in the header file? Is MOC being run on the header file?

Yes, yes and yes.

The code to demonstrate the first two points:

#include <QObject> //Included QObject
#include "ResourceItem.h"
#include "MonWindow.h"
#include "FCTab.h"
#include "ResourceItem.h"
#include "FCAbstract.h"
#include "FCInterface.h"

class CFCConnections: public CResourceItem
{

Q_OBJECT //QObject MACRO

public:
CFCConnections(QObject*);
~CFCConnections();

private:
CFCInterface* pParent;


};


#endif

That was the FCConnections.h code. And I know that MOC has run on the header file because I had to manually change its build step to include MOC. This image below shows the inheritance hierarchy. CFCConnections is derived from CResourceItem which has QObject as its base class.
12471

POSSIBLE SOLUTION

I just had to change the line

CFCConnections* f_pConn=0;

to

CFCConnections* f_pConn;
f_pConn = new CFCConnections(this);

d_stranz
16th May 2017, 17:45
I just had to change the line

So you mean to say you were trying to add a NULL pointer to an instance of CFCConnection to your hierarchy? Of course that won't work. qobject_cast<> won't magically turn a NULL or uninitialized pointer into a real one, it turns a real pointer to some QObject-based class into a pointer to another QObject-based class if the conversion is valid (that is, the type you are trying to cast to inherits from the type that is passed in).

cryomicl
17th May 2017, 04:54
Of course that won't work. qobject_cast<> won't magically turn a NULL or uninitialized pointer into a real one.

Now I know this from experience, the best way to never forget a lesson.
Thank you for all your help