PDA

View Full Version : Best way to pass data between child's model and parent ?



homerun4711
6th January 2011, 16:13
Hello!

I have a invoice-form which opens a addressbook to select a customer. Both are bound to a SQL-database using QSqlTableModel.

The selected customer should be written into some QLineEdits within the invoice-form.

The addressbook's selection model is working fine, but I wonder which is the best way to pass the selected row (the customer) to its parent if a pushButton sending the accept()-signal is clicked.

I thought about passing a QModelIndex somehow, but the Qt API Doc says that QModelIndex should be proccessed on the spot.


QModelIndex index = tableView->currentIndex();

Another possibility would be to pass a QSqlRecord like this:


QSqlRecord record = modelCustomer->record(index.row());

I am not only unsure about the container to pass the data in, also I don't know if its better to send the data via a signal or to interact on the accept()-signal from parent-side like


if (selectCustomer->exec())
{
selectCustomer->doSomeThing...
}

I hope you have some good advice for me.

Kind regards,
HomeR

Added after 1 58 minutes:

After reading here in the forum for a while I decided to use a getter-function like


if (selectCustomer->exec())
{
selectCustomer->doSomeThing...
}

But which data should I pass? An SQL-record? An index?

nroberts
6th January 2011, 16:47
I don't know what you're trying to do and what's the "best thing" (it's all opinion anyway) is so much dependent upon what you're trying to do. I'll try to provide some help anyway but it's probably going to be limited.

First thing I note is the warning in the documentation:


Note: Model indexes should be used immediately and then discarded. You should not rely on indexes to remain valid after calling model functions that change the structure of the model or delete items. If you need to keep a model index over time use a QPersistentModelIndex.

So it, first of all, looks like you have an answer if you need the index: QPersistentModelIndex.

Second of all, it looks like the, "...ModelIndex should be used immediately...," warning is for exactly the reason I expect: it can become invalid if the model changes. In a multi-threaded environment it would thus be very difficult to pass a ModelIndex out of your model. You'd need to lock the model up so it couldn't be modified until processing of that index was accomplished. If you're not using MT then you probably have less to worry about and passing the ModelIndex directly to whatever might work just fine. But a) you shouldn't take shortcuts that kill a future decision (I want to MT now) without good reason, and b) you have a "right" way to do it.

Next, I have a feeling that what you're doing might not be best anyway. There might not be a best answer to, "How do I send the data to the parent," because the question itself might be based on a flawed method. I really try to get as much of the model manipulation logic as possible into that model. I like to think of my models as encapsulated boxes that spit out domain data (meaning data specific to the problem, not model indexes or whatnot). A button press that then does something to the model may very well be best handled by the model itself. It's hard to say if this is right and it would be hard to say HOW I would do it in your case without knowing your problem intimately.

My *guess* at what might be best for you is the SQL record data. If that's enough for the thing to do its job, that's what you should send.

homerun4711
6th January 2011, 18:34
My *guess* at what might be best for you is the SQL record data. If that's enough for the thing to do its job, that's what you should send.

Thanks for your answer. Perfect, thats what I chose to try. Here is what I wrote:


if (customerSelect->exec())
{
QSqlRecord record = customerSelect->passRecord();
...
}



QSqlRecord SelectAddress::passRecord()
{
QModelIndex index = tableView->currentIndex();
QSqlRecord record = modelCustomer->record(index.row());
return record;
}


It's working fine.

Can you help me with another quick question:

I would like to assign a customer number to every entry in the customer-database. It should be a concatenation of a Prefix, e.g. "C" and a number.
As number I would like to use either the number of rows (means customers) in the database or the PrimaryKey for that row.

Do you know how I can access the primary key for the row, or the highest primary key?

So far I found two possibilities, but maybe there are "cleaner" ones.

1.) The number of entries ist listed in a special table for SQLite databases, read the value from there:


QSqlQuery query("SELECT * FROM sqlite_sequence");
int fieldNo = query.record().indexOf("seq");

while (query.next()) {
QString rows = query.value(fieldNo).toString();
out << "rows: " << rows << "\n";
}


2.) List the all customer entries and read the number of lines using query.size() or just count them using a loop.



QSqlQuery query("SELECT * FROM customers");
int rows = query2.size();


But using size() always returns -1, I found out right know that's because SQLite does not support size().

Are there other possibilities?

But both methods are not working if rows have been deleted...

wysota
7th January 2011, 01:23
You should really learn a bit about databases before tackling with such a subject. Relational databases have a concept of auto incrementing fields or sequences (depending on the database) that are used for assigning primary key values automatically. Read this: http://www.sqlite.org/autoinc.html

homerun4711
7th January 2011, 08:44
Do you know how I can access the primary key for the row, or the highest primary key?

