PyQt6の基本の使い方からのまとめとPyQt5との違い


最近PyQt6を使い始めたので、色々試してここで基本を始めとして使い方をまとめておきたいと思います。

ここではPyQtの基本的な使い方を紹介します。書いたのはPyQt6のコードですが、全部の機能はPyQt5/PySide2でもほとんど同じように通用します。PyQt6とPyQt5のコードの書き方の違うところについても指摘します。

私は元々以前からPyQt4/PySideとPyQt5/PySide2を使ったことがあります。（以前PyQt4/PySideで[mmdpaimaya](https://qiita.com/phyblas/items/e5fe203c955e273b26a4)というMayaのGUIを作ったこともあります）

現在PyQt6/PySide6になってもやはり書き方の違いは少ないようなので、大した問題はありません。

ここで表示する画像は全部macで試した結果となります。windowsやlinuxを使っても結果は大体同じですが、大きく違う場合もあります。


# はじめに

## インストール

pyqt6はpythonのほとんどのモジュールと同じようにpipで簡単にインストールできます。

```sh
pip install pyqt6
```


## ウィンドウを作成する

まずは簡単なウィンドウの作成から始めます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setWindowTitle('空っぽな窓') # ウィンドウのタイトル
        self.setGeometry(100,100,200,150) # ウィンドウの位置と大きさ
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/bd68f18a-0627-fa99-d2be-f493f0ab3a28.png)

自分の欲しいウィンドウを作成するために、まずはPyQt6.QtWidgets.QWidgetクラスを継承するのです。

作成するクラスの名前はどんな名前でも構いませんが、「ウィンドウ」は「窓」という意味だから、こういう素朴な理由でここではウィンドウのクラス名を全部「Madoka」にします。他の変数の名前も適当に付けられたものが多いので気にしないでください。

ここで`.setWindowTitle`メソッドでウィンドウのタイトルを、`.setGeometry`メソッドでウィンドウの位置と大きさを指定します。

`.setGeometry`は位置も大きさも一気に指定することになりますが、位置だけ指定したい場合は`.move`を、大きさだけの場合は`.resize`を使います。

#### PyQt5からの変更

PyQt5とPyQt6の違いはざっくり言うと、`.exec_()`を`.exec()`に書き換えることです。

`.exec_()` → `.exec()`

以下のコードも全部そうです。PyQt5で書かれたコードをPyQt6で使いたい場合、これだけ書き換えておけば大体対応できます。


## ボタン（QPushButton）などのウィジェットを入れる

次はウィジェットを入れる方法です。まずはボタンのウィジェットである`QPushButton`から例をあげます。

ウィンドウと同じように、`.setGeometry`で位置と大きさを指定できます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        botan = QPushButton('とあるボタン',self)
        botan.setGeometry(25,25,150,100)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/0c89fe0e-429e-e38f-d6fd-cff612616243.png)

## setStyleSheetで色々調整

ウィンドウのウィジェットも、ボタンなどの他のウィジェットも、大体`.setStyleSheet`メソッドでスタイルを調整できます。書き方はウェブサイトに使うcssとほぼ同じです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        css = '''
        background-color: #e7869f;
        color: #635d81;
        font-family: Kaiti SC;
        font-weight: bold;
        font-size: 36px;
        font-style: italic;
        text-decoration: underline;
        '''
        self.setStyleSheet(css)
        botan = QPushButton('第二ボタン',self)
        css = '''
        background-color: #fae4cb;
        border: 3px solid red;
        text-align: right;
        padding: 15px;
        '''
        botan.setStyleSheet(css)
        botan.setGeometry(20,20,250,110)
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/c97851cc-31d3-8cb4-ef0b-d9e52fb4258e.png)

ちなみに、文字を調整するには、`PyQt6.QtGui.QFont`と`.setFont`メソッドを使うという方法もありますが、`.setStyleSheet`の方が便利なのでここでは割愛します。


## QLabelと線で文字を囲む

文字を表示したいだけの場合は`QLabel`を使うのです。`.setFrameShape`と`.setFrameShadow`と`.setLineWidth`で囲む線を描くことができます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QFrame
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        shirase = QLabel('囲まれた領域',self)
        shirase.setGeometry(10,10,120,40)
        shirase.setAlignment(Qt.AlignmentFlag.AlignCenter)
        shirase.setLineWidth(3) # 線の広さ
        shirase.setFrameShape(QFrame.Shape.Box) # 線の形
        shirase.setFrameShadow(QFrame.Shadow.Raised) # 線の影
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/01197d0f-03eb-e4fb-d200-98a2dba25084.png)

#### PyQt5からの変更

PyQt6ではフラグなどの置かれた場所はPyQt5とはちょっと違います。

`Qt.AlignCenter` → `Qt.AlignmentFlag.AlignCenter`
`QFrame.Box` → `QFrame.Shape.Box`
`QFrame.Raised` → `QFrame.Shadow.Raised`


## ボタンが押されたら何か引き起こす

次はボタンに機能を指定します。機能のないボタンはただのハリボテです。

クリックされたらやらせたいメソッドを`.clicked.connect`で指定するのです。

例えばクリックしたらウィンドウが閉じられるボタン。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        botan = QPushButton('閉じろ',self)
        botan.clicked.connect(self.tojiro)
    
    def tojiro(self):
        print('今日はここまで。さよなら')
        self.close()
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
こんなポタンができます。押してみたらこのウィンドウがすぐ閉じられます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/72137edd-19cc-39fd-372c-80a9122c901f.png)


## ボタンが押された時や放された時に何かさせる

`.clicked.connect`がクリックされた時に発動するのです。つまり、押してすぐ放すことです。もしただ押しかければすぐ発動させたい場合は`.pressed.connect`を使うのです。同じく、ボタンからマウスが解放された時に発動させたい場合は`.released.connect`を使うのです。

例えばボタンが押された時間を計るGUI。

