如何在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()
关键细节说明
LineOverlay类:
- 用列表存储所有需要绘制的线条信息
- 通过
WA_TransparentForMouseEvents属性让覆盖层不拦截鼠标事件,底层按钮仍可正常点击 - 重写
paintEvent方法实现线条绘制,开启抗锯齿让线条更美观
主窗口设置:
- 保留按钮的引用以便后续获取位置信息
- 使用PyQt6内置的
QOverlay层来挂载覆盖控件,确保它始终显示在最上层 - 绑定窗口大小变化事件,让覆盖层自动适配窗口尺寸
动态画线:
- 点击"Draw Cross-Widget Line"按钮会触发
draw_cross_widget_line方法,计算两个按钮的中心位置并绘制线条 - 你可以修改这个方法来绘制任意线条,只需传入中心控件坐标系下的起点和终点即可
- 点击"Draw Cross-Widget Line"按钮会触发
备注:内容来源于stack exchange,提问作者dspaedt
相关产品推荐
相关产品推荐

