You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在PyQt6布局内的控件上方绘制跨控件的线条?

如何在PyQt6布局内的控件上方绘制跨控件的线条?

Hey there! To draw lines that span across multiple widgets in your PyQt6 app, you need a way to render on top of all your existing UI elements. The cleanest approach (especially with recent PyQt6 versions) is to use a transparent overlay widget that sits above your main layout. Here's how to integrate this into your code:

核心思路

  • 自定义一个透明的覆盖层控件,专门处理线条绘制,同时不影响底层控件的交互
  • 将这个覆盖层添加到主窗口中心控件的内置覆盖层中,确保它始终显示在所有控件上方
  • 通过事件触发(比如按钮点击)来调用绘制线条的方法,实现动态画线

完整代码

import sys
from PyQt6.QtCore import QSize, Qt, QPoint
from PyQt6.QtWidgets import QApplication, QMainWindow, QPushButton, QVBoxLayout, QWidget
from PyQt6.QtGui import QPainter, QPen

class LineOverlay(QWidget):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.lines = []  # 存储线条信息:(起点, 终点, 颜色)
        # 让覆盖层对鼠标事件透明,确保底层按钮可点击
        self.setAttribute(Qt.WidgetAttribute.WA_TransparentForMouseEvents)
        # 移除背景色,保持透明
        self.setAttribute(Qt.WidgetAttribute.WA_NoSystemBackground)

    def add_line(self, start: QPoint, end: QPoint, color=Qt.GlobalColor.red):
        """添加一条线条并触发重绘"""
        self.lines.append((start, end, color))
        self.update()

    def paintEvent(self, event):
        """绘制所有已存储的线条"""
        painter = QPainter(self)
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)  # 开启抗锯齿,让线条更平滑
        
        for start, end, color in self.lines:
            pen = QPen(color, 2)  # 设置线条宽度为2px
            painter.setPen(pen)
            painter.drawLine(start, end)

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("My App")
        
        # 创建主布局和按钮
        layout = QVBoxLayout()
        
        self.button1 = QPushButton("Press Me!")
        self.button1.setFixedSize(QSize(200, 60))
        self.button2 = QPushButton("Press Me!")
        self.button2.setFixedSize(QSize(200, 60))
        self.button3 = QPushButton("Press Me!")
        self.button3.setFixedSize(QSize(200, 60))
        
        # 添加触发画线的按钮
        draw_btn = QPushButton("Draw Cross-Widget Line")
        draw_btn.clicked.connect(self.draw_cross_widget_line)
        
        # 将控件添加到布局
        layout.addWidget(self.button1)
        layout.addWidget(self.button2)
        layout.addWidget(self.button3)
        layout.addWidget(draw_btn)
        
        # 设置中心控件
        central_widget = QWidget()
        central_widget.setLayout(layout)
        self.setCentralWidget(central_widget)
        
        # 创建并挂载覆盖层
        self.line_overlay = LineOverlay()
        central_overlay = central_widget.overlay()
        central_overlay.addWidget(self.line_overlay)
        
        # 让覆盖层始终填满中心控件,窗口大小变化时自动调整
        self.line_overlay.setGeometry(central_widget.rect())
        central_widget.resize.connect(lambda: self.line_overlay.setGeometry(central_widget.rect()))

    def draw_cross_widget_line(self):
        """绘制一条从button1中心到button3中心的线条"""
        # 获取按钮在中心控件坐标系中的中心位置
        btn1_center = self.button1.pos() + QPoint(self.button1.width()//2, self.button1.height()//2)
        btn3_center = self.button3.pos() + QPoint(self.button3.width()//2, self.button3.height()//2)
        
        # 将线条添加到覆盖层
        self.line_overlay.add_line(btn1_center, btn3_center)

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.show()
    app.exec()

关键细节说明

  1. LineOverlay类:

    • 用列表存储所有需要绘制的线条信息
    • 通过WA_TransparentForMouseEvents属性让覆盖层不拦截鼠标事件,底层按钮仍可正常点击
    • 重写paintEvent方法实现线条绘制,开启抗锯齿让线条更美观
  2. 主窗口设置:

    • 保留按钮的引用以便后续获取位置信息
    • 使用PyQt6内置的QOverlay层来挂载覆盖控件,确保它始终显示在最上层
    • 绑定窗口大小变化事件,让覆盖层自动适配窗口尺寸
  3. 动态画线:

    • 点击"Draw Cross-Widget Line"按钮会触发draw_cross_widget_line方法,计算两个按钮的中心位置并绘制线条
    • 你可以修改这个方法来绘制任意线条,只需传入中心控件坐标系下的起点和终点即可

备注:内容来源于stack exchange,提问作者dspaedt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.14 12:34:35