[PySide] detecting left/right click in QListView

Hello everyone,

I am having issues with proper detection of left and light click events inside a QListView element. I use a model to display items in this list. The issue is that if I use the clicked.connect signal, I cannot detect if left or right click was pressed. I currently use the “mouseReleaseEvent” of QListView and here I get the arguments and can therefore detect left/right click but the issue is that this event triggers even if I don’t click on an actual item in the list and this causes a very annoying effect that a user can double click on an empty space and my code will register it as a double click on the last item that was selected. So my questions here are:

  • is there a way to get the mouse event arguments on clicked signal?
  • is it possible to check if mouse is over an item in the list when the “mouseReleaseEvent” event triggers?
  • is there maybe some other and proper way to separately detect left and right clicks in QListView using a model to display items?

Thank you in advance :slight_smile:

You need to install a QMouseEvent to capture the click and then inspect it to determine which button was clicked.

http://doc.qt.io/qt-4.8/qmouseevent.html

you will need to re-implement the mousePressEvent
here is an example from one of my Tree Views - I want a Right-Click to pop up a context menu, but by default, left and right clicks select items in a Tree View.


    def mousePressEvent(self, event):
        '''re-implemented to suppress Right-Clicks from selecting items.'''
        
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                return
            else:
                super(MyView, self).mousePressEvent(event)



[QUOTE=rgkovach123;27456]You need to install a QMouseEvent to capture the click and then inspect it to determine which button was clicked.

http://doc.qt.io/qt-4.8/qmouseevent.html

you will need to re-implement the mousePressEvent
here is an example from one of my Tree Views - I want a Right-Click to pop up a context menu, but by default, left and right clicks select items in a Tree View.


    def mousePressEvent(self, event):
        '''re-implemented to suppress Right-Clicks from selecting items.'''
        
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                return
            else:
                super(MyView, self).mousePressEvent(event)



[/QUOTE]

Thank you for your reply, I haven’t tried your solution yet, I will do so on monday and leave another reply then :slight_smile:

[QUOTE=rgkovach123;27456]You need to install a QMouseEvent to capture the click and then inspect it to determine which button was clicked.

http://doc.qt.io/qt-4.8/qmouseevent.html

you will need to re-implement the mousePressEvent
here is an example from one of my Tree Views - I want a Right-Click to pop up a context menu, but by default, left and right clicks select items in a Tree View.


    def mousePressEvent(self, event):
        '''re-implemented to suppress Right-Clicks from selecting items.'''
        
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                return
            else:
                super(MyView, self).mousePressEvent(event)



[/QUOTE]

I tried the method above but it triggers the event even if I don’t click over an item in the list (I click on an empty space in the list view). I need to find a way to detect the event only when the item in the list is clicked and in there I need to know if it was a left or a right click.

the event will always occur, if you click in the empty space, you will get an invalid index AKA the “invisible root”. When the mouse event occurs and the selected item was the invisible root, you just return without doing anything.

[QUOTE=rgkovach123;27514]the event will always occur, if you click in the empty space, you will get an invalid index AKA the “invisible root”. When the mouse event occurs and the selected item was the invisible root, you just return without doing anything.[/QUOTE]

How do I check that? Is that info in the event object? I’m sorry for asking so much questions, but I’m confused :slight_smile:

what do you want to happen when the user clicks on items? Right-click does nothing? Left-click does something in addition to selecting the item?

there are multiple concepts to wrap your head around. Overriding Events, connecting Signals, finding the item under the mouse, etc.

[QUOTE=rgkovach123;27516]what do you want to happen when the user clicks on items? Right-click does nothing? Left-click does something in addition to selecting the item?

there are multiple concepts to wrap your head around. Overriding Events, connecting Signals, finding the item under the mouse, etc.[/QUOTE]

I currently have this


self.list = QtGui.QListView()
self.list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.list.customContextMenuRequested.connect(self.listContextMenuRequested)
self.list.mouseReleaseEvent = self.mouseClicked

def mouseClicked(self, e):
        # Here I need to check if it was a left click, and if it was I also need to check if the item in the list was actually clicked

I would use the clicked signal wich gives me back the model of the clicked item but problem with clicked signal is that it also registers right click (and middle click and what not) and this ruins the flow because when the user right clicks on the item it registers as left click and it opens the context menu at the same time.

All I need is to filter out the left clicks that did not occur over the actual item in the QListView.

you are on the right track, but instead of connecting to the mouseReleaseEvent, I would just subclass the QListView and re-implement the Mouse event.

class MyListView(QtGui.QListView):
   

    def __init__(self):
        super(MyListView, self).__init__()  

  
    def mousePressEvent(self, event):     
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                return
            else:
                super(MyListView, self).mousePressEvent(event)

