PySideでintSliderGrpモドキを作る方法!!

takkun
どうも!たっくんです!
最近欲しいものがなかなかポチれません、、、誰かポチ勇気をください!w

Mayaって、単なる数値入力でもスライダーがついていて、地味にスライダーで数値をいじれるのって便利だと思っています。

しかし!

いちいち、QSpinBox作って、QSlider作って、、、、っとやってると、ひじょーに面倒くさいので、カスタムウィジェットというほどではありませんが、有り物をくみあわせて、intSliderGrpモドキを作ってみたいと思います!

intSliderGrpの構造

まずは、intSliderGrpを作ってみると以下のようになります!

よくよく見れば、3つのウィジェットを1つにまとめたような感じになっていますね!コレをPySideに置き換えると、、、

QLabel」「QSpinBox」「QSlider」を3つまとめた感じになります!

ただ、、、PySideで「ラベル」と「ウィジェット」を組み合わせてレイアウトしたい時は、「QFormLayout」が便利なので「ラベル」をなくしたものにしたいと思います!(`・ω・´)ゞ

作成の手順!

  1. 入れ物の用意
  2. QSpinBox」と「QSlider」の作成
  3. QSpinBox」「QSlider」の関連付け
  4. 各種パラメーターの窓口を作成
  5. カスタムのSignalの用意

クラスの用意

まず、複数のウィジェットの入れ物を用意したいので、「QWidget」を継承した「IntSlider」を用意します。

from PySide import QtCore, QtGui

class IntSlider(QtGui.QWidget):
	def __init__(self, *args, **kwargs):
		super(IntSlider, self).__init__(*args, **kwargs)

「QSpinBox」と「QSlider」の作成

続いて、入れ物にレイアウトを作成して、「QSpinBox」と「QSlider」を作成していきます。「QSpinBox」はMayaっぽくするために、ある程度設定を済ませておきます。

from PySide import QtCore, QtGui

class IntSlider(QtGui.QWidget):
	def __init__(self, *args, **kwargs):
		super(IntSlider, self).__init__(*args, **kwargs)
		
		# レイアウトの作成
		layout = QtGui.QHBoxLayout(self)
		
		# まわりに余計な隙間ができてしまうので、マージンをなくす。
		layout.setContentsMargins(0, 0, 0, 0)
		
		# QSpinBoxの作成
		# あとで数値を調べたり、変更したりできるようにインスタンス変数に代入します。
		self.__spinBox = QtGui.QSpinBox(self)
		
		# Mayaっぽくするため、setMinimumWidthを使って横幅が80pxより小さくならないようにします。
		self.__spinBox.setMinimumWidth(80)
		
		# 数値をいじるためのボタンが不要なので、setButtonSymbolsを使ってボタンをなくします。
		self.__spinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		
		# レイアウトにQSpinBoxを追加
		layout.addWidget(self.__spinBox)
		
		# QSliderの作成
		# 水平方向のスライダーが欲しいので、「QtCore.Qt.Horizontal」を指定します。
		# あとで数値を調べたり、変更したりできるようにインスタンス変数に代入します。
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		
		# レイアウトにQSliderを追加
		layout.addWidget(self.__slider)

「QSpinBox」「QSlider」の関連付け

あとで「カスタムのSignal」を作るために、「QSpinBox」と「QSlider」を直接関連付けするのではなく、メソッド「valueChangedCallback」に関連付けするようにしました。

from PySide import QtCore, QtGui

class IntSlider(QtGui.QWidget):
	def __init__(self, *args, **kwargs):
		super(IntSlider, self).__init__(*args, **kwargs)
		
		layout = QtGui.QHBoxLayout(self)
		layout.setContentsMargins(0, 0, 0, 0)
		
		self.__spinBox = QtGui.QSpinBox(self)
		self.__spinBox.setMinimumWidth(80)
		self.__spinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		layout.addWidget(self.__spinBox)
		
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		layout.addWidget(self.__slider)
		
		# 値が変更された時、「valueChangedCallback」を呼び出すように設定する。
		self.__spinBox.valueChanged[int].connect(self.valueChangedCallback)
		self.__slider.valueChanged[int].connect(self.valueChangedCallback)
	
	# 値が変更された時の処理
	# 変更された値が引数で受け取れるようにします。
	def valueChangedCallback(self, value):
	
		# 「sender」を使って、どのウィジェットから呼び出されたか調べる。
		sender = self.sender()
		
		# QSpinBoxが変更された場合
		if sender == self.__spinBox:
			# QSliderのシグナルが発生しないようにブロックする
			self.__slider.blockSignals(True)
			
			# QSliderの値を変更する
			self.__slider.setValue(value)
			
			# QSliderのシグナルが発生するように戻す
			self.__slider.blockSignals(False)
		
		# QSliderが変更された場合
		elif sender == self.__slider:
			# QSpinBoxのシグナルが発生しないようにブロックする
			self.__spinBox.blockSignals(True)
			
			# QSpinBoxの値を変更する
			self.__spinBox.setValue(value)
			
			# QSpinBoxのシグナルが発生するように戻す
			self.__spinBox.blockSignals(False)

sender

PySideには、どのウィジェットから呼び出されたか簡単にわかるように「sender」というものがあります。これのおかげで、スロットの処理を1つのメソッドにしても、処理を分岐することができます。(*´ω`*)

