View Single Post
Posts: 4 | Thanked: 1 time | Joined on Apr 2012
#1
Newbie to Qt and PySide. Having terrible trouble trying to suppress finger scrolling in a scrollable widget. Could someone help me understand Qt events or see what I'm doing wrong?

(The code I'm about to present which tries to suppress finger scrolling entirely is a simplified test case. I do have a reason for this; in my actual application I actually only want to suppress the finger scroll on one particular column of a table, where the column will be filled with checkboxes (or something which looks like it, not necessarily QCheckBox itself) and if you swipe down that column it toggles all the checkboxes instead of scrolling.)

I make a QScrollArea. I set its 'widget' (the large widget that I will only be able to see a portion of at any one time as it slides around) to a QWidget with a minimum size of 1000x1000 px, which is definitely bigger than my N900's screen and the QScrollArea itself. By the way I'm going to call that widget the 'sliding' widget from now on for clarity (it's a shame Nokia didn't come up with a distinctive name for it). I install an event filter on the sliding widget which filters out the mouse events (MouseButtonPress, MouseMove, MouseButtonRelease), by returning True for them (I'm also filtering out all touch events: TouchBegin, TouchUpdate, TouchEnd - just to be sure, though in my early tests I saw that it doesn't make any difference - it's the mouse events that lead to finger scrolling).

Then I add a single QLabel to the sliding widget so that the sliding widget's not completely blank and it'll be easier to see if it's moving or not when I try and finger scroll it.

When I run the program, if I touch the scroll area - avoiding the label - and drag, there is no scrolling. Good. What happens next I can't explain though. If I start by touching the label, then drag - then the scroll area does scroll, which I think should not be happening. Weirdest of all though is that if I now try and scroll like I did the first time, by initially touching somewhere other than the label, then this time the scrolling does happen! In fact my scrolling restriction seems to have disappeared for good, until I restart the program.

According to my understanding - someone please correct me if this is wrong - the parent/child widget tree looks like this:
Code:
 QScrollArea
  QScrollArea's viewport (get with QAbstractScrollArea.viewport())
   The sliding widget
    QLabel
The QScrollArea's viewport is a QWidget that's stays pretty much the same size as QScrollArea (maybe a bit smaller if you have scrollbars permanently visible, for example). The sliding widget, because it is a child, is visually clipped by the parent viewport widget, and when you do scroll around, the QScrollArea changes the sliding widget's position (QWidget.pos(), QWidget.move()) in order to effect the movement (so when you scroll down and right, the sliding widget's x and y take on negative values). (I checked all this with 'print' statements.)

A mouse event goes to the visually front-most widget first (which is the same as the child-most widget, Z-order always follows parent/child relationship in Qt, right?), which is QLabel. Firstly the event goes to my event filter on the label, which prints a log message then returns False, which allows Qt to pass it on to the QLabel's internal mouse handlers (which do nothing, labels have no mouse interaction) and then after that the event is propagated to the parent widget, which is the sliding widget. It goes to my event filter on the sliding widget which prints a log message and returns True, to kill the event then and there. The event does not go on to the internal event handlers of the sliding widget (just a QWidget, so again probably does nothing), and does not go to the next parent which is the QScrollArea's viewport or anywhere else.

That's my understanding, which the log output bears out. When I'm dragging the scroll area - avoiding the label - at the beginning:

Code:
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseButtonPress
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseMove
[... ditto MouseMove events ...]
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseButtonRelease
When I'm starting my drag on the label:

Code:
LabelEventFilter: PySide.QtCore.QEvent.Type.MouseButtonPress
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseButtonPress
LabelEventFilter: PySide.QtCore.QEvent.Type.MouseMove
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseMove
[... ditto pairs of MouseMove events ...]
LabelEventFilter: PySide.QtCore.QEvent.Type.MouseButtonRelease
SlidingWidgetEventFilter: PySide.QtCore.QEvent.Type.MouseButtonRelease
When I'm dragging the scroll area - avoiding the label - again at the end, I get the same log output as at the beginning - 'SlidingWidgetEventFilter' only. Although this time scrolling is actually happening.

Surely the touch scrolling must be implemented in the internal event handlers of the QScrollArea's viewport, right? Or maybe the QScrollArea itself. I put event filters on both of those as well, but they never log anything since I kill the events in the sliding widget event filter by returning True. If the mouse (and touch) events are getting killed early in the chain at the sliding widget, then how can scrolling ever happen? And what on earth is causing the behaviour to change after scrolling by the label (which shouldn't work in the first place anyway) for the first time?

