在 PyQt 中从 QLabel 到 QLabel 画线

Drawing Line from QLabel to QLabel in PyQt

我对 PyQt 还很陌生 我正在尝试从 1 QLabel 到另一个 QLabel 画一条线。
我的 2 QLabel 位于另一个 QLabel 上,它在我的 GUI 中充当图像。
我已经设法跟踪鼠标事件并四处移动标签,但我无法使用 QPainter 在它们之间画线。
提前谢谢你:)

这是我的 MouseTracking class

class MouseTracker(QtCore.QObject):
    positionChanged = QtCore.pyqtSignal(QtCore.QPoint)

    def __init__(self, widget):
        super().__init__(widget)
        self._widget = widget
        self.widget.setMouseTracking(True)
        self.widget.installEventFilter(self)

    @property
    def widget(self):
        return self._widget

    def eventFilter(self, o, e):
        if e.type() == QtCore.QEvent.MouseMove:
            self.positionChanged.emit(e.pos())
        return super().eventFilter(o, e)

这是我的 DraggableLabel class:

class DraggableLabel(QLabel):

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.LabelIsMoving = False
        self.setStyleSheet("border-color: rgb(238, 0, 0); border-width : 2.0px; border-style:inset; background: transparent;")
        self.origin = None
        # self.setDragEnabled(True)

    def mousePressEvent(self, event):
        if not self.origin:
            # update the origin point, we'll need that later
            self.origin = self.pos()
        if event.button() == Qt.LeftButton:
            self.LabelIsMoving = True
            self.mousePos = event.pos()
            # print(event.pos())

    def mouseMoveEvent(self, event):
        if event.buttons() == Qt.LeftButton:
            # move the box
            self.move(self.pos() + event.pos() - self.mousePos)

            # print(event.pos())

    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton:
            print(event.pos())

    def paintEvent(self, event):
        painter = QPainter()
        painter.setBrush(Qt.red)
        # painter.setPen(qRgb(200,0,0))
        painter.drawLine(10, 10, 200, 200)

这是我为 QTabwigdet 定制的 class(因为每当用户 add/insert 一个新标签时我需要控制和跟踪 2 个 QLabel 的位置)

class DynamicTab(QWidget):

    def __init__(self):
        super(DynamicTab, self).__init__()
        # self.count = 0
        self.setMouseTracking(True)
        self.setAcceptDrops(True)
        self.bool = True
        self.layout = QVBoxLayout(self)
        self.label = QLabel()
        self.layout.addChildWidget(self.label)

        self.icon1 = DraggableLabel(parent=self)
        #pixmap for icon 1
        pixmap = QPixmap('icon1.png')
        # currentTab.setLayout(QVBoxLayout())
        # currentTab.layout.setWidget(QRadioButton())
        self.icon1.setPixmap(pixmap)
        self.icon1.setScaledContents(True)
        self.icon1.setFixedSize(20, 20)

        self.icon2 = DraggableLabel(parent=self)
        pixmap = QPixmap('icon1.png')
        # currentTab.setLayout(QVBoxLayout())
        # currentTab.layout.setWidget(QRadioButton())
        self.icon2.setPixmap(pixmap)
        self.icon2.setScaledContents(True)
        self.icon2.setFixedSize(20, 20)
            #self.label.move(event.x() - self.label_pos.x(), event.y() - self.label_pos.y())

MainWindow 和主要方法:

class UI_MainWindow(QMainWindow):

    def __init__(self):
        super(UI_MainWindow, self).__init__()
        self.setWindowTitle("QHBoxLayout")
        self.PictureTab = QTabWidget

    def __setupUI__(self):
        # super(UI_MainWindow, self).__init__()
        self.setWindowTitle("QHBoxLayout")
        loadUi("IIML_test2.ui", self)
        self.tabChanged(self.PictureTab)
        # self.tabChanged(self.tabWidget)
        self.changeTabText(self.PictureTab, index=0, TabText="Patient1")
        self.Button_ImportNew.clicked.connect(lambda: self.insertTab(self.PictureTab))
        # self.PictureTab.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.PictureTab))
        # self.tabWidget.currentChanged.connect(lambda: self.tabChanged(QtabWidget=self.tabWidget))

    def tabChanged(self, QtabWidget):
        QtabWidget.currentChanged.connect(lambda : print("Tab was changed to ", QtabWidget.currentIndex()))

    def changeTabText(self, QTabWidget, index, TabText):
        QTabWidget.setTabText(index, TabText)

    def insertTab(self, QtabWidget):
        # QFileDialog.getOpenFileNames(self, 'Open File', '.')
        QtabWidget.addTab(DynamicTab(), "New Tab")
        # get number of active tab
        count = QtabWidget.count()
        # change the view to the last added tab
        currentTab = QtabWidget.widget(count-1)
        QtabWidget.setCurrentWidget(currentTab)

        pixmap = QPixmap('cat.jpg')
        #currentTab.setLayout(QVBoxLayout())
        #currentTab.layout.setWidget(QRadioButton())

        # currentTab.setImage("cat.jpg")
        currentTab.label.setPixmap(pixmap)
        currentTab.label.setScaledContents(True)
        currentTab.label.setFixedSize(self.label.width(), self.label.height())
        tracker = MouseTracker(currentTab.label)
        tracker.positionChanged.connect(self.on_positionChanged)
        self.label_position = QtWidgets.QLabel(currentTab.label, alignment=QtCore.Qt.AlignCenter)
        self.label_position.setStyleSheet('background-color: white; border: 1px solid black')
        currentTab.label.show()
        # print(currentTab.label)

    @QtCore.pyqtSlot(QtCore.QPoint)
    def on_positionChanged(self, pos):
        delta = QtCore.QPoint(30, -15)
        self.label_position.show()
        self.label_position.move(pos + delta)
        self.label_position.setText("(%d, %d)" % (pos.x(), pos.y()))
        self.label_position.adjustSize()

    # def SetupUI(self, MainWindow):
    #
    #     self.setLayout(self.MainLayout)


    if __name__ == '__main__':
        app = QApplication(sys.argv)
        UI_MainWindow = UI_MainWindow()
        UI_MainWindow.__setupUI__()
        widget = QtWidgets.QStackedWidget()
        widget.addWidget(UI_MainWindow)
        widget.setFixedHeight(900)
        widget.setFixedWidth(1173)
        widget.show()
        try:
            sys.exit(app.exec_())
        except:
            print("Exiting")