[QUOTE=rgkovach123;27518]you are on the right track, but instead of connecting to the mouseReleaseEvent, I would just subclass the QListView and re-implement the Mouse event.

class MyListView(QtGui.QListView):
   

    def __init__(self):
        super(MyListView, self).__init__()  

  
    def mousePressEvent(self, event):     
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.RightButton:
                return
            else:
                super(MyListView, self).mousePressEvent(event)

[/QUOTE]

I completely understand what you are telling me and I have left/right click filtering working the way I want but the main problem is that I want to know weather I clicked on the item in the list or I just clicked on the list but not on the item. How can I know if the click on the QListView occured over an item in the list or not? That is my only problem, maybe I’m not explaining myself that well.

This should give you the row the cursor is positioning over. -1 if the cursor is on empty space.

from PySide import QtGui, QtCore

class MyList(QtGui.QListView):
    def __init__(self, *args, **kwargs):
        super(MyList, self).__init__(*args, **kwargs)
            
    def mousePressEvent(self, event):
        if event.type() == QtCore.QEvent.MouseButtonPress:
            pos = self.mapFromGlobal(QtGui.QCursor.pos())
            row = self.indexAt(pos).row()
            if event.button() == QtCore.Qt.RightButton:
                print "Right clicked on row %s" % row
            else:
                print "Left clicked on row %s" % row

        
ML = MyList()
ML.show()
ML.raise_()

model = QtGui.QStandardItemModel()
model.appendRow(QtGui.QStandardItem('adad'))
model.appendRow(QtGui.QStandardItem('12354'))
ML.setModel(model)

[QUOTE=panupat;27537]This should give you the row the cursor is positioning over. -1 if the cursor is on empty space.

from PySide import QtGui, QtCore

class MyList(QtGui.QListView):
    def __init__(self, *args, **kwargs):
        super(MyList, self).__init__(*args, **kwargs)
            
    def mousePressEvent(self, event):
        if event.type() == QtCore.QEvent.MouseButtonPress:
            pos = self.mapFromGlobal(QtGui.QCursor.pos())
            row = self.indexAt(pos).row()
            if event.button() == QtCore.Qt.RightButton:
                print "Right clicked on row %s" % row
            else:
                print "Left clicked on row %s" % row

        
ML = MyList()
ML.show()
ML.raise_()

model = QtGui.QStandardItemModel()
model.appendRow(QtGui.QStandardItem('adad'))
model.appendRow(QtGui.QStandardItem('12354'))
ML.setModel(model)

[/QUOTE]

Thank you panupat, I already found the solution but it’s good to have more than 1 solution for other people that will come accross this thread. Here is how I managed to get the right result.

First I extended the QtGui.QListView class and reimplemented mousePressedEvent and mouseReleasedEvent like this:


from PySide import QtGui, QtCore

class QListView(QtGui.QListView):

    def __init__(self):
        super(QListView, self).__init__()

    def mousePressEvent(self, event):
        if event.type() == QtCore.QEvent.MouseButtonPress:
            if event.button() == QtCore.Qt.LeftButton:
                super(QListView, self).mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        if event.type() == QtCore.QEvent.MouseButtonRelease:
            if event.button() == QtCore.Qt.LeftButton:
                super(QListView, self).mouseReleaseEvent(event)

And then in the class I named QSingleListView where I extend QtGui.QWidget I did this:


class QSingleListView(QtGui.QWidget):

    def __init__(self):
        super(QSingleListView, self).__init__()
        self.model = None
        self.initUI()

    def initUI(self):
        self.layout = QtGui.QVBoxLayout()

        self.list = QListView() # This is the QListView class I created (mentioned above)
        self.list.clicked.connect(self.clickedConnect)

        self.layout.addWidget(self.list)

        self.setLayout(self.layout)

    def clickedConnect(self, model):
        pass

Because of the overriden methods in QListView, this signal only triggers on left click (so no need to check if item was clicked or not because clicked signal only occurs on clicked items already)

So the final thought would be:

  • If you wish to archieve the result only with mouse events, use panupat’s solution
  • If you wish to achieve the result using connect signal, use my solution (thank you rgkovach123)

ok, yea to get the item that was selected, you use the “clicked” signal. it will pass the index of the item under the mouse cursor. If you clicked in the empty area, you will get the Invalid Index AKA the “invisible root”.

if you want to pop up a menu over the item, you use the “customContextMenuRequested” signal which passes the mouse position, which needs to be mapped to the underlying item in the view, using QViewport.mapToGlobal()