【问题标题】:Run Method on Widget Move小部件移动上的运行方法
【发布时间】:2021-03-08 16:57:02
【问题描述】:

我正在尝试设计一个继承自 PyQt5 基 QLabel 类的标签类,该类能够跟踪另一个小部件。这是我班级的当前代码:

class AttachedLabel(QLabel):
    def __init__(self, attachedTo, *args, side="left", ** kwargs):
        super().__init__(*args, **kwargs) # Run parent initialization
        # Define instance variables
        self.attached = attachedTo
        self.side = side

        # Update label position
        self.updatePos()

    def updatePos(self):
        # Get "attached widget" position and dimensions
        x = self.attached.geometry().x()
        y = self.attached.geometry().y()
        aWidth = self.attached.geometry().width()
        aHeight = self.attached.geometry().height()

        # Get own dimensions
        width = self.geometry().width()
        height = self.geometry().height()

        if self.side == "top":  # Above of attached widget
            self.setGeometry(x, y-height, width, height)
        elif self.side == "bottom":  # Below attached widget
            self.setGeometry(x, y+height+aHeight, width, height)
        elif self.side == "right":  # Right of attached widget
            self.setGeometry(x + width + aWidth, y, width, height)
        else:  # Left of attached widget
            self.setGeometry(x - width, y, width, height)

我希望能够像这样实例化标签:

AttachedLabel(self.pushButton, self.centralwidget)

self.pushButton 是它应该关注的小部件。问题是我不知道如何检测小部件何时移动以运行我的updatePos() 函数。理想情况下,我只会在其他小部件移动时更新标签位置,但我想避免将额外代码添加到正在跟踪的小部件的类中。我尝试过覆盖paintEvent,但这只会在对象本身需要重绘时触发,因此它甚至不能作为次优解决方案。

我可以使用/覆盖一些内置方法来检测小部件何时移动或屏幕本身何时更新?

【问题讨论】:

    标签: python python-3.x pyqt5


    【解决方案1】:

    您必须使用与 QEvent::Move 事件相交的 eventFilter,您还应该通过 QEvent::Resize 事件跟踪调整大小。

    from dataclasses import dataclass, field
    import random
    
    from PyQt5 import QtCore, QtWidgets
    
    
    class GeometryTracker(QtCore.QObject):
        geometryChanged = QtCore.pyqtSignal()
    
        def __init__(self, widget):
            super().__init__(widget)
            self._widget = widget
            self.widget.installEventFilter(self)
    
        @property
        def widget(self):
            return self._widget
    
        def eventFilter(self, source, event):
            if self.widget is source and event.type() in (
                QtCore.QEvent.Move,
                QtCore.QEvent.Resize,
            ):
                self.geometryChanged.emit()
            return super().eventFilter(source, event)
    
    
    @dataclass
    class TrackerManager:
        widget1: field(default_factory=QtWidgets.QWidget)
        widget2: field(default_factory=QtWidgets.QWidget)
        alignment: QtCore.Qt.Alignment = QtCore.Qt.AlignLeft
        enabled: bool = True
    
        valid_alignments = (
            QtCore.Qt.AlignLeft,
            QtCore.Qt.AlignRight,
            QtCore.Qt.AlignHCenter,
            QtCore.Qt.AlignTop,
            QtCore.Qt.AlignBottom,
            QtCore.Qt.AlignVCenter,
        )
    
        def __post_init__(self):
            self._traker = GeometryTracker(self.widget1)
            self._traker.geometryChanged.connect(self.update)
            if not any(self.alignment & flag for flag in self.valid_alignments):
                raise ValueError("alignment is not valid")
    
        def update(self):
            if not self.enabled:
                return
            r = self.widget1.rect()
    
            p1 = r.center()
            c1 = r.center()
    
            if self.alignment & QtCore.Qt.AlignLeft:
                p1.setX(r.left())
            if self.alignment & QtCore.Qt.AlignRight:
                p1.setX(r.right())
    
            if self.alignment & QtCore.Qt.AlignTop:
                p1.setY(r.top())
            if self.alignment & QtCore.Qt.AlignBottom:
                p1.setY(r.bottom())
    
            p2 = self.convert_position(p1)
            c2 = self.convert_position(c1)
    
            g = self.widget2.geometry()
            g.moveCenter(c2)
    
            if self.alignment & QtCore.Qt.AlignLeft:
                g.moveRight(p2.x())
            if self.alignment & QtCore.Qt.AlignRight:
                g.moveLeft(p2.x())
    
            if self.alignment & QtCore.Qt.AlignTop:
                g.moveBottom(p2.y())
            if self.alignment & QtCore.Qt.AlignBottom:
                g.moveTop(p2.y())
    
            self.widget2.setGeometry(g)
    
        def convert_position(self, point):
            gp = self.widget1.mapToGlobal(point)
            if self.widget2.isWindow():
                return gp
            return self.widget2.parent().mapFromGlobal(gp)
    
    
    class MainWindow(QtWidgets.QMainWindow):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.button = QtWidgets.QPushButton("Press me", self)
    
            self.label = QtWidgets.QLabel(
                "Tracker\nLabel", self, alignment=QtCore.Qt.AlignCenter
            )
            self.label.setAttribute(QtCore.Qt.WA_TransparentForMouseEvents, True)
            self.label.setFixedSize(200, 200)
            self.label.setStyleSheet(
                "background-color: salmon; border: 1px solid black; font-size: 40pt;"
            )
    
            self.resize(640, 480)
    
            self.manager = TrackerManager(
                widget1=self.button,
                widget2=self.label,
                alignment=QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter,
            )
    
            self.move_button()
    
        def move_button(self):
            pos = QtCore.QPoint(*random.sample(range(400), 2))
            animation = QtCore.QPropertyAnimation(
                targetObject=self.button,
                parent=self,
                propertyName=b"pos",
                duration=1000,
                startValue=self.button.pos(),
                endValue=pos,
            )
            animation.finished.connect(self.move_button)
            animation.start(QtCore.QAbstractAnimation.DeleteWhenStopped)
    
    
    def main():
        import sys
    
        app = QtWidgets.QApplication(sys.argv)
    
        w = MainWindow()
        w.show()
    
        sys.exit(app.exec_())
    
    
    if __name__ == "__main__":
        main()
    

    【讨论】:

    • 完美,多么广泛的参考!谢谢!
    猜你喜欢
    • 2018-09-03
    • 1970-01-01
    • 2011-12-20
    • 2018-11-03
    • 1970-01-01
    • 2020-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多