PyQt および PySide ウィジェットのベスト プラクティス

ウィジェットへのリファレンスの維持

PyQt または PySide を使用して Maya のユーザ インタフェースをカスタマイズする場合は、Maya のメイン ウィンドウなど、既存の Maya ウィジェットの下に自分のウィジェットをペアレント化する必要があります。ウィジェットがペアレント化されていないと、ウィジェットへのリファレンスが維持されていない場合、Python インタプリタのガベージ コレクターによってウィジェットが破棄される場合があります。

このベスト プラクティスのコード例を次に示します。このコードは、PySide ではなく PyQt モジュールを読み込む方法でも機能します。

from maya import OpenMayaUI as omui 

try:
  from PySide2.QtCore import * 
  from PySide2.QtGui import * 
  from PySide2.QtWidgets import *
  from PySide2 import __version__
  from shiboken2 import wrapInstance 
except ImportError:
  from PySide.QtCore import * 
  from PySide.QtGui import * 
  from PySide import __version__
  from shiboken import wrapInstance 

mayaMainWindowPtr = omui.MQtUtil.mainWindow() 
mayaMainWindow= wrapInstance(long(mayaMainWindowPtr), QWidget) 

# WORKS: Widget is fine 
hello = QLabel("Hello, World", parent=mayaMainWindow) 
hello.setObjectName('MyLabel') 
hello.setWindowFlags(Qt.Window) # Make this widget a standalone window even though it is parented 
hello.show() 
hello = None # the "hello" widget is parented, so it will not be destroyed. 

# BROKEN: Widget is destroyed 
hello = QLabel("Hello, World", parent=None) 
hello.setObjectName('MyLabel') 
hello.show() 
hello = None # the "hello" widget is not parented, so it will be destroyed.

後述の PySide ミックスイン クラスを使用している場合、このペアレント化は自動的に処理されます。

maya.app.general.mayaMixin クラスの使用

maya.app.general.mayaMixin モジュールには、PySide ベースのウィジェットの Maya UI への統合を簡略化するためのクラスが含まれています。

MayaQWidgetBaseMixin クラスは初期化中に Maya Qt ウィジェットの一般的なアクションを行います。たとえば、ウィジェットを自動的に命名し、文字列として maya.OpenMayaUI.MQtUtil.findControl() からルックアップできるようにします。また、親が明示的に指定されていない場合にウィジェットをメイン Maya ウィンドウ下でペアレント化し、インスタンス変数が範囲から逸したときにウインドウが消えないようにします(前述の「ウィジェットへのリファレンスの維持」を参照してください)。

このクラスを使用するには、このクラスがウィジェットの親クラス リストにおいてウィジェットの派生元 Qt クラスの前に表示されていることを確認してください。

これらのクラスによって提供されるメソッドおよびプロパティの詳細については、Maya スクリプト エディタ(Maya Script Editor)Python タブにある Python のヘルプ機能を参照してください(たとえば、help(MayaQWidgetDockableMixin))。

from maya.app.general.mayaMixin import MayaQWidgetBaseMixin

try:
  from PySide2.QtCore import * 
  from PySide2.QtGui import * 
  from PySide2.QtWidgets import *
  from PySide2 import __version__
  from shiboken2 import wrapInstance 
except ImportError:
  from PySide.QtCore import * 
  from PySide.QtGui import * 
  from PySide import __version__
  from shiboken import wrapInstance 

class MyButton(MayaQWidgetBaseMixin, QPushButton):
    def __init__(self, parent=None):
        super(MyButton, self).__init__(parent=parent)
        self.setText('Push Me')

# Create an instance of the button and display it.
#
button = MyButton()
button.show()

# A valid Maya control name has been automatically assigned
# to the button.
#
buttonName = button.objectName()
print('# ' + buttonName)
# MyButton_368fe1d8-5bc3-4942-a1bf-597d1b5d3b83

# Losing our only reference to the button does not cause it to be
# destroyed.
#
myButton = None

# We can use the button's name to find it as a Maya control.
#
from maya.OpenMayaUI import MQtUtil
from shiboken2 import wrapInstance

ptr = MQtUtil.findControl(buttonName)
foundControl = wrapInstance(long(ptr), QPushButton)

# Print out the button's text.
#
print('# ' + foundControl.text())
# Push Me

MayaQWidgetDockableMixin クラスは、Maya のドッキング可能なアクションのサポートを提供します。ウィジェットのドッキング動作は show() メソッドに渡すパラメータで制御します。たとえば、ウィジェットがドッキング可能かどうかを指定する dockable、既定のドック領域を指定する area などがあります。

このクラスを使用するには、このクラスがウィジェットの親クラス リストにおいてウィジェットの派生元 Qt クラスの前に表示されていることを確認してください。

from maya.app.general.mayaMixin import MayaQWidgetDockableMixin

try:
  from PySide2.QtCore import * 
  from PySide2.QtGui import * 
  from PySide2.QtWidgets import *
  from PySide2 import __version__
  from shiboken2 import wrapInstance 
except ImportError:
  from PySide.QtCore import * 
  from PySide.QtGui import * 
  from PySide import __version__
  from shiboken import wrapInstance 

class MyDockableButton(MayaQWidgetDockableMixin, QPushButton):
    def __init__(self, parent=None):
        super(MyDockableButton, self).__init__(parent=parent)
        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred )
        self.setText('Push Me')

# Show the button as a non-dockable floating window.
#
button = MyDockableButton()
button.show(dockable=False)

# showRepr() can be used to display the current dockable settings.
#
print('# ' + button.showRepr())
# show(dockable=False, height=23, width=70, y=610, x=197, floating=True)

# Change it to a dockable floating window.
#
button.show(dockable=True)
print('# ' + button.showRepr())
# show(dockable=True, area='none', height=23, width=70, y=610, x=197, floating=True)