QtDesignerでGUIを作り、Pythonファイル(PySide2)に変換して表示させる

MayaのツールをPythonで作る際、QtDesignerを使ってみたかったので学習してみました。

その時の備忘録を残します。
今回は、デスクトップに作ったUIを表示させる、をゴールにします。

こんなウィンドを作ります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136274/890f6d99-8115-5dbf-c872-da02b18d8efc.png)

用意したファイル

- `mittsuUI_test.ui`
- `mittsuUI_test.py`
- `mittsuUI_show.py`

今回使うファイルは上記の３つです。

作業の流れは、

1. QtDesignerでGUIを作る。
2. 作ったGUIをPython(PySide2)に変換する
3. ２を表示するファイルを作る
4. ３をターミナルで実行して完了

このような流れです。
※PythonとPySide2はインストール済みとします。

## 使用ファイルの内容

`mittsuUI_test.ui` 

```xml:mittsuUI_test.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>494</width>
    <height>305</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>Dialog</string>
  </property>
  <widget class="QWidget" name="layoutWidget">
   <property name="geometry">
    <rect>
     <x>61</x>
     <y>101</y>
     <width>326</width>
     <height>68</height>
    </rect>
   </property>
   <layout class="QHBoxLayout" name="horizontalLayout">
    <item>
     <widget class="QLabel" name="label">
      <property name="minimumSize">
       <size>
        <width>100</width>
        <height>0</height>
       </size>
      </property>
      <property name="text">
       <string>mittsu</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QLineEdit" name="lineEdit">
      <property name="minimumSize">
       <size>
        <width>100</width>
        <height>0</height>
       </size>
      </property>
     </widget>
    </item>
    <item>
     <layout class="QVBoxLayout" name="verticalLayout">
      <item>
       <widget class="QPushButton" name="pushButton">
        <property name="text">
         <string>OK</string>
        </property>
       </widget>
      </item>
      <item>
       <widget class="QPushButton" name="pushButton_2">
        <property name="text">
         <string>Cancel</string>
        </property>
       </widget>
      </item>
     </layout>
    </item>
   </layout>
  </widget>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>pushButton</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>accept()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>344</x>
     <y>113</y>
    </hint>
    <hint type="destinationlabel">
     <x>351</x>
     <y>50</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>pushButton_2</sender>
   <signal>clicked()</signal>
   <receiver>Dialog</receiver>
   <slot>reject()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>367</x>
     <y>151</y>
    </hint>
    <hint type="destinationlabel">
     <x>395</x>
     <y>201</y>
    </hint>
   </hints>
  </connection>
  <connection>
   <sender>lineEdit</sender>
   <signal>textChanged(QString)</signal>
   <receiver>label</receiver>
   <slot>setText(QString)</slot>
   <hints>
    <hint type="sourcelabel">
     <x>197</x>
     <y>129</y>
    </hint>
    <hint type="destinationlabel">
     <x>101</x>
     <y>137</y>
    </hint>
   </hints>
  </connection>
 </connections>
</ui>
```

`mittsuUI_test.py` の内容

