祝PySide2デビュー! ~ただひたすらウィジェットを紹介するページ~
2016/10/17
Tag: maya2017,PySide,python,UI
みなさんお久しぶりです。TDの小野[@jimie930]です。
先日Mayaもバージョン2017が発表され、様々な新機能が搭載されましたね。そんな中、今回私が注目するのはPySideでございます。な、な、なんと、Maya2017からPySideがPySide2になりました。ワーイ、ワーイ。
って、あまり喜ばれている方の話は私の耳に入ってきてないです。耳が遠いもので。
どちらかと言うと、今までPySideでツール作りをしていた方の、「今までのツール使えるのかなぁ~」という声。
PySideとPySide2では、大きく何が変わったのでしょうか?
今までQtGui
に入っていたウィジェット群が、別のQWidgets
というモジュールに出されたことが一番大きいと思います。
なんでこんなことしたんだ…。
とにかく対応が必要そうですね。
PySide2を動かしてみよう
早速ですが今回はMaya2017でも、それ以前のバージョンでも動くサンプルツールを作ってみました!
いまのところPySide2のクラスリファレンスなどのドキュメントはなさそうなので、Qt 5.x系のドキュメントを参考にしてください。
このツール、ダウンロードもできるようにしておきますので、ダウンロードされた方はソースコードまでじっくり見てやってください。
さてさて、先程も述べましたが、ウィジェットのクラス群が入っているモジュールが違うので、2017以前のMayaとMaya2017のどちらでも動くUIを作るためには、インポートを変えなければなりませんね。みなさんいろいろ試行錯誤されてるようですが、私自身はこんな感じでいけるのかなぁと思っています。
import imp try: imp.find_module('PySide2') from PySide2.QtWidgets import * from PySide2.QtGui import * from PySide2.QtCore import * except ImportError: from PySide.QtGui import * from PySide.QtCore import *
Qtのクラスたちの名前は一意になっているので、この読み方でとりあえずは大丈夫なはず。。。読みたいクラスが明確なのであれば、import
の後のアスタリスク部分はそれらのクラスを列挙するのがいいでしょう。また、QtCore
に関しては同じなので、
from PySide2 import QtCore
でも構いません。
QWidgets
QWidgets
というモジュールに今回移ったウィジェットたち。どんなものがあるのでしょうか。
今日は「PySide2使ってツール作りました」だけでは面白く無いんで、ついでにいろんなウィジェットを配置しつつ、それらのウィジェットの特徴とちょっとしたTipsなんかを紹介できればと思います。
今まで意外とこういう記事なかったですよね。PySideってなに?って方も、この記事を読んで我々TDがどうやってMayaのUIを作っているのか知っていただければと思います。(ダイアログも下の方でついでに紹介しています。というかダイアログもウィジェットの一部です。)
こんな感じに標準で用意されているウィジェットをいろいろ並べてみました。(意外と見た目、かっこよくなった♪)
種類、こんなにあるんですね。
上の画像で各機能がなんのウィジェットで作られているか、画像上で気になるところクリックしてみてください。そのウィジェットについての紹介部分へジャンプします。
QLabel
単純なテキストを表示したい場合はこのウィジェットを使います。
ただこのウィジェットが意外と優秀で、テキスト以外に、画像や動画を表示する場合にも使います。
UIで表示しているDFのロゴ画像もこのQLabel
を使っています。
# 画像を表示 imageWidget = QLabel() imageWidget.setPixmap(QPixmap("画像パス"))
QLineEdit
単純な文字列の入力ができるウィジェットです。ユーザーから文字を入力して欲しい時などに使用します。
setInputMask
関数を使うと、入力できる文字列の制限ができます。
例えば以下のようなマスクをかけた場合、9の部分に0~9の数字のみを入力することができます。
lineEdit = QLineEdit() lineEdit.setInputMask("s9999_9999") lineEdit.show()
もっと細かい制限をしたい場合はsetValidator
関数を使って、指定した正規表現などにマッチするかどうかで制限させることもできます。
正規表現についてはこちらの記事をご参考に→【Python】正規表現を使った快適コーディングのすゝめ ~はじめの一歩から三歩ぐらいまで~(宣伝ですw)
下のサンプルコードは数字三桁のバージョン名のみを入力させることができるウィジェットです。
lineEdit = QLineEdit() # 正規表現のマッチングクラスをValidatorとしてセット lineEdit.setValidator(QRegExpValidator(QRegExp("^v\d{3}$")))
QTextEdit
このウィジェットは複数行に渡るテキストの表示や入力に便利です。
弊社ではパブリッシュの際のコメント入力や、データの情報表示に使っています。
setReadOnly
関数を使うと、編集できなくなるので、表示だけでいい場合はReadOnlyを有効にします。
また、HTMLを読み取って表示してくれるので、簡単に文字の色やスタイルを変更できます。
HTMLでセットされている文字列を単純な文字列で取得する場合にはtoPlainText
関数を使います。
textArea = QTextEdit() # テキストを複数行でセットする場合 textArea.setPlainText("Text are\ncan be set\nmultiple lines and HTML.") # HTMLをセットする場合 textArea.setHtml("<h1><font color='#ff0000'>abcd</color></h1>") # 文字列で取得 text = textArea.toPlainText()
QComboBox
いくつかの値から一つをユーザーに選択させたいような場合に使用します。
setEditable
をオンにすると、文字列補完で、値を選択できるようになります。

