最終更新日:2021/08/31 原本2017-03-14

PyQt5とpython3によるGUIプログラミング[6]

Qt DesignerでGUIを作ってみる

Qt Designerで画面を作り、uiファイルをpythonのソースに変換する方法を示していきます。
※なぜ、これをもっと初めに紹介しなかったかというと、実はこのツールは中級から上級者が対象のためでした。ツール類は基本を理解していることが前提で作られているようなので、初心者がいきなりこのツールを使って、思うものを作ろうとすると大体は挫折してしまうようです。

Qt Designer 自体の使い方は、Qt Designer使い方入門Qt Designer使い方入門 がわかりやすい。

ユーザインタフェースを設計し.uiファイルに保存した後、使用する前に、それをコードに変換しなければなりません。これはpyuic5コマンドラインプログラムを使用して行います。
.uiファイルのあるディレクトリで、以下を実行します。

pyuic5 -o ui_test_designer.py test_designer.ui

ここで注意が必要なのは上記のコマンドで作成したpythonのコードを直接編集してはいけないということです。なぜなら次にpyuic5コマンドを実行すると上書きされるため、追加したコードは消えてしまいます。

それでは作っていきましょう。

test_designer.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>Dialog</class>
 <widget class="QDialog" name="Dialog">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>400</width>
    <height>300</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QDial" name="dial">
   <property name="geometry">
    <rect>
     <x>280</x>
     <y>-50</y>
     <width>101</width>
     <height>221</height>
    </rect>
   </property>
  </widget>
  <widget class="QSpinBox" name="spinBox">
   <property name="geometry">
    <rect>
     <x>310</x>
     <y>120</y>
     <width>48</width>
     <height>24</height>
    </rect>
   </property>
  </widget>
  <widget class="QFontComboBox" name="fontComboBox">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>30</y>
     <width>217</width>
     <height>28</height>
    </rect>
   </property>
  </widget>
  <widget class="QLabel" name="label">
   <property name="geometry">
    <rect>
     <x>20</x>
     <y>80</y>
     <width>221</width>
     <height>16</height>
    </rect>
   </property>
   <property name="text">
    <string>TextLabel</string>
   </property>
  </widget>
  <widget class="QSlider" name="horizontalSlider">
   <property name="geometry">
    <rect>
     <x>30</x>
     <y>240</y>
     <width>341</width>
     <height>22</height>
    </rect>
   </property>
   <property name="orientation">
    <enum>Qt::Horizontal</enum>
   </property>
  </widget>
  <widget class="QLCDNumber" name="lcdNumber">
   <property name="geometry">
    <rect>
     <x>40</x>
     <y>170</y>
     <width>151</width>
     <height>51</height>
    </rect>
   </property>
  </widget>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>horizontalSlider</sender>
   <signal>valueChanged(int)</signal>
   <receiver>lcdNumber</receiver>
   <slot>display(int)</slot>
   <hints>
    <hint type="sourcelabel">
     <x>115</x>
     <y>249</y>
    </hint>
    <hint type="destinationlabel">
     <x>116</x>
     <y>188</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>fontComboBox</sender>
   <signal>activated(QString)</signal>
   <receiver>label</receiver>
   <slot>setText(QString)</slot>
   <hints>
    <hint type="sourcelabel">
     <x>60</x>
     <y>43</y>
    </hint>
    <hint type="destinationlabel">
     <x>59</x>
     <y>91</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>dial</sender>
   <signal>valueChanged(int)</signal>
   <receiver>spinBox</receiver>
   <slot>setValue(int)</slot>
   <hints>
    <hint type="sourcelabel">
     <x>320</x>
     <y>58</y>
    </hint>
    <hint type="destinationlabel">
     <x>315</x>
     <y>131</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>

Qt Designerで開くとこんな感じになっています。
スクリーンショット 2016-09-24 1.12.13.png