By the way, if I make the label event filter return True so that it kills events as well as the sliding widget, then the scrolling is suppressed completely. But I don't believe that this should be necessary, and I want to understand what's going on, and in my actual app this would just lead to nasty duplication of code.

Thanks for your attention. Here's the ol' test program:
Code:
# QT
from PySide.QtCore import *
from PySide.QtGui import *


### Event filters

class MyScrollAreaEventFilter(QObject):
    def eventFilter(self, i_obj, i_event):
        ## Touch events: silently suppress
        if i_event.type() == QEvent.TouchBegin:
            return True
        if i_event.type() == QEvent.TouchUpdate:
            return True
        if i_event.type() == QEvent.TouchEnd:
            return True
        ## Mouse events: log and suppress
        if i_event.type() == QEvent.MouseButtonPress:
            print "MyScrollAreaEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseMove:
            print "MyScrollAreaEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseButtonRelease:
            print "MyScrollAreaEventFilter: " + str(i_event.type())
            return True
        ## Any other events: leave alone
        return False

class ViewportEventFilter(QObject):
    def eventFilter(self, i_obj, i_event):
        ## Touch events: silently suppress
        if i_event.type() == QEvent.TouchBegin:
            return True
        if i_event.type() == QEvent.TouchUpdate:
            return True
        if i_event.type() == QEvent.TouchEnd:
            return True
        ## Mouse events: log and suppress
        if i_event.type() == QEvent.MouseButtonPress:
            print "ViewportEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseMove:
            print "ViewportEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseButtonRelease:
            print "ViewportEventFilter: " + str(i_event.type())
            return True
        ## Any other events: leave alone
        return False

class SlidingWidgetEventFilter(QObject):
    def eventFilter(self, i_obj, i_event):
        ## Touch events: silently suppress
        if i_event.type() == QEvent.TouchBegin:
            return True
        if i_event.type() == QEvent.TouchUpdate:
            return True
        if i_event.type() == QEvent.TouchEnd:
            return True
        ## Mouse events: log and suppress
        if i_event.type() == QEvent.MouseButtonPress:
            print "SlidingWidgetEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseMove:
            print "SlidingWidgetEventFilter: " + str(i_event.type())
            return True
        if i_event.type() == QEvent.MouseButtonRelease:
            print "SlidingWidgetEventFilter: " + str(i_event.type())
            return True
        ## Any other events: leave alone
        return False

class LabelEventFilter(QObject):
    def eventFilter(self, i_obj, i_event):
        ## Touch events: silently suppress
        if i_event.type() == QEvent.TouchBegin:
            return True
        if i_event.type() == QEvent.TouchUpdate:
            return True
        if i_event.type() == QEvent.TouchEnd:
            return True
        ## Mouse events: log and don't suppress
        if i_event.type() == QEvent.MouseButtonPress:
            print "LabelEventFilter: " + str(i_event.type())
            return False
        if i_event.type() == QEvent.MouseMove:
            print "LabelEventFilter: " + str(i_event.type())
            return False
        if i_event.type() == QEvent.MouseButtonRelease:
            print "LabelEventFilter: " + str(i_event.type())
            return False
        ## Any other events: leave alone
        return False


### Main class

class MyScrollArea(QScrollArea):
    def __init__(self):
        QScrollArea.__init__(self)

        # Monitor events on the scroll area
        self.installEventFilter(MyScrollAreaEventFilter(self))

        # Monitor events on the scroll area's viewport
        self.viewport().installEventFilter(ViewportEventFilter(self))

        # Set the scroll area's widget (to be known as the 'sliding' widget
        # from here on) to a plain QWidget, 1000x1000 px
        slidingWidget = QWidget()
        slidingWidget.setMinimumSize(1000, 1000)
        self.setWidget(slidingWidget)
        # Monitor events on it
        slidingWidget.installEventFilter(SlidingWidgetEventFilter(self))

        # Add a label to the sliding widget
        label = QLabel("Hello", slidingWidget)
        label.move(200, 200)
        # Monitor events on it
        label.installEventFilter(LabelEventFilter(self))


import sys

app = QApplication(sys.argv)

scrollArea = MyScrollArea()
scrollArea.show()

app.exec_()
sys.exit()

Last edited by CmdrKrool; 2012-04-30 at 17:17. Reason: better code comments