PDA

View Full Version : Data Linking and Representation



fatsack
27th November 2016, 16:08
Hey, I'm brand spanking new to Qt and I've been building an application for the past couple of days. I've watched a few videos and read some documentation on how to use some Qt related objects, and all was going well. I've run into a problem now however. I am trying to build a GUI that will allow the user to edit data that's apart of a structure. I'm trying to find the best way to handle this so bare with me while I explain what I am trying to do.

Currently I have a few structures like this, varying in size ( around 70 vars in some ):


typedef struct
{
int health;
int mana;
short stamina;
char active;
int gold;
int level;
int rank;
int group;
float position[ 3 ];
INVENTORY slots[ 5 ];
} PERSON;

Now I have all of this data dynamically allocated, and pointers for that data stored into a std::map. I have around 20 std::maps that have different data, like an item or another object. Currently I am adding a name for each variable into a QStringList, and displaying it in a ListView. I am trying to link the strings like "Health", "Mana", and "Stamina" to the underlying data type in that structure. An example would be the user double clicks on the string, it would open a new window and ask for input. After the user accepts the input, it would update the underlying data variable in that specific structure so that I could easily save the output into a file.

The current program flow I have is once the user pushes a button, a ComboBox fills with a list of all of the indexes. After selecting an index the ListView model is set to a QStringList of variable names. From here I am trying to link the QStringList to another model type ( i think ), and then access the structure data by the index value of the selected QModelIndex in the ListView.

d_stranz
27th November 2016, 23:44
That sounds bizarrely complicated and unusable. If I understand you correctly, if a user wants to edit a structure with 70 variables, they are going to have to go through 70 different entry dialogs to do it, along with navigating a hierarchy of list and combo boxes to get to the specific field they want to edit? Would you use such an interface more than once or twice before moving on to a better game?

In my practice, when I have a case where users need to edit a multi-entry data structure, I create a custom widget class that contains all of the relevant editing and label widgets in an attractive and logical layout. In the constructor for that widget, I add an additional argument that accepts a const reference to an instance of the data structure I want to edit. Inside the widget constructor, I copy that to a member variable of the same type. The contents of this member variable will be modified by the editing. If I want to support a reset or undo function, I create two copies of the original data; one that will be edited, and one that is the same as the original that I copy into the editable structure on reset.

The custom widget contains slots that are connected to the appropriate "done editing" signals of the edit widgets. Each of these will retrieve the new value from the GUI, validate it, and then update the appropriate entry in the editable copy of the data structure.

If the custom widget is embedded in a QDialog, then when the OK button is clicked, the caller can retrieve the edited copy of the data from the dialog (using a getter method) and copy it into the original structure. If the user clicks cancel, then nothing gets retrieved and copied. If the widget is embedded in something like a toolbox, I'll still usually add an Apply button so the user can commit the changes.

This copy-edit-retrieve scenario works for most of the cases where multiple related variables must be edited. If you are backing your data up to a database, you'll still need to do something like this to handle the case where the user wants to cancel the edit. If you are dynamically updating the data model with each edit, it's pretty hard to handle the "Oops, I was editing the wrong Person!" case.

fatsack
27th November 2016, 23:57
Here's a more somewhat complete code example of what I have, and am trying to do.


QStringListModel treeModel;
QStringList treeList;

QList< QVariant > varList; // trying to store a pointer to a struct variable

std::map< int, PERSON* > personMap;

void PopulateTreeNames( )
{
treeList << "Health" << "Mana" << "Gold"; // adding up to 70 items in a structure
treeModel.setStringList( treeList );
ui->listView->setModel( &treeModel );
}

void SetTreeReferences( int index )
{
PERSON* pPerson = personMap.at( index ).get( );

varList.clear( );

// not sure how to store this as a pointer to the structure variable
// using something like &pPerson->health doesn't work
varList << pPerson->health << pPerson->mana << pPerson->gold; // manually adding up to 70 references into this list
}

void QT_WIDGET::on_listView_doubleClicked(const QModelIndex &index)
{
// open window, and get user input
int retVal = userInput from Window;

// varList should reference the pointer to the var in the structure
varList.at( index ) = retVal;
}


Hopefully this is a bit easier to understand than my previous post. I am looking for advice on how to cleanly implement this, without having to write too much code for each and every structure variable.

EDIT:


That sounds bizarrely complicated and unusable. If I understand you correctly, if a user wants to edit a structure with 70 variables, they are going to have to go through 70 different entry dialogs to do it, along with navigating a hierarchy of list and combo boxes to get to the specific field they want to edit? Would you use such an interface more than once or twice before moving on to a better game?

Saw your post after I finished typing this up. This isn't designed to be used in a game, but rather used as a tool to load and edit data from a binary file. Something like saving a KEYBOARD structure holding information for each key, allowing the user to edit the value of the key in by selecting it from the table view.