```python:mittsuUI_test.py
# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'mittsuUI-test.ui'
##
## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        if not Dialog.objectName():
            Dialog.setObjectName(u"Dialog")
        Dialog.resize(494, 305)
        self.layoutWidget = QWidget(Dialog)
        self.layoutWidget.setObjectName(u"layoutWidget")
        self.layoutWidget.setGeometry(QRect(61, 101, 326, 68))
        self.horizontalLayout = QHBoxLayout(self.layoutWidget)
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.label = QLabel(self.layoutWidget)
        self.label.setObjectName(u"label")
        self.label.setMinimumSize(QSize(100, 0))

        self.horizontalLayout.addWidget(self.label)

        self.lineEdit = QLineEdit(self.layoutWidget)
        self.lineEdit.setObjectName(u"lineEdit")
        self.lineEdit.setMinimumSize(QSize(100, 0))

        self.horizontalLayout.addWidget(self.lineEdit)

        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.pushButton = QPushButton(self.layoutWidget)
        self.pushButton.setObjectName(u"pushButton")

        self.verticalLayout.addWidget(self.pushButton)

        self.pushButton_2 = QPushButton(self.layoutWidget)
        self.pushButton_2.setObjectName(u"pushButton_2")

        self.verticalLayout.addWidget(self.pushButton_2)

        self.horizontalLayout.addLayout(self.verticalLayout)

        self.retranslateUi(Dialog)
        self.pushButton.clicked.connect(Dialog.accept)
        self.pushButton_2.clicked.connect(Dialog.reject)
        self.lineEdit.textChanged.connect(self.label.setText)

        QMetaObject.connectSlotsByName(Dialog)
    # setupUi

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
        self.label.setText(QCoreApplication.translate("Dialog", u"mittsu", None))
        self.pushButton.setText(QCoreApplication.translate("Dialog", u"OK", None))
        self.pushButton_2.setText(QCoreApplication.translate("Dialog", u"Cancel", None))
    # retranslateUi
```

`mittsuUI_show.py` の内容

```python:mittsuUI_show.py
# -*- coding: utf-8 -*-
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from mittsuUI_test 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_())
```

QtDesignerの使い方はこちらの『[QtDesignerでUIを作る](https://fereria.github.io/reincarnation_tech/11_PySide/01_PySide_Basic/00_Tutorial/02_create_designer/)』記事を参考にしました。

https://fereria.github.io/reincarnation_tech/11_PySide/01_PySide_Basic/00_Tutorial/02_create_designer/

QtDesignerの使い方が気になる人は上記を参考にして下さい。

## .uiファイルを.pyファイルに変換する

QtDesignerでGUIを作り終えたら、.uiファイルで保存されます。
そのままでは使えないので、`pyside2-uic` というコマンドを使って、Pythonファイルに変換していきます。
### 『pyside2-uic』を使ってのファイル変換

ターミナルで、

```bash
pyside2-uic -o ****.py ****.ui
```

って感じで、Pythonファイルの名前を書いて、その後に置き換えたい`.ui` ファイルの名前を指定します。

今回の場合なら、

```bash
pyside2-uic -o mittsuUI_test.py mittsuUI_test.ui
```

みたいな感じです。
ターミナルでの実行は、カレントフォルダにいることを確認してください。
これってマジで重要です。
私はいつも忘れます。

## GUIを表示する処理を書く

上記で変換したPythonファイルは、GUIの情報が書かれてるだけなので、GUIを表示させるって処理を書いていきます。

```python:mittsuUI_show.py
# -*- coding: utf-8 -*-
import sys
from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
from mittsuUI_test 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_())
```

`mittsuUI_show.py`の中身はこんな感じです。
先程作った`mittsuUI_test.py` の中のクラスをインポートし、そのクラスを継承して〜
って処理ですが、、、。

正直、まだ100％理解できてないんですよね...。
具体的には、

- `if __name__ == '__main__':`
- `app = QApplication(sys.argv)`
- `sys.exit(app.exec_())`

この３つが、まだよくわかってない。
実行したら表示される。
されるんだが、なぜ表示されるかは理解できてない。

まぁとりあえず、こういうものなんだなくらいの認識で進めます。
止まってても仕方ないですしね...。

## ターミナルでPythonファイルを実行しGUIを表示させる

上記で作ったPythonファイル、`mittsuUI_show.py` をターミナルで実行し、GUIを表示させます。

### ターミナルで実行するコマンド

今回はデスクトップに `work` ってフォルダを作り、その中に `20210321-PySide2` ってフォルダを作って作業してます。
パスでいうと、

```
desktop/work/20210321-PySide2
```

です。
なので `cd` コマンドを使い、そこに移動します。
カレントフォルダが`20210321-PySide2` になってることを確認して、

```bash
python3 mittsuUI_show.py
```

を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136274/94bdb71b-d96f-fefc-028e-9c1c5ad2b960.png)
こんなウィンドが出てくれば成功です。

