覆盖 dragEnterEvent 和 dropEvent 光标后的 PyQt 不闪烁和改变
PyQt after override dragEnterEvent and dropEvent cursor donot blinking and change
- 我编写了 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())
- 在应用程序中使用它,用于覆盖现有对象中的方法:
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, 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 确实如此。请记住,虽然您可以随心所欲地命名对象,但命名是 非常 重要的。
- 我编写了 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())
- 在应用程序中使用它,用于覆盖现有对象中的方法:
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, 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 确实如此。请记住,虽然您可以随心所欲地命名对象,但命名是 非常 重要的。