Maybe I was not clear enough. I am familiar with the concepts of primary keys and auto-incrementing . I do not want to write / set this key, I just want to read it to assign an unique customerID to each customer (a new, non-autoinc column), I just have to read the primary key.


SQLite keeps track of the largest ROWID that a table has ever held using the special SQLITE_SEQUENCE table. The SQLITE_SEQUENCE table is created and initialized automatically whenever a normal table that contains an AUTOINCREMENT column is created.

So maybe the best would be to use that value + 1 to assign a customerID (Prefix + MAX(autoinc)). But since this is limitied to SQLite, I thought it might be better to use a more common method to be able to change the database-type afterwards.

wysota
7th January 2011, 11:01
Based on what you write here I can state you are not familiar with what yo claim to be familiar with. The id of a customer is a primary key of the customer table (your "C" prefix is completely unnecessary in the database, you can have it in your program to show it to your users but don't place it in the database) so you don't assign the numbers yourself but rather let the database do it by using an auto incrementing primary key. After you have inserted a new customer to the database you can ask the database about the last inserted row using QSqlQuery::lastInsertId(). If the database doesn't support it (sqlite should but anyway...) then you can always ask for the highest id in the table (SELECT MAX(id) from customers) and unless you messed with id manually, that will be the id you are looking for.

Please save yourself the effort and don't reinvent the wheel at every step - learn more about database programming before doing something on your own.

homerun4711
7th January 2011, 11:48
QSqlQuery::lastInsertId(), table (SELECT MAX(id) from customers)

Thank you for the suggestions, this was the information I was looking for. I do not want to use the customerID to count something or as a primary key. It just will be a QString concatenated of a changeable prefix and the primary key (to make the customerID-number-part unique, independently of the prefix).

wysota
7th January 2011, 12:06
Thank you for the suggestions, this was the information I was looking for. I do not want to use the customerID to count something or as a primary key. It just will be a QString concatenated of a changeable prefix and the primary key (to make the customerID-number-part unique, independently of the prefix).

That's not a good idea. Make the prefix a separate field (if at all), don't concatenate the two in the database. The primary key is your real customer id, regardless of what you show to your users. You should always associate the primary key with customer fields in other tables and not your concatenation because this will lead to more trouble and is likely to trash your whole database. Primary key is the only value that can (logically) never change during the whole lifetime of the database. Unless you are planning on allowing your users to manually input or change "customer id" of a particular customer to something completely custom (like "my friend joe" instead of "C01234") the primary key is sufficient. And even if you plan to do that, you can have an "alias" field that can be null or can contain a custom string ("my friend joe") but you will still refer to him in the database as "1234" and not "my friend joe". Otherwise if you rename "my friend joe" to "my ex-friend joe" some day then you will lose all the data from the database related to your customer (because in other tables you will still have "my friend joe" as the customer id).

homerun4711
7th January 2011, 12:27
You should always associate the primary key with customer fields in other tables and not your concatenation because this will lead to more trouble and is likely to trash your whole database.

Yes, I am aware of that and I will for sure only use the primary key for QSqlRelationalTableModels and joins to achieve a reliable database. The concatenation is only a QString to display on the pdf- or print-version of an invoice, but it will also be unique.

wysota
7th January 2011, 12:30
The concatenation is only a QString to display on the pdf- or print-version of an invoice, but it will also be unique.
Since the prefix will always be the same for every client what is the point in keeping it in every record of the database? What if some day you decide to change the prefix?

homerun4711
7th January 2011, 12:59
What if some day you decide to change the prefix?
This is even an wanted option. The user should be able to change the prefix if he wants to use another format (this is an wanted option). But to be consistent with invoices that have been send as letters or pdfs, I have to keep the customer number (prefix + number ) inside the database.
The customer number will be unique and fixed to that customer, even if the prefix does change later, it will not change for customers created before a new prefix was used. (C0001, Cst0002, CNo0003)

wysota
7th January 2011, 13:08
This is even an wanted option. The user should be able to change the prefix if he wants to use another format (this is an wanted option). But to be consistent with invoices that have been send as letters or pdfs, I have to keep the customer number (prefix + number ) inside the database.
The customer number will be unique and fixed to that customer, even if the prefix does change later, it will not change for customers created before a new prefix was used. (C0001, Cst0002, CNo0003)

What's the point of the prefix then? Anyway, don't concatenate, keep the prefix as a separate field. This gives you more flexibility. And if you want to do the concatenation then do it in the server - create a trigger that will automatically fill the "customer id" column based on the current prefix and the primary key.

http://www.sqlite.org/lang_createtrigger.html

homerun4711
7th January 2011, 13:21
What's the point of the prefix then?

Just to keep the numbers for customers and invoices printed on the invoices clearly different.


Anyway, don't concatenate, keep the prefix as a separate field. This gives you more flexibility.

Ok, this is a very good idea. Thanks for that.