Home > OS >  PyQt eventFilter return Value Changes behaviour
PyQt eventFilter return Value Changes behaviour

Time:07-29

i am trying to realize kind a like a swipe gesture on a ListView. The listView is part of a stackedWidget which should change the page if the Event gets triggered.

It works if the return Value of eventFilter is True. But if so the ListView disappears. If the value is False, the ListView reappears, but different events get triggered.

I added a minimal example, which makes the Problem i am facing much more clear.

I understood that the return Value determines if the event should be filtered (True) or not (False) but i dont understand what happens here.

I am Thankful for every hint,tip or for another approach to this. Best with a minimal working example.

main.py

import sys

from PyQt6 import QtCore, QtWidgets
from PyQt6.QtGui import QIcon, QStandardItem
from PyQt6.QtWidgets import QVBoxLayout, QPushButton

from listView_minimal_example import ListView_Categories

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):

        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(1190, 870)
        MainWindow.setStyleSheet(
            "QWidget{\n"
            "    background-color: rgb(61, 121, 60);\n"
            "}\n"
            "\n"
        )

        MainWindow.Wrapper_Kategories = QtWidgets.QStackedWidget(MainWindow)
        MainWindow.Wrapper_Kategories.setGeometry(QtCore.QRect(9, 179, 1200, 621))
        MainWindow.Wrapper_Kategories.setObjectName("Wrapper_Kategories")

        self.page_1 = ListView_Categories(MainWindow)
        self.page_1.setGeometry(QtCore.QRect(150, 110, 1200, 80))
        self.page_1.setObjectName("view 1")

        self.page_2 = ListView_Categories(MainWindow)
        self.page_2.setGeometry(QtCore.QRect(150, 110, 1200, 80))
        self.page_2.setObjectName("view 2")

        MainWindow.Wrapper_Kategories.addWidget(self.page_1)
        MainWindow.Wrapper_Kategories.addWidget(self.page_2)

        item = QStandardItem()
        item.setIcon(QIcon("000_Ordnerstruktur/003_Test/1.ico"))
        self.page_1.m_model.appendRow(item)

        item = QStandardItem()
        item.setIcon(QIcon("000_Ordnerstruktur/003_Test/1.ico"))
        self.page_2.m_model.appendRow(item)

        self.btn = QPushButton()
        self.btn.setText("Switch View")
        layout = QVBoxLayout()
        layout.addWidget(self.btn)
        layout.addWidget(MainWindow.Wrapper_Kategories)

        self.btn.mousePressEvent = self.switch_view

        MainWindow.setLayout(layout)

    def switch_view(self,data):
        MainWindow.Wrapper_Kategories.setCurrentIndex(1)

if __name__ == "__main__":
    app = QtWidgets.QApplication(sys.argv)

    MainWindow = QtWidgets.QWidget()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()

    sys.exit(app.exec())

listView_minimal_example.py

from PyQt6.QtWidgets import QListView, QAbstractItemView
from PyQt6.QtCore import QSize, QEvent
from PyQt6.QtGui import QStandardItemModel

class ListView_Categories(QListView):
    def __init__(self, parent:None):
        super().__init__(parent)
        self.parent = parent
        self.m_model = QStandardItemModel(self)
        self.setModel(self.m_model)
        self.setAcceptDrops(False)
        self.setIconSize(QSize(150,150))
        self.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
        self.setResizeMode(QListView.ResizeMode.Adjust)
        self.setViewMode(QListView.ViewMode.IconMode)
        # Catch the event
        self.installEventFilter(self)

        self.setStyleSheet(
            "QListView{\n"
            "    background-color:rgb(92, 52, 19)\n"
            "}"
        )

    # True if the event should be filtered
    # else Flase
    def eventFilter(self, widget, event):
        print("event ", event.type())
        if(event.type() == QEvent.Type.MouseButtonPress): 
            print("event")
            print(event.type())
            print(event.spontaneous())

            pos = event.pos()
            self.position_1 = pos.x()
            print("Entered at:", pos.x(), pos.y())

            return True
        elif(event.type() == QEvent.Type.MouseButtonRelease):
            pos_2 = event.pos()
            self.position_2 = pos_2.x()
            
            if(self.position_1 < self.position_2 and abs(self.position_1-self.position_2) >= 100):
                print("left swipe")
                self.parent.Wrapper_Kategories.setCurrentIndex(self.parent.Wrapper_Kategories.currentIndex()   1)
                return True
            elif(abs(self.position_1-self.position_2) >= 100):
                print("right swipe")
                self.parent.Wrapper_Kategories.setCurrentIndex(self.parent.Wrapper_Kategories.currentIndex() - 1)
                return True

        #return super().eventFilter(widget,event)
        #if True listView disappears if False diffrent Event Types occur
        return True

