PDA

View Full Version : How should I create a view with dynamic fields?



ls4f
21st December 2011, 12:54
I`ve been reading now for some time and have not figured yet a proper way to do this.
What I mean by 'dynamic' is a field (a column in a table, in my case) which reads a new value each time it is repainted (or at the very least, each time it reappears on the screen). If anyone is familiar with M$`s 'virutal mode' - that`s what I`ve used before. The reason I need this is because (in theory) those values can change every 60 seconds for all the 3000+ items in the table.
I was thinking of emitting a signal for data change from time to time, but it seems a bit ugly and will most probably be slow as hell ...
I took a look at QtQuick and so far I`m considering a QtQuick view with no cache. Not sure if it is a good idea, though, since I`d prefer it not to hog 100% of the CPU time.

Any suggestions for a proper way to do this are highly appreciated.

Spitfire
21st December 2011, 14:29
Sending a signal that will trigger update IMHO makes more sense.
Or start a timer that will update table every now and then.
You don't need to update everything, just what's visible.
Later when table is scrolled, depending on your model, the values should get updated anyway if they've changed.

ls4f
21st December 2011, 15:36
Well I know emitting a signal is the usual way to handle things, but my problem is that one of the fields in my model is dynamically calculated (based on date/time and a few other properties).

It is obtained by going through a sorted list of objects (price lists) and getting the first possible value (price) that is applicable (it is valid at that moment, this day of the week, for this particular customer, for this particular shop and so on).

Most of those changes are predictable enough for a signal, but I have no real way of knowing which rows will have new value at the turn of a new minute and which won`t. I could theoretically emit a data changed signal for that column every 60 seconds, but won`t that be too much of a waste of CPU time?

amleto
21st December 2011, 17:23
so instead of once every 60s, you want to check every repaint? That makes no sense at all!

ls4f
21st December 2011, 19:23
Well checking is quite fast, so doing 40 checks (usual number of rows on screen) on widget refresh has no significant impact on usability. On the other hand, If I do it every 60 seconds I will have to reset the entire column (as I mentioned - there`s no normal way to detect what will change), and I`m not sure how that will behave on 5-6k rows. I`d prefer the view to have some consistent behavior (for example slower scrolling is a better alternative then one frozen second every minute).

amleto
21st December 2011, 23:54
you could combine them. you know you have to refresh on screen after 60s. you know that you have to refresh on scrolling if newly seen cells are 'older' than 60s.

ls4f
22nd December 2011, 07:29
That might turn out to be how I go for it. There is no way of preventing the view from caching a specific column than?

Or is there a more suitable widget (or one I should subclass in some way) to display this? There are a few other, more predictable columns with a similar fashion. I`m trying to avoid all that 'data changed' since those are basically ghost columns and calling a 'refresh' on the widget is a lot less overhead than all the signals I should emit (mostly because of the underlying monitoring that has to happen).

Spitfire
22nd December 2011, 13:54
I think the basic mistake you're making is putting model logic in the view.

It's the model that changes so IMHO it's a bad practice to put any logic in paint event.

What you should have is a periodic update of the data which is currently visible on the screen.

If the test doesn't take too long you could do it for every new row that appears on the screen when scrolling.
If the test takes very long, do it in separate thread so the UI won't freeze and the cell will get updated as soon as the test if finished.

And size of the whole table won't matter if you're updating only 10's of rows/fields.

ls4f
22nd December 2011, 14:11
If the test doesn't take too long you could do it for every new row that appears on the screen when scrolling.

Well the short form of my question was 'how do I do that?'. I don`t really want to put any logic in the view, but since the model by itself has not real knowledge of what is displayed and what not - it seemed like the next best choice ...

Here`s a brief paste of what my data() function does :


switch(index.column())
{
case C_SIMPLE_ID:
return rsTheInfo->ID();
case C_SIMPLE_NAME:
return rsTheInfo->Name();
case C_SIMPLE_CODE:
return rsTheInfo->Code();
case C_SIMPLE_NAL:
dTmp = rsWorkingWithSklad->Nal(iTheRow);
return (dTmp!=0) ? dTmp : QVariant();
case C_SIMPLE_PRICE:
return this->GetPrice(iTheRow, dTmp) ? QString::number(dTmp,'f',2) : QVariant();
case C_SIMPLE_ME:
return rsTheInfo->MerEd()->Name();
case C_SIMPLE_ACTIVE:
return (rsTheInfo->Flags() & SimpleFlagActive) != 0;
case C_SIMPLE_USL:
return (rsTheInfo->Flags() & SimpleFlagUsluga) != 0;
default:
return QVariant();
}
As you can see - there is no actual data there to change (guess a close analogy would be if it were a temperature sensor - no one really cares what the value 'was', it only matters what it 'is' at that particular repaint).

Basically GetPrice() can return different result if the database has changed (which is nice and predictable) or because the time has changed (which might change anything from 0 to all possible prices).


And size of the whole table won't matter if you're updating only 10's of rows/fields.


You mean emitting a data changed signal every 60 seconds will not be hard on the view if it is currently showing only 30-40 items?

Spitfire
22nd December 2011, 15:34
If you mean - how to know when view was scrolled and what's now visible then here's how:



connect( TableViewObject->verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(updateTable(int)));

...

void updateTable( int val )
{
int visibleRowsCount = TableViewObject->rowCount() - TableViewObject->verticalScrollBar()->maximum();
visibleRowsCount += 1; // extra row in case next row it's partially visible

int indexOfFirstVisibleRow = val;
int indexOfLastVisibleRow = indexOfFirstVisibleRow + visibleRowsCount;

// for each of visible rows call GetPrice() and set new value on the corresponding item
}
that's 'on a knee' solution but it should get you where you want.


You mean emitting a data changed signal every 60 seconds will not be hard on the view if it is currently showing only 30-40 items?
It won't be hard if the getPrice() is fast. if it's not then use separate thread to do that and it will still be nice and easy.
Same for scrolling, if getPrice() is slow, stick it into separate thread and update the field when thread finishes. I guess you'll know if it's too slow when you try it.

Also remember to call the updateTable() method from resizeEvent() as well.

ls4f
22nd December 2011, 16:36
Thanks a lot. Seems that is 'the Qt way' to do this.
GetPrice() is fast enough in c#, so I guess it will be fast enough here:) Looking at it that way - I might make the model use a simplified structure (currently a it`s basically a bunch of QMaps)