我在将“使用 Python 和 Qt 的快速 GUI 编程”中的程序示例从 PyQt4 移植到 PyQt5 时遇到了麻烦。示例程序演示了一个 MDI 应用程序,可以通过该应用程序在主窗口中运行多个文本编辑窗口。
我使用 python 3.4.4 和 PyQt 4.8.7 作为 PyQt4 版本。我使用 python 3.4.4 和 PyQt 5.5.1 作为 PyQt5 版本。
我首先将原始 PyQt4 程序中的所有旧式信号定义更改为新式信号。在 PyQt 4.5 中实现了新样式的信号,因此我能够通过这些更改运行原始程序。将所有旧式信号更新为新式信号后,应用程序成功运行。
原程序使用 PyQt4.QtGui.QWidget.QWorkspace 类来实现 MDI 工作区。QWorkspace 被 PyQt4.3 中的 PyQt5.QtWidgets.QMdiArea 类取代。我的问题出现在尝试修改原始代码以使用 QMdiArea 时。
每个文本文档都使用自定义 TextEdit 小部件(QTextEdit 的子类)的一个实例来呈现和编辑。
MDI应用的最小PyQt5版本——texteditor.py
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class TextEdit(QTextEdit):
NextId = 1
def __init__(self, filename="", parent=None):
print("TextEdit __init__")
super(TextEdit, self).__init__(parent)
self.setAttribute(Qt.WA_DeleteOnClose)
self.filename = filename
if not self.filename:
self.filename = "Unnamed-{}.txt".format(TextEdit.NextId)
TextEdit.NextId += 1
self.document().setModified(False)
self.setWindowTitle(QFileInfo(self.filename).fileName())
def load(self):
print("load - TextEdit")
exception = None
fh = None
try:
fh = QFile(self.filename)
if not fh.open(QIODevice.ReadOnly):
raise IOError(fh.errorString())
stream = QTextStream(fh)
stream.setCodec("UTF-8")
self.setPlainText(stream.readAll())
self.document().setModified(False)
except EnvironmentError as e:
exception = e
finally:
if fh is not None:
fh.close()
if exception is not None:
raise exception
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
__version__ = "1.0.0"
class MainWindow(QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.mdi = QMdiArea()
self.setCentralWidget(self.mdi)
fileOpenAction = QAction("&Open...", self)
fileOpenAction.setShortcut(QKeySequence.Open)
fileOpenAction.triggered.connect(self.fileOpen)
fileMenu = self.menuBar().addMenu("&File")
fileMenu.addAction(fileOpenAction)
settings = QSettings()
self.restoreGeometry(settings.value("MainWindow/Geometry",
QByteArray()))
self.restoreState(settings.value("MainWindow/State",
QByteArray()))
QTimer.singleShot(0, self.loadFiles)
def loadFiles(self):
if len(sys.argv) > 1:
for filename in sys.argv[1:31]: # Load at most 30 files
if QFileInfo(filename).isFile():
self.loadFile(filename)
QApplication.processEvents()
else:
settings = QSettings()
files = settings.value("CurrentFiles") or []
for filename in files:
if QFile.exists(filename):
self.loadFile(filename)
QApplication.processEvents() #todo What does this do?
def fileOpen(self):
filename, _ = QFileDialog.getOpenFileName(self,
"Text Editor -- Open File")
if filename:
for textEdit in self.mdi.subWindowList():
print(type(textEdit))
if textEdit.filename == filename:
self.mdi.setActiveSubWindow(textEdit)
break
else:
self.loadFile(filename)
def loadFile(self, filename):
textEdit = TextEdit(filename)
try:
textEdit.load()
except EnvironmentError as e:
QMessageBox.warning(self, "Text Editor -- Load Error",
"Failed to load {}: {}".format(filename, e))
textEdit.close()
del textEdit
else:
self.mdi.addSubWindow(textEdit)
textEdit.show()
app = QApplication(sys.argv)
app.setWindowIcon(QIcon(":/icon.png"))
app.setOrganizationName("Qtrac Ltd.")
app.setOrganizationDomain("qtrac.eu")
app.setApplicationName("Text Editor")
form = MainWindow()
form.show()
app.exec_()
问题出现在 fileOpen() 方法中:
PyQt4 fileOpen() 方法
def fileOpen(self):
filename = QFileDialog.getOpenFileName(self,
"Text Editor -- Open File")
if filename:
for textEdit in self.mdi.windowList():
if textEdit.filename == filename:
self.mdi.setActiveWindow(textEdit)
break
else:
self.loadFile(filename)
PyQt5 fileOpen() 方法
def fileOpen(self):
filename, _ = QFileDialog.getOpenFileName(self,
"Text Editor -- Open File")
if filename:
for textEdit in self.mdi.subWindowList():
if textEdit.filename == filename:
self.mdi.setActiveSubWindow(textEdit)
break
else:
self.loadFile(filename)
windowList() 在 PyQt5 中实现为 subWindowList()。问题是在 PyQt4 版本中,for textEdit in self.mdi.windowList():
执行 textEdit 的时间是 TextEdit 类型,所以下一行
if textEdit.filename == filename
工作,因为 TextEdit 确实有一个文件名参数。而 textEdit 是一个 {TextEdit}textedit.TextEdit 对象,但是在 PyQt5 版本中,在for textEdit in self.mdi.subWindowList():
执行之后,textEdit 的类型是 QMdiSubWindow 所以,当然回溯会生成:
Traceback (most recent call last):
File "texteditor3.py", line 292, in fileOpen
if textEdit.filename == filename:
AttributeError: 'QMdiSubWindow' object has no attribute 'filename'
真正让我困惑的是 PyQt4 版本中的 textEdit 是如何变成 TextEdit 类型的。我认为这将是一个 str 类型。