问题描述
- 我编写了用于在 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())
- 在应用程序中使用它,用于覆盖现有对象中的方法:
class Application(QtWidgets.QMainWindow,design.Ui_MainWindow,QtWidgets.QWidget,QtCore.QEvent):
def __init__(self):
...
super().__init__()
...
self.setupUi(self)
...
ChangeMethodsDnDTagList(self.in_tags_list)
-
拖放效果很好,但之后,光标冻结并且不更新(闪烁、改变位置)。
-
最小的可重现示例: 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),它不是主窗口所做的。请记住,虽然您可以随意命名对象,但命名是非常重要的。