PyQt5中QGraphicsScene元素添加与拖拽移动问题求助
PyQt5 QGraphicsScene元素无法选中移动的问题
我正在为毕业设计开发一款PyQt5应用,包含四个区域:关键词列表、文件名列表、图形工作区和控制台。原本想把前两个区域的元素拖拽到图形工作区,没实现,就改用双击添加的方式。现在元素能成功添加到QGraphicsScene,但无法选中和移动,试过把所有窗口改成QGraphicsScene也没用,求正确处理QGraphicsScene的方法。
主类代码
import sys import json from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QVBoxLayout, QSplitter, QWidget, QListWidget, QTextEdit, QLabel from PyQt5.QtCore import Qt, QRectF from PyQt5.QtWidgets import * from CustomWidget import CustomWidget class Main(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Application PyQt5") self.setGeometry(100, 100, 800, 600) self.current_x = 0 self.current_y = 0 self.keywords = [] self.scripts = [] self.view1 = QGraphicsView() self.scene1 = QGraphicsScene() self.view1.setScene(self.scene1) self.keywordList = QListWidget() self.fileListe = QListWidget() self.console = QTextEdit() top_layout = QSplitter(Qt.Horizontal) top_layout.addWidget(self.keywordList) top_layout.addWidget(self.view1) bottom_layout = QSplitter(Qt.Horizontal) bottom_layout.addWidget(self.fileListe) bottom_layout.addWidget(self.console) layout = QVBoxLayout() layout.addWidget(top_layout, stretch=3) layout.addWidget(bottom_layout, stretch=1) self.keywordList.setDragEnabled(True) self.fileListe.setDragEnabled(True) self.view1.setSceneRect(QRectF(0, 0, self.view1.width(), self.view1.height())) self.view1.setAcceptDrops(True) self.custom_widget = CustomWidget(self.view1) central_widget = CustomWidget(self.view1) central_widget.setLayout(layout) central_widget.view1 = self.view1 self.setCentralWidget(central_widget) self.load_data() self.keywordList.itemDoubleClicked.connect(self.add_label_to_scene) self.fileListe.itemDoubleClicked.connect(self.add_label_to_scene) def load_data(self): self.keywordList.clear() self.fileListe.clear() with open('keywords.json', 'r') as f: data = json.load(f) self.keywords = data['keywords'] self.scripts = data['script'] for keyword_data in self.keywords: keyword = keyword_data['keyword'] self.keywordList.addItem(keyword) for script_data in self.scripts: file_name = script_data['file_name'] self.fileListe.addItem(file_name) def add_label_to_scene(self, item): text = item.text() label = QLabel(text) label.setStyleSheet("border: 1px solid black; padding: 10px;") proxy = self.view1.scene().addWidget(label) self.custom_widget.enable_draggable(proxy) proxy.setPos(self.current_x, self.current_y) self.current_x += 50 self.current_y += 50 if __name__ == "__main__": app = QApplication(sys.argv) window = Main() window.show() sys.exit(app.exec_())
CustomWidget类代码
from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class CustomWidget(QWidget): def __init__(self, view1): super().__init__() self.view1 = view1 self.painter = QPainter(self) self.painter.drawRect(self.rect()) def enable_draggable(self, item): item.setFlag(QGraphicsItem.ItemIsMovable, True) def mousePressEvent(self, event): print("Left mouse button pressed.") self.checkItemsUnderMouse(event.pos()) event.accept() def checkItemsUnderMouse(self, pos): print("Element clicked:") for item in self.view1.scene().items(): if isinstance(item, QGraphicsProxyWidget): widget = item.widget() if isinstance(widget, QLabel): print("Text:", widget.text()) print("Position:", item.pos()) def dragMoveEvent(self, event): event.setAccepted(True) def dragLeaveEvent(self, event): self.update()
问题分析与修复方案
核心问题
- CustomWidget抢占鼠标事件:你把
CustomWidget设为中央窗口,它会拦截所有鼠标点击,导致QGraphicsView无法接收事件,选不中场景元素。 - 代理控件缺少选中标记:只设置
ItemIsMovable不够,必须开启ItemIsSelectable才能让元素被选中。 - 初始化绘图代码错误:在CustomWidget的
__init__里直接用QPainter会报错,绘图逻辑应该放在paintEvent中。
具体修改步骤
1. 替换中央部件为普通QWidget
删除CustomWidget作为中央部件的逻辑,改用普通QWidget承载布局:
# 替换原central_widget代码 central_widget = QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget)
同时删除self.custom_widget = CustomWidget(self.view1)这句。
2. 完善代理控件的属性设置
在添加元素到场景时,同时开启可移动、可选中标记:
def add_label_to_scene(self, item): text = item.text() label = QLabel(text) label.setStyleSheet("border: 1px solid black; padding: 10px;") proxy = self.view1.scene().addWidget(label) # 同时设置可移动、可选中、位置变化通知标记 proxy.setFlag(QGraphicsItem.ItemIsMovable, True) proxy.setFlag(QGraphicsItem.ItemIsSelectable, True) proxy.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) proxy.setPos(self.current_x, self.current_y) self.current_x += 50 self.current_y += 50
3. 重构鼠标点击日志功能
把CustomWidget里的日志功能移到Main类,绑定到QGraphicsView的鼠标事件:
# 在Main类__init__中添加 self.view1.mousePressEvent = self.view_mouse_press # 添加以下方法 def view_mouse_press(self, event): print("Left mouse button pressed.") # 转换视图坐标为场景坐标 scene_pos = self.view1.mapToScene(event.pos()) self.checkItemsUnderMouse(scene_pos) # 调用父类方法,保证正常选中逻辑 QGraphicsView.mousePressEvent(self.view1, event) def checkItemsUnderMouse(self, scene_pos): print("Element clicked:") for item in self.view1.scene().items(scene_pos): if isinstance(item, QGraphicsProxyWidget): widget = item.widget() if isinstance(widget, QLabel): print("Text:", widget.text()) print("Position:", item.pos()) self.console.append(f"Clicked: {widget.text()} at {item.pos()}")
4. 修复场景大小自适应
添加窗口 resize 事件,让场景大小随视图变化:
def resizeEvent(self, event): super().resizeEvent(event) self.view1.setSceneRect(QRectF(0, 0, self.view1.width(), self.view1.height()))
修复后的完整主类代码
import sys import json from PyQt5.QtWidgets import QApplication, QMainWindow, QGraphicsView, QVBoxLayout, QSplitter, QWidget, QListWidget, QTextEdit, QLabel from PyQt5.QtCore import Qt, QRectF from PyQt5.QtWidgets import * class Main(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("Application PyQt5") self.setGeometry(100, 100, 800, 600) self.current_x = 0 self.current_y = 0 self.keywords = [] self.scripts = [] self.view1 = QGraphicsView() self.scene1 = QGraphicsScene() self.view1.setScene(self.scene1) self.keywordList = QListWidget() self.fileListe = QListWidget() self.console = QTextEdit() top_layout = QSplitter(Qt.Horizontal) top_layout.addWidget(self.keywordList) top_layout.addWidget(self.view1) bottom_layout = QSplitter(Qt.Horizontal) bottom_layout.addWidget(self.fileListe) bottom_layout.addWidget(self.console) layout = QVBoxLayout() layout.addWidget(top_layout, stretch=3) layout.addWidget(bottom_layout, stretch=1) self.keywordList.setDragEnabled(True) self.fileListe.setDragEnabled(True) self.view1.setAcceptDrops(True) # 普通QWidget作为中央部件 central_widget = QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) self.load_data() self.keywordList.itemDoubleClicked.connect(self.add_label_to_scene) self.fileListe.itemDoubleClicked.connect(self.add_label_to_scene) self.view1.mousePressEvent = self.view_mouse_press def load_data(self): self.keywordList.clear() self.fileListe.clear() try: with open('keywords.json', 'r') as f: data = json.load(f) self.keywords = data.get('keywords', []) self.scripts = data.get('script', []) except FileNotFoundError: self.console.append("Error: keywords.json not found") for keyword_data in self.keywords: keyword = keyword_data.get('keyword', '') self.keywordList.addItem(keyword) for script_data in self.scripts: file_name = script_data.get('file_name', '') self.fileListe.addItem(file_name) def add_label_to_scene(self, item): text = item.text() label = QLabel(text) label.setStyleSheet("border: 1px solid black; padding: 10px;") proxy = self.view1.scene().addWidget(label) proxy.setFlag(QGraphicsItem.ItemIsMovable, True) proxy.setFlag(QGraphicsItem.ItemIsSelectable, True) proxy.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True) proxy.setPos(self.current_x, self.current_y) self.current_x += 50 self.current_y += 50 def view_mouse_press(self, event): print("Left mouse button pressed.") scene_pos = self.view1.mapToScene(event.pos()) self.checkItemsUnderMouse(scene_pos) QGraphicsView.mousePressEvent(self.view1, event) def checkItemsUnderMouse(self, scene_pos): print("Element clicked:") for item in self.view1.scene().items(scene_pos): if isinstance(item, QGraphicsProxyWidget): widget = item.widget() if isinstance(widget, QLabel): print("Text:", widget.text()) print("Position:", item.pos()) self.console.append(f"Clicked: {widget.text()} at {item.pos()}") def resizeEvent(self, event): super().resizeEvent(event) self.view1.setSceneRect(QRectF(0, 0, self.view1.width(), self.view1.height())) if __name__ == "__main__": app = QApplication(sys.argv) window = Main() window.show() sys.exit(app.exec_())
效果说明
修改后,双击列表元素添加到场景后,即可正常选中和移动元素,同时保留了鼠标点击日志输出到控制台的功能。如果后续需要实现拖拽添加功能,可以给QGraphicsView添加拖放事件处理逻辑,接收列表的拖拽数据。
内容的提问来源于stack exchange,提问作者Mouin Mohsni
相关产品推荐
相关产品推荐