以下のコマンドで.pyファイルに変換しましょう。
pyuic5 -o ui_test_designer.py test_designer.ui

以下のようなコードが生成されています。

ui_test_designer.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test_designer.ui'
#
# Created by: PyQt5 UI code generator 5.7.dev1607250143
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.dial = QtWidgets.QDial(Dialog)
        self.dial.setGeometry(QtCore.QRect(280, -50, 101, 221))
        self.dial.setObjectName("dial")
        self.spinBox = QtWidgets.QSpinBox(Dialog)
        self.spinBox.setGeometry(QtCore.QRect(310, 120, 48, 24))
        self.spinBox.setObjectName("spinBox")
        self.fontComboBox = QtWidgets.QFontComboBox(Dialog)
        self.fontComboBox.setGeometry(QtCore.QRect(20, 30, 217, 28))
        self.fontComboBox.setObjectName("fontComboBox")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setGeometry(QtCore.QRect(20, 80, 221, 16))
        self.label.setObjectName("label")
        self.horizontalSlider = QtWidgets.QSlider(Dialog)
        self.horizontalSlider.setGeometry(QtCore.QRect(30, 240, 341, 22))
        self.horizontalSlider.setOrientation(QtCore.Qt.Horizontal)
        self.horizontalSlider.setObjectName("horizontalSlider")
        self.lcdNumber = QtWidgets.QLCDNumber(Dialog)
        self.lcdNumber.setGeometry(QtCore.QRect(40, 170, 151, 51))
        self.lcdNumber.setObjectName("lcdNumber")

        self.retranslateUi(Dialog)
        self.horizontalSlider.valueChanged['int'].connect(self.lcdNumber.display)
        self.fontComboBox.activated['QString'].connect(self.label.setText)
        self.dial.valueChanged['int'].connect(self.spinBox.setValue)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.label.setText(_translate("Dialog", "TextLabel"))

上のコードだけでは実行できないので、importしてUIを作成した上で表示するmainを実装します。

test_designer.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from ui_test_designer import Ui_Dialog

class Test(QDialog):
    def __init__(self,parent=None):
        super(Test, self).__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Test()
    window.show()
    sys.exit(app.exec_())

上記を実行するとこんな感じです。

スクリーンショット 2016-09-22 18.53.14.png

とりあえず基本はこんな感じです。

Qt Designerでexamples→mainwindows→application→application.pyをリメイク

スクリーンショット 2016-10-16 19.11.18.png

この画面をQt Designerでリメイクしてみます。
application.pyは全てがコードで実装されていなすが、Qt Designerで作成する利点は出来上がりのイメージが共有できる点にあると思います。

まずはQt Designerで画面を設定していきます。

スクリーンショット 2016-10-16 19.17.11.png

Main Windowを選択し作成ボタンを押します。
ツールバーを追加する必要があるので、右クリックします。
すると以下のようにメニューが出るので、ツールバーを追加を選択します。

ああああ.png

ツールバーを追加するとこんな画面になります。
スクリーンショット 2016-10-16 19.27.29.png

この細い棒状のエリアにツールを設定していきます。
まずはアクションを設定していきます。
アイコンの画像はapplication.pyのものを使いましょう。
iiiiii.png

アクションエディタで作成した項目をツールバーにドラッグするとこんな感じになります。
いいいい.png

これを繰り返すと以下のような画面になります。
スクリーンショット 2016-10-16 21.21.38.png

ファイルメニューにもactionを登録していきましょう。
基本ツールバーのアクションの登録と同じです。
同じアクション名で登録すると勝手に*_2という感じで設定されます。
action1.png

それでは、uiファイルを見てみましょう。