```python
import sys,time
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.botan = QPushButton('押してみて',self)
        self.botan.setGeometry(10,10,150,50)
        self.botan.pressed.connect(self.osaretara)
        self.botan.released.connect(self.hanasaretara)
    
    def osaretara(self):
        self.kaishi = time.time() # 押されたら時間を計って記録する
        self.botan.setText('押されている') # ボタンに書いた文字も変更
    
    def hanasaretara(self):
        print('%.3f秒押されていた'%(time.time()-self.kaishi)) # 放されたらまた時間を計って押され始めた時の時間でひく
        self.botan.setText('押してみて')
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f135b393-8af0-69d0-ec30-3fb8aa56b880.png)


## QPixmapで画像を入れる

GUIに画像を入れることもできます。

例えばサンプルとしてこの画像を使います

*madoka.jpg*
![madoka.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a1b5108a-c288-bd18-aa1c-3b3cad8e2bb3.jpeg) 
ソース https://www.pixiv.net/artworks/46330232

この画像をウィンドウの中に入れてみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLabel
from PyQt6.QtGui import QPixmap

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        label = QLabel(self) # 画像を置くQLabel
        label.move(10,10)
        pix = QPixmap('madoka.jpg') # 画像を読み込むQPixmap
        pix = pix.scaledToWidth(180) # 大きさの変更
        label.setPixmap(pix)
        self.setFixedSize(pix.width()+20,pix.height()+20) # ウィンドウの大きさも画像の大きさに合わせて変更

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

こうやって画像がちょうどウィンドウの中に収められます
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/892469be-8b35-8c8d-6e3b-3920542b5c23.jpeg)

`.scaledToWidth`メソッドは指定した広さまでリサイズしますが、同じように高さだけを指定したい場合は`.scaledToHeight`を使います。

その他に`QGraphicsView`などのウィジェットで画像をいじることもできますが、ここでは割愛します。


# レイアウト

`.setGeometry`や`.move`で直接数字で位置を指定するという方法の他に、レイアウトでウィジェットを置くという方法があります。こうやって直接数字でいちいち指定する必要がなくなるので、楽になる場合も多いです。

## QHBoxLayoutとQVBoxLayout

横で並べる場合は`QHBoxLayout`を、縦で並べる場合は`QVBoxLayout`を使うのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QHBoxLayout,QVBoxLayout

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 30px')
        hbl = QHBoxLayout()
        self.setLayout(hbl)
        vbl1 = QVBoxLayout()
        hbl.addLayout(vbl1)
        vbl1.addWidget(QPushButton('鯖'))
        vbl1.addWidget(QPushButton('鰯'))
        vbl2 = QVBoxLayout()
        hbl.addLayout(vbl2)
        vbl2.addWidget(QPushButton('鮭'))
        vbl2.addWidget(QPushButton('鯛'))
        vbl2.addWidget(QPushButton('鮪'))
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

こうやってボタンはこのように並びます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/d508ffe0-d321-e22e-8037-6dd3cac416d4.png)


## QGridLayout

グリッドでウィジェットを置く場合は`QGridLayout`を使ったら便利です。

例えば「一二三四五六七八九」のボタンを3×3のグリッドに並べる例です。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QGridLayout

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 22px;')
        grid = QGridLayout()
        self.setLayout(grid)
        suuji = '一二三四五六七八九'
        for j in range(3): # 行
            for i in range(3): # 列
                grid.addWidget(QPushButton(suuji[i+j*3]),j,i)
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f88548e5-ca68-e382-1f41-53025d5ca996.png)


## QScrollArea

スクロールで見える部分を変えられる領域を作成したい場合は`QScrollArea`を使うという方法もあります。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QFrame,QScrollArea

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 30px;')
        seiiki = QScrollArea(self)
        seiiki.setGeometry(10,10,130,130)
        kotoba = QLabel('大義名分\n大言壮語\n大気汚染\n大会記録')
        seiiki.setWidget(kotoba)
        seiiki.setLineWidth(6)
        seiiki.setFrameShape(QFrame.Shape.Panel)
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/96091be1-8c23-9b66-271a-158f3bf60024.png)

ここで`QLabel`の中の文字が指定された`QScrollArea`の大きさより大きいので、スクロールをして全体を見るということになります。


## QTabWidget

`QTabWidget`を使って、クリックするとページが切り替えられるタブを作成することができます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QTabWidget,QHBoxLayout,QVBoxLayout,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;')
        basho = QTabWidget(self)
        basho.setFixedSize(200,150)
        
        umi = QWidget()
        basho.addTab(umi,'海')
        hbl = QHBoxLayout()
        umi.setLayout(hbl)
        hbl.addWidget(QPushButton('儒艮'))
        hbl.addWidget(QPushButton('人手'))
        
        mori = QWidget()
        basho.addTab(mori,'森')
        vbl = QVBoxLayout()
        mori.setLayout(vbl)
        vbl.addWidget(QPushButton('麒麟'))
        vbl.addWidget(QPushButton('馬鹿'))
        
        sabaku = QPushButton('無')
        basho.addTab(sabaku,'砂漠')
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
こんな3つの多分が出ます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/5b1d76eb-7285-a2d0-e34c-b30fe6db0301.png)
選んだ多分を切り替えてみたら。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f5331288-930a-d399-5955-ccf9673f9508.png)


## QSplitter

`QSplitter`で可変長の領域を作ることができます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QSplitter,QPushButton
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;')
        sayuu = QSplitter(Qt.Orientation.Horizontal,self) # 横で分裂
        sayuu.setGeometry(10,10,400,120)
        sayuu.addWidget(QPushButton('左'))
        jouge = QSplitter(Qt.Orientation.Vertical,self) # 縦で分裂
        sayuu.addWidget(jouge)
        jouge.addWidget(QPushButton('上'))
        jouge.addWidget(QPushButton('中'))
        jouge.addWidget(QPushButton('下'))
        sayuu.addWidget(QPushButton('右'))
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/297de864-e8b3-fedf-3c31-950273d1b10f.png)

#### PyQt5からの変更

PyQt6では横と縦を決めるフラグの置かれた場所はPyQt5から変更されます。

`Qt.Horizontal` → `Qt.Orientation.Horizontal`
`Qt.Vertical` → `Qt.Orientation.Vertical`


# 入力するためのウィジェット

## QLineEdit

一行だけの文字を入力したい場合は`QLineEdit`を使うことができます。

`.returnPressed.connect`でenterが押された時に発動したいメソッドを指定することができます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLineEdit

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.hako = QLineEdit('',self)
        self.hako.setGeometry(10,10,150,30)
        self.hako.returnPressed.connect(self.enterOsaretara)
    
    # enterを押したら書かれた文字を表示します
    def enterOsaretara(self):
        print(self.hako.text())
    
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
こんなウィンドウができて、何か文字を入れてenterキーを押して試してみたらいいです。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/b64ff853-33dc-3582-bb63-09b3791de7e6.png)

### QLineEditが編集されたら何か引き起こす

少しでも編集されただけで即座に発動させたいメソッドは`.textEdited`や`.textChanged`を使います。

`.textEdited`はユーザーが直接箱で書いたり消したりする場合だけ発動しますが、`.textChanged`を使ったらメソッドなど間接な手段で文字が変更する時も発動するのです。

例えば`QLineEdit`と同じ全く同じ文字を`QLabel`に表すこと。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLineEdit,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 20px;')
        self.hako = QLineEdit('編集してみて',self)
        self.hako.setGeometry(10,10,150,30)
        self.hako.textChanged.connect(self.henshuu)
        
        self.shirase = QLabel('編集してみて',self)
        self.shirase.setGeometry(10,50,150,30)
    
    def henshuu(self):
        self.shirase.clear()
        self.shirase.setText(str(self.hako.text()))
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/6ca086f7-9823-17fc-428c-9d0d730432f3.png)


## QTextEdit

`QLineEdit`が一行しか書けないが、複数行書く場合は`QTextEdit`を使うのです。

`.setText`などのメソッドは`QTextEdit`と同じですが、入力された文字を取得したい場合は`QLineEdit`では`.text`メソッドを使うのに対し、`QTextEdit`は`.toPlainText`メソッドを使うのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QTextEdit,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 18px;')
        jouge = QVBoxLayout()
        self.setLayout(jouge)
        
        def utsusu():
            yomikake.setText(kakikake.toPlainText())
        kakikake = QTextEdit('ここに書いたものは下の方に写される')
        jouge.addWidget(kakikake)
        kakikake.setFixedSize(400,70)
        kakikake.textChanged.connect(utsusu)
        
        yomikake = QLabel('')
        jouge.addWidget(yomikake)
        yomikake.setFixedSize(400,70)
        utsusu()
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/7f2c313e-bc68-07b4-d60c-a9ff44d7f600.png)


## QCheckBox

チェックボックスはチェックする（選ぶ）ための四角形です。その四角形がチェックされているかどうかは `.isChecked()`メソッドで調べれます。チェックに変更が起きた時に発動させたいメソッドは`.toggled.connect`で指定するのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QCheckBox,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 18px;')
        self.shikaku = QCheckBox('選ぶ？',self)
        self.shikaku.move(10,10)
        self.shikaku.toggled.connect(self.henkou)
        
        self.shirase = QLabel('いいえ',self)
        self.shirase.move(10,40)

    # チェックに変化が起きたら、下にあるQLabelのテクストもそれに従って変える
    def henkou(self):
        if(self.shikaku.isChecked()):
            self.shirase.setText('はい') # チェックされている場合
        else:
            self.shirase.setText('いいえ') # チェックされていない場合
    
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/02479c37-6b05-aefa-8410-f5800fadafb1.png)


## QRadioButtonとQButtonGroup

ラジオボタンは複数の選択肢の中から一つだけ選択するための円盤です。QtでラジオボタンはQRadioButtonクラスで作成できます。機能させるためにはQButtonGroupを作成して、同じグループのQRadioButtonを入れる必要があります。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QRadioButton,QLabel,QButtonGroup

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 21px;')
        jouge = QVBoxLayout()
        self.setLayout(jouge)
        
        self.sentakushi = QButtonGroup() # 選択肢のグループ
        shitsumon = QLabel('一番人口が多い国は？') # 質問を表示する
        shitsumon.setStyleSheet('color: blue;')
        jouge.addWidget(shitsumon)
        for kuni in ['日本','中国','韓国','越南']:
            botan = QRadioButton(kuni) # 一人ずつラジオボタンを作成する
            jouge.addWidget(botan) # レイアウトに追加
            self.sentakushi.addButton(botan) # 選択グループに追加
        # ラジオボタンがクリックされたら下の方のテクストの変化もさせる
        self.sentakushi.buttonClicked.connect(self.eranda)
        # 正解かどうかを表示する文字
        self.kekka = QLabel('')
        jouge.addWidget(self.kekka)
    
    def eranda(self,botan):
        if(botan.text()=='中国'):
            self.kekka.setText('正解')
            self.kekka.setStyleSheet('color: green;')
        else:
            self.kekka.setText('違う')
            self.kekka.setStyleSheet('color: red;')
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

`.buttonClicked.connect`でラジオボタンがクリックされたら発動するメソッドを指定できます。そのメソッドのパラメータには押された選択肢のオブジェクトが入ります。そのオブジェクトの`.text()`メソッドでその選択肢のテクストとを取得できます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/4278b867-5984-6b99-fda7-08f0891fad88.png)
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/80e3869b-4fb5-dd33-23f8-b1b78d39780e.png)


## QComboBox

コンボボックスは`QRadioButton`と似ていますが`QRadioButton`より作り方は簡単です。まずは`QComboBox`を作成して、`.addItem`メソッドで選択肢にしたい文字列を入れるのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QHBoxLayout,QComboBox,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        sayuu = QHBoxLayout() # 横レイアウト
        self.setLayout(sayuu)
        
        self.hako = QComboBox() # コンボボックスを作成する
        sayuu.addWidget(self.hako) # レイアウトにコンボボックスを追加する
        # 択肢を一人ずつコンボボックスに追加する
        for s in ['西瓜','南瓜','胡瓜','冬瓜']:
            self.hako.addItem(s)
        # 選ばれた時に発動するメソッドを指定する
        self.hako.currentTextChanged.connect(self.eranda)
        self.hako.setCurrentIndex(-1)
        
        self.erandano = QLabel('') # 選ばれた文字列を表示する場所
        self.erandano.setStyleSheet('font-family: Kaiti SC; font-size: 32px; color: #f04625')
        sayuu.addWidget(self.erandano)
    # 選択肢の選びが更新されたらその新しい選択肢の文字列を取得する
    def eranda(self,text):
        self.erandano.setText(text)
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/bca7a189-6193-e921-276c-6f0d8396a709.png)

コンボボックスの中の選択肢が更新された時に発動させるメソッドの指定は`.currentTextChanged.connect`と`.currentIndexChanged.connect`があります。違いは取得できるパラメータです。

`.currentTextChanged.connect`の場合は選んだアイテムの文字列を取りますが、`.currentIndexChanged.connect`はそのアイテムの置かれた順番を取るのです。

その他に、選んだ選択が変わらなくてもただコンボボックスの中のアイテムがクリックされた時に発動するメソッドを指定する場合は`.activated.connect`を使うこともできます。


## QSpinBox

[スピンボックス](https://ja.wikipedia.org/wiki/スピンボタン)はQSpinBoxで作成できます。

QSpinBoxは整数しか入れられないが、小数を入れたい場合はQDoubleSpinBoxを使うのです。

値が更新された時に発動するメソッドは`.valueChanged.connect`で指定します。

ここでボタンの長さを決めるためのスピンボックスを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QSpinBox,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(350,150) # ウィンドウのサイズの指定
        jouge = QVBoxLayout() # 縦レイアウトを使う
        self.setLayout(jouge)
        
        self.nagasa = QSpinBox() # スピンボックスの作成
        jouge.addWidget(self.nagasa) # スピンボックスをレイアウトに入れる
        self.nagasa.setRange(25,300) # 範囲の指定
        self.nagasa.setValue(250) # 当初の値の指定
        self.nagasa.setSingleStep(25) # 一度ボタンをクリックする時に変わる値の指定
        self.nagasa.valueChanged.connect(self.henka) # 値が更新されたら発動するメソッドの指定
        
        self.botan = QPushButton() # スピンボックスの下にボタンを作る
        jouge.addWidget(self.botan)
        self.botan.setFixedSize(250,50)
        self.botan.setStyleSheet('background-color: #7125f0')
    # ボタンの長さをスピンボックスの値に従って変更させる
    def henka(self,x):
        self.botan.setFixedSize(x,50)
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/5428e3bd-61c6-96f8-07d3-c68cdf8127c6.png)


## QDoubleSpinBox

QDoubleSpinBoxの使い方は基本的にQSpinBoxと同じですが小数が入れられます。`.setDecimals`で小数部の細かさを指定できます。

試しに、長さをメートルで入れたらインチとセンチの長さを表示するGUIを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QDoubleSpinBox,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        jouge = QVBoxLayout()
        self.setLayout(jouge)

        self.meetoru = QDoubleSpinBox()
        jouge.addWidget(self.meetoru)
        self.meetoru.setDecimals(3) # 小数部の細かさ
        self.meetoru.setRange(0,1)
        self.meetoru.setValue(0.5)
        self.meetoru.setSingleStep(0.001)
        self.meetoru.setSuffix('メートル') # 語尾に表示する文字列
        self.meetoru.valueChanged.connect(self.henka)

        self.inchi = QLabel() # インチの長さ
        jouge.addWidget(self.inchi)

        self.senchi = QLabel() # センチの長さ
        jouge.addWidget(self.senchi)

        self.henka(0.5)

    def henka(self,x):
        self.inchi.setText('%.3f インチ'%(x*39.37))
        self.senchi.setText('%.1f センチ'%(x*100))

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/64773ad8-a9b9-ed80-ade8-155a76908efd.png)


## QSlider

QSliderはQSpinBoxと似ていますが、数字ではなく円盤の位置で値を代表するウィジェットです。

デフォルトではスライダーの方向が縦ですが、横にしたい場合.setOrientation()で変更できます。

スピンボックスと同じく、値が変えられた時に発動させたいメソッドを`.valueChanged.connect`を指定します。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QSlider,QLabel
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        jouge = QVBoxLayout()
        self.setLayout(jouge)
        self.atai = QLabel()
        jouge.addWidget(self.atai)

        self.guruguru = QSlider()
        jouge.addWidget(self.guruguru)
        self.guruguru.setOrientation(Qt.Orientation.Horizontal) # 横にする
        self.guruguru.setRange(1,100) # 調整できる値の範囲
        self.guruguru.setValue(50) # 最初の値
        self.guruguru.valueChanged.connect(self.henka)
        self.henka(50)

    def henka(self,x):
        self.atai.setText('#'*(int(x/10))+' %d'%x)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/55b3b776-aceb-2522-76a2-1539017db665.png)

#### PyQt5からの変更

QSplitterと同じく、縦と横を示すフラグを使うので、フラグの置かれた場所の変更も気をつけないとです。

`Qt.Horizontal` → `Qt.Orientation.Horizontal`
`Qt.Vertical` → `Qt.Orientation.Vertical`


## QScrollBar

QScrollBarはQSliderと似ていますが、円盤の代わりに矩形とボタンが使われます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QScrollBar,QPushButton
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        jouge = QVBoxLayout()
        self.setFixedWidth(200)
        self.setLayout(jouge)
        self.botan = QPushButton()
        jouge.addWidget(self.botan)
        self.botan.setStyleSheet('background-color: #8848f2')
        jouge.setAlignment(self.botan,Qt.AlignmentFlag.AlignCenter)

        self.subesube = QScrollBar()
        jouge.addWidget(self.subesube)
        self.subesube.setStyleSheet('background-color: #ceb9f0')
        self.subesube.setOrientation(Qt.Orientation.Horizontal) # 横にする
        self.subesube.setRange(5,160) # 範囲
        self.subesube.setValue(40) # 初期値
        self.subesube.setSingleStep(4) # 一度ボタンをクリックしたら変更する値
        self.subesube.setPageStep(20)
        self.subesube.valueChanged.connect(self.henka)
        self.henka(40)

    def henka(self,x):
        self.botan.setFixedSize(x,30)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/04baa526-4d87-bbe7-1e9e-2ec0bee36c4f.png)

#### PyQt5からの変更

`Qt.Horizontal` → `Qt.Orientation.Horizontal`
`Qt.Vertical` → `Qt.Orientation.Vertical`


## QDial

QDialはQSliderと似ていますが、円盤を回ることで値を指定するのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QVBoxLayout,QDial,QLabel
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        jouge = QVBoxLayout()
        self.setLayout(jouge)

        self.mawaru = QDial()
        jouge.addWidget(self.mawaru)
        self.mawaru.setRange(-600,600) # 範囲の指定
        self.mawaru.valueChanged.connect(self.henka)

        self.shirase = QLabel()
        jouge.addWidget(self.shirase)
        jouge.setAlignment(self.shirase,Qt.AlignmentFlag.AlignCenter)
        self.mawaru.setValue(120)

    def henka(self,x):
        self.shirase.setText('%d'%x)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/55bb2918-9bbb-a76d-845c-2d1d21e1d295.png)


## QDateTimeEdit

`QDateTimeEdit`は日付と時間を入力するためのウィジェットです。

その他に日付だけなら`QDateEdit`を、時間だけだったら`QTimeEdit`を使います。

`.setCalendarPopup`で`True`にしたら暦で年月日を選ぶこともできます。

暦に表示される言語はlocaleによります。これは`.setLocale`で使いたいlocaleで設定することもできます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QDateTimeEdit,QVBoxLayout
from PyQt6.QtCore import QLocale,QDateTime
class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 20px')
        vbl = QVBoxLayout()
        self.setLayout(vbl)
        hizuke = QDateTimeEdit(self)
        vbl.addWidget(hizuke)
        hizuke.setDateTime(QDateTime(2021,8,9,19,30)) # 初期値の時間
        hizuke.setCalendarPopup(True) # 暦を表示する
        koyomi = hizuke.calendarWidget()
        koyomi.setLocale(QLocale('ru')) # 暦のlocaleを変えてロシア語にしてみる

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a59407d6-d7cb-2641-df1d-ac1ec050b07d.png)


# QDialog

## QMessageBox

重要なお知らせとかしたい時にQMessageBoxを使います。

例えばボタンをクリックしたらお知らせを表示する例。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton,QMessageBox

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 16px')
        botan = QPushButton('何ですか！？',self)
        botan.setGeometry(10,10,125,55)
        botan.clicked.connect(self.oshirase)
    # ボタンをクリックしたらメッセージボックスが現れる
    def oshirase(self,x):
        QMessageBox.about(self,'','お前様はもうお死にになっております')
        
qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/d44f1574-586b-d22a-6f79-50bfd2b0ff1a.png)

`QMessageBox.about`に入れるパラメータは`QMessageBox.about(親ウィジェット,タイトル,メッセージの内容)`となりますが、macなどではタイトルが無効なのでここでは空にします

今回使ったQMessageBox.aboutの他にも、似ているような`QMessageBox.warning`、`QMessageBox.critical`、`QMessageBox.information`があります。

yes/noの答えを取りたい時に`QMessageBox.question`を使うことができます。

`QMessageBox.question`を使う例は後記の`closeEvent`の例と共に書きます。



## QInputDialog

ユーザーに何かを一つだけ入力して欲しいウィンドウを作る場合はQInputDialogを使うことができます。

データの種類によって使い分けするのです。
一行だけの文字列を取りたい場合は`QInputDialog.getText`を使うのです。
数行の文字列の場合は`QInputDialog.getMultiLineText`。
整数の場合は`QInputDialog.getMultiLineText`。
小数の場合は`QInputDialog.getDouble`。
コンボボックスの選択肢から文字列を選ぶ場合は`QInputDialog.getItem`。

例えば文字列を入れる例をあげみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QInputDialog

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 16px')
        title,okkei = QInputDialog.getText(self,'入力ウィンドウ','ウィンドウのタイトルを入れてください')
        if(okkei and title!=''):
            self.setWindowTitle(title)
        else:
            self.setWindowTitle('名無し')

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/72499bf1-887c-0d17-d4f3-df053e5fc709.png)

`QInputDialog.getText`からの返り値は2つあります。1つ目は入力された文字列で、2つ目は、キャンセルが押された場合はFalseとなり、そうではない場合はTrueとなります。



## QFileDialog

ファイルを読み込んだり保存したりしたい場合はQFileDialogを使うことができます。

`QFileDialog.getOpenFileName`は一つだけファイルの名前を取りたい時に使います。
複数のファイルの場合は`QFileDialog.getOpenFileNames`。
ファルダだけ選ぶ場合は`QFileDialog.getExistingDirectory`。
ファイルを保存する場合は`QFileDialog.getSaveFileName`。

例えば、ファイルを選んで、そのファイルの中の文字列を取得して文字数と行数を数えるGUIを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QFileDialog,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 22px')
        namae,ok = QFileDialog.getOpenFileName(self)
        if(ok):
            with open(namae) as f:
                naiyou = f.read()
                jouhou = QLabel('文字数：%d\n%d行'%(len(naiyou),len(naiyou.split('\n'))),self)
                jouhou.setGeometry(10,10,160,70)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

`QFileDialog.getOpenFileName`からの返り値は2つあります。1つ目はファイルの名前で、2つ目はokが押されたかどうかを示すTrue/Falseです。


## QColorDialog

`QColorDialog`は色を選びたい時に使います。

ボタンの色を選ぶGUIを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QColorDialog,QPushButton

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        iro = QColorDialog.getColor() # 色を選ばウィンドウ
        iro = '(%d,%d,%d)'%(iro.red(),iro.green(),iro.blue()) # RGBの値にする
        botan = QPushButton(iro,self)
        botan.setStyleSheet('background-color: rgb'+iro) # 選んだ色でボタンの色に指定する
        botan.setGeometry(10,10,100,100)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f590f22e-fea4-b658-dcd9-3f19b5467ba3.jpeg)

色を選んでみたらその色のボタンが現れます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/08c370f9-262f-915c-01ed-5caedb3a9217.png)


## QFontDialog

`QFontDialog`は文字のフォントを選びたい時に使います。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QFontDialog,QLabel

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        moji = QLabel('窓',self)
        moji.setGeometry(5,5,250,200)
        font,ok = QFontDialog.getFont() # フォントを選ばウィンドウ
        if(ok):
            moji.setFont(font) # 取得したフォントで文字を書く

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/1dd8e56a-67a9-682c-f1df-bef040fbf9ec.png)


# QMainWindow

ウィンドウのウィジェットは`QWidget`の代わりに`QMainWindow`を使うこともできます。その場合色んな機能が追加されます。

## ステータスバー

QMainWindowではウィンドウのメイン部分の下の方にあるステータスバーを簡単に作れます。

まずはメインの部分にするウィジェットを`.setCentralWidget`で指定するのです。

そしてステータスバーは`.statusBar`メソッドで作成できます。

その作成されたステータスバーの`.showMessage`メソッドを使って、伝えたいメッセージを表示するのです。

```python
import sys
from PyQt6.QtWidgets import QApplication,QMainWindow,QWidget,QPushButton