blockSignals

今回は、値を変更したら処理をするようにしていますが、コレはプログラム側で値を変更した時にも発生してしまいます。Signal&Slotは素晴らしい機能なのですが、気をつけないと想定以上にシグナルが発生してしまうことがあります。

こういう時は、blockSignalsを使ってシグナルの発生を強制的に止めてあげると、思った通りに動くようになります(*´ω`*)b

各種パラメーターの窓口を作成

いわゆる?「settter」「getter」を用意していきます。今回は、最低限のモノだけ作成します。必要に応じて増やしていただければっと思います(`・ω・´)ゞ

from PySide import QtCore, QtGui

class IntSlider(QtGui.QWidget):
	def __init__(self, *args, **kwargs):
		super(IntSlider, self).__init__(*args, **kwargs)
		
		layout = QtGui.QHBoxLayout(self)
		layout.setContentsMargins(0, 0, 0, 0)
		
		self.__spinBox = QtGui.QSpinBox(self)
		self.__spinBox.setMinimumWidth(80)
		self.__spinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		layout.addWidget(self.__spinBox)
		
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		layout.addWidget(self.__slider)
		
		self.__spinBox.valueChanged[int].connect(self.valueChangedCallback)
		self.__slider.valueChanged[int].connect(self.valueChangedCallback)
	
	def valueChangedCallback(self, value):	
		sender = self.sender()
		if sender == self.__spinBox:
			self.__slider.blockSignals(True)
			self.__slider.setValue(value)
			self.__slider.blockSignals(False)
		
		elif sender == self.__slider:
			self.__spinBox.blockSignals(True)
			self.__spinBox.setValue(value)
			self.__spinBox.blockSignals(False)
			
	# 値の取得
	def value(self):
		return self.__spinBox.value()
		
	# 値の設定
	def setValue(self, value):
		# QSliderは変更しなくても、シグナルが発生して勝手に変わります。
		self.__spinBox.setValue(value)
		
	# 値を設定できる範囲の設定
	def setRange(self, min, max):
		self.__spinBox.setRange(min, max)
		self.__slider.setRange(min, max)

カスタムのSignalの用意

QSpinBox」「QSlider」が値が変更された時のシグナルがあると便利なように、このウィジェットにも値が変更されたときのシグナルがあると便利に使えると思います。

from PySide import QtCore, QtGui

class IntSlider(QtGui.QWidget):
	
	# シグナルの用意。
	# 変更された値を渡せるように、「QtCore.Signal(int)」として引数があることを通知します。
	valueChanged = QtCore.Signal(int)
	
	def __init__(self, *args, **kwargs):
		super(IntSlider, self).__init__(*args, **kwargs)
		
		layout = QtGui.QHBoxLayout(self)
		layout.setContentsMargins(0, 0, 0, 0)
		
		self.__spinBox = QtGui.QSpinBox(self)
		self.__spinBox.setMinimumWidth(80)
		self.__spinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		layout.addWidget(self.__spinBox)
		
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		layout.addWidget(self.__slider)
		
		self.__spinBox.valueChanged[int].connect(self.valueChangedCallback)
		self.__slider.valueChanged[int].connect(self.valueChangedCallback)
	
	def valueChangedCallback(self, value):	
		sender = self.sender()
		if sender == self.__spinBox:
			self.__slider.blockSignals(True)
			self.__slider.setValue(value)
			self.__slider.blockSignals(False)
		
		elif sender == self.__slider:
			self.__spinBox.blockSignals(True)
			self.__spinBox.setValue(value)
			self.__spinBox.blockSignals(False)
		
		# シグナルをエミットする。
		# スロットが設定されていなければ何も起きません。
		self.valueChanged.emit(value)
			
	def value(self):
		return self.__spinBox.value()
		
	def setValue(self, value):
		self.__spinBox.setValue(value)
		
	def setRange(self, min, max):
		self.__spinBox.setRange(min, max)
		self.__slider.setRange(min, max)

以上、intSliderGrpモドキの完成です!(`・ω・´)ゞ