test_main.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>593</width>
    <height>502</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget"/>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>593</width>
     <height>22</height>
    </rect>
   </property>
   <widget class="QMenu" name="menuNew">
    <property name="title">
     <string>ファイル</string>
    </property>
    <addaction name="actionNew_2"/>
    <addaction name="actionOpen_2"/>
    <addaction name="actionSave_2"/>
    <addaction name="actionSaveAs"/>
   </widget>
   <widget class="QMenu" name="menuEdit">
    <property name="title">
     <string>編集</string>
    </property>
    <addaction name="actionCut_2"/>
    <addaction name="actionCopy_2"/>
    <addaction name="actionPaste_2"/>
   </widget>
   <addaction name="menuNew"/>
   <addaction name="menuEdit"/>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
  <widget class="QToolBar" name="toolBar">
   <property name="sizePolicy">
    <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
     <horstretch>0</horstretch>
     <verstretch>0</verstretch>
    </sizepolicy>
   </property>
   <property name="windowTitle">
    <string>toolBar</string>
   </property>
   <attribute name="toolBarArea">
    <enum>TopToolBarArea</enum>
   </attribute>
   <attribute name="toolBarBreak">
    <bool>false</bool>
   </attribute>
   <addaction name="actionNew"/>
   <addaction name="actionOpen"/>
   <addaction name="actionSave"/>
   <addaction name="actionCopy"/>
   <addaction name="actionPaste"/>
   <addaction name="actionCut"/>
  </widget>
  <action name="actionNew">
   <property name="icon">
    <iconset>
     <normaloff>images/new.png</normaloff>images/new.png</iconset>
   </property>
   <property name="text">
    <string>new</string>
   </property>
   <property name="toolTip">
    <string>new</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+N</string>
   </property>
  </action>
  <action name="actionOpen">
   <property name="icon">
    <iconset>
     <normaloff>images/open.png</normaloff>images/open.png</iconset>
   </property>
   <property name="text">
    <string>open</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+O</string>
   </property>
  </action>
  <action name="actionSave">
   <property name="icon">
    <iconset>
     <normaloff>images/save.png</normaloff>images/save.png</iconset>
   </property>
   <property name="text">
    <string>save</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+S</string>
   </property>
  </action>
  <action name="actionCopy">
   <property name="icon">
    <iconset>
     <normaloff>images/copy.png</normaloff>images/copy.png</iconset>
   </property>
   <property name="text">
    <string>copy</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+C</string>
   </property>
  </action>
  <action name="actionPaste">
   <property name="icon">
    <iconset>
     <normaloff>images/paste.png</normaloff>images/paste.png</iconset>
   </property>
   <property name="text">
    <string>paste</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+V</string>
   </property>
  </action>
  <action name="actionCut">
   <property name="icon">
    <iconset>
     <normaloff>images/cut.png</normaloff>images/cut.png</iconset>
   </property>
   <property name="text">
    <string>cut</string>
   </property>
   <property name="toolTip">
    <string>cut</string>
   </property>
   <property name="shortcut">
    <string>Ctrl+X</string>
   </property>
  </action>
  <action name="actionSaveAs">
   <property name="text">
    <string>SaveAs</string>
   </property>
  </action>
  <action name="actionNew_2">
   <property name="icon">
    <iconset>
     <normaloff>images/new.png</normaloff>images/new.png</iconset>
   </property>
   <property name="text">
    <string>New</string>
   </property>
  </action>
  <action name="actionOpen_2">
   <property name="icon">
    <iconset>
     <normaloff>images/open.png</normaloff>images/open.png</iconset>
   </property>
   <property name="text">
    <string>Open</string>
   </property>
  </action>
  <action name="actionSave_2">
   <property name="icon">
    <iconset>
     <normaloff>images/save.png</normaloff>images/save.png</iconset>
   </property>
   <property name="text">
    <string>Save</string>
   </property>
  </action>
  <action name="actionCut_2">
   <property name="icon">
    <iconset>
     <normaloff>images/cut.png</normaloff>images/cut.png</iconset>
   </property>
   <property name="text">
    <string>cut</string>
   </property>
  </action>
  <action name="actionCopy_2">
   <property name="icon">
    <iconset>
     <normaloff>images/copy.png</normaloff>images/copy.png</iconset>
   </property>
   <property name="text">
    <string>copy</string>
   </property>
  </action>
  <action name="actionPaste_2">
   <property name="icon">
    <iconset>
     <normaloff>images/paste.png</normaloff>images/paste.png</iconset>
   </property>
   <property name="text">
    <string>paste</string>
   </property>
  </action>
 </widget>
 <resources/>
 <connections/>
