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

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()

问题分析与修复方案

核心问题

  1. CustomWidget抢占鼠标事件:你把CustomWidget设为中央窗口,它会拦截所有鼠标点击,导致QGraphicsView无法接收事件,选不中场景元素。
  2. 代理控件缺少选中标记:只设置ItemIsMovable不够,必须开启ItemIsSelectable才能让元素被选中。
  3. 初始化绘图代码错误:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 00:33:10