【问题标题】:PyQt after override dragEnterEvent and dropEvent cursor donot blinking and change覆盖dragEnterEvent和dropEvent光标后的PyQt不闪烁和改变
【发布时间】:2021-05-18 23:01:00
【问题描述】:
  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, 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()

【问题讨论】:

  • @eyllanesc,准备好了
  • 顺便说一句,您所做的是不是子类化-我希望您不会认为它是-并且这不是一种非常优雅的方法覆盖(我也相信,也不是很pythonic)。
  • @musicamante 你能告诉我如何改进我的代码吗?我将不胜感激。我是新手

标签: python qt pyqt overriding pyside


【解决方案1】:

你也需要重新实现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)

【讨论】:

    【解决方案2】:

    虽然在技术上公认的解决方案正确地解决了手头的具体问题(缺少 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
    

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

    问题在于您使用的是内置设计器,该设计器已经基于标准 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),它不是窗口所做的。请记住,虽然您可以随意命名对象,但命名非常很重要。

    【讨论】:

    • 非常感谢您的宝贵时间,您的分析对我很有帮助。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-05
    • 2011-12-22
    • 2017-07-17
    相关资源
    最近更新 更多