通常のQComboBoxの選択

テキスト入力でアイテムを補完
大量のShot名から一つを選ばせたい場合、ズラッと並んだ中から探すのは大変ですよね。そのような場合は、このテキスト入力をオンにして、ユーザーがフィルタリングできるようにすると選択しやすくなります。
comboBox = QComboBox() comboBox.addItems(["This", "is", "combobox", "it's", "useful"]) # テキスト入力ON comboBox.setEditable(True) # NoInsertにすると、テキスト入力でユーザーが間違った値を入れてもアイテムとしてComboBoxに登録されません。 comboBox.setInsertPolicy(QComboBox.NoInsert) # 入力補完時に候補リストを出す場合、Completerをセットしてやります。 comboBox.completer().setCompletionMode(QCompleter.PopupCompletion)
QSpinBox
このウィジェットは数値の入力を補助するためのものです。カチカチと横の矢印を押すことで数値の増減ができます。
QLineEdit
のように数字を直接入力することもできます。QLineEdit
のinputMask
のように数値のみの入力に制限できるので、誤入力を防ぐ効果もあります。
下の例では、接尾辞“min”を指定して、ユーザーになんの値を入れているのか意識しやすくしています。
spinBox = QSpinBox() spinBox.setMinimum(0) spinBox.setMaximum(10) # 接尾辞を設定 spinBox.setSuffix("min")
QCheckBox
オン、オフの値が欲しい場合に有効なウィジェットです。
checkState
、setCheckState
関数が用意されていますが、QCheckBox
の親クラスであるQAbstractButton
のsetChecked
、isChecked
関数の方がBOOL
型で設定できるので、私自身は後者でコントロールすることが多いです。
checkBox = QCheckBox("Check box") checkBox.setChecked(True) checked = checkBox.isChecked()
QRadioButton
複数の選択肢から一つを選ばせる場合に使用するウィジェットです。
ラジオボタンを複数作り、それらをグループ化することで、「一つをチェックすれば、他のボタンのチェックが外れる」といった挙動を自動で行ってくれます。
これらのボタンが同じグループかどうかは、親のウィジェット(radioButton.parent()
で取得できるウィジェット)が共通であるかどうかで判断されます。
複数のグループをUI内で持ちたい場合は、それらを別ウィジェットの子供として追加していかなければなりません。PySideはこのグループ化のために、UIの表示に影響を及ぼさないQButtonGroup
というウィジェットを用意しています。QButtonGroup
への追加例は以下を参考にしてください。
# まずはグループを作成 radioGroup = QButtonGroup() # ラジオボタンを作成して、グループに追加 radioBtn1 = QRadioButton("Option 1") radioGroup.addButton(radioBtn1) radioBtn2 = QRadioButton("Option 2") radioGroup.addButton(radioBtn2) radioBtn3 = QRadioButton("Option 3") radioGroup.addButton(radioBtn3) radioBtn1.setChecked(True)
QCalendarWidget
日付を選択するのに有効なウィジェットです。スケジュール管理ツールなんかにあると便利ですね。
Tipsは特にないですw
calendar = QCalendarWidget() calendar.setFirstDayOfWeek(Qt.Monday) calendar.setVerticalHeaderFormat(QCalendarWidget.NoVerticalHeader) calendar.setSelectedDate(QDate(2016, 10, 17))
QLCDNumber
カウントダウンを表示してユーザーを焦らせるときに便利なウィジェットですw あ、私はそんな用途で使ったことないですよ。
とにかく数字を強調して、認識して欲しい時に使うと有効的です。ちょっとおもしろいので載せてみました。
数字の他に、2進数や16進数を表示することもできます。これらの使い道はよくわかりません。
lcdNumber = QLCDNumber() lcdNumber.display(1234)
QSlider
連続的な数値入力によるインタラクションを実装するのに便利なウィジェットです。例としては、スライダーを動かして、Mayaオブジェクトのスケールを変えるなどでしょうか。スライダーを動かしている間中、シグナルを発信させることができるので、それを受け取って、何かアクションを連続的に起こすことができます。
setRange
関数で、数値の範囲を指定できます。
また、ステップを指定することでキーボード操作時に、いくつづつ数値が増減するかを指定できます。setPageStep
はキーボードの”PageUp”、”PageDown”が押された時のステップ値の設定で、setSingleStep
はキーボードの矢印キーで操作された時のステップ値の設定です。
setTickPosition
関数を使うと、目盛りの表示場所を指定できます。そして、setTickInterval
関数で、その目盛りの間隔を指定します。(Mayaでは目盛りのスタイルが設定されていないのか、setTickPosition
で目盛りの表示はできませんでした。)
slider = QSlider(Qt.Horizontal) # Qt.Horizontalで横向きのスライダーにしています slider.setRange(0, 100) slider.setTickPosition(QSlider.TicksBothSides) slider.setSingleStep(5) slider.setPageStep(10) slider.setTickInterval(10)
QDial
このウィジェットも連続的な数値変化を扱いたい時に使用します。QSlider
とはスタイルが違う程度です。ステップの指定の方法も同じです。ただし、目盛りに関する名前が、Notchになっていることに注意してください。目盛りのサイズを変えたい場合はintervalではなく、notchTargetで設定します。(ちなみにMayaでもNotchは表示されました。)
dial = QDial() dial.setRange(0, 100) dial.setSingleStep(5) dial.setPageStep(10) dial.setNotchesVisible(True) dial.setWrapping(True) dial.setNotchTarget(5)
QListWidget
データの表示によく使うウィジェットの一つです。文字列のリストを表示できます。
QListWidget
の中の各アイテムはQListWidgetItem
クラスが用いられていますが、下の例のように、文字列での追加もできるので、お手軽に使えるウィジェットです。
文字列を編集可能にしたり、ドラッグ&ドロップできるようにしたりできます。
listWidget = QListWidget() listWidget.addItems(["this", "is", "list", "widget"])
QTableWidget
このウィジェットもデータの表示に適しています。
データベースを使ったデータ管理をしていると、二次元データをデータベースから取得してツールで表示することが多いので、このウィジェットをよく使います。ただし、設定がいろいろあって、使いこなすのはなかなか難しいです。
QListWidget
と違って、文字列だけでアイテムを追加することはできません。各セルのアイテムを表現するためにQTableWidgetItem
クラスが用いられているので、まずはQTableWidgetItem
に文字列を指定してインスタンス化し、行番号と列番号を指定して、テーブルにセットします。
また、データをセットする前にsetColumnCount
関数とsetRowCount
関数を使用して、テーブルにデータをセットする枠を設けておかないといけません。自動でcolmunCount
やrowCount
を増加してくれる仕様ではないので、テーブルが3行3列に設定されている場合、1行目の4列目にデータを追加することはできません。
簡単なテーブル作成サンプルを書いたので、参考にしてみてください。
tableWidget = QTableWidget() headerLabels = ["Name", "Age", "Sex"] # columnCountをまずは設定してしまいます。 tableWidget.setColumnCount(len(headerLabels)) tableWidget.setHorizontalHeaderLabels(headerLabels) # 各行にヘッダーはいらない場合は、このようにヘッダーを消せます。 tableWidget.verticalHeader().setVisible(False) dataList = [ ["Taro", "25", "Male"], ["Hanako", "30", "Female"], ["Ichiro", "50", "Male"], ["Jiro", "40", "Male"] ] # 入力データの数が決まったのでrowCountも設定します。 tableWidget.setRowCount(len(dataList)) # forでまわして各セルにアイテムをセットします。 for row, colData in enumerate(dataList): for col, value in enumerate(colData): # QTableWidgetItemをまずは作成して追加 item = QTableWidgetItem(value) tableWidget.setItem(row, col, item)
QTreeWidget
階層構造を持ったデータや、グループ化できるデータを表示するのに有効なウィジェットです。
QTableWidget
では各セルがアイテムになっていましたが、このウィジェットでは各行がQTreeWidgetItem
というアイテムになります。そしてデータの階層構造はこのQTreeWidgetItem
の親子付けで表現されます。
こちらも簡単なサンプルコードを書いてみました。
treeWidget = QTreeWidget() headerLabels = ["Name", "Age"] treeWidget.setColumnCount(len(headerLabels)) treeWidget.setHeaderLabels(headerLabels) treeData = { "Male":[ {"name":"Taro", "age":"25"}, {"name":"Ichiro", "age":"50"}, {"name":"Jiro", "age":"40"} ], "Female":[ {"name":"Hanako", "age":"30"} ] } for sex, profiles in treeData.iteritems(): topItem = QTreeWidgetItem([sex]) treeWidget.addTopLevelItem(topItem) for profile in profiles: # 第一引数に親になるアイテムを指定すると親子付けできます。 # 各カラムへの文字列の追加はリストでできます。 childItem = QTreeWidgetItem(topItem, [profile.get("name"), profile.get("age")])
QPushButton
単純なボタンを表現するウィジェットです。使い方もテキストを指定するだけで簡単です。
サンプルのプログラムではこれらのボタンを押すといろんなダイアログが出るように作りました。ポチポチ押してみてください。
ダイアログについてもこの後いろいろ紹介します。
↑ウィジェット目次
QDockWidget
MayaのAttributeEditorのようなメインウィンドウにドッキングしたり、分離したりすることのできるウィジェットです。
これができるだけで、すごいツールのように思われますが、実装自体はそんなに難しくないです。
setAllowedAreas
関数でメインウィンドウのどこにドッキングできるかを設定できます。
あとはQMainWindow
クラスにaddDockWidget
関数があるので、それを用いてウィジェットをドッキングします。
また、デフォルトではQDockWidget
にクローズボタンがついており、押されるとそのウィジェットが消えてしまうので、注意が必要です。実際は削除されているわけではなく、visibilityがオフになっているだけなので、何かしらshowするアクションを作っておくか、QDockWidget.DockWidgetClosable
フラグをsetFeatures
関数で外すように設定する必要があります。
dockWidget = QDockWidget("Dock Window", mainwindow) # Dockの中身を別ウィジェットで作成 dockWrapper = QWidget() dockWidget.setWidget(dockWrapper) dockLayout = QVBoxLayout() dockWrapper.setLayout(dockLayout) dockDescription = QLabel("This is dock widget contents.") dockLayout.addWidget(dockDescription) dockButton = QPushButton("OK") dockLayout.addWidget(dockButton) # どこにドッキングできるかを指定 dockWidget.setAllowedAreas(Qt.RightDockWidgetArea | Qt.BottomDockWidgetArea) # メインウィンドウにドッキング mainwindow.addDockWidget(Qt.BottomDockWidgetArea, dockWidget)
Dialogの紹介
QDlaog
クラスを派生させた様々なダイアログが準備されています。QMessageBox
こちらは単純なメッセージを表示するのに有効なダイアログです。
メインとなるテキストやサブテキスト、様々なボタンの追加といった細かな設定もできますが、下記のようなスタティック関数が用意されているので、それらを用いることで、簡単にダイアログを作成できます。これらの関数は表示したいメッセージの内容によって使い分けます。
- QMessageBox.information : 何か情報を表示したい場合
- QMessageBox.question : ユーザーに選択させたいアクションがある場合(Publish時の確認など)
- QMessageBox.warning : ワーニングメッセージを表示したい場合
- QMessageBox.critical : クリティカルエラーを表示したい場合
response = QMessageBox().information(None, "Info Message", "This is normal information message.") response = QMessageBox().question(None, "Question Message", "This is normal question. OK?") response = QMessageBox().critical(None, "Critical Error", "This is critical error message.")
細かな設定をする場合は、下のサンプルのようにQMessageBox
クラスをインスタンス化してから、テキストなどを設定していきます。テキストはリッチテキストを受け付けてくれるので、文字の色を変えたり、太字にしたりということも、ある程度は行えます。
最後にexec_
関数を実行すると、ダイアログが表示されます。
クリックされたボタンをexec_
が終わった後に取得することで、何が押されたかを判断します。
msgBox = QMessageBox() msgBox.setWindowTitle("DF Talk Info") # リッチテキストをセット msgBox.setText(r'<b>Do you like DF Talk?</b>') # リッチテキストを使用していることを教えてやります msgBox.setTextFormat(Qt.RichText) # 補助的なテキストを追加 msgBox.setInformativeText("This is important question for us.") # 詳細を表示するためのボタンができて、押すとテキストエリアが開いて表示されます msgBox.setDetailedText("We are really looking forward to hearing from you.") yesButton = msgBox.addButton(QMessageBox.Yes) noButton = msgBox.addButton(QMessageBox.No) msgBox.exec_() # 押されたボタンを取得 clickedBtn = msgBox.clickedButton() if clickedBtn == yesButton: print("yay!") else: print("boooo!")
QColorDialog
色を選択させたい時に有効なダイアログです。OSに用意されているデフォルトのカラーピッカーが表示されます。
colorDialog = QColorDialog() response = colorDialog.exec_() if response == QDialog.Accepted: # OKが押された時は処理を続ける chosen = colorDialog.currentColor()
QProgressDialog
重い処理などをツールで実行する際に有効なダイアログです。どの程度、処理が終了しているかを表示することができます。
私がよく行う実装方法としては、重い処理を別スレッドで実行して、各処理が終わるたびにシグナルをメインスレッドに送信し、プログレスを進めるという流れでプログラムを書きます。
デフォルトではsetValue
関数に値を入れるとダイアログが表示され、Max値に到達するとクローズされます。
max = 100 progressDialog = QProgressDialog("Progress...", "Cancel", 0, max, self) progressDialog.setWindowTitle("Progress Dialog") for count in range(max+1): qApp.processEvents() if progressDialog.wasCanceled(): break progressDialog.setValue(count) progressDialog.setLabelText("Progress... %d %%" % count) time.sleep(0.1)
QFileDialog
ファイル読み込みや保存の時に有効なダイアログです。デフォルトで開くフォルダのパスや、表示するファイルのフォーマットを指定することができます。
このダイアログも簡単にパスやフィルターを設定して、実行できる関数が用意されています。行いたい用途に合わせて使い分けます。
- QFileDialog.getExistingDirectory : フォルダを選択させたい場合に使用します。
- QFileDialog.getOpenFileName : ファイルを選択させたい場合に使用します。
- QFileDialog.getOpenFileNames : 複数ファイルを選択させたい場合に使用します。
- QFileDialog.getSaveFileName : ファイルを保存する際に使用します。
response = QFileDialog.getOpenFileName(None, "File Select", options = QFileDialog.DontUseNativeDialog)
QErrorMessage
これは単純なエラーメッセージを表示するためのダイアログです。同じ内容のメッセージの場合、再度表示しないといった機能がデフォルトでついています。
ただし、同じインスタンスでないといけないので、メインウィンドウなどでこのダイアログ作成し、保持しておき、エラー表示の際はshowMessage
関数だけを呼ぶという仕組みにしておく必要があります。
errorMsgDialog = QErrorMessage() def showError(): print("error") errorMsgDialog.showMessage("Error") showError() showError() showError()
QInputDialog
ユーザーからの簡単な値の入力を受付たい場合に有効なダイアログです。例えば、文字列を入力させたい場合は、getText
というスタティック関数を実行すればテキスト入力のウィジェットを持ったダイアログとして表示されます。その他に、double値の入力、int値の入力、アイテム(複数文字列)選択型があります。