I am unsure if the problem gets clear or stays the same without adding an icon to the listView(s)

CodePudding user response:

There are many issues with your attempt.

The problem you're referring to is caused by the fact that you're always returning True from the event filter. This completely prevents the target widget to handle any event (including painting itself).

Besides, installing an event filter on the same object is pointless, as you could just override its event().

But this won't work, though, because the actual widget that receives mouse buttons in a scroll area is its viewport (the "contents" that are scrolled in the view).

For subclasses, you would then need to override viewportEvent(), but for mouse events you can just override the basic handlers: mousePressEvent() and mouseReleaseEvent(); this is because all classes that inherit from QAbstractScrollArea always remap most user events of the viewport to the widget itself (see the last paragraphs of the documentation).

Then, you shall not try to directly access a parent widget[1], but instead emit a custom signal; you will then connect that signal from the instance that contains the views (and the stacked widget), to a function that actually swaps pages.

Finally, you shall never, ever try to edit pyuic files, as it's considered a bad practice (for countless reasons I won't explain here). Instead, follow the official guidelines about using Designer.

In the following code, I'm assuming you've rebuilt the python script of the main window with the original UI and added the wrapper_Kategories stacked widget there (with the default two pages). This also means that you need to add the list views inside a proper layout of each page (which is empty by default).

While you can do that directly from Designer, you're using a subclass, so it's easier to add the view from code.

The alternative would be to add the views in Designer, and in that case you have two choices:

  1. install an event filter on the viewport (self.someView.viewport().installEventFilter(self)) and then only check for mouse events (but always return the base implementation: return super().eventFilter(obj, event));
  2. use a promoted widget with the custom subclass (do some research on the topic, as there are many answers even here in SO);
from PyQt6.QtCore import *
from PyQt6.QtGui import *
from PyQt6.QtWidgets import *

# Might have a different name, depending on the object name used in
# Designer; by default, basic "form widgets" are called `Form`, so it
# would be `Ui_Form`. The module name depends on the arguments of pyuic.
from ui_mainWindow import Ui_MainWindow

class ListView_Categories(QListView):
    swap = pyqtSignal(int)
    def __init__(self, parent:None):
        super().__init__(parent)
        self.parent = parent
        self.m_model = QStandardItemModel(self)
        self.setModel(self.m_model)
        self.setAcceptDrops(False)
        self.setIconSize(QSize(150,150))
        self.setDragDropMode(QAbstractItemView.DragDropMode.NoDragDrop)
        self.setResizeMode(QListView.ResizeMode.Adjust)
        self.setViewMode(QListView.ViewMode.IconMode)
        self.setMovement(QListView.Movement.Static)

        self.setStyleSheet("""
            QListView {
                background-color: rgb(92, 52, 19);
            }
        """)

    def mousePressEvent(self, event):
        super().mousePressEvent(event)
        if event.button() == Qt.MouseButton.LeftButton:
            self.pressPos = event.pos().x()

    def mouseReleaseEvent(self, event):
        super().mousePressEvent(event)
        if event.button() == Qt.MouseButton.LeftButton:
            delta = event.pos().x() - self.pressPos
            if abs(delta) >= 100:
                # emit  1 or -1 depending on the value
                self.swap.emit(abs(delta) // delta)


class MainWindow(QWidget, Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        for i, page in enumerate((self.page_1, self.page_2)):
            layout = page.layout()
            if layout is None:
                layout = QVBoxLayout(page)
            listView = ListView_Categories()
            layout.addWidget(listView)
            listView.swap.connect(self.swap)

            item = QStandardItem(str(i   1))
            item.setIcon(QIcon("000_Ordnerstruktur/003_Test/1.ico"))
            listView.m_model.appendRow(item)

        self.btn.clicked.connect(self.goToSecond)

    def swap(self, delta):
        newIndex = self.wrapper_Kategories.currentIndex()   delta
        self.wrapper_Kategories.setCurrentIndex(newIndex)

    def goToSecond(self):
        self.wrapper_Kategories.setCurrentIndex(1)


if __name__ == "__main__":
    app = QApplication(sys.argv)

    mainWindow = MainWindow()
    mainWindow.show()

    sys.exit(app.exec())

[1] About object hierarchy and how they should (or should not) interact with their parents, read this related post

  • Related