PyQt技术实现:创建随按钮移动的LineItem
解决连线跟随按钮移动的问题
当然可以直接修改LineItem的位置来实现连线随按钮移动!完全不用删除重建,咱们只需要让每条连线记住它绑定的两个按钮,然后在按钮移动时实时更新连线的起点和终点就好。我帮你调整一下代码,核心思路如下:
关键修改点
- 给
GraphicsLineItem增加属性,存储它连接的两个按钮对象,这样能随时获取按钮的实时位置 - 把原来记录坐标的逻辑改成记录按钮引用,避免坐标过时
- 按钮移动时触发连线的位置更新,这里我们通过重写
DragButton的moveEvent来实现
修改后的完整代码
import sys, os from PyQt5 import QtCore, QtGui, QtWidgets can_draw = 0 start_btn = None # 改为存储按钮对象 end_btn = None class DragButton(QtWidgets.QPushButton): moved = QtCore.pyqtSignal() # 新增移动信号,用来通知连线更新 def __init__(self, title, parent=None): super().__init__(title, parent) self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.showMenu) def showMenu(self): menu = QtWidgets.QMenu() menu.addAction("connect", self.connectLine) menu.exec_(self.cursor().pos()) def mouseMoveEvent(self, e): if e.buttons() != QtCore.Qt.LeftButton: return mimeData = QtCore.QMimeData() drag = QtGui.QDrag(self) drag.setMimeData(mimeData) drag.setHotSpot(e.pos() - self.rect().topLeft()) dropAction = drag.exec_(QtCore.Qt.MoveAction) def moveEvent(self, e): super().moveEvent(e) self.moved.emit() # 按钮移动时发送信号 def connectLine(self): global can_draw, start_btn, end_btn view = self.parent() can_draw +=1 if can_draw == 1: start_btn = self # 记录第一个按钮 print("选中第一个按钮") if can_draw == 2: end_btn = self # 记录第二个按钮 can_draw -= 2 view.createLineItem(start_btn, end_btn) # 把按钮传给创建连线的方法 start_btn = None end_btn = None class GraphicsLineItem(QtWidgets.QGraphicsLineItem): def __init__(self, start_btn, end_btn, parent=None): super().__init__(parent) self.start_btn = start_btn self.end_btn = end_btn # 绑定按钮的移动信号到更新方法 self.start_btn.moved.connect(self.updateLine) self.end_btn.moved.connect(self.updateLine) self.updateLine() # 初始化时绘制第一条线 def updateLine(self): # 获取按钮在场景中的位置(取按钮中心更合理) view = self.start_btn.parent() start_pos = QtCore.QPointF(view.mapToScene(self.start_btn.pos()) + QtCore.QPointF(self.start_btn.width()/2, self.start_btn.height()/2)) end_pos = QtCore.QPointF(view.mapToScene(self.end_btn.pos()) + QtCore.QPointF(self.end_btn.width()/2, self.end_btn.height()/2)) self.setLine(QtCore.QLineF(start_pos, end_pos)) def contextMenuEvent(self, event): menu = QtWidgets.QMenu() menu.addAction("Delete", self.remove) menu.exec_(self.cursor().pos()) def remove(self): # 移除前断开信号连接,避免内存泄漏 self.start_btn.moved.disconnect(self.updateLine) self.end_btn.moved.disconnect(self.updateLine) self.scene().removeItem(self) def shape(self): p = super(GraphicsLineItem, self).shape() stroker = QtGui.QPainterPathStroker() stroker.setWidth(20) return stroker.createStroke(p) class View(QtWidgets.QGraphicsView): def __init__(self, parent=None): super(View, self).__init__(parent) self.setScene(QtWidgets.QGraphicsScene(self)) self.setAcceptDrops(True) self.setSceneRect(QtCore.QRectF(self.viewport().rect())) self.btn1 = QtWidgets.QPushButton("Start") self.btn1.setGeometry(230, 80, 100, 30) self.btn1.setCheckable(True) self.btn1.clicked.connect(self.add_Text) def createLineItem(self, start_btn, end_btn): # 创建连线时传入两个按钮 line_item = GraphicsLineItem(start_btn, end_btn) self.scene().addItem(line_item) def clearScene(self): self.scene().clear() def add_Text(self): self.button = DragButton('Text', self) self.button.setGeometry(230, 80, 100, 30) self.button.show() def dragEnterEvent(self, e): e.accept() def dragMoveEvent(self, e): e.accept() def dropEvent(self, e): btn = e.source() position = e.pos() btn.move(position - btn.rect().topLeft()) # 修正位置,让鼠标落点对应按钮的拖动点 e.setDropAction(QtCore.Qt.MoveAction) e.accept() class Window(QtWidgets.QWidget): def __init__(self, parent=None): super(Window, self).__init__(parent) self.view = View() self.button = QtWidgets.QPushButton( "Clear View", clicked=self.view.scene().clear ) self.btn1 = QtWidgets.QPushButton("add button") self.btn1.setCheckable(True) self.btn1.clicked.connect(self.view.add_Text) layout = QtWidgets.QVBoxLayout(self) layout.addWidget(self.view) layout.addWidget(self.btn1) layout.addWidget(self.button) # 把Clear按钮加回去 if __name__ == "__main__": import sys app = QtWidgets.QApplication(sys.argv) window = Window() window.resize(640, 480) window.show() sys.exit(app.exec_())
代码说明
- 按钮移动信号:给
DragButton添加了moved信号,每次按钮移动(不管是拖拽还是其他方式)都会触发这个信号 - 连线绑定按钮:
GraphicsLineItem初始化时接收两个按钮对象,并且绑定它们的moved信号到自己的updateLine方法 - 实时更新连线:
updateLine方法会获取两个按钮的当前位置(这里取按钮中心,比取左上角更合理),然后调用setLine方法直接修改连线的坐标,完全不需要删除重建 - 修正拖拽位置:原来的
dropEvent里直接btn.move(position)会导致按钮位置偏移,改成btn.move(position - btn.rect().topLeft())让鼠标落点和按钮的拖动点对应,体验更自然
这样修改后,你拖动任意一个按钮,对应的连线都会实时跟着移动,完全符合你的需求!
内容的提问来源于stack exchange,提问作者於二號
相关产品推荐
相关产品推荐