class Madoka(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(290,180)
        self.setStyleSheet('font-family: Kaiti SC; font-size: 18px')

        chuuou = QWidget()
        self.setCentralWidget(chuuou) # 中央に置くウィジェット
        botan = QPushButton('中央ボタン',chuuou)
        botan.setGeometry(10,10,270,110)

        staba = self.statusBar() # 下にあるステータスバー
        staba.showMessage('ステータスバーのメッセージ') # 書きたいメッセージ
        staba.setFixedHeight(50) # ステータスバーの高さを指定することもできる

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/9f43a0b0-01ed-69cf-2e35-2c97fb5a3780.png)


## メニューバー

`QMainWindow`を使うと、`.menuBar()`メソッドでメイン部分の上に置かれるメニューバーを作ることができます。

メニューバーの選択肢に`QAction`が使われます。

例えば料理メニューを選んで会計するGUIを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QLabel,QMainWindow
from PyQt6.QtGui import QAction

class Madoka(QMainWindow):
    def __init__(self):
        super().__init__()
        css = '''
        * {
            font-family: Kaiti SC;
            font-size: 20px;
        }
        QMenuBar {
            background-color: #def;
            border: 2px solid #222;
        }
        QMenu {
            background-color: #fbd;
            border: 1px solid #444;
        }
        '''
        self.setStyleSheet(css)
        self.ryoushuusho = QLabel('0 円')
        self.setCentralWidget(self.ryoushuusho)
        self.staba = self.statusBar()
        self.menubar = self.menuBar()
        self.menubar.setNativeMenuBar(False)

        self.soukei = 0
        self.menu1 = self.menubar.addMenu('丼物')
        self.ryouri1 = QAction('カツ丼',self)
        self.menu1.addAction(self.ryouri1)
        self.ryouri1.setStatusTip('350 円')
        self.ryouri1.triggered.connect(lambda :self.tsuika(350))

        self.ryouri2 = QAction('牛丼',self)
        self.menu1.addAction(self.ryouri2)
        self.ryouri2.setStatusTip('400 円')
        self.ryouri2.triggered.connect(lambda :self.tsuika(400))

        self.menu2 = self.menubar.addMenu('ラーメン')
        self.ryouri3 = QAction('叉焼メン',self)
        self.menu2.addAction(self.ryouri3)
        self.ryouri3.setStatusTip('550 円')
        self.ryouri3.triggered.connect(lambda :self.tsuika(550))

        self.ryouri4 = QAction('豚骨ラーメン',self)
        self.menu2.addAction(self.ryouri4)
        self.ryouri4.setStatusTip('800 円')
        self.ryouri4.triggered.connect(lambda :self.tsuika(800))

    def tsuika(self,nedan):
        self.soukei += nedan
        self.ryoushuusho.setText(str(self.soukei)+' 円')

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