In my practice, when I have a case where users need to edit a multi-entry data structure, I create a custom widget class that contains all of the relevant editing and label widgets in an attractive and logical layout. In the constructor for that widget, I add an additional argument that accepts a const reference to an instance of the data structure I want to edit. Inside the widget constructor, I copy that to a member variable of the same type. The contents of this member variable will be modified by the editing. If I want to support a reset or undo function, I create two copies of the original data; one that will be edited, and one that is the same as the original that I copy into the editable structure on reset.

Could you show or link me to an example of what you mean? I don't really understand what you mean by creating a custom widget class that contains all of the labels.

The problem I'm having right now is even if I sent a const reference to the instance of a structure, I am trying to link the table view up to the object data in the structure. I feel like this is a bad way of handling it, but I have no idea how else to do it at the moment.

anda_skoa
28th November 2016, 11:24
There are a cooupl of options, maybe this one is the easiest:

Create a QObject derived class that has one Q_PROPERTY for each of your fields, e.g.



class PersonAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(int health READ health WRITE setHealth)

public:
// use this in SetTreePreferences
void setPerson(PERSON *person) { m_person = person; }


// this is the READ function described above
int health() const { return m_person->health; }

// this is the WRITE function described above
void setHealth(int health) { m_person->health = health; }

private:
PERSON *m_person;
};


In the slot that retrieves the new value, you need the name of the property to change


QByteArray propertyName; // get that e.g. from a list based on the clicked index

// m_adapter is an instance of the PersonAdapter class
m_adapter->setProperty(propertyName.constData(), valueFromDialog);


Cheers,
_

fatsack
28th November 2016, 17:10
There are a cooupl of options, maybe this one is the easiest:

Create a QObject derived class that has one Q_PROPERTY for each of your fields, e.g.



class PersonAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(int health READ health WRITE setHealth)

public:
// use this in SetTreePreferences
void setPerson(PERSON *person) { m_person = person; }


// this is the READ function described above
int health() const { return m_person->health; }

// this is the WRITE function described above
void setHealth(int health) { m_person->health = health; }

private:
PERSON *m_person;
};




typedef struct
{
float x;
float y;
float z;
} POSITION;

typedef struct
{
int health;
POSITION pos[ 3 ];
} PERSON;

class PersonAdapter : public QObject
{
Q_OBJECT
Q_PROPERTY(POSITION position1 READ position1 WRITE setPosition1)
Q_PROPERTY(POSITION position2 READ position2 WRITE setPosition2)
Q_PROPERTY(POSITION position3 READ position3 WRITE setPosition3)

public:
// use this in SetTreePreferences
void setPerson(PERSON *person) { m_person = person; }


// this is the READ function described above
int position1() const { return m_person->position[0]; }
int position2() const { return m_person->position[1]; }
int position3() const { return m_person->position[2]; }

// this is the WRITE function described above
void setPosition1(POSITION pos) { m_person->position[0].x = pos.x; m_person->position[0].y = pos.y; m_person->position[0].z = pos.z; }
void setPosition2(POSITION pos) { m_person->position[1].x = pos.x; m_person->position[1].y = pos.y; m_person->position[1].z = pos.z; }
void setPosition3(POSITION pos) { m_person->position[2].x = pos.x; m_person->position[2].y = pos.y; m_person->position[2].z = pos.z; }

private:
PERSON *m_person;
};


With this adapter class, is this how you would handle an array of another structure inside the original structure?


In the slot that retrieves the new value, you need the name of the property to change


QByteArray propertyName; // get that e.g. from a list based on the clicked index

// m_adapter is an instance of the PersonAdapter class
m_adapter->setProperty(propertyName.constData(), valueFromDialog);


Cheers,
_



QStringList treeList;
QStringListModel treeModel;

void PopulateTreeNames( )
{
treeList << "Health" << "Mana" << "Gold";
treeModel.setStringList( treeList );
ui->listView->setModel( &treeModel );
}

void SetPropertyByName( int index )
{
QByteArray propertyName = treeList.at( index );

m_adapter->setProperty( propertyName.constData(), valueFromDialog );
}


propertyName would be the string equivalent of the index in the ListView's QStringListModel? So the Q_PROPERTY name would need to match whatever I input into the QStringList at the same index, correct?

d_stranz
28th November 2016, 23:51
propertyName must match (exactly) whatever you have defined as the "name" entry in the Q_PROPERTY macro:


Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[CONSTANT]
[FINAL])

So in your case:


m_adapter->setProperty( "position1", 42 );

The "MEMBER memberName" variant of the Q_PROPERTY macro allows you to map a user-friendly property name to an internal member variable name, but in that case you can specify either a READ function or a WRITE function, but not both.

anda_skoa
29th November 2016, 06:53
[code]
typedef struct
{
float x;
float y;
float z;
} POSITION;

To use that in a Q_PROPERTY it also needs to be marked with Q_DECLARE_METATYPE and registered with qRegisterMetaType()


With this adapter class, is this how you would handle an array of another structure inside the original structure?

Your position getter functions just return int, not POSITION.



propertyName would be the string equivalent of the index in the ListView's QStringListModel?

That is one way to do it, but you probably want different strings to display.
So you could either have a list/vector of property names that you use the index's row to access or you store the property name as another role on the entry of the model.

Cheers,
_