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

PyQt after override dragEnterEvent and dropEvent cursor donot blinking and change

  1. 我编写了 class 用于覆盖 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, design.Ui_MainWindow, QtWidgets.QWidget, 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, 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())


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 方法,我坚信它在许多方面都是错误的。

首先下面是不是 subclassing:

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

虽然 class 正确地继承自 QPlainTextEdit(因此,它“是”subclassed),但实际上 never 是这样使用的,并且以下无论如何都会起作用:

class <b>ChangeMethodsDnDTagList</b>:
    def __init__(self, obj):
        self.obj = obj
        # ...

如你所见,上面是一个标准的python对象class(对于Python 2,它是class ChangeMethodsDnDTagList(object):),这对你来说已经足够了需要。

事实上,即使没有 class:

你也可以这样做
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

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

问题是因为您在设计器中使用了 ui built,它已经基于标准 Qt class 创建了小部件的实例es。为了使用您自己的子classes,您需要推广 小部件。

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

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

最后,Qt 不能很好地处理其自身的多重继承 classes,你很幸运,以下工作:

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

QMainWindow 已经继承自 QWidget,并且 QEvent 通常根本不应该被子classed(并且在任何情况下都不应该与 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)

请注意,我还更改了 class 名称:已经有一个“应用程序”class (QApplication),它不是主要的window 确实如此。请记住,虽然您可以随心所欲地命名对象,但命名是 非常 重要的。