QInputDialog テキストインプット

QInputDialog コンボボックス
# テキスト入力ができるダイアログ表示 response = QInputDialog.getText(self, "Input Text", "Input text here.") # コンボボックスで選択させるダイアログ表示 response = QInputDialog.getItem(self, "Select Item", "Select item from the combo box.", ["item1", "item2", "item3", "item4"], editable=False)
QDialog
今まで紹介したダイアログたちのベースとなるクラスで、これをカスタマイズすることで独自のダイアログも作成できます。
試しにこんな感じのダイアログを作ってみました。サンプルコードは以下です。
class MyDialog(QDialog): def __init__(self, parent = None, f = 0): super(MyDialog, self).__init__(parent, f) #----------------------------------------------------------------------- # Layout #----------------------------------------------------------------------- mainLayout = QVBoxLayout() self.setLayout(mainLayout) #----------------------------------------------------------------------- # Image #----------------------------------------------------------------------- imageWidget = QLabel() imageWidget.setPixmap(QPixmap(LOGO_IMAGE)) mainLayout.addWidget(imageWidget) #----------------------------------------------------------------------- # Description #----------------------------------------------------------------------- description = QLabel("This is coustom dialog.") mainLayout.addWidget(description) #----------------------------------------------------------------------- # Text input #----------------------------------------------------------------------- self._inputWidget = QLineEdit() mainLayout.addWidget(self._inputWidget) #----------------------------------------------------------------------- # Buttons #----------------------------------------------------------------------- buttonArea = QHBoxLayout() mainLayout.addLayout(buttonArea) buttonArea.addStretch() okBtn = QPushButton("OK") buttonArea.addWidget(okBtn) okBtn.clicked.connect(self.accept) cancelBtn = QPushButton("Cancel") buttonArea.addWidget(cancelBtn) cancelBtn.clicked.connect(self.reject) def getInputText(self): return self._inputWidget.text() dialog = MyDialog() response = dialog.exec_() if response == QDialog.Accepted: print(dialog.getInputText())
今回のスクリプト
今回のサンプルツールはQtDesignerを使わず全てスクラッチからウィジェットを配置しました。
なので、ダウンロードしていただいたスクリプトをMayaのScriptEditorに貼り付けるだけで実行できます。
ただ、画像のパスだけ、ダウンロードした後のパスを記入しておいてください。21行目のLOGO_IMAGE変数です。
LOGO_IMAGE = r"C:\path\to\image\logo.png"
いろいろパラメータを変えたりすると、UIが変化していくので、楽しいですよ。
おわりに
ふ~疲れた。あ、すいません。つい本音が。。。
いやぁ、長々とここまでお付き合い頂きましてありがとうございました。
今回はたくさんの方にPySideを身近に感じていただきたいと思いまして、PySide2を使ってみたという名目で、できるだけ多くのウィジェットを紹介させていただきました。
それぞれのウィジェットの特徴を知り、作成したい機能に適したものを使うことでユーザビリティーがぐーんと上がります。
しかぁし!この他にもまだまだ紹介しきれなかったウィジェットがたくさんあります!あとは是非みなさんで、調べて、使って、試してみてください。
今後も「このウィジェットをもっと解説して欲しい」などご要望がございましたら、取り上げていきたいと思います。
では、また次回。
※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください。
コメント
コメントフォーム