PySide

QSplitter についてまとめてみる

各レイアウトの幅を自由に変更できるようになる QSplitter についてです。
色々とクセがあってこれまで使う度に1から調べることになっていたので、
いい加減ちゃんとまとめておこうと思います。

基本的な使い方

  1. QSplitter を作ってレイアウトに設置
  2. QSplitter に入れたいウィジェットを作る
  3. QSplitter に配置

というのが最も基本的な使い方です。

import sys

from PySide2 import QtWidgets, QtCore
from PySide2.QtCore import Qt


class MainWindow(QtWidgets.QMainWindow):
    """

    QSplitter

    """

    def __init__(self):
        super(MainWindow, self).__init__()

        # ウィンドウサイズの指定
        self.resize(QtCore.QSize(600, 300))

        wrapper = QtWidgets.QWidget()
        self.setCentralWidget(wrapper)

        #
        #   ルートエリアの作成と設置
        #
        root_area = QtWidgets.QVBoxLayout()
        wrapper.setLayout(root_area)

        #
        #   スプリッターの作成
        #

        splitter = QtWidgets.QSplitter(Qt.Horizontal)
        root_area.addWidget(splitter)

        #
        #   ウィジェットの作成
        #

        plane_text_1 = QtWidgets.QPlainTextEdit()
        plane_text_2 = QtWidgets.QPlainTextEdit()

        #
        #   スプリッターへウィジェットを設置
        #

        splitter.addWidget(plane_text_1)
        splitter.addWidget(plane_text_2)




def launch_from_standalone():
    """

    スタンドアロン用のウィンドウ起動

    """
    standalone_app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    window.show()
    sys.exit(standalone_app.exec_())


if __name__ == '__main__':
    launch_from_standalone()

横へ分割

splitter = QtWidgets.QSplitter(Qt.Horizontal)

上のコードは 横方向へ分割しています。
QSplitter の引数に Qt.Horizontal を渡します。

縦へ分割

splitter = QtWidgets.QSplitter(Qt.Vertical)

Qt.Horizontal だった部分を QtVertical に変更することで縦に分割できます。

スクロールエリアはデフォルトでついてない

デフォルトで QScrollArea はついていません。
そのため、サイズを固定したウィジェットを入れた場合は
スクロールせずに折り畳まれてしまいます。

スクロールエリアを入れた分割

スクロールエリアを入れることでちゃんとスクロールしてくれるようになります。

#
#   スクロールエリアの作成
#

scroll_area_a = QtWidgets.QScrollArea()
scroll_area_b = QtWidgets.QScrollArea()
scroll_area_a.setWidgetResizable(True)
scroll_area_b.setWidgetResizable(True)

widget_a = QtWidgets.QWidget()
widget_b = QtWidgets.QWidget()

scroll_area_a.setWidget(widget_a)
scroll_area_b.setWidget(widget_b)

scroll_vbox_a = QtWidgets.QVBoxLayout(widget_a)
scroll_vbox_b = QtWidgets.QVBoxLayout(widget_b)

#
#   スプリッターへスクロールエリアを設置
#

splitter.addWidget(scroll_area_a)
splitter.addWidget(scroll_area_b)

QScrollArea を入れた QWidget をクラス化

スプリッターを多用する場合は
QScrollArea を入れた QWidget をクラス化しておくと便利です。

下記の文は上のものと全く同じ結果になりますが、コードがシンプルになるので保守がしやすくなります。

class ScrollWidget(QtWidgets.QScrollArea):
    """

    スクロールエリアを入れたウィジェット

    """

    def __init__(self):
        super(ScrollWidget, self).__init__()

        self.setWidgetResizable(True)

        #
        #   ウィジェット / レイアウトの設置
        #

        widget = QtWidgets.QWidget()
        self.setWidget(widget)

        self.vbox = QtWidgets.QVBoxLayout(widget)

スプリッターをスプリッターで分割できる

QSplitter は QSplitter で分割できます。
これにより手軽に複雑な分割が可能です。

たまに使うメソッド

今のところスプリッターのメソッドを使う機会はほぼないんですが、
それでも何回か使ったことがあるメソッドだけ置いておきます。

スプリッターの位置を指定する

self.splitter.moveSplitter(<左端からの距離>, <インデックス>)

でスプリッターの位置を指定することができます。
Gif のコードは下記の文で実行しています。

self.splitter.moveSplitter(300, 2)

これを利用することでウィンドウが立ち上がった時の
スプリッターの位置を指定できます。

各ウィジェットのサイズを取得する

size = self.splitter.sizes()

上記のコードで size に各ウィジェットのサイズがリスト型で入ります。
3つに分けている場合は3つ分のサイズが入ります。

QSS

見た目に関してはシンプルなウィジェットなので、QSS は特に書くものはないです。

class Splitter(QtWidgets.QSplitter):

    def __init__(self, direction):
        super(Splitter, self).__init__(direction)

        self.handle_property = ''
        self.handle_horizontal_property = ''
        self.handle_horizontal_pressed_property = ''
        self.handle_vertical_property = ''
        self.handle_vertical_pressed_property = ''

        self.init_widget()

    def generate_property(self):
        """

        プロパティの生成

        """
        self.handle_property = '''
        
        '''

        self.handle_horizontal_property = '''
            image: url(images/splitter_horizontal.png);
        '''

        self.handle_horizontal_pressed_property = '''
            image: url(images/splitter_horizontal_pressed.png);
        '''

        self.handle_vertical_property = '''
            image: url(images/splitter_vertical.png);
        '''

        self.handle_vertical_pressed_property = '''
            image: url(images/splitter_vertical_pressed.png);
        '''


    def init_widget(self):
        """

        ウィジェットの初期化

        """

        #
        #   スプリッターの幅
        #
        self.setHandleWidth(25)

        #
        #   プロパティ生成
        #
        self.generate_property()

        #
        #   セレクタでプロパティを指定
        #
        self.setStyleSheet(
            'QSplitter::handle{}'.format('{' + self.handle_property + '}') +
            'QSplitter::handle:horizontal{}'.format('{' + self.handle_horizontal_property + '}') +
            'QSplitter::handle:horizontal:pressed{}'.format('{' + self.handle_horizontal_pressed_property + '}') +
            'QSplitter::handle:vertical{}'.format('{' + self.handle_vertical_property + '}') +
            'QSplitter::handle:vertical:pressed{}'.format('{' + self.handle_vertical_pressed_property + '}')
        )

幅を変更する

プロパティの width でもできるそうなんですが(PyQt や PySide6 だけかもしれない)、
私はメソッドでできるものはメソッドで指定する癖があります。

self.setHandleWidth(25)

上記は QSplitter の幅を 25px にします。

セレクタとプロパティ

セレクタとプロパティは特に気にするものはないように思います。
hover が効かないくらいでしょうか。
画像が指定できればそれで充分な気がします。

self.setStyleSheet(
        '''
        
        QSplitter::handle{
            background-color: #aa0098;
        }
        
        QSplitter::handle:horizontal{
            image: url(images/hoge.png);
        }
        
        QSplitter::handle:horizontal:pressed{
            image: url(images/hoge.png);
        }
        
        QSplitter::handle:vertical{
            image: url(images/hoge.png);
        }
        
        QSplitter::handle:vertical:pressed{
            image: url(images/hoge.png);
        }
        
        '''
)

-PySide