## Mayaでも表示してみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/136274/efca4e7c-addc-c003-f184-19610956948c.png)

Mayaではこんな感じです。
Mayaのスクリプトエディタで実行する場合は、ちょっと改変する必要がありますが、この記事は一旦ここで終わります。

一応スクリプトエディタに書くコードだけ載せておきます。
何かの参考にしてくれると幸いです。
### mayaのスクリプトエディタに書くコード

```python
# -*- coding: utf-8 -*-

################################################################################
## Form generated from reading UI file 'mittsuUI-test.ui'
##
## Created by: Qt User Interface Compiler version 5.15.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################

from PySide2.QtCore import *
from PySide2.QtGui import *
from PySide2.QtWidgets import *
#from mittsuUI_test import Ui_Dialog ここはコメントアウト

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        if not Dialog.objectName():
            Dialog.setObjectName(u"Dialog")
        Dialog.resize(494, 305)
        self.layoutWidget = QWidget(Dialog)
        self.layoutWidget.setObjectName(u"layoutWidget")
        self.layoutWidget.setGeometry(QRect(61, 101, 326, 68))
        self.horizontalLayout = QHBoxLayout(self.layoutWidget)
        self.horizontalLayout.setObjectName(u"horizontalLayout")
        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
        self.label = QLabel(self.layoutWidget)
        self.label.setObjectName(u"label")
        self.label.setMinimumSize(QSize(100, 0))

        self.horizontalLayout.addWidget(self.label)

        self.lineEdit = QLineEdit(self.layoutWidget)
        self.lineEdit.setObjectName(u"lineEdit")
        self.lineEdit.setMinimumSize(QSize(100, 0))

        self.horizontalLayout.addWidget(self.lineEdit)

        self.verticalLayout = QVBoxLayout()
        self.verticalLayout.setObjectName(u"verticalLayout")
        self.pushButton = QPushButton(self.layoutWidget)
        self.pushButton.setObjectName(u"pushButton")

        self.verticalLayout.addWidget(self.pushButton)

        self.pushButton_2 = QPushButton(self.layoutWidget)
        self.pushButton_2.setObjectName(u"pushButton_2")

        self.verticalLayout.addWidget(self.pushButton_2)

        self.horizontalLayout.addLayout(self.verticalLayout)

        self.retranslateUi(Dialog)
        self.pushButton.clicked.connect(Dialog.accept)
        self.pushButton_2.clicked.connect(Dialog.reject)
        self.lineEdit.textChanged.connect(self.label.setText)

        QMetaObject.connectSlotsByName(Dialog)
    # setupUi

    def retranslateUi(self, Dialog):
        Dialog.setWindowTitle(QCoreApplication.translate("Dialog", u"Dialog", None))
        self.label.setText(QCoreApplication.translate("Dialog", u"mittsu", None))
        self.pushButton.setText(QCoreApplication.translate("Dialog", u"OK", None))
        self.pushButton_2.setText(QCoreApplication.translate("Dialog", u"Cancel", None))
    # retranslateUi

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_())
```


## 所感

- QtDesignerでGUIを作る
- それをMayaで表示させる

このたった２行をやるためだけに、どれだけの地雷を踏んだことやら、、、。
とりあえずクラスの概要を掴むことはできた。
フレームワーク、モジュール、クラス、メソッド、、、。

この辺も大体理解しかけてる。
あとは特殊メソッドって呼ばれるやつの把握や、MayaでPythonを扱う上でのお作法。
この辺を理解できたら、およそのツールは作れるんじゃなかろうか…。
って感じましたね。

まぁとりあえずは、
アルファベットの羅列を、インターフェースっていう、目に見える形に変換出来ただけでも進歩したと思いましょう。
ここからやな、楽しいのは。