覆盖dragEnterEvent 和dropEvent 光标后的PyQt 不闪烁并改变

问题描述

  1. 我编写了用于在 QtWidgets.QPlainTextEdit 对象中覆盖 dragEnterEvent 和 dropEvent 方法的类:
class ChangeMethodsDnDTagList(QtWidgets.QPlainTextEdit):
    def __init__(self,obj):
        super().__init__()
        self.obj = obj  # will be type = BeforeQtWidgets.QPlainTextEdit
        self.obj.dragEnterEvent = self.dragEnterEvent
        self.obj.dropEvent = self.dropEvent

    def dragEnterEvent(self,event):
        if event.mimeData().hasText():
            event.accept()
        else:
            event.ignore()

    def dropEvent(self,event):
        if check_text(event.mimeData().text()):
            if self.obj is not None:
                if self.obj.toPlainText() != '':
                    self.obj.setPlainText(self.obj.toPlainText() + ',' + event.mimeData().text())
                else:
                    self.obj.setPlainText(event.mimeData().text())
  1. 在应用程序中使用它,用于覆盖现有对象中的方法:
    class Application(QtWidgets.QMainWindow,design.Ui_MainWindow,QtWidgets.QWidget,QtCore.QEvent):
        def __init__(self):
            ...
            super().__init__()
            ...
            self.setupUi(self)
            ...
            ChangeMethodsDnDTagList(self.in_tags_list)
  1. 拖放效果很好,但之后,光标冻结并且不更新(闪烁、改变位置)。

  2. 最小的可重现示例: testform.py:

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'testform.ui'
#
# Created by: PyQt5 UI code generator 5.15.2
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore,QtGui,QtWidgets


class Ui_MainWindow(object):
    def setupUi(self,MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(502,271)
        MainWindow.setMinimumSize(QtCore.QSize(502,271))
        MainWindow.setMaximumSize(QtCore.QSize(502,271))
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.in_tags_list = QtWidgets.QPlainTextEdit(self.centralwidget)
        self.in_tags_list.setGeometry(QtCore.QRect(5,5,481,201))
        self.in_tags_list.setInputMethodHints(QtCore.Qt.ImhLatinOnly|QtCore.Qt.ImhMultiLine)
        self.in_tags_list.setPlainText("")
        self.in_tags_list.setObjectName("in_tags_list")
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self,MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow","MainWindow"))

test.py:

import sys
from PyQt5 import QtWidgets
from PyQt5 import QtCore
import testform as design
import re


class Application(QtWidgets.QMainWindow,QtCore.QEvent):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        self.setCentralWidget(self.centralwidget)
        ChangeMethodsDnDTagList(self.in_tags_list)


class ChangeMethodsDnDTagList(QtWidgets.QPlainTextEdit):
    def __init__(self,' + event.mimeData().text())
                else:
                    self.obj.setPlainText(event.mimeData().text())


def check_text(text,mask="""^[a-zA-Z0-9_:,\n\t]+$"""):
    """Check input text"""
    match = re.match(mask,text)
    return bool(match)


def main():
    app = QtWidgets.QApplication(sys.argv)
    window = Application()
    window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

解决方法

您还需要重新实现 dragMoveEvent,它可以与 dragEnterEvent 相同,因此只需添加一行与其他类似的行:

class ChangeMethodsDnDTagList(QtWidgets.QPlainTextEdit):
    def __init__(self,obj):
        super().__init__()
        self.obj = obj  # will be type = BeforeQtWidgets.QPlainTextEdit
        self.obj.dragEnterEvent = self.dragEnterEvent
        self.obj.dropEvent = self.dropEvent

        self.obj.dragMoveEvent = self.dragEnterEvent

然后在 dropEvent 中包含一行以在放置后将光标移动到文档的末尾。

    def dropEvent(self,event):
        if check_text(event.mimeData().text()):
            if self.obj is not None:
                if self.obj.toPlainText() != '':
                    self.obj.setPlainText(self.obj.toPlainText() + ',' + event.mimeData().text())
                else:
                    self.obj.setPlainText(event.mimeData().text())
                    
                self.obj.moveCursor(QtGui.QTextCursor.End)
,

虽然从技术上讲,公认的解决方案正确地解决了手头的具体问题(缺少的 dragMoveEvent 实现),但它仍然遵循 OP 方法,我坚信这种方法在许多方面都是错误的。

首先,以下是不是子类化:

class ChangeMethodsDnDTagList(QtWidgets.QPlainTextEdit):
    def __init__(self,obj):
        super().__init__()
        self.obj = obj  # will be type = BeforeQtWidgets.QPlainTextEdit
        self.obj.dragEnterEvent = self.dragEnterEvent
        self.obj.dropEvent = self.dropEvent

虽然该类正确地从 QPlainTextEdit 继承(因此,它“是”子类化的),但它实际上从未如此使用,并且以下内容无论如何都会起作用:

class ChangeMethodsDnDTagList:
    def __init__(self,obj):
        self.obj = obj
        # ...

如您所见,上面是一个标准的 Python 对象类(对于 Python 2,它将是 class ChangeMethodsDnDTagList(object):),这足以满足您的需要。

事实上,即使没有类,你也可以这样做:

def changeDndMethods(obj):
    # any access to the instance is now done through obj,not self.obj
    def dragEnterEvent(event):
        # ...
    def dropEvent(event):
        # ...
    obj.dragEnterEvent = dragEnterEvent
    obj.dragMoveEvent = dragEnterEvent
    obj.dropEvent = dropEvent

虽然上述任何一种方法都有效,但并没有提供实际的子类化,因为它使访问实例方法变得更加尴尬或困难。

问题来自于您使用的是内置设计器的 ui,该设计器已经基于标准 Qt 类创建了小部件的实例。为了使用您自己的子类,您需要提升小部件。

这个过程非常简单,但我建议你做一些研究以更好地理解它是如何工作的:

  • 右键单击要在 Designer 中创建的 ui 中子类化的小部件;
  • 从上下文菜单中,选择“提升到...”;
  • 确保在组合“基类名称”中选择了正确的类(在您的情况下为 QPlainTextEdit,它应该已经被选中);
  • 在“Promoted class name”字段中输入您要使用的子类名称(例如MyCustomPlainText);
  • 在“头文件”字段中输入定义子类的 python 文件名(不带 .py 扩展名,例如“mycustomclasses”);
  • 点击“添加”以创建提升的类,然后点击“提升”以实际提升您的小部件;
  • 保存并再次调用pyuic
  • 在文件“mycustomclasses.py”中创建类并适当地覆盖其方法:
class MyCustomPlainText(QtWidgets.QPlainTextEdit):
   def dropEvent(self,event):
        if check_text(event.mimeData().text()):
            if self.toPlainText() != '':
                # ...

最后,Qt 不能很好地处理它自己的类的多重继承,你很幸运,下面的工作:

class Application(QtWidgets.QMainWindow,design.Ui_MainWindow,QtWidgets.QWidget,QtCore.QEvent):

QMainWindow 已经从 QWidget 继承,并且 QEvent 通常根本不应该被子类化(并且在任何情况下都不应该与 QWidget 一起),因此,以下内容就足够了。

class MainWindow(QtWidgets.QMainWindow,design.Ui_MainWindow):
    def __init__(self):
        super().__init__()
        self.setupUi(self)
        # unnecessary,setupUi already does this
        # self.setCentralWidget(self.centralwidget)

请注意,我还更改了类名:已经有一个“应用程序”类(QApplication),它不是窗口所做的。请记住,虽然您可以随意命名对象,但命名是非常重要的。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...