ここで`.setNativeMenuBar(False)`はmacの場合に必要です。macではこれを書かなければメニューバーが表示されないのです。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/a59628e4-21c0-95d5-82c9-daa59fca9b75.png)

`.setStatusTip`は、マウスがその項目に置かれた時にステータスバーに文字列を表示させるために使われます。

項目が選ばれた時に発動するメソッドは`.triggered.connect`で指定するのです。

#### PyQt5からの変更

PyQt5では`QAction`が`QtWidgets`に入れられますが、PyQt6では`QAction`は`QtGui`に属します。使う時に注意する必要があります。

`from PyQt6.QtWidgets import QAction` → `from PyQt6.QtGui import QAction`


## ツールバー

ツールバーもメニューバーと同じようにメイン部分の上に置かれるエリアです。ただしドラッグで場所を変えることができます。

メニューバーと同じく、`QAction`が入れられます。ここで`QAction`を作成する時に入れるのは`(アイコン, テクスト, 親ウィジェット)`です。アイコンが指定された場合、アイコンだけが表示されて、テクストはマウスが置かれた時に現れるのです。

例えばパソコン用品を買う時に会計するためのGUIを作ってみます。

アイコンはこの画像を使います。
![icon_mouse.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/8deb9195-8114-e8bd-ebe3-52e7d24fedad.png)![icon_keyboard.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/19ce4048-6db5-c617-ed79-c50f07a75319.png)![icon_earphone.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/93fd2fbf-3b2b-da3d-020d-0c9d49db8565.png)

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QLabel,QMainWindow,QToolBar,QHBoxLayout
from PyQt6.QtGui import QIcon,QAction

