PySide2をゼロから学んでいく～#2 ボタン～

https://qiita.com/karakuri-t910/items/c5dbd714cd54520f415d


* [#0 環境構築](https://qiita.com/karakuri-t910/items/f0736a322a6ba217a36c)
* [#1 ウィンドウ](https://qiita.com/karakuri-t910/items/7d2aac29d979f2413162)
* [#2 ボタン ← ここ](https://qiita.com/karakuri-t910/items/5aae0ba3bed05eeb5109)
* [#3 ラベル](https://qiita.com/karakuri-t910/items/1bdbcbf8b4b92555c123)
* [#4 ラジオボタン](https://qiita.com/karakuri-t910/items/e54e771c576d2a0aa11a)
* [#5 チェックボックス](https://qiita.com/karakuri-t910/items/03debf242efd944eecc2)
* [#6 コンボボックス](https://qiita.com/karakuri-t910/items/bfd18dffbe2674a3b478)

# はじめに
PythonでGUI開発をするためのライブラリ「PySide2」の基本的な使い方を、いくつかの段階に分けて説明していきます。

当ページではPySide2のボタンについて説明しています。

# 環境
以下の通りになります。

* Windows 10
* Python 3.8以降

# 公式リファレンス
[QPushButton](https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QPushButton.html)

# ベースプログラム
以下のウィンドウを表示するだけのプログラムをベースにして実装していきます。

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能（今はウィンドウだけ）
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()


# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

# PySide2でボタン

## ボタンを表示する
単純にボタンを表示してみます。

### サンプルプログラム

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能（今はウィンドウだけ）
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        
        # ボタンを表示
        self.button1 = QtWidgets.QPushButton("Button 1", self)


# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

### 実行結果
![000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614920/26e52986-8517-dc63-5407-d3bb94fd72ac.png)


### プログラム解説
ボタンを表示するのに以下のメソッドを使用しています。

```
書式:
    QtWidgets.QPushButton("<文字列>", window_object)
引数:
    "文字列": ボタンに表示する内容
    window_object: ボタンを表示させるウィンドウ（基本的に self を指定）
```

ウィンドウオブジェクトは、ボタンを表示させたいウィンドウを指定します。

PySide2で複数のウィンドウを開いた際に、ある一つのウィンドウに対してボタンをつけたい場合に指定します。そのような場合はほとんど出くわすことはないので、サンプルプログラムのように基本的に**自身のウィンドウを意味する`self`を指定**します。

#### 特定のウィンドウオブジェクトに表示させてみる
気になる方は以下のタブを展開してみてください。
正直知らなくても問題ないです。

<details><summary>複数のウィンドウのある1つにボタンを表示させる</summary><div>
<b>●サンプルプログラム</b>

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能
class MainWindow(QtWidgets.QWidget):
    def __init__(self, title="None Title"):
        super().__init__()
        self.setGeometry(100, 100, 300, 200)
        self.setWindowTitle(title)


# アプリの実行と終了
app = QtWidgets.QApplication()

# ウィンドウオブジェクトの生成
window1 = MainWindow("Window 1")
window2 = MainWindow("Window 2")
window3 = MainWindow("Window 3")
window4 = MainWindow("Window 4")
window5 = MainWindow("Window 5")

# Window 3 にボタンを配置
button = QtWidgets.QPushButton("Window 3 だけにボタンを表示しました。", window3)

# ウィンドウを表示
window1.show()
window2.show()
window3.show()
window4.show()
window5.show()

app.exec_()
```

<b>●実行結果</b>
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614920/172c5afb-cd4c-e505-f0d4-9800e94cbd6b.png)


<b>●プログラム解説</b>
`MainWindow`クラスはインスタンス生成時にウィンドウタイトルを設定するだけにしてます。

`MainWindow`クラス内でボタンを表示しない理由は、クラス内で他のウィンドウオブジェクトを取得するのが難しくなり、全体的なコードが長くなってしまうからです（単に私が知らないだけです）。

そこでメイン処理で5つのウィンドウオブジェクトを生成し、Window 3 だけ個別にボタンを表示するようにしています。
</div>


## ボタンに機能をつける
ボタンをクリックしたら関数を呼び出すようにします。

### サンプルプログラム
`Button 1`をクリックしたら以下のアスキーアートを表示するプログラムです。

ﾊﾞﾝ（∩`･ω･）ﾊﾞﾝﾊﾞﾝ 
  ＿/_ﾐつ/￣￣￣/
       ＼/＿＿＿/”

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        button1 = QtWidgets.QPushButton("Button 1", self)
        button1.clicked.connect(self.pushed_button1)

    def pushed_button1(self):
        print("ﾊﾞﾝ（∩`･ω･）ﾊﾞﾝﾊﾞﾝ \n  ＿/_ﾐつ/￣￣￣/\n       ＼/＿＿＿/”")


# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

### 実行結果
![000.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614920/e6994f05-0a19-d375-980e-ef08878a0bfc.gif)


### プログラム解説
サンプルプログラムのミソの部分です。

```
button = QtWidgets.QPushButton("Button", self)
button.clicked.connect(呼び出したい関数名)
```

`QtWidgets.QPushButton("表示内容", window_object)`でプッシュボタンを定義し、戻り値として定義したボタン情報を渡します。

この戻り値にはシグナルと呼ばれるメソッドを持ち、簡単に言うと「このボタンにどういう操作をしたら、どんな処理をするの？」ということを指定できます。

サンプルプログラムでは`clicked`というシグナルと、そのシグナルが送られたら何の関数を呼び出すのかという`connect`を指定しています。

シグナルについては、当ページの「こぼれ話」でちょこっと取り扱います。

## メニューボタン
一つのボタンに幾つかの選択肢を与えてみます。
**入門レベルを超えた（中級も超えてる？）内容**なので「そんなものもあるんだぁ」程度でOKです。

### サンプルプログラム
* <b><font color="red">注記</font></b>
PySide2公式リファレンスやStack Overflowなどでメニューボタンの実装方法を調べましたが一切出てきませんでした。以下に載せているサンプルプログラムは、私が試行錯誤で実現した方法になります。そのため<font color="red">正規のボタンメニューの実装方法なのか否かは分からない</font>ため真に受けないようご注意を。

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        # メニューインスタンス生成（この変数menuListに項目と機能を追加していく）
        menuList = QtWidgets.QMenu(self)

        # 喜びのメニューとその機能を追加
        action = QtWidgets.QAction("喜", menuList)  # 喜のメニューを作成
        action.setObjectName("Emotions")            # 識別子（無くても良い）
        action.triggered.connect(self.print_happy)  # ボタンを押したら呼び出す関数
        menuList.addAction(action)                  # 喜の機能をメニューに追加

        # 怒りのメニューとその機能を追加
        action = QtWidgets.QAction("怒", menuList)
        action.setObjectName("Emotions")
        action.triggered.connect(self.print_angry)
        menuList.addAction(action)

        # 哀しみのメニューとその機能を追加
        action = QtWidgets.QAction("哀", menuList)
        action.setObjectName("Emotions")
        action.triggered.connect(self.print_sad)
        menuList.addAction(action)

        # 楽しみのメニューとその機能を追加
        action = QtWidgets.QAction("楽", menuList)
        action.setObjectName("Emotions")
        action.triggered.connect(self.print_pleasant)
        menuList.addAction(action)

        # メニューボタンを定義
        menu_button = QtWidgets.QPushButton("喜怒哀楽を表現", self)
        menu_button.setMenu(menuList)  # ボタンに上記に定義した全メニューを登録

    # 喜び
    def print_happy(self):
        print("ヾ(*´∀`*)ﾉ")

    # 怒り
    def print_angry(self):
        print("（#＾ω＾）ﾋﾟｷﾋﾟｷ")

    # 哀しみ
    def print_sad(self):
        print("（´・ω・`）ｼｮﾎﾞｰﾝ")

    # 楽しみ
    def print_pleasant(self):
        print("(≧∇≦)")



# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

### 実行結果
![001.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/614920/e3085b34-1b14-d932-7d0d-768be9c4c96e.gif)


### プログラム解説
一つずつ説明していきます。

#### ①メニューの種類を格納できる変数の宣言

```python
# メニューインスタンス生成（この変数menuListに項目と機能を追加していく）
menuList = QtWidgets.QMenu(self)
```

`QtWidgets.QMenu()`クラスは「どんなメニューを作り、各々はどんな機能なのか」を保存できる変数を戻り値として渡します。あくまで**保存だけ**です。ボタンにメニューの種類と機能を追加するには`QtWidgets.QPushButton.setMenu(メニュー情報)`を使用しますが、これは**③で説明**します。

```
書式:
    class QtWidgets.QMenu(window_object)
引数:
    window_object: メニューの追加対象となるウィンドウ
```

> メニューバーというのもありますが、こちらは`QtWidgets.QMenuBar`というクラスがあります。
> 当ページでは割愛します。

#### ②各メニューとその機能を追加

```python
# 喜びのメニューとその機能を追加
action = QtWidgets.QAction("喜", menuList)  # 喜のメニューを作成
action.setObjectName("Emotions")            # 識別子（無くても良い）
action.triggered.connect(self.print_happy)  # ボタンを押したら呼び出す関数
menuList.addAction(action)                  # 喜の機能をメニューに追加
```

メニューと機能の追加処理は基本的に上記の通りなので、メニュー登録用関数・メソッドを実装すると一層スッキリとしてそれでいて見やすいコードになります。

メニューボタンなので各項目と各々に機能を持たせる必要があります。そこで機能を与えるために`QtWidgets.QAction("メニュー名", メニューインスタンス)`を用いています。`QActio`クラスは第一引数に機能名、第二引数に登録対象の変数を渡します。

`setObjectName("識別子")`は「複数のメニューがあり、これは何のグループなのか？」を分けるためのグループ名みたいなものです。幾つかのメニューに各々の名前を付けるってことです。`setObjectName("識別子")`は記述しなくても問題ないですが、いくつもメニューがある場合は区別のために実装した方が後々管理しやすいかと。

`triggered.connect(関数名)`は「何の機能を実行するのか」を登録するメソッドです。`triggered`は何かしらの操作（クリックとかキー入力とか）があったら実行するというシグナルです。

`addAction()`で上記3つの処理をメニューインスタンスに保存しています。

#### ③メニューボタンの生成とメニューの登録

```python
# メニューボタンを定義
menu_button = QtWidgets.QPushButton("喜怒哀楽を表現", self)
menu_button.setMenu(menuList)  # ボタンに上記に定義した全メニューを登録
```

「①メニューの種類を格納できる変数の宣言」では`QtWidgets.QMenu()`クラスで「メニューとその機能の**保存だけ**」と説明しました。その保存したメニューの内容をボタンに登録するメソッドが`QtWidgets.QPushButton.setMenu()`メソッドになります。

```
書式:
    class QtWidgets.QMenu(window_object)
引数:
    window_object: メニューの追加対象となるウィンドウ
```

# ボタンを使ったサンプルプログラム
私が1分程度で考えたボタンを使ったプログラムをいくつかご披露。

## PySide2のアプリを閉じるボタン
ウィンドウの閉じるボタンとは別に、PySide2のボタンで閉じるを実装しました。

```python
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能（今はウィンドウだけ）
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(300, 300, 300, 200)

        quitButton = QtWidgets.QPushButton("アプリを終了する", self)
        quitButton.clicked.connect(self.application_quit)

    def application_quit(self):
        # Qtが用意しているcloseスロットを実行
        self.close()


# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

## よく使うアプリを起動するボタン
以下のアプリを起動するボタンを作ってみました。

* Chrome
* サクラエディタ
* Kindle

```python
import subprocess
# PySide2のモジュールを読み込む
from PySide2 import QtWidgets


# ウィンドウの見た目と各機能（今はウィンドウだけ）
class MainWindow(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        self.setGeometry(300, 300, 300, 200)

        # メニューインスタンス生成
        menuList = QtWidgets.QMenu(self)

        # Chromeを起動するメニューとその機能を登録
        action = QtWidgets.QAction("Chromeを起動", menuList)
        action.setObjectName("run_applications")
        action.triggered.connect(self.run_chrome)
        menuList.addAction(action)

        # サクラエディタを起動するメニューとその機能を登録
        action = QtWidgets.QAction("サクラエディタを起動", menuList)
        action.setObjectName("run_applications")
        action.triggered.connect(self.run_sakura_editor)
        menuList.addAction(action)

        # Kindleを起動するメニューとその機能を登録
        action = QtWidgets.QAction("Kindleを起動", menuList)
        action.setObjectName("run_applications")
        action.triggered.connect(self.run_kindle)
        menuList.addAction(action)

        # 全部を起動するメニューとその機能を登録
        action = QtWidgets.QAction("全部を起動", menuList)
        action.setObjectName("run_applications")
        action.triggered.connect(self.run_all_applications)
        menuList.addAction(action)

        run_application_button = QtWidgets.QPushButton("何のアプリを起動する？", self)
        run_application_button.setMenu(menuList)

    def run_chrome(self):
        # エスケープシーケンスを無効にする「R」を忘れないで！！
        self.chromePath = R"C:\Program Files\Google\Chrome\Application\chrome.exe"
        subprocess.Popen(self.chromePath)

    def run_sakura_editor(self):
        # エスケープシーケンスを無効にする「R」を忘れないで！！
        self.sakuraeditorPath = R"C:\Program Files (x86)\sakura\sakura.exe"
        subprocess.Popen(self.sakuraeditorPath)

    def run_kindle(self):
        # エスケープシーケンスを無効にする「R」を忘れないで！！
        self.kindlePath = R"C:\Users\からくり\AppData\Local\Amazon\Kindle\application\Kindle.exe"
        subprocess.Popen(self.kindlePath)

    def run_all_applications(self):
        subprocess.Popen(self.chromePath)
        subprocess.Popen(self.sakuraeditorPath)
        subprocess.Popen(self.kindlePath)



# アプリの実行と終了
app = QtWidgets.QApplication()
window = MainWindow()
window.show()
app.exec_()
```

# こぼれ話

## シグナルとスロット
このページでは簡単に説明するだけです。
この話はちゃんと別の記事で説明しようと思います。

### シグナル
何か操作されたら、他のオブジェクトに知らせる関数・メソッドのことをシグナルといいます。当ページでは「ボタンを**クリックしたら**関数を呼び出す」処理を実装しましたが、この「クリックしたら」がシグナルになります。

シグナルには以下のようなものがあります。

| ボタンのシグナル名 | 機能 |
| --- | --- |
| clicked | クリックしたら（押して離したら）シグナルを送る |
| pressed | 押した瞬間にシグナルを送る |
| released | 離した瞬間にシグナルを送る |
| 他多数 | 他に何があるのか分からないだけです... |

### スロット
シグナルを受け取ったら実行する関数・メソッドのことです。当ページでは「ボタンをクリックしたら**関数を呼び出す**」処理を実装しましたが、この「関数を呼び出す」がスロットになります。

Qtではもともと用意されているスロットもありますが、当ページはスロットを自作しております。`pushed_button1`メソッドや`print_happy`メソッドを自作してます。これらがスロットです。

Qtで用意されているスロットには以下があります。

| ウィジェットのシグナル名 | 機能 |
| --- | --- |
| close | アプリを終了する |
| hide | ウィジェットを非表示にする？<br><font color="red">hideについてはあまり理解していませんのでご注意を。</font> |
| 他多数 | 他に何があるのか分からないだけです... |

### シグナルとスロットはどうやって結びつけてるの？
`connect(関数・メソッド名)`で結びつけています。

サンプルプログラムを見返せば分かると思いますが「`Button 1`をクリックしたら`pushed_button1`メソッドを呼び出す」処理を以下のように実装しました。

```python
button1.clicked.connect(self.pushed_button1)
```

`clicked`（シグナル内容）→ `connect`（シグナル送信）という流れでシグナルを送ります。その後`pushed_button1`メソッド（スロット）を実行しています。

## 複数のボタンを使いたいとき
当ページでは、2つ以上ボタンがある場合の説明は割愛させて頂きます。

`QtWidgets.QFormLayout`というモジュールの使い方を学ぶ必要があります。
ボタンやチェックボックスなど、ウィジェットのレイアウトを決める方法として、当シリーズの別ページ記載します。

ここではあくまでもボタンの表示と機能の与え方のみをお伝えした次第であります。

## メニューについて
メニューボタンの使い方は正直理解しにくかったと思います。

正直私自身、PySide2でメニュー系の処理を理解しきれていないので、勉強して言語化できるようになったらメニューに焦点を当てた解説ページを投稿します。

詳しい説明については私が言語化できるくらい理解してから。。。