PySide2で作成したウィンドウを半透過させる

この記事は[Takumi Akashiro ひとり Advent Calendar 2020](https://qiita.com/advent-calendar/2020/takumi_akashiro)の3日目になります。


## 前置きの前置き

はい、Advent Calendarの3日目にして、もうネタが無くなりました。
最初の方にはちょっと実用的な記事を書く意識[^1]がありましたが
仕方ないので、ここから来週の月曜までは好き勝手調べた内容を書いてきます。
TAとかまったく関係ないです。

[^1]: そこら辺の話は土曜当たりの記事で書く予定です。

というわけで、3日目の記事、どうぞ！

## 前置き

皆さんはウィンドウの透過処理、使ったことありますか？自分は今までの仕事では一回も無いですね！
突然、ウィンドウにオーバーレイさせるアプリケーションとか作りたくなったので、ちょっと覚えていこうと思います(投げやり)

ウィンドウを半透過するには得手不得手があり、使い分けが必要だったので、
今日の記事はその辺を踏まえながら解説していきたいと思います！


## 本日のサンプルコード

```python
#!python3
# encoding:utf-8

from PySide2 import QtWidgets
from PySide2 import QtCore

class Window(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        self.setWindowTitle("Sample")
        self.setStyleSheet("background-color:white;")
        self.setFixedSize(480, 320)

        hbox = QtWidgets.QHBoxLayout()
        vbox = QtWidgets.QVBoxLayout()
        hbox.addLayout(vbox)

        button_first = QtWidgets.QPushButton("Button: 1")
        button_first.setStyleSheet("background-color:gray; color:white;height:100%")
        vbox.addWidget(button_first)

        button_second = QtWidgets.QPushButton("Button: 2")
        button_second.setStyleSheet("background-color:darkgray; color:white;height:100%")
        vbox.addWidget(button_second)

        textedit = QtWidgets.QTextEdit("QtWidgets.QTextEdit")
        textedit.setStyleSheet("background-color:lightgray;")
        hbox.addWidget(textedit)

        self.setLayout(hbox)


if __name__ == "__main__":
    app = QtWidgets.QApplication()
    window = Window()
    window.show()
    exit(app.exec_())

```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/4c9a91fb-7aea-5469-b29e-9199122f0832.png)

はい、Qt製の何の変哲もない雑なウィンドウですね。
本日はこれを透過させてみたいと思います。

### 手法1 ウィンドウ背景の透明化オプションをセットする

self.setWindowFlagsに「WA_TranslucentBackground」をセットすることで[^1]ウィンドウの背景が透過するようになります。

[^1]: 正確にはQtCore.Qt.FramelessWindowHintも同時にセットしないと透過しない。これさえなければ……

ただしそのままでは、「WA_TranslucentBackground」をセットした時に
追加される背景を描画しない「WA_NoSystemBackground」の影響で背景色が描画されないので、
paintEvent側で背景色を描画する必要があります。

```python
class Window(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        # ~中略~
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.setStyleSheet("background-color: rgba(255, 255, 255, 127);")

    def paintEvent(self, event):
        # NOTE: トップレベルのWidgetはWA_TranslucentBackgroundフラグが立つと、
        #       背景が描画されなくなるので、paintEvent側で描画する。
        painter = QtGui.QPainter(self)
        painter.fillRect(0, 0, self.width(), self.height(), painter.background())
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/02cc9056-0b3b-356f-bdba-31597e1a4efc.png)

| | |
| :-- | :-- |
|**メリット**|特定Widgetのみを透過できる|
|**デメリット**|ウィンドウのWidgetの透過には<br>WindowFlagsのFramelessWindowHintが必要なため、<br>タイトルバーが消えてしまう|


### 手法2 ウィンドウに直接透明度を適用する

```python
class Window(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        # ~中略~
        self.setWindowOpacity(0.5)
```

| | |
| :-- | :-- |
|**メリット**|めちゃくちゃ簡単に透過を実装できる|
|**デメリット**|Widget個別に透過度を設定できない|


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/72b98d70-6047-6268-5d24-791d8a63b1f3.png)


## 締め

|評価ラベル|ランク(5段階)|
| --:| :--: |
|おすすめ度|★|
|難易度|★|
|ニッチ|★★★★|
|汎用性|★|
|もやもや度|★★★★|

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294868/7b48c2a6-f46e-7490-d7f0-e5e736ff79a9.png)


(上記は加工した画像です)

今回、実はオーバーレイの件とは別件でこういう感じをやりたくて、調べていたんですがダメでした……

一応PySide2+Qtをビルドしなおして、QtWinモジュールを追加すれば、Win7時代によくあったガラスフレームが使えるようで、
上記の画像っぽい感じになるんですが…今回はそこまでの情熱はないです……

まあ手法1に自作のタイトルバーを追加すればいいだけなんで、問題はないんですけどね！！！！(精一杯の負け惜しみ)

というわけで個人的には若干消化不良な結果になりましたが、
今日の記事はここまでで終わりです。

ではでは！