class Madoka(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 19px')
        self.staba = self.statusBar()

        cenwid = QWidget()
        self.setCentralWidget(cenwid)
        hbl = QHBoxLayout()
        cenwid.setLayout(hbl)

        hbl.addWidget(QLabel('総計 '))
        self.ryoushuusho = QLabel('0')
        hbl.addWidget(self.ryoushuusho)
        hbl.addWidget(QLabel('円'))
        hbl.addStretch()

        self.tana = self.addToolBar('')
        self.dougu1 = QAction(QIcon('icon_mouse.png'),'マウス',self)
        self.tana.addAction(self.dougu1)
        self.dougu1.setStatusTip('+1000 円')
        self.dougu1.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+1000)))

        self.dougu2 = QAction(QIcon('icon_keyboard.png'),'キーボード',self)
        self.dougu2.setStatusTip('+1500 円')
        self.tana.addAction(self.dougu2)
        self.dougu2.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+1500)))

        self.dougu3 = QAction(QIcon('icon_earphone.png'),'イヤフォン',self)
        self.dougu3.setStatusTip('+2000 円')
        self.tana.addAction(self.dougu3)
        self.dougu3.triggered.connect(lambda :self.ryoushuusho.setText(str(int(self.ryoushuusho.text())+2000)))

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/07bb0feb-16ad-d91f-85ab-f39e83f34751.png)


