ウインドウの位置や大きさを保存する!!

ツールのオプションを表示して、あれこれ設定して、実行して、、、また表示したら、、、

アレ設定残らないの?
(;´∀`)

なんて経験ございませんか?地味なところだと、毎回ウインドウが邪魔な位置に表示される、、、とかもあると思います。

PySide(Qt)で作ったウインドウに、色々なパラメータを設定するWidgetをおいていると思いますが、ただ作っただけでは、使用者にとってはいまいちなんですよね(´;ω;`)

そこで、今回は、ウインドウの位置、大きさ、状態、各種Widgetに入力された値って勝手に保存されて、次回以降復元される、、、ってところをご紹介したいと思います!(`・ω・´)ゞ

保存するには?

ウインドウを閉じた時に、色々処理を実行するには「closeEvent」をオーバーライドするのが便利かと思います。「closeEvent」は、「QWidget」から派生しているもので使うことができます(*´ω`*)b

クラスの骨格

まずは、元となるクラスの骨格を見てみたいと思います!

import os
from PySide import QtGui
from PySide import QtCore
from maya import OpenMayaUI
import shiboken

def mainWindow():
	ptr = OpenMayaUI.MQtUtil.mainWindow()
	return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
	
class MainWindow(QtGui.QMainWindow):
	def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
		if parent is None:
			parent = mainWindow()
			
		super(MainWindow, self).__init__(parent, flags)
		
	def restore(self):
		pass
	
	def show(self):
		super(MainWindow, self).show()
		
	def closeEvent(self, event):
		super(MainWindow, self).closeEvent(event)

まず、ウインドウの大きさと位置の保存と言うものは、使用頻度が高い内容かと思います。せっかくですのでこれから作るクラス「MainWindow」を継承して使われることを前提に見ていきたいと思います!

QMainWindowの引数で、「parent」が設定されていない場合、Mayaではいい感じになりませんので、Mayaの子になるような処理を入れました。コレについては「新!Mayaウインドウの親子関係MayaQWidgetBaseMixin!」でもご紹介いたしましたので、合わせてご覧いただければ幸いです。

「restore」「show」「closeEvent」については、実装しながら解説したいと思います!(`・ω・´)ゞ

データを出し入れする入れ物

PySide(Qt)には、データを保存したり、読み込んだりする便利な「QSettings」がありますので、コチラを使った例でご紹介したいと思います。JSONやXMLなどといった汎用フォーマットを使用したい場合は、オリジナルのロジックに変更していただければOKです!

Windowsでの「QSettings」は、「レジストリー」という特殊な領域に保存する方法と、INIという汎用フォーマットで保存する方法の2種類があります。レジストリーにガンガンデータを保存するのは、気持ち悪いっという人が多そうなので、INIを使ってみたいと思います。

import os
from PySide import QtGui
from PySide import QtCore
from maya import OpenMayaUI
import shiboken

def mainWindow():
	ptr = OpenMayaUI.MQtUtil.mainWindow()
	return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
	
class MainWindow(QtGui.QMainWindow):
	settingFileName = 'mainWindow.ini'# ※1
	
	def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
		if parent is None:
			parent = mainWindow()
			
		super(MainWindow, self).__init__(parent, flags)
		
		filename = os.path.join(
					os.getenv('MAYA_APP_DIR'),
					'myTool',
					self.settingFileName
					)# ※1
		
		self.__settings = QtGui.QSettings(filename, QtGui.QSettings.IniFormat)
		self.__settings.setIniCodec('utf-8')
		
	def restore(self):
		pass
	
	def show(self):
		super(MainWindow, self).show()
		
	def closeEvent(self, event):
		super(MainWindow, self).closeEvent(event)
ファイルの保存先には、運用ルールが必要そうです。。。ここでは、「マイドキュメント/maya」にフォルダ「myTool」を作り、設定ファイルが保存されていくようにしたいと思います。ファイル名はクラス変数にして、継承したクラスで変更できるようにします。(※1)

QSettingsは、ファイル名と、フォーマットを指定してインスタンスを作成します。「setIniCodec」を使って文字コードを「utf-8」を指定すると、日本語があった場合にいい感じになります!

ウインドウの位置と大きさの保存

では、早速「ウインドウの位置と大きさ」を保存してみましょう!

import os
from PySide import QtGui
from PySide import QtCore
from maya import OpenMayaUI
import shiboken

def mainWindow():
	ptr = OpenMayaUI.MQtUtil.mainWindow()
	return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
	
class MainWindow(QtGui.QMainWindow):
	settingFileName = 'mainWindow.ini'
	
	def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
		if parent is None:
			parent = mainWindow()
			
		super(MainWindow, self).__init__(parent, flags)
		
		filename = os.path.join(
					os.getenv('MAYA_APP_DIR'),
					'myTool',
					self.settingFileName
					)
		
		self.__settings = QtGui.QSettings(filename, QtGui.QSettings.IniFormat)
		self.__settings.setIniCodec('utf-8')
		
	def restore(self):
		pass
	
	def show(self):
		super(MainWindow, self).show()
		
	def closeEvent(self, event):
		self.__settings.setValue('geometry', self.saveGeometry())
		super(MainWindow, self).closeEvent(event)

QSettingsを使って、データを保存するには「setValue」を使い、「名前と値」を指定することで保存できます。ウインドウの位置と大きさっと聞くと、「pos」と「size」かなー?っと思った方もいるかもしれませんが、「saveGeometry」を使うと保存に適したデータを一発で取得することができます!

データを保存する時に指定した名前は、読み込みの時にも使います!

保存されたデータを見てみると、こんな感じで保存されます!

[General]
geometry=@ByteArray(\x1ÙÐË\0\x1\0\0\0\0\0±\0\0\x1'\0\0\x2\x1a\0\0\x2\x3\0\0\0¹\0\0\x1\x46\0\0\x2\x12\0\0\x1û\0\0\0\0\0\0)

読み込むには?

保存と違い、読み込みは、ちょっとややっこしいです。

継承することを前提に考えると、「__init__」で復元するのは好ましくありません。継承したクラスAでも「__init__」を定義すると思います。そしてすぐに「super」を使って基底クラスの「__init__」を実行することになると思います。

っということは!

  1. クラスAで「__init__」の定義
  2. クラスAの「super」が実行
  3. 基底クラスの「__init__」が実行
  4. ウインドウの位置と大きさを復元
  5. クラスAの「__init__」の処理
    もし、サイズの指定や位置の指定があると、復元した意味がなくなる

っとなるわけです(;´∀`)