</ui>

pyに吐き出すコマンドはこれです。
pyuic5 -o ui_test_main.py test_main.ui

ui_test_main.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'test_main.ui'
#
# Created by: PyQt5 UI code generator 5.7.dev1607250143
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(593, 502)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtWidgets.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 593, 22))
        self.menubar.setObjectName("menubar")
        self.menuNew = QtWidgets.QMenu(self.menubar)
        self.menuNew.setObjectName("menuNew")
        self.menuEdit = QtWidgets.QMenu(self.menubar)
        self.menuEdit.setObjectName("menuEdit")
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)
        self.toolBar = QtWidgets.QToolBar(MainWindow)
        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Fixed)
        sizePolicy.setHorizontalStretch(0)
        sizePolicy.setVerticalStretch(0)
        sizePolicy.setHeightForWidth(self.toolBar.sizePolicy().hasHeightForWidth())
        self.toolBar.setSizePolicy(sizePolicy)
        self.toolBar.setObjectName("toolBar")
        MainWindow.addToolBar(QtCore.Qt.TopToolBarArea, self.toolBar)
        self.actionNew = QtWidgets.QAction(MainWindow)
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("images/new.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionNew.setIcon(icon)
        self.actionNew.setObjectName("actionNew")
        self.actionOpen = QtWidgets.QAction(MainWindow)
        icon1 = QtGui.QIcon()
        icon1.addPixmap(QtGui.QPixmap("images/open.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionOpen.setIcon(icon1)
        self.actionOpen.setObjectName("actionOpen")
        self.actionSave = QtWidgets.QAction(MainWindow)
        icon2 = QtGui.QIcon()
        icon2.addPixmap(QtGui.QPixmap("images/save.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionSave.setIcon(icon2)
        self.actionSave.setObjectName("actionSave")
        self.actionCopy = QtWidgets.QAction(MainWindow)
        icon3 = QtGui.QIcon()
        icon3.addPixmap(QtGui.QPixmap("images/copy.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionCopy.setIcon(icon3)
        self.actionCopy.setObjectName("actionCopy")
        self.actionPaste = QtWidgets.QAction(MainWindow)
        icon4 = QtGui.QIcon()
        icon4.addPixmap(QtGui.QPixmap("images/paste.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionPaste.setIcon(icon4)
        self.actionPaste.setObjectName("actionPaste")
        self.actionCut = QtWidgets.QAction(MainWindow)
        icon5 = QtGui.QIcon()
        icon5.addPixmap(QtGui.QPixmap("images/cut.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.actionCut.setIcon(icon5)
        self.actionCut.setObjectName("actionCut")
        self.actionSaveAs = QtWidgets.QAction(MainWindow)
        self.actionSaveAs.setObjectName("actionSaveAs")
        self.actionNew_2 = QtWidgets.QAction(MainWindow)
        self.actionNew_2.setIcon(icon)
        self.actionNew_2.setObjectName("actionNew_2")
        self.actionOpen_2 = QtWidgets.QAction(MainWindow)
        self.actionOpen_2.setIcon(icon1)
        self.actionOpen_2.setObjectName("actionOpen_2")
        self.actionSave_2 = QtWidgets.QAction(MainWindow)
        self.actionSave_2.setIcon(icon2)
        self.actionSave_2.setObjectName("actionSave_2")
        self.actionCut_2 = QtWidgets.QAction(MainWindow)
        self.actionCut_2.setIcon(icon5)
        self.actionCut_2.setObjectName("actionCut_2")
        self.actionCopy_2 = QtWidgets.QAction(MainWindow)
        self.actionCopy_2.setIcon(icon3)
        self.actionCopy_2.setObjectName("actionCopy_2")
        self.actionPaste_2 = QtWidgets.QAction(MainWindow)
        self.actionPaste_2.setIcon(icon4)
        self.actionPaste_2.setObjectName("actionPaste_2")
        self.menuNew.addAction(self.actionNew_2)
        self.menuNew.addAction(self.actionOpen_2)
        self.menuNew.addAction(self.actionSave_2)
        self.menuNew.addAction(self.actionSaveAs)
        self.menuEdit.addAction(self.actionCut_2)
        self.menuEdit.addAction(self.actionCopy_2)
        self.menuEdit.addAction(self.actionPaste_2)
        self.menubar.addAction(self.menuNew.menuAction())
        self.menubar.addAction(self.menuEdit.menuAction())
        self.toolBar.addAction(self.actionNew)
        self.toolBar.addAction(self.actionOpen)
        self.toolBar.addAction(self.actionSave)
        self.toolBar.addAction(self.actionCopy)
        self.toolBar.addAction(self.actionPaste)
        self.toolBar.addAction(self.actionCut)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.menuNew.setTitle(_translate("MainWindow", "ファイル"))
        self.menuEdit.setTitle(_translate("MainWindow", "編集"))
        self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
        self.actionNew.setText(_translate("MainWindow", "new"))
        self.actionNew.setToolTip(_translate("MainWindow", "new"))
        self.actionNew.setShortcut(_translate("MainWindow", "Ctrl+N"))
        self.actionOpen.setText(_translate("MainWindow", "open"))
        self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O"))
        self.actionSave.setText(_translate("MainWindow", "save"))
        self.actionSave.setShortcut(_translate("MainWindow", "Ctrl+S"))
        self.actionCopy.setText(_translate("MainWindow", "copy"))
        self.actionCopy.setShortcut(_translate("MainWindow", "Ctrl+C"))
        self.actionPaste.setText(_translate("MainWindow", "paste"))
        self.actionPaste.setShortcut(_translate("MainWindow", "Ctrl+V"))
        self.actionCut.setText(_translate("MainWindow", "cut"))
        self.actionCut.setToolTip(_translate("MainWindow", "cut"))
        self.actionCut.setShortcut(_translate("MainWindow", "Ctrl+X"))
        self.actionSaveAs.setText(_translate("MainWindow", "SaveAs"))
        self.actionNew_2.setText(_translate("MainWindow", "New"))
        self.actionOpen_2.setText(_translate("MainWindow", "Open"))
        self.actionSave_2.setText(_translate("MainWindow", "Save"))
        self.actionCut_2.setText(_translate("MainWindow", "cut"))
        self.actionCopy_2.setText(_translate("MainWindow", "copy"))
        self.actionPaste_2.setText(_translate("MainWindow", "paste"))

これでuiの準備が整いました。

では、このuiを使用するtest_main.pyを作っていきましょう。

importの部分はいつもの通りです。

QTextEdit()はこの中で宣言しています。
Qt Designerで宣言することもできるのですが、MainWindowのサイズを変えた時ついてこないのです、設定の方法はあると思うのですが、今の私ではちょっと分からずこの方法を取ります。
cut、copy、pasteはQTextEdit()で宣言したインスタンスにメソッドがあるようなのでそれを使います。

テキストに変更があったかどうかは、maybeSaveのインスタンスが参考になります。
この部分ですね、
if self.ui.textEdit.document().isModified():

試してみてください。

test_main.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qt import *
from ui_test_main import Ui_MainWindow

class Test(QMainWindow):
    def __init__(self,parent=None):
        super(Test, self).__init__(parent)
        self.curFile = ''

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.ui.textEdit = QTextEdit()
        self.setCentralWidget(self.ui.textEdit)

        self.ui.exitAct = QAction("E&xit", self, shortcut="Ctrl+Q",
                           statusTip="Exit the application", triggered=self.close)

        #ツールバーのメニュー
        self.ui.actionNew.triggered.connect(self.newFile)
        self.ui.actionOpen.triggered.connect(self.open)
        self.ui.actionSave.triggered.connect(self.save)
        self.ui.actionCopy.triggered.connect(self.ui.textEdit.copy)
        self.ui.actionPaste.triggered.connect(self.ui.textEdit.paste)
        self.ui.actionCut.triggered.connect(self.ui.textEdit.cut)
        #ファイルメニュー
        self.ui.actionNew_2.triggered.connect(self.newFile)
        self.ui.actionOpen_2.triggered.connect(self.open)
        self.ui.actionSave_2.triggered.connect(self.save)
        self.ui.actionSaveAs.triggered.connect(self.saveAs)
        self.ui.actionCopy_2.triggered.connect(self.ui.textEdit.copy)
        self.ui.actionPaste_2.triggered.connect(self.ui.textEdit.paste)
        self.ui.actionCut_2.triggered.connect(self.ui.textEdit.cut)

        self.ui.actionCut.setEnabled(False)
        self.ui.actionCut_2.setEnabled(False)
        self.ui.actionCopy.setEnabled(False)
        self.ui.actionCopy_2.setEnabled(False)
        self.ui.textEdit.copyAvailable.connect(self.ui.actionCut.setEnabled)
        self.ui.textEdit.copyAvailable.connect(self.ui.actionCut_2.setEnabled)
        self.ui.textEdit.copyAvailable.connect(self.ui.actionCopy.setEnabled)
        self.ui.textEdit.copyAvailable.connect(self.ui.actionCopy_2.setEnabled)

    def closeEvent(self, event):
        self.setCurrentFile('')
        if self.maybeSave():
            self.writeSettings()
            event.accept()
        else:
            event.ignore()

    def newFile(self):
        if self.maybeSave():
            self.ui.textEdit.clear()
            self.setCurrentFile('')

    def open(self):
        if self.maybeSave():
            fileName, _ = QFileDialog.getOpenFileName(self)
            print(fileName)
            if fileName:
                self.loadFile(fileName)

    def save(self):
        if self.curFile:
            return self.saveFile(self.curFile)

        return self.saveAs()

    def saveAs(self):
        fileName, _ = QFileDialog.getSaveFileName(self)
        if fileName:
            return self.saveFile(fileName)

        return False

    def loadFile(self, fileName):
        file = QFile(fileName)
        if not file.open(QFile.ReadOnly | QFile.Text):
            QMessageBox.warning(self, "Application",
                    "Cannot read file %s:\n%s." % (fileName, file.errorString()))
            return

        inf = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        self.ui.textEdit.setPlainText(inf.readAll())
        QApplication.restoreOverrideCursor()

        self.setCurrentFile(fileName)
        #self.ui.statusBar().showMessage("File loaded", 2000)




    def maybeSave(self):
        if self.ui.textEdit.document().isModified():
            ret = QMessageBox.warning(self, "Application",
                                      "文書が変更されました。\n変更した文章を保存しますか?",
                                      QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel)

            if ret == QMessageBox.Save:
                return self.save()

            if ret == QMessageBox.Cancel:
                return False

        return True


    def readSettings(self):
        settings = QSettings("Trolltech", "Application Example")
        pos = settings.value("pos", QPoint(200, 200))
        size = settings.value("size", QSize(400, 400))
        self.resize(size)
        self.move(pos)


    def writeSettings(self):
        settings = QSettings("Trolltech", "Application Example")
        settings.setValue("pos", self.pos())
        settings.setValue("size", self.size())


    def setCurrentFile(self, fileName):
        self.curFile = fileName
        self.ui.textEdit.document().setModified(False)
        self.setWindowModified(False)

        if self.curFile:
            shownName = self.strippedName(self.curFile)
        else:
            shownName = 'untitled.txt'

        self.setWindowTitle("%s[*] - Application" % shownName)


    def strippedName(self, fullFileName):
        return QFileInfo(fullFileName).fileName()

    def saveFile(self, fileName):
        file = QFile(fileName)
        if not file.open(QFile.WriteOnly | QFile.Text):
            QMessageBox.warning(self, "Application",
                    "Cannot write file %s:\n%s." % (fileName, file.errorString()))
            return False

        outf = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        outf << self.ui.textEdit.toPlainText()
        QApplication.restoreOverrideCursor()

        self.setCurrentFile(fileName);
        self.ui.statusBar().showMessage("File saved", 2000)
        return True


    def saveFile(self, fileName):
        file = QFile(fileName)
        if not file.open(QFile.WriteOnly | QFile.Text):
            QMessageBox.warning(self, "Application",
                                "Cannot write file %s:\n%s." % (fileName, file.errorString()))
            return False

        outf = QTextStream(file)
        QApplication.setOverrideCursor(Qt.WaitCursor)
        outf << self.ui.textEdit.toPlainText()
        QApplication.restoreOverrideCursor()

        self.setCurrentFile(fileName);
        self.statusBar().showMessage("File saved", 2000)
        return True


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Test()
    window.show()
    sys.exit(app.exec_())

Qt Designerでタブ順を設定してみる

Line Editのウィジェットを複数作成して、そのタブ順を設定してみます。

まずは、Qt Designerを起動して、新しいフォームからDialog without Bottonsを選んで作成ボタンを押します。
スクリーンショット 2016-11-04 10.28.22.png

プロパティエディタのwindowTitleで適当な名前をつけます。
以下のような感じです。

スクリーンショット 2016-11-04 10.32.49.png

Line Editウィジェットを3つ並べます。objectNameは適当な名前をつけます。
スクリーンショット 2016-11-04 10.40.20.png

Line Editウィジェットの選択を解除した状態で、編集からタブ順を編集をクリックします。
こんな感じです。
あああああ.png

するとこんな感じになります。
いいいい.png

数字の部分をクリックするとその順番が変わります。
うううう.png

好きな順番になったら、フォームのプレビューで確認します。
プレビューから抜けるにはEscキーを押します。
ウィジェットを編集に戻るには、F3キーを押します。

では、このまま保存します。
ここではTab_test01.uiとします。
そして、uiファイルを保存したディレクトリで以下のコマンドを実行します。

pyuic5 -o ui_Tab_test01.py Tab_test01.ui

以下のソースが書き出されました。

ui_Tab_test01.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'Tab_test01.ui'
#
# Created by: PyQt5 UI code generator 5.7.dev1607250143
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.lineEdit1 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit1.setGeometry(QtCore.QRect(150, 30, 113, 21))
        self.lineEdit1.setObjectName("lineEdit1")
        self.lineEdit2 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit2.setGeometry(QtCore.QRect(150, 80, 113, 21))
        self.lineEdit2.setObjectName("lineEdit2")
        self.lineEdit3 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit3.setGeometry(QtCore.QRect(150, 130, 113, 21))
        self.lineEdit3.setObjectName("lineEdit3")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)
        Dialog.setTabOrder(self.lineEdit3, self.lineEdit2)
        Dialog.setTabOrder(self.lineEdit2, self.lineEdit1)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "テスト"))

以下のコードでタブ順を設定しているようです。
Dialog.setTabOrder(self.lineEdit3, self.lineEdit2)
Dialog.setTabOrder(self.lineEdit2, self.lineEdit1)

では、このUIをインポートして、表示するmainを作っていきます。

Tab_test01.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qt import *
from ui_Tab_test01 import Ui_Dialog

class Test(QMainWindow):
    def __init__(self,parent=None):
        super(Test, self).__init__(parent)

        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Test()
    window.show()
    sys.exit(app.exec_())

こんな感じになります。
これでも動くのですが、上記でTest(QMainWindow)の部分はTest(QWidget, Ui_Dialog)とするのが正しいのだと思います。

スクリーンショット 2016-11-04 11.38.27.png

これだけでは面白くないので、Qt DesignerでLabelを追加してFocasイベントをそこに書くように改造してみます。

スクリーンショット 2016-11-04 13.11.45.png

テキストは何も入れずに上のような感じにします。

そして相変わらず、以下を実行です。

pyuic5 -o ui_Tab_test01.py Tab_test01.ui

ui_Tab_test01.py
# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'Tab_test01.ui'
#
# Created by: PyQt5 UI code generator 5.7.dev1607250143
#
# WARNING! All changes made in this file will be lost!

from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(400, 300)
        self.lineEdit1 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit1.setGeometry(QtCore.QRect(150, 30, 113, 21))
        self.lineEdit1.setObjectName("lineEdit1")
        self.lineEdit2 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit2.setGeometry(QtCore.QRect(150, 80, 113, 21))
        self.lineEdit2.setObjectName("lineEdit2")
        self.lineEdit3 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit3.setGeometry(QtCore.QRect(150, 130, 113, 21))
        self.lineEdit3.setObjectName("lineEdit3")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setGeometry(QtCore.QRect(110, 200, 200, 20))
        self.label.setText("")
        self.label.setObjectName("label")
        self.label_2 = QtWidgets.QLabel(Dialog)
        self.label_2.setGeometry(QtCore.QRect(110, 230, 200, 20))
        self.label_2.setText("")
        self.label_2.setObjectName("label_2")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)
        Dialog.setTabOrder(self.lineEdit3, self.lineEdit2)
        Dialog.setTabOrder(self.lineEdit2, self.lineEdit1)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "テスト"))

Labelが追加されているのがわかります。

それではTab_test01.pyを実装します。
肝は、installEventFilterでイベントフィルターをインストールしているところと
self.ui.lineEdit3.installEventFilter(self)

以下のイベントを拾う処理を実装している部分です。
def eventFilter(self, source, event):

FocusInとFocusOutを拾う場合、FocusOutが後に発生するので、同じLabelにテキストを入れようとすると、FocusInのイベントによって上書きされるので、FocusOutのイベントが発生していないように見えてしまいます。
改造したコードは以下になります。

Tab_test01.py
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.Qt import *
from ui_Tab_test01 import Ui_Dialog

class Test(QWidget, Ui_Dialog):
    def __init__(self,parent=None):
        super(Test, self).__init__(parent)

        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.ui.lineEdit3.installEventFilter(self)
        self.ui.lineEdit2.installEventFilter(self)
        self.ui.lineEdit1.installEventFilter(self)

    def eventFilter(self, source, event):

        if event.type() == QEvent.FocusIn:
            if source is self.ui.lineEdit1:
                self.ui.label.setText("lineEdit1ーFocusIn")
            elif source is self.ui.lineEdit2:
                self.ui.label.setText("lineEdit2ーFocusIn")
            elif source is self.ui.lineEdit3:
                self.ui.label.setText("lineEdit3ーFocusIn")
        elif event.type() == QEvent.FocusOut:
            if source is self.ui.lineEdit1:
                self.ui.label_2.setText("lineEdit1ーFocusOut")
            elif source is self.ui.lineEdit2:
                self.ui.label_2.setText("lineEdit2ーFocusOut")
            elif source is self.ui.lineEdit3:
                self.ui.label_2.setText("lineEdit3ーFocusOut")
        return QWidget.eventFilter(self, source, event)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = Test()
    window.show()
    sys.exit(app.exec_())

スクリーンショット 2016-11-04 13.36.23.png