## QDockWidget

`QMainWindow`に使えるもう一つの機能は`QDockWidget`を入れることです。

`QDockWidget`はドラッグで移動できるウィジェットです。`QMainWindow`の`.addDockWidget`メソッドで作成された`QDockWidget`を入れることができます。追加する時にQt.DockWidgetAreaのフラグで、始めた時の位置を指定する必要があります。

例えば`QDockWidget`を2つ入れる例です。

```python
import sys
from PyQt6.QtWidgets import QApplication,QLabel,QPushButton,QMainWindow,QDockWidget,QTextEdit,QScrollArea
from PyQt6.QtCore import Qt

class Madoka(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 19px')
        self.resize(400,300)
        chuuou = QPushButton('中央')
        self.setCentralWidget(chuuou)

        staba = self.statusBar()
        staba.showMessage('ステータスバー')

        dowi1 = QDockWidget('dock 1',self) # 一つドックウィジェットを作成する
        teit = QTextEdit('ドック1')
        teit.setStyleSheet('background-color: #d4a1c5')
        dowi1.setWidget(teit)
        #右の方にドックウィジェットを入れる
        self.addDockWidget(Qt.DockWidgetArea.RightDockWidgetArea,dowi1)

        dowi2 = QDockWidget('dock 2',self) # 2つ目のドックウィジェット
        scar = QScrollArea()
        dowi2.setWidget(scar)
        dock2la = QLabel('ドック2')
        dock2la.setFixedSize(700,220)
        scar.setWidget(dock2la)
        scar.setStyleSheet('background-color: #a1d4bf')
        #上の方にドックウィジェットを入れる
        self.addDockWidget(Qt.DockWidgetArea.TopDockWidgetArea,dowi2)

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```
こんなウィンドウができて、ドックウィジェットをドラッグしてみたら移動できます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/f059948b-6467-3809-c3cb-a4c9b88a4b7b.png)