これを踏まえて、実装していきましょう!

import os
from PySide import QtGui
from PySide import QtCore
from maya import OpenMayaUI
import shiboken

def mainWindow():
	ptr = OpenMayaUI.MQtUtil.mainWindow()
	return shiboken.wrapInstance(long(ptr), QtGui.QWidget)
	
class MainWindow(QtGui.QMainWindow):
	settingFileName = 'mainWindow.ini'
	
	def __init__(self, parent=None, flags=QtCore.Qt.WindowFlags()):
		if parent is None:
			parent = mainWindow()
			
		super(MainWindow, self).__init__(parent, flags)
		
		filename = os.path.join(
					os.getenv('MAYA_APP_DIR'),
					'myTool',
					self.settingFileName
					)
		
		self.__settings = QtGui.QSettings(filename, QtGui.QSettings.IniFormat)
		self.__settings.setIniCodec('utf-8')
		
	def restore(self):
		self.restoreGeometry(self.__settings.value('geometry'))
	
	def show(self):
		self.restore()
		super(MainWindow, self).show()
		
	def closeEvent(self, event):
		self.__settings.setValue('geometry', self.saveGeometry())
		super(MainWindow, self).closeEvent(event)

まずはメソッド「restore」から見ていきたいと思います。このメソッドは、設定ファイルからデータを読み込み復元する、オリジナルのメソッドです。

QSettingsで保存したデータを読み込むには「value」に保存した時の名前を指定すると取り出すことができます。取り出したデータは「restoreGeometry」を使うと前回の位置や大きさに戻すことが簡単にできます。「saveGeometry」と「restoreGeometry」がセットになっているわけですね!(*´ω`*)v

このままでは、メソッド「restore」が実行されないので、元に戻すことができません。継承されたクラスで「restore」を実行してもらう手もありますが、メンドイ!!

そこで、メソッド「show」をオーバーライドして、表示する前に設定を復元してから表示する!っという流れにしました。

継承して使うには?

継承して使う場合は、ファイル名が重複しないように、オーバーライドを忘れないようにしましょう!

class MyToolWindow(MainWindow):
	settingFileName = 'MyToolWindow.ini'
	
	def __init__(self, *args, **kwargs):
		super(MyToolWindow, self).__init__(*args, **kwargs)