「Python と Qt を使用した高速 GUI プログラミング」のプログラム例を PyQt4 から PyQt5 に移植する際に問題が発生しました。サンプル プログラムは、メイン ウィンドウ内で複数のテキスト編集ウィンドウを実行できる MDI アプリケーションを示しています。
PyQt4 バージョンには python 3.4.4 と PyQt 4.8.7 を使用しました。PyQt5 バージョンには python 3.4.4 と PyQt 5.5.1 を使用しました。
元の PyQt4 プログラムで、すべての古いスタイルのシグナル定義を新しいスタイルのシグナルに変更することから始めました。新しいスタイルのシグナルが PyQt 4.5 で実装されたため、これらの変更を加えて元のプログラムを実行できました。すべての古いスタイルのシグナルを新しいスタイルのシグナルに更新した後、アプリケーションは正常に実行されました。
元のプログラムは PyQt4.QtGui.QWidget.QWorkspace クラスを使用して MDI ワークスペースを実装します。QWorkspace は PyQt4.3 で PyQt5.QtWidgets.QMdiArea クラスに置き換えられました。私の問題は、QMdiArea で動作するように元のコードを変更しようとしたときに表面化しました。
各テキスト ドキュメントは、QTextEdit のサブクラスであるカスタム TextEdit ウィジェットのインスタンスを使用して表示および編集されます。
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型だと思います。