Get all widgets under cursor

本秂侑毒 提交于 2020-12-29 13:18:53

问题


The widgetAt function gives me the widget directly under the cursor, at the highest z-order.

pos = QtGui.QCursor.pos()
widget = QtGui.qApp.widgetAt(pos)

But how can I get all widgets under the cursor? Including those behind the top-most one? Something like:

pos = QtGui.QCursor.pos()
widgets = QtGui.qApp.widgetsAt(pos)

Example

As an example of use, picture an overlay which adds animated graphics, like ripples on the surface of water, wherever the user clicked. Naturally, the overlay would need to receive and respond to clicks, but it must do so without interfering with clicks propagating to widgets underneath it.

Another example is usage tracking; to intercept and record clicks for analysis, the same overlay can be used but this time nothing is drawn, only collected.

In both cases, if only one widget is getting the click event, I'll need a way of distinguishing what widgets reside underneath it so as to pass the event on.


回答1:


Here's another possible solution, calling the existing QApplication.widgetAt multiple times.

import sys
from PySide import QtGui, QtCore


def widgets_at(pos):
    """Return ALL widgets at `pos`

    Arguments:
        pos (QPoint): Position at which to get widgets

    """

    widgets = []
    widget_at = QtGui.qApp.widgetAt(pos)

    while widget_at:
        widgets.append(widget_at)

        # Make widget invisible to further enquiries
        widget_at.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents)
        widget_at = QtGui.qApp.widgetAt(pos)

    # Restore attribute
    for widget in widgets:
        widget.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, False)

    return widgets

Example

image

class Overlay(QtGui.QWidget):
    def __init__(self, parent=None):
        super(Overlay, self).__init__(parent)
        self.setAttribute(QtCore.Qt.WA_StyledBackground)
        self.setStyleSheet("QWidget { background-color: rgba(0, 255, 0, 50) }")

    def mousePressEvent(self, event):
        pos = QtGui.QCursor.pos()
        print [w.objectName() for w in widgets_at(pos)]
        return super(Overlay, self).mousePressEvent(event)


app = QtGui.QApplication(sys.argv)
window = QtGui.QWidget()
window.setObjectName("Window")
window.setFixedSize(200, 100)

button = QtGui.QPushButton("Button 1", window)
button.setObjectName("Button 1")
button.move(10, 10)
button = QtGui.QPushButton("Button 2", window)
button.setObjectName("Button 2")
button.move(50, 15)
overlay = Overlay(window)
overlay.setObjectName("Overlay")
overlay.setFixedSize(window.size())

window.show()
app.exec_()

Here is some output.

[u'Overlay', u'Window'] # Clicking an empty area
[u'Overlay', u'Button 1', u'Window'] # Button 1
[u'Overlay', u'Button 2', u'Window'] # Button 2
[u'Overlay', u'Button 2', u'Button 1', u'Window'] # Overlap

It looks like the results are O(1), with the added benefit of returning each widget in the order in which they overlap. It does however cause overlapping windows to flash (Windows 8) as their attribute is changed and restored.




回答2:


Not pretty, but off the top of my head: You'll need to cycle through all of your widgets and use their coordinates against the mouse coordinates.

EDIT: If you want O(1), you can do this but it will require more overhead, and you'll need to store the coordinates after each move. I actually have a class that does this, as I want movable (by user) widgets to come up in their previous state, and I don't want to go through the hassle of coding this in all of my projects. As such, I've subclassed QDialog (etc), and I add it to a convenience class which is essentially a glorified QSettings object. When QDialog emits anything useful, the convenience class's slots will store those values (Geometry(), basically) in QSettings. For any project to use this behavior, a simple ConvenienceClass::addWidget() call w/ the QWidget, and a QString as an argument (as the key) and it's all stored under the sheets. If you wanted to expand on this, you can store all of your QWidget's coordinates in this convenience class, and simply grap them O(1) when you deem necessary.

The storing of the coordinates is trivial, as the work done to calculate them is done anyway. The convenience class which holds these isn't too difficult, but does add some complexity.



来源:https://stackoverflow.com/questions/27363267/get-all-widgets-under-cursor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!