# classのメソッドの書き換え

ウィジェットのclassにメソッドを書き換えることである特定の動作が起きる時にやらせたいことを指定することができます。

## マウスが入ったり出たりしたイベント

マウスがウィジェットに入るというイベントが発生した時に`enterEvent`メソッドで作動が決められます。このメソッドを書き換えることで、イベントが発生した時にやらせたいことを指定できます。

同じように、マウスがウィジェットから出たイベントは`leaveEvent`です。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.resize(180,120)

    def enterEvent(self,e):
        self.setStyleSheet('background-color: red')

    def leaveEvent(self,e):
        self.setStyleSheet('background-color: blue')

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

マウスを通してみたらウィンドウはこんな風に赤くなります。離れたら藍色になります。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/13376449-a63f-3197-67cb-df119daed45d.png)


## ウィジェットがクリックされたイベント

マウスが押されたイベントは`mousePressEvent`で、解放されたイベントは`mouseReleaseEvent`です。

ダブルクリックの場合は`mouseDoubleClickEvent`。

マウスの情報はパラメータとして取得できます。

例えばマウスの置かれた位置は`.position()`で取れます。

マウスを押したり放したりダブルクリックしたりする時にマウスの位置を出力するウィンドウを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget

class Madoka(QWidget):
    def __init__(self):
        super().__init__()

    def mousePressEvent(self,e):
        p = e.position() # マウスが押された場所
        print('(%d,%d)でマウスが押された'%(p.x(),p.y()))

    def mouseReleaseEvent(self,e):
        p = e.position() # マウスが解放された場所
        print('(%d,%d)でマウスが放された'%(p.x(),p.y()))

    def mouseDoubleClickEvent(self,e):
        p = e.position() # マウスがダブルクリックされた場所
        print('(%d,%d)でダブルクリック'%(p.x(),p.y()))

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

### PyQt5からの変更

PyQt5では`.pos()`で位置を取得できますが、PyQt6では`.position()`に変更されます。更に取得できた数字はintではなく、floatとなります。intに変更したい場合は`.position().toPoint()`です。

`e.pos()` → `e.position().toPoint()`


## ウィンドウのサイズが変更されたイベント

ウィンドウの大きさが変えられた時に`resizeEvent`が発生します。その`resizeEvent`のパラメータとして、元の大きさを`.oldSize`メソッドで、新しい大きさを`.size`メソッドで取得することができます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget

class Madoka(QWidget):
    def __init__(self):
        super().__init__()

    def resizeEvent(self,e):
        s0 = e.oldSize() # 元の大きさ
        s1 = e.size() # 今の大きさ
        print('(%d,%d) > (%d,%d)'%(s0.width(),s0.height(),s1.width(),s1.height()))

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```


## ウィンドウが閉じられそうになったイベント

ウィンドウが閉じられそうになった時に発生するのは`closeEvent`です。

これを書き換えることで、閉じる時の確認などすることができます。

例えば`QMessageBox.question`を使って本当に閉じたいかどうかを確かめらせる例です。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QMessageBox

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.setStyleSheet('font-family: Kaiti SC; font-size: 20px')
        self.show()

    def closeEvent(self,e):
        yesno = QMessageBox.question(self,'','本当に閉じてしまってもよろしいのでしょうか？')
        if yesno == QMessageBox.StandardButton.Yes:
            e.accept() # yesと答えたら閉じる
        else:
            e.ignore() # noと答えたら何も起きない

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/7ff9b6ef-9f87-bec1-5f41-894a8e3b86a3.png)

#### PyQt5からの変更

`QMessageBox.Yes` → `QMessageBox.StandardButton.Yes`


## キーが押されたイベント

キーボードが押された時に`keyPressEvent`が、キーが解放された時に`keyReleaseEvent`が発生します。メソッドのパラメータで押されたキーの情報を取得できます。

`.key()`メソッドを使ったらそのキーのフラグが得られます。

これを使って特定のキーが押された時にやらせたいことを指定することができます。

例えば ← → ↑ ↓ キーでボタンを移動するGUIを作ってみます。

```python
import sys
from PyQt6.QtWidgets import QApplication,QWidget,QPushButton
from PyQt6.QtCore import Qt

