获取光标下的所有小部件

8
< p > widgetAt函数会返回当前鼠标下方最高层级的小部件。

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

但是我该如何获取鼠标下方的所有小部件?包括最上面的那些小部件?类似这样:

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

示例

举个例子,想象一下一个覆盖层,它可以添加动画图形,如水面上的涟漪,无论用户在哪里点击。显然,覆盖层需要接收和响应点击事件,但它必须在不干扰其下方小部件的情况下进行。

另一个例子是使用跟踪; 为了拦截并记录点击以供分析,可以再次使用相同的覆盖层,但这次不绘制任何内容,只收集数据。

在这两种情况下,如果只有一个小部件获取点击事件,我需要一种区分其下方小部件的方法,以便将事件传递给它们。


你需要循环遍历所有的小部件,并将它们的坐标与鼠标坐标进行比较... 我想是这样的。 - kiss-o-matic
这是个好主意,虽然有点不正规,但可行。你介意把它发表为答案吗? - Marcus Ottosson
2个回答

13

这里有另一种可能的解决方案,多次调用现有的QApplication.widgetAt函数。

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

示例

图像

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_()

这里是一些输出。

[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

看起来结果是O(1),并且额外的好处是以它们重叠的顺序返回每个小部件。但是,这会导致重叠的窗口闪烁(在Windows 8上),因为它们的属性被更改和恢复。


1
不太好看,但是我脑子里想到的是:你需要遍历所有的小部件,并将它们的坐标与鼠标坐标进行比较。
如果你想要O(1)的复杂度,你可以这样做,但是会增加额外的开销,并且你需要在每次移动后存储坐标。
实际上,我有一个类可以做到这一点,因为我希望可移动(由用户)的小部件能以它们之前的状态出现,并且我不想在所有项目中都编写这个代码。
因此,我派生了QDialog(等等),并将其添加到一个方便类中,该类本质上是一个改进的QSettings对象。
当QDialog发出任何有用的信号时,方便类的槽函数将把这些值(基本上是geometry())存储在QSettings中。
对于任何要使用这种行为的项目,只需调用ConvenienceClass::addWidget(),传入QWidget和一个QString作为参数(作为键),然后所有内容都会被存储起来。
如果你想进一步扩展,你可以将所有的QWidget坐标存储在这个方便的类中,并在需要时轻松地以O(1)的时间复杂度获取它们。
坐标的存储是微不足道的,因为计算它们的工作已经完成了。持有这些坐标的方便类并不太复杂,但确实增加了一些复杂性。

除非有更好的解决方案出现,否则我很快会将其标记为已接受。理想情况下,希望有一个更干净利落的解决方案,其性能为O(1),而不是这个O(n)的解决方案。 - Marcus Ottosson
但是我没有进行任何移动? - Marcus Ottosson
好的...不确定另一个小部件会在哪里(除了对话框上的按钮或画布之类的)。在这种情况下,您将不得不在它显示后立即抓取它,或者像以前一样采用线性方法。 - kiss-o-matic
明白了——我认为在这种情况下,您可以将它们添加到叠加层时将它们的坐标存储起来。除此之外,我只能想到O(n)的方法。 - kiss-o-matic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接