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

takkun
どうも!たっくんです。
そろそろAmazonプライムに加入しようか迷ってます。。。
みなさんは加入されていますか?

今回は、前回やった「intSliderGrpモドキ」をベースに「floatSliderGrpモドキ」を作っていきたいと思います!まだ、みてないーって方は、昨日の記事も見てから読む事をオススメします!(`・ω・´)ゞ

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

2017.08.23

簡単にできない理由

intSliderGrpモドキ」をベースに作成しますが、、、

QSpinBox」を「QDoubleSpinBox」に変えればいいんじゃない?

と思った方もいると思いますが、、、

スライダーを動かしても整数の部分しか変化しなくて、細かい調整ができない!!「QSlider」は「intでしか操作できない」ので、そのままじゃダメなんですね(´・ω・`)

今回は、「QSlider」とのやり取りにひと工夫して、「QDoubleSpinBox」と関連付けて行きたいと思います!!

作成の手順!

  1. クラスの骨格
  2. QSlider」とやりとりする考え方
  3. QDoubleSpinBox」の設定から「QSlider」の範囲を割り出す
  4. valueChangedCallback」の修正
  5. __updateSliderRange」を必要なところに追加する

クラスの骨格

まずは、クラスの骨格を用意します!「QSpinBox」を「QDoubleSpinBox」に変えるだけで、前回とかぶる部分は飛ばします!

from PySide import QtCore, QtGui

class DoubleSlider(QtGui.QWidget):
	#floatに変更
	valueChanged = QtCore.Signal(float)
	
	def __init__(self, *args, **kwargs):
		super(DoubleSlider, self).__init__(*args, **kwargs)
		
		layout = QtGui.QHBoxLayout(self)
		layout.setContentsMargins(0, 0, 0, 0)
		
		# QDoubleSpinBoxに変更
		self.__doubleSpinBox = QtGui.QDoubleSpinBox(self)
		self.__doubleSpinBox.setMinimumWidth(80)
		self.__doubleSpinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		layout.addWidget(self.__doubleSpinBox)
		
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		layout.addWidget(self.__slider)

		# floatに変更
		self.__doubleSpinBox.valueChanged[float].connect(self.valueChangedCallback)
		self.__slider.valueChanged[int].connect(self.valueChangedCallback)
		
	def valueChangedCallback(self, value):	
		sender = self.sender()
		if sender == self.__doubleSpinBox:
			self.__slider.blockSignals(True)
			self.__slider.setValue(value)
			self.__slider.blockSignals(False)
		
		elif sender == self.__slider:
			self.__doubleSpinBox.blockSignals(True)
			self.__doubleSpinBox.setValue(value)
			self.__doubleSpinBox.blockSignals(False)
		
		self.valueChanged.emit(value)
			
	def value(self):
		return self.__doubleSpinBox.value()
		
	def setValue(self, value):
		self.__doubleSpinBox.setValue(value)
		
	def setRange(self, min, max):
		self.__doubleSpinBox.setRange(min, max)
		self.__slider.setRange(min, max)

	# 新規追加
	def setDecimals(self, prec):
		self.__doubleSpinBox.setDecimals(prec)

QDoubleSpinBox」の、少数の桁を設定できるように「setDecimals」を追加で用意しました。

「QSlider」とやりとりする考え方

QDoubleSpinBox」は、デフォルトの場合「0.00~99.99」まで数値が入力できるようになっていますが、「QSlider」で細かく設定できるようにするには「QSlider」の範囲を「100倍」して「0~9999」に設定してあげます!

でも、このまま「QDoubleSpinBox」に設定したらおかしくなるので、「100」で割り算して「0.00~99.99」の範囲に変更してから設定してあげます!

そう!QDoubleSpinBoxに設定された「少数の桁数(decimals)」を打ち消すように掛け算してあげればいいんですね(*´ω`*)

少数の桁数が1なら10倍、2なら100倍、3なら1000倍という具合です!

「QDoubleSpinBox」の設定から「QSlider」の範囲を割り出す

QDoubleSpinBox」の設定から「QSlider」の範囲を割り出すために、メソッド「__updateSliderRange」を追加します。

from PySide import QtCore, QtGui

class DoubleSlider(QtGui.QWidget):
	valueChanged = QtCore.Signal(float)
	
	def __init__(self, *args, **kwargs):
		~中略~
		
	def valueChangedCallback(self, value):	
		~中略~
			
	def value(self):
		return self.__doubleSpinBox.value()
		
	def setValue(self, value):
		self.__doubleSpinBox.setValue(value)
		
	def setRange(self, min, max):
		~中略~
		
	def setDecimals(self, prec):
		self.__doubleSpinBox.setDecimals(prec)
	
	# 「QDoubleSpinBox」の設定から「QSlider」の範囲を割り出す
	def __updateSliderRange(self):
		# 少数の桁数を取得する
		decimals = self.__doubleSpinBox.decimals()
		
		# 設定できる最小値を取得する
		minimum  = self.__doubleSpinBox.minimum()
		
		# 設定できる最大値を取得する
		maximum  = self.__doubleSpinBox.maximum()
		
		# 少数の桁数を使って、倍率を文字列で求めてintに変換する。
		# この値は、あとで使うので、インスタンス変数に代入します。
		#
		# <decimalsが2の場合>
		# int('1'+('0'*2))は、int('1'+'00')→int('100')となります。
		# 
		# <decimalsが0の場合>
		# int('1'+('0'*0))は、int('1'+'')→int('1')となります。
		#
		self.__boost = int('1'+('0'*decimals))
		
		# QDoubleSpinBoxの範囲に、少数を打ち消す倍率を掛け算して設定する。
		#
		# QDoubleSpinBoxの範囲が「0.00~99.99」の場合、
		# QSliderの範囲は「0~9999」になる
		#
		self.__slider.setRange(minimum*self.__boost, maximum*self.__boost)

文字列に対して掛け算をするっというのは、Pythonならでは?という感じがしますが結構便利ですね!

同じ文字を繰り返したい時は、掛け算してしまえば文字が増殖してくれます(*´ω`*)b