class Madoka(QWidget):
    def __init__(self):
        super().__init__()
        self.botan = QPushButton('※',self)
        self.botan.setGeometry(40,30,60,50)
        self.show()

    def keyPressEvent(self,e):
        x = self.botan.x()
        y = self.botan.y()
        if(e.key()==Qt.Key.Key_Left):
            self.botan.move(x-10,y)
            self.botan.setText('←')
        elif(e.key()==Qt.Key.Key_Right):
            self.botan.move(x+10,y)
            self.botan.setText('→')
        elif(e.key()==Qt.Key.Key_Up):
            self.botan.move(x,y-10)
            self.botan.setText('↑')
        elif(e.key()==Qt.Key.Key_Down):
            self.botan.move(x,y+10)
            self.botan.setText('↓')
        elif(e.key() in [Qt.Key.Key_Escape,Qt.Key.Key_Q]):
            self.close()
        print('(%d,%d) > (%d,%d)'%(x,y,self.botan.x(),self.botan.y()))

    def keyReleaseEvent(self,e):
        self.botan.setText('※')

qAp = QApplication(sys.argv)
mado = Madoka()
mado.show()
qAp.exec()
```

このようにボタンとウィンドウが出ます。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/0a4124ec-c184-0b37-bcde-cd5402f63263.png)
キーボードで「←」を押したらボタンが左の方へ移ります。
![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/202903/981b92ae-2240-976a-afc1-bc637a3c6240.png)

#### PyQt5からの変更

キーのフラグの置かれた場所はPyQt5とは違います。

`Qt.Key_Left` → `Qt.Key.Key_Left`
`Qt.Key_Right` → `Qt.Key.Key_Right`
`Qt.Key_Up` → `Qt.Key.Key_Up`
`Qt.Key_Down` → `Qt.Key.Key_Down`
`Qt.Key_Escape` → `Qt.Key.Key_Escape`


# 参考

今までqiitaでPyQt5/PySide2の色んな記事を読んでとても参考になりましたので、自分の読んだ記事のリンクをここにまとめてみます。

- [PyQt5入門(1)](https://qiita.com/MamoruItoi/items/340efff6c079a00b04fc)
- [PyQt5とpython3によるGUIプログラミング](https://qiita.com/kenasman/items/471b9930c0345562cbbf)
- [PyQt5とpython3によるGUIプログラミング：実践編[0]](https://qiita.com/kenasman/items/b9ca3beb25ecf87bfb06)
- [PyQt5とpython3によるGUIプログラミング［１］](https://qiita.com/kenasman/items/70a3ef914b0e7e55a123)
- [PyQt5とpython3によるGUIプログラミング［２］](https://qiita.com/kenasman/items/73d01df973a25ae704e4)
- [GUIとしてのPyQt5の使い方](https://qiita.com/montblanc18/items/0188ff680acf028d4b63)
- [【PyQt】QLabelのサイズ変化に合わせて文字サイズを自動的に変更する【Qt】](https://qiita.com/marksard/items/7afeb3ab3ffa61ae8d27)
- [PySideでQDockWidgetの不便なところを解消する](https://qiita.com/pashango2/items/7770a27e86d739186ed3)
- [【PythonでGUI】PyQt5 -ダイアログ-](https://qiita.com/Nobu12/items/acd3caa625be8eebc09c)
- [【PythonでGUI】PyQt5 -ウィジェットⅡ-](https://qiita.com/Nobu12/items/36bc4cc6cf4163b66847)
- [【PythonでGUI】PyQt5 -レイアウト管理-](https://qiita.com/Nobu12/items/81da82b8b984bf54f0a0)
- [【PythonでGUI】PyQt5 -ウィジェット-](https://qiita.com/Nobu12/items/6248c509401b0e666a55)
- [【PythonでGUI】PyQt5 -始めの一歩-](https://qiita.com/Nobu12/items/67abf075177fc36484ff)
- [【PythonでGUI】PyQt5 -メニューとツールバー-](https://qiita.com/Nobu12/items/d5f6cc57274a64170734)
- [PySide2をゼロから学んでいく～#1 ウィンドウ～](https://qiita.com/karakuri-t910/items/7d2aac29d979f2413162)
- [PySide2をゼロから学んでいく～#2 ボタン～](https://qiita.com/karakuri-t910/items/5aae0ba3bed05eeb5109)
- [PySide2をゼロから学んでいく～#3 ラベル～](https://qiita.com/karakuri-t910/items/1bdbcbf8b4b92555c123)
- [PySide2をゼロから学んでいく～#4 ラジオボタン～](https://qiita.com/karakuri-t910/items/e54e771c576d2a0aa11a)
- [PySide2をゼロから学んでいく～#5 チェックボックス～](https://qiita.com/karakuri-t910/items/03debf242efd944eecc2)
- [PySide2をゼロから学んでいく～#6 コンボボックス～](https://qiita.com/karakuri-t910/items/bfd18dffbe2674a3b478)
- [PyQt 入門編](https://qiita.com/ynuma/items/6452927e4332f1923f59)

これらほとんどはPyQt5ですが、やはりPyQt6にもほとんど同じように使えます。



# 終わりに

以上PyQt6の大体の使い方とサンプルコードを紹介してみました。全部PyQt5から使える機能なので、書き方の違いがわかれば全部PyQt5にも通用します。

ドラッグ&ドロップの使い方については次の記事で説明します。

https://qiita.com/phyblas/items/fbf71baf48460862c83f