粗大メモ置き場

個人用,たまーに来訪者を意識する雑記メモ

Python,PyQtで簡単にGUIを作る ① リスト表示とグラフPlot

モチベーション

身の回りのありとあらゆる便利パッケージにGUIがあったらな,と思うことが多いためやり方をメモします。

初回はひとまず

  • リストの複数を選択
  • matplotlibの図をリストの選択に応じて表示

ができるようにします。

サンプルを通した理解

わかりやすいサンプルコードを介して理解を深めるのが手っ取り早いです。 今回の目的には下記の内容が大部分で合致しているので参考にしました。

qiita.com

配置

構造としてはまずはQwidgetというキャンバスを用意して,QVboxlayoutというコンテナの中にグラフのプロットであったり,チェックボックスであったり具体的な要素を詰めていくという構造をとります。(理解がUpdateされ次第Updateします。)

f:id:ossyaritoori:20210717170350p:plain

超最低限に書くと下記のようになります。

# 初期化のサンプル
class Test(QtWidgets): # QWidgetを継承
    def __init__(self):
        super().__init__() # 継承元のInit
        vbox = QtWidgets.QVBoxLayout(self) #Vboxlayoutの定義。縦にオブジェクトを並べる。(QHboxなら横に) 
        vbox.addWidget( QtWidgets.QListWidget() ) # リスト表示Widgetを追加。
        vbox.addWidget( QtWidgets.QLabel(self) ) # ラベルWidgetを追加。(テキスト設定していないからこの段階では無)
        # ...このように追加していって
        self.setLayout(vbox) # VboxlayoutをWidgetにはめ込む

他にオブジェクトを配置する手法としては最後の部分はsetGeometryなどでゴリゴリ配置しても良さそうですし,

        # 配置
        self.setGeometry(0,0,900,600)
        self.FigureWidget.setGeometry(200,0,700,600)
        self.FileList.setGeometry(0,0,200,600)

配置をこりたい人は,Qt Designer というGUIがあるのでそれでレイアウトを作ったほうがいいと思います。

www7a.biglobe.ne.jp

自分は今はSplitterという機能を使って画面を分割して配置するようにしています。また次回にメモします。

イベントの設定

配置したオブジェクトたちにはイベントが起きたときにどうするかという設定をすることができます。

# インデント略
self.listWidget = QtWidgets.QListWidget()
self.listWidget.itemClicked.connect(self.printItemText) #クリックされたらprintItemText関数を呼ぶ

# 呼ばれる側の関数
    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        x = []
        for i in range(len(items)):
            x.append(str(self.listWidget.selectedItems()[i].text()))
        print (x)

実行

Qtにおいては、QApplicationというクラスがメインのアプリケーションを定義するので, 最後に作ったWidgetクラスを表示する際には下記のようにします。

#アプリケーション本体のインスタンスを作る
app = QtWidgets.QApplication(sys.argv)    #アプリケーションのインスタンスを作る
Test.show()                        #作ったWidgetを表示する
app.exec()                          #アプリケーションのメッセージループを開始する

小ネタ

大体のオブジェクトは配置してから初期設定などをする必要があります。 そうするとaddWidgetでの配置が見づらくなるので関数化して一行で表せるようにしたほうがはかどります。

ラベルテキスト追加

ラベルオブジェクトを配置するならこうします。

    def add_label(self,text):
        # ラベルを作る
        label1=QtWidgets.QLabel(self)
        label1.setText(text)
        label1.adjustSize()
        return label1

リストでの複数選択

QListWidgetでSelectionModeを設定すると複数選択可能にできます。

毎回リセットするなら ExtendedSelection,トグルするなら MultiSelectionがおすすめです。 下記のように書きます。

        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする https://doc.qt.io/qt-5/qabstractitemview.html
            QtWidgets.QAbstractItemView.MultiSelection # QtWidgets.QAbstractItemView.ExtendedSelection 
        )

参考: QAbstractItemView Class | Qt Widgets 5.15.5

リストで番号を選択してPlotするサンプル

ということでリストで番号を選択してPlotするサンプルを下記に載せます。

f:id:ossyaritoori:20210717182134p:plain
選択した順番に下の図にPlotされます。

# -*- coding: utf-8 -*-
"""
Stackoverflowの文章より原型を拝借
QtのListwidgetで複数選択をしてPlotする。
"""

from PyQt5 import QtWidgets, QtCore
import matplotlib.pyplot as plt
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas

class Test(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        self.layout = QtWidgets.QVBoxLayout()
        # リスト作成
        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.setSelectionMode( #複数選択可能にする
            QtWidgets.QAbstractItemView.MultiSelection
        )
        self.listWidget.setGeometry(QtCore.QRect(10, 10, 211, 291))
        for i in range(10):
            item = QtWidgets.QListWidgetItem("%i" % i)
            self.listWidget.addItem(item)
        self.listWidget.itemClicked.connect(self.printItemText)

        # Figureを作成
        self.Figure = plt.figure()
        self.FigureCanvas = FigureCanvas(self.Figure)  # FigureをFigureCanvasに追加
        self.axis = self.Figure.add_subplot(1,1,1) # axを持っておく

        self.layout.addWidget(self.add_label("Ctrlを押しながらで複数選択"))
        self.layout.addWidget(self.listWidget)
        self.layout.addWidget(self.FigureCanvas)
        self.setLayout(self.layout)


    def add_label(self,text):
        # ラベルを作る
        label1=QtWidgets.QLabel(self)
        label1.setText(text)
        label1.adjustSize()
        return label1


    def printItemText(self):
        # 選択したアイテムを表示
        items = self.listWidget.selectedItems()
        x = []
        for i in range(len(items)):
            x.append(int(self.listWidget.selectedItems()[i].text()))

        print(x)
        self.update_Figure(x)

    # Figure
    def update_Figure(self,x):
        self.axis.cla() # リセットを掛ける必要がある。
        self.axis.plot(x,'-o')
        plt.grid(); plt.legend(["selected number"])
        self.FigureCanvas.draw()

if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    form = Test()
    form.show()
    sys.exit(app.exec_())