我的概念:我有一个DynamicTab(QTabWidget)作为图片opener(每当用户按下 Import Now 时)。这个小部件的 child 是 3 个 Qlabels:self.label 是它自己的图片,另外两个 Qlabels 是 icon1 和 icon2,我试图 interact/drag with (Draggable Label)

我的问题: 我正在尝试跟踪我的鼠标移动并自定义画家以相应地绘画。我正在通过告诉画家 class 每当我抓住标签并用鼠标移动它时进行绘画(因此,可拖动)来尝试这一点。但是,每当我没有按住或单击鼠标左键时,我只能跟踪主 QLabel(主图片)内的鼠标位置。 任何帮助将不胜感激。 谢谢大家。

绘画只能在小部件矩形内进行,因此您不能在 DraggableLabel 的边界之外绘制。

解决方案是创建一个共享同一父级的进一步自定义小部件,然后绘制连接其他两个中心的线。

在下面的示例中,我在两个可拖动标签上安装了一个事件过滤器,它将根据它们更新自定义小部件的大小(这样它的几何图形将始终包含这两个几何图形)并调用 self.update()安排重新粉刷。请注意,由于小部件是在其他两个部件之上创建的,因此它可能会捕获用于其他部件的鼠标事件;为防止这种情况,必须设置 Qt.WA_TransparentForMouseEvents 属性。

class Line(QWidget):
    def __init__(self, obj1, obj2, parent):
        super().__init__(parent)
        self.obj1 = obj1
        self.obj2 = obj2

        self.obj1.installEventFilter(self)
        self.obj2.installEventFilter(self)

        self.setAttribute(Qt.WA_TransparentForMouseEvents)

    def eventFilter(self, obj, event):
        if event.type() in (event.Move, event.Resize):
            rect = self.obj1.geometry() | self.obj2.geometry()
            corner = rect.bottomRight()
            self.resize(corner.x(), corner.y())
            self.update()
        return super().eventFilter(obj, event)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.setRenderHint(painter.Antialiasing)
        painter.setPen(QColor(200, 0, 0))
        painter.drawLine(
            self.obj1.geometry().center(), 
            self.obj2.geometry().center()
        )


class DynamicTab(QWidget):
    def __init__(self):
        # ...
        self.line = Line(self.icon1, self.icon2, self)

备注:

  • 为了简单起见,我只用了resize()(不是setGeometry()),这样widget会一直放在parent的左上角,我们可以直接拿到另一个没有任何转换的widget坐标;
  • 自定义小部件位于其他两个之上,因为它是在它们之后添加的;如果你想把它放在下面,使用self.line.lower();
  • painter 必须始终使用 paint 设备参数进行初始化,可以使用 QPainter(obj)painter.begin(obj),否则不会进行任何绘画(并且输出中会出现很多错误) ;
  • 不使用layout.addChildWidget()(布局内部使用),而是布局适当的addWidget()功能;
  • 可以使用 border: 2px inset rgb(238, 0, 0);;
  • 缩短样式表边框语法
  • insertTab 的第一行可以更简单:currentTab = DynamicTab() QtabWidget.addTab(currentTab, "New Tab");
  • currentTab.label.setFixedSize(self.label.size());
  • QMainWindow 通常用作顶级小部件,通常不鼓励将其添加到 QStackedWidget;请注意,如果你这样做是因为 Youtube 教程,那么该教程以建议糟糕的做法(如最后的 try/except 块)而闻名,应该 遵循这些做法;
  • 只有 类 和常量的名称应该大写,变量和函数则应始终以小写字母开头;