「valueChangedCallback」の修正

少数の桁を打ち消す倍率を保存したインスタンス変数「self.__boost」を使って、相互の値のやり取りを修正します。

from PySide import QtCore, QtGui

class DoubleSlider(QtGui.QWidget):
	valueChanged = QtCore.Signal(float)
	
	def __init__(self, *args, **kwargs):
		~中略~
		
	def valueChangedCallback(self, value):	
		sender = self.sender()
		if sender == self.__doubleSpinBox:
			self.__slider.blockSignals(True)
			
			# 少数を打ち消す倍率を適用して、QSliderに値を設定する
			self.__slider.setValue(value*self.__boost)
			
			self.__slider.blockSignals(False)
		
		elif sender == self.__slider:
			# 少数を打ち消す倍率を適用して、変数の値を上書きする
			# 値を上書きするのは、カスタムのSignalをエミットする必要があるからです。
			value = float(value)/self.__boost
			
			self.__doubleSpinBox.blockSignals(True)
			self.__doubleSpinBox.setValue(value)
			self.__doubleSpinBox.blockSignals(False)
		
		self.valueChanged.emit(value)
			
	def value(self):
		return self.__doubleSpinBox.value()
		
	def setValue(self, value):
		self.__doubleSpinBox.setValue(value)
		
	def setRange(self, min, max):
		~中略~
		
	def setDecimals(self, prec):
		~中略~
	
	def __updateSliderRange(self):
		~中略~

「__updateSliderRange」を必要なところに追加する

最後に、「QDobuleSpinBox」の設定を後から変えられるようにしているので、各所にスライダーを更新するようにメソッド「__updateSliderRange」の呼び出しを追加します。

from PySide import QtCore, QtGui

class DoubleSlider(QtGui.QWidget):
	valueChanged = QtCore.Signal(float)
	
	def __init__(self, *args, **kwargs):
		super(DoubleSlider, self).__init__(*args, **kwargs)
		
		layout = QtGui.QHBoxLayout(self)
		layout.setContentsMargins(0, 0, 0, 0)
		
		self.__doubleSpinBox = QtGui.QDoubleSpinBox(self)
		self.__doubleSpinBox.setMinimumWidth(80)
		self.__doubleSpinBox.setButtonSymbols(QtGui.QAbstractSpinBox.NoButtons)
		layout.addWidget(self.__doubleSpinBox)
		
		self.__slider = QtGui.QSlider(QtCore.Qt.Horizontal, self)
		self.__updateSliderRange()# QDoubleSpinBoxに合わせて、スライダーを初期化する
		layout.addWidget(self.__slider)
		
		self.__doubleSpinBox.valueChanged[float].connect(self.valueChangedCallback)
		self.__slider.valueChanged[int].connect(self.valueChangedCallback)
		
	def valueChangedCallback(self, value):	
		sender = self.sender()
		if sender == self.__doubleSpinBox:
			self.__slider.blockSignals(True)
			self.__slider.setValue(value*self.__boost)
			self.__slider.blockSignals(False)
		
		elif sender == self.__slider:
			value = float(value)/self.__boost
			self.__doubleSpinBox.blockSignals(True)
			self.__doubleSpinBox.setValue(value)
			self.__doubleSpinBox.blockSignals(False)
		
		self.valueChanged.emit(value)
			
	def value(self):
		return self.__doubleSpinBox.value()
		
	def setValue(self, value):
		self.__doubleSpinBox.setValue(value)
		
	def setRange(self, min, max):
		self.__doubleSpinBox.setRange(min, max)
		self.__updateSliderRange()# 範囲が変更されたので、スライダーも更新する
		
	def setDecimals(self, prec):
		self.__doubleSpinBox.setDecimals(prec)
		self.__updateSliderRange()# 桁数が変更されたので、スライダーも更新する
		
	def __updateSliderRange(self):
		decimals = self.__doubleSpinBox.decimals()
		minimum  = self.__doubleSpinBox.minimum()
		maximum  = self.__doubleSpinBox.maximum()
		self.__boost = int('1'+('0'*decimals))
		self.__slider.setRange(minimum*self.__boost, maximum*self.__boost)

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