how to get and purge events from queue?
-
when scrolling, the scroll for my app is a bit sluggish (do not ask about that)
which means that mouse wheel scroll events can pile up in the queue while the first scroll event is being processed.
meaning that a dozen more slow events get handled after the user stops scrolling.what i want to do is: when i get a wheel scroll event, at that moment i see if there are MORE scroll events in the queue. if not, i just handle this event. but if there ARE i want to get the most recent one, and handle only that ONE event, while purging the rest.
how can i do this?
-dave
-
turns out that what i described above was EXACTLY what i needed. code below:
typedef std::deque<float> FloatQueue; class CScrollQueue : public CT_Timer { typedef CT_Timer _inherited; static CScrollQueue *s_this; FloatQueue i_floatQueue; CDataBrowser *i_browserP; #define kScrollTimerInterval 0.1f void DoVScroll(float incrementF) { PointS32 scrollPos(i_browserP->GetScrollPos()); scrollPos.v += incrementF; i_browserP->SetScrollPos(scrollPos); } public: CScrollQueue() : _inherited("CScrollQueue", kScrollTimerInterval), i_browserP(gApp->GetDataBrowser(BW_Control_TRACKS)) { call(); } static CScrollQueue* Get() { if (s_this == NULL) { s_this = new CScrollQueue(); } return s_this; } void vscroll(float angleF) { if (angleF) { if (i_floatQueue.empty()) { SetFireInterval(kScrollTimerInterval); } i_floatQueue.push_back(angleF); } } OSStatus operator()() { while (!i_floatQueue.empty()) { float velocityF(-i_floatQueue.back() / 2); i_floatQueue.clear(); // during DoVScroll, more events // may come into the floatQueue DoVScroll(velocityF); } SetFireInterval(kEventDurationForever); return threadTimerContinue_noPrime; } }; // static CScrollQueue* CScrollQueue::s_this = NULL;
then, in my eventFilter method:
case QEvent::Wheel: { if (objP == getTracksList()->viewport()) { QWheelEvent *wheel_event(static_cast<QWheelEvent*>(eventP)); double angleF(wheel_event->angleDelta().y()); handledB = angleF != 0; if (handledB) { CScrollQueue::Get()->vscroll(angleF); } } } break;
-
Hi,
You will likely have to implement your own QAbstractEventDispatcher.
-
so, there's no "purge events" method?
-
No, and there is no reason for it.
If you don't want to scroll, overwrite scrollEent() or similar. Or fix your application to not use that much CPU. I don't have a model/view which is laggy due it's size. -
if you must know, i have a table view with up to 100 columns and 100k or more rows, which allows for filtering via search (showing only search results, a subset of the full DB), and sorting via columns. and i need the search / sort results to load instantly (or as fast as possible), therefore i only load what is showable in the view, and the scroll action actually re-fetches what to show, since loading a sortfilterproxymodel with all that data is crazy time consuming. so it is, shall we say, NOT lickety split to scroll.
but that's beside the point.
telling me there's no reason for my use-case is not ... helpful. i have a reason for it. just because you don't know about something doesn't mean that that thing doesn't exist, it just means you can't imagine it. but i digress.
it's not that i don't want to scroll, it's that i don't want a pile of scroll events to come in one at a time, i want to purge any unhanded ones at the time i am handling one.
-
@davecotter said in how to get and purge events from queue?:
what i want to do is: when i get a wheel scroll event, at that moment i see if there are MORE scroll events in the queue. if not, i just handle this event. but if there ARE i want to get the most recent one, and handle only that ONE event, while purging the rest.
Qt does not allow you to "look ahead" to examine, or "purge", its event queue, as that is regarded as "internal". There are a couple of approaches you could use for your use case.
First, I must say I wonder if your situation is not quite as you describe it. When you get the first scroll event the subsequent ones may well not be there yet? Your problem may then be that while you handle the first scroll event time passes (because it's "slow") and it is during that period that the user scrolls further, either deliberately or because they do not see anything happening and so try continuing to scroll further. So even if you could look ahead/purge on the initial scroll event it would not actually find anything to ignore at that instant.
One possibility is to set off a short timer (say, a tenth of a second or so?) on the first event, and only do the scroll when that expires. During the "delay" period, any further scroll events received are just used to re-extend the timer from that new instant. Only when the timer expires with no new scrolls do you perform the slow operation. You'd have to test this, I can see a danger that the user might see nothing happen and just keep scrolling, which would probably defeat it.
Another possibility would be: do the slow operation on the first scroll. Note the time that you complete the operation. Now ignore all scroll events which arrive until a certain delay time has passed after operation completion. Scroll events which piled up during the operation, as well as any which arrive shortly after completion, will be ignored.
If your database-paging-scrolls are "costly", you might want the principle of the first approach somehow combined with the second. If the user wants to do a "2-notch-scroll", it may be frustrating if that does 1-notch, slowly, and then has to do another 1-notch, also slow, when it would have been better to do the 2-notch in one go.
-
I wonder if your situation is not quite as you describe it
it is as i describe it AND as you describe it. to be more clear, yes when the FIRST event is handled, there is only one event, and it is during the "costly" view update that others arrive. It is at the time of the SECOND scroll event that several have accumulated. that is precisely why i said "when i get a wheel scroll event, at that moment i see if there are MORE scroll events in the queue. if not, i just handle this event", this covers the first case and the second case.
set off a short timer
the UX for this is FAR FAR WORSE than how it is now. the UI should be responsive, and the very LAST thing anyone wants is to introduce MORE delays. imagine the user scrolling with the wheel and... nothing happens? a frustrated trigger-finger user will try to scroll more while nothing keeps happening. No, I need (and users need) a scroll response that happens at the time the user is scrolling, and not later.
ignore all scroll events which arrive until a certain delay time
again: further delays are absolutely out of the question. I'm trying to make the UI responsive.
But given what i'm learning here, this is my latest idea: When handling scroll events, send them to my own, custom-written queue (CScrollQueue), without actually handling the scroll event. BUT: Whenever there are events in my CScrollQueue, a timer is installed that fires pretty rapidly (say 10x per second). When that timer fires, all events in the CScrollQueue are purged except the last one, but the last event is actually sent on to execute the scroll event. I could also at that time do something smart about velocity, if necessary. Thoughts?
-
@davecotter
In that case, best of luck. If you start the population at the instant you receive the first scroll event, then by definition you cannot handle "if there is another scroll soon then I want to move by another 'page/notch'", you will be committed to populating for one notch.In any case, if the population "takes some time" you're going to have a problem for your trigger-happy user who, because he does not see an immediate update, is bound to re-scroll. If your code could already keep up with the scroll events you wouldn't be having the problem in the first place. So you'd better ensure the database is re-read and the UI updated within, say, 1/60th of a second.
I know you want it to be responsive. I didn't suggest otherwise. I don't see why the second suggestion should not satisfy that. All I am proposing is a way to ignore those further scrolls which occur while the first one(s) are being satisfied, which seems to be what you want.
Yes, you may find maintaining your own queue of events, which you can purge etc., will help.
Otherwise, depending on what your backend is, to remain as responsive as you would like you'd probably have to put the data acquisition in its own thread. And then have a capability to "cancel" one acquisition in its thread and set off a new one if user scrolls while it's in progress
Or, be daringly heuristic: if the data acquisition is "fast" relative to the amount of data you request, but the UI is "slow" or each data call is slow (example: data from a remote database has a high latency overhead for each individual request, but a relatively lower overhead for an increase in the payload returned), then you would actually benefit from requesting more data in one call in anticipation of potential further-scrolls, so that when you're ready to update the UI you have already fetched the further data.
Both the last paragraphs would take a bit of work.
-
turns out that what i described above was EXACTLY what i needed. code below:
typedef std::deque<float> FloatQueue; class CScrollQueue : public CT_Timer { typedef CT_Timer _inherited; static CScrollQueue *s_this; FloatQueue i_floatQueue; CDataBrowser *i_browserP; #define kScrollTimerInterval 0.1f void DoVScroll(float incrementF) { PointS32 scrollPos(i_browserP->GetScrollPos()); scrollPos.v += incrementF; i_browserP->SetScrollPos(scrollPos); } public: CScrollQueue() : _inherited("CScrollQueue", kScrollTimerInterval), i_browserP(gApp->GetDataBrowser(BW_Control_TRACKS)) { call(); } static CScrollQueue* Get() { if (s_this == NULL) { s_this = new CScrollQueue(); } return s_this; } void vscroll(float angleF) { if (angleF) { if (i_floatQueue.empty()) { SetFireInterval(kScrollTimerInterval); } i_floatQueue.push_back(angleF); } } OSStatus operator()() { while (!i_floatQueue.empty()) { float velocityF(-i_floatQueue.back() / 2); i_floatQueue.clear(); // during DoVScroll, more events // may come into the floatQueue DoVScroll(velocityF); } SetFireInterval(kEventDurationForever); return threadTimerContinue_noPrime; } }; // static CScrollQueue* CScrollQueue::s_this = NULL;
then, in my eventFilter method:
case QEvent::Wheel: { if (objP == getTracksList()->viewport()) { QWheelEvent *wheel_event(static_cast<QWheelEvent*>(eventP)); double angleF(wheel_event->angleDelta().y()); handledB = angleF != 0; if (handledB) { CScrollQueue::Get()->vscroll(angleF); } } } break;