ウインドウの位置情報を保存/復元するクラス

前回閉じたときと同じ場所にウインドウを開きたいと思うことがしばしばある。そういうときに使うためのクラス。

ウインドウ位置の取得、復元、レジストリへの書き込み、レジストリからの読み込みなどに対応する。


■ウインドウの位置を保存したいときは WM_DESTROY などが呼ばれたときに以下のように呼ぶ。
	CDnpWindowPlacement	cPlacement;
	cPlacement.GetCurrentPos(m_hWnd);
	cPlacement.SaveToReg(HKEY_CURRENT_USER,_T("software\\dinop\\test"),_T("WindowPlacement"));


■ウインドウの位置を復元したいときは CreateWindow によるウインドウ生成処理が終わった後で以下のように呼ぶ。
	CDnpWindowPlacement	cPlacement;
	cPlacement.LoadFromReg(HKEY_CURRENT_USER,_T("software\\dinop\\test"),_T("WindowPlacement"));
	cPlacement.Restore(hWnd);


依存環境:ATL
#pragma	once


#include "atlfile.h"
#include "atlstr.h"


/*!
 * \brief
 * ウインドウ位置取得/復元クラス
 * 
 * アプリケーション終了時の位置を保存しておく用途を想定
 * 
 * \remarks
 * APIのGetWindowPlacecement/SetWindowPlacecementにより
 *ウインドウ位置の取得と復元を行う
 * 
 * \attention
 * GetMonitorInfoを利用しているためWindows 2000以上必須
 * 
 */
class	CDnpWindowPlacement
{
public:
	WINDOWPLACEMENT	_sWindowPlacement;		//!< ウインドウの位置情報


	CDnpWindowPlacement()
	{
		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
		//IsValid()で_sWindowPlacement.lengthを利用しているので
		//ここでそのメンバを初期化しない
	}




	/*!
	 * \brief
	 * このクラスに正常な情報が入っているかどうかのチェック
	 * 
	 * \retval	true	保持しているWINDOWPLACEMENT構造体に正しい情報が入っている
	 * \retval	false	正しい情報を保持していない
	 * 
	 * \todo
	 * もう少しチェックした方がいい?
	 */
	bool	IsValid(void)	const
	{
		if(_sWindowPlacement.length != sizeof(WINDOWPLACEMENT))
			return	false;

		return	true;
	}



	/*!
	 * \brief
	 * 指定したウインドウの位置情報をクラス内に読み込む
	 * 
	 * \param hWnd
	 * 位置情報を読み込みたいウインドウのHWND
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetCurrentPos(HWND hWnd)
	{
		BOOL	ret;

		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
		_sWindowPlacement.length = sizeof(WINDOWPLACEMENT);

		ret = ::GetWindowPlacement(hWnd,&_sWindowPlacement);
		if(ret == FALSE)
		{
			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		return	true;
	}






	/*!
	 * \brief
	 * ウインドウの位置情報を指定されたウインドウに適用
	 * 
	 * \param hWnd
	 * 
	 * \param bMinimizeToRestore
	 * 位置情報保存時に最小化されていた場合の処理指定\n
	 * trueなら最小化されたウインドウは元に戻して表示\n
	 * falseなら最小化されたウインドウは最小化して表示\n
	 * 
	 * \param bNotModifyPlace
	 * trueならウインドウ位置は保存されていた情報通りに復元\n
	 * falseのときはウインドウ位置が変な場合は適宜変更する\n
	 * 
	 * \param prectDefault
	 * bNotModifyPlace==trueのときは無視される。\n
	 * prectDefaultが設定されていないときで、かつ、、、\n
	 * 復元したときにタイトルバーが一部しか見えない場合は、タイトルバーが見えるように上下移動\n
	 * 復元したときにウインドウが全然見えない場合は、占有モニターの左上に移動する\n
	 * prectDefaultが設定されている場合はprectDefaultに復元箇所を変更する
	 * 
	 * \attention
	 * OnCreate(WM_CREATE処理)内では利用できない!
	 * 起動時に処理したい場合はCreateWindowを呼んだ後もしくは
	 * CreateWindowを派生するか、何度も実行しないようにフラグ
	 * 変数を用意した上で何かのメッセージ処理時に利用する
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	Restore(HWND hWnd,bool bMinimizeToRestore=true,bool bNotModifyPlace=false,const RECT* prectDefault=NULL)	const
	{
		BOOL			ret;
		WINDOWPLACEMENT	sWorkWP;			//作業用のWINDOWPLACEMENT

		if(IsValid() == false)
			return	false;

		sWorkWP = _sWindowPlacement;

		if(bNotModifyPlace == false)
		{
			HMONITOR	hMonitor;
			MONITORINFO	sMonitorInfo;

			hMonitor = ::MonitorFromRect(&sWorkWP.rcNormalPosition,MONITOR_DEFAULTTONEAREST);
			if(hMonitor)
			{
				bool	bModify;
				LONG	cx;
				LONG	cy;
				RECT	rect;

				bModify = false;

				::ZeroMemory(&sMonitorInfo,sizeof(MONITORINFO));
				sMonitorInfo.cbSize = sizeof(MONITORINFO);

				ret = ::GetMonitorInfo(hMonitor,&sMonitorInfo);
				if(ret)
				{
					//ウインドウの上部10ピクセル(タイトルバー全体とは言わないま
					//でも、その一部)が見えるかどうかのチェック
					if(bModify == false)
					{
						RECT	rectUp;

						rectUp = sWorkWP.rcNormalPosition;
						rectUp.bottom = (rectUp.top + 10 < rectUp.bottom) ? rectUp.top + 10 : rectUp.bottom;

						ret = IntersectRect(&rect,&rectUp,&sMonitorInfo.rcMonitor);
						if(ret == FALSE)
						{
							//復元場所はタイトルバーが表示されない位置にある

							//サイズは変更せずにウインドウ上部が見える位置に上下移動
							cy = sWorkWP.rcNormalPosition.bottom - sWorkWP.rcNormalPosition.top;
							sWorkWP.rcNormalPosition.top		= sMonitorInfo.rcMonitor.top;
							sWorkWP.rcNormalPosition.bottom	= sWorkWP.rcNormalPosition.top + cy;
							bModify = true;
						}
					}

					//画面全体のRECTとウインドウのRECTの重複部分領域を調べ
					//きちんと表示されるかをチェック
					if(bModify == false)
					{
						ret = IntersectRect(&rect,&sWorkWP.rcNormalPosition,&sMonitorInfo.rcMonitor);

						if(ret == FALSE)
						{
							//復元場所はウインドウがまったく見えない位置にある

							//サイズは変更せずにモニターの左上に移動
							cx = sWorkWP.rcNormalPosition.right - sWorkWP.rcNormalPosition.left;
							cy = sWorkWP.rcNormalPosition.bottom - sWorkWP.rcNormalPosition.top;
							sWorkWP.rcNormalPosition.left		= sMonitorInfo.rcMonitor.left;
							sWorkWP.rcNormalPosition.top		= sMonitorInfo.rcMonitor.top;
							sWorkWP.rcNormalPosition.right		= sWorkWP.rcNormalPosition.left + cx;
							sWorkWP.rcNormalPosition.bottom		= sWorkWP.rcNormalPosition.top + cy;
							bModify = true;
						}
					}

					//ウインドウの移動が必要な場合で、かつ、prectDefaultが設定されていたら
					//デフォルト表示位置をprectDefaultに変える
					if(bModify && prectDefault)
						sWorkWP.rcNormalPosition = *prectDefault;

					//bModify == falseでも表示できる
				}
			}
		}

		if(bMinimizeToRestore)
		{
			//最小化された状態の位置データが保存されていた
			//「最小化」して表示ではなく、「元の状態に戻す」をして表示する

			switch(sWorkWP.showCmd)
			{
			case	SW_MINIMIZE:
			case	SW_SHOWMINIMIZED:
			case	SW_SHOWMINNOACTIVE:
			case	SW_FORCEMINIMIZE:
			   if(sWorkWP.flags & WPF_RESTORETOMAXIMIZED)
					sWorkWP.showCmd = SW_SHOWMAXIMIZED;
			   else
					sWorkWP.showCmd = SW_RESTORE;
			   break;
			}
		}

		ret = ::SetWindowPlacement(hWnd,&sWorkWP);

		return	ret ? true : false;
	}



	/*!
	 * \brief
	 * ファイルに位置情報を保存(テスト用関数)
	 * 
	 * \param pszFile
	 * 保存するファイルパス
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 * \remarks
	 * これはテスト用の関数です。実用性ゼロ。
	 * 
	 */
	bool	SaveFile(LPCTSTR pszFile)	const
	{
		CAtlFile	cFile;
		HRESULT		hr;
		DWORD		dwWritten;

		if(IsValid() == false)
			return	false;

		hr = cFile.Create(pszFile,GENERIC_WRITE,FILE_SHARE_READ,CREATE_ALWAYS);
		if(FAILED(hr))
			return	false;

		dwWritten = 0;
		cFile.Write(&_sWindowPlacement,sizeof(WINDOWPLACEMENT),&dwWritten);

		cFile.Close();

		if(dwWritten != sizeof(WINDOWPLACEMENT))
			return	false;

		return	true;
	}




	/*!
	 * \brief
	 * ファイルから位置情報を読み込む(テスト用関数)
	 * 
	 * \param pszFile
	 * 保存されているファイルパス
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 * \remarks
	 * これはテスト用の関数です。実用性ゼロ。
	 * 読み込んだ位置情報はクラス内に保持され、 CDnpWindowPlacement::Restore() 関数でウインドウを
	 * 復元できます。
	 * 
	 */
	bool	ReadFile(LPCTSTR pszFile)
	{
		CAtlFile	cFile;
		HRESULT		hr;
		DWORD		dwRead;

		WINDOWPLACEMENT	sTmp;

		hr = cFile.Create(pszFile,GENERIC_READ,FILE_SHARE_READ,OPEN_EXISTING);
		if(FAILED(hr))
			return	false;

		dwRead = 0;
		cFile.Read(&sTmp,sizeof(WINDOWPLACEMENT),dwRead);

		cFile.Close();

		if(dwRead != sizeof(WINDOWPLACEMENT))
			return	false;

		if(sTmp.length != sizeof(WINDOWPLACEMENT))
			return	false;

		::memcpy_s(&_sWindowPlacement,sizeof(WINDOWPLACEMENT),&sTmp,sizeof(WINDOWPLACEMENT));

		return	true;
	}



	/*!
	 * \brief
	 * 位置情報のレジストリへの書き込み
	 * 
	 * このクラス内に保持する位置情報を書き込む。事前に GetCurrentPos() を実行して位置情報を取得しておく必要あり。
	 * 
	 * \param[in]	hRootKey
	 * 書き込むレジストリのルート種類。\n
	 * HKEY_CURRENT_USER を推奨
	 * 
	 * \param[in]	pszSubKey
	 * 書き込むレジストリの場所。\n
	 * 「software\\dinop\\test\\」のようにする。
	 * 
	 * \param[in]	pszName
	 * 位置情報を書き込むときのレジストリの名前。たとえば「WindowPlacement」のようにする。
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	SaveToReg(HKEY hRootKey,LPCTSTR pszSubKey,LPCTSTR pszName)
	{
		LONG	ret;
		CRegKey	cReg;

		if(IsValid() == false)
			return	false;

		ret = cReg.Create(hRootKey,pszSubKey);
		if(ret != ERROR_SUCCESS)
			return	false;

		ret = cReg.Open(hRootKey,pszSubKey,KEY_WRITE);
		if(ret != ERROR_SUCCESS)
			return	false;

		ret = cReg.SetBinaryValue(pszName,&_sWindowPlacement,sizeof(WINDOWPLACEMENT));

		cReg.Close();

		if(ret != ERROR_SUCCESS)
			return	false;

		return	true;

	}



	/*!
	 * \brief
	 * 位置情報のレジストリからの読み込み
	 * 
	 * あくまでもこのクラス内に位置情報を読み込むだけで、実際のウインドウの位置は変更しない。
	 * 
	 * \param[in]	hRootKey	SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * \param[in]	pszSubKey	SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * \param[in]	pszName		SaveToReg() で書き込むときに指定したのと同じ値を指定
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	LoadFromReg(HKEY hRootKey,LPCTSTR pszSubKey,LPCTSTR pszName)
	{
		LONG	ret;
		CRegKey	cReg;
		ULONG	nBytes;

		ret = cReg.Open(hRootKey,pszSubKey,KEY_READ);
		if(ret != ERROR_SUCCESS)
			return	false;

		nBytes = sizeof(WINDOWPLACEMENT);
		ret = cReg.QueryBinaryValue(pszName,&_sWindowPlacement,&nBytes);

		cReg.Close();

		if(ret != ERROR_SUCCESS || nBytes != sizeof(WINDOWPLACEMENT) || IsValid() == false)
		{
			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		return	true;

	}







	/*!
	 * \brief
	 * 16進文字列として位置情報を返す
	 * 
	 * \param strData
	 * 取得した位置情報文字列
	 * 
	 * \remarks
	 * WINDOWPLACEMENT構造体をASCIIのHEX文字列でベタに返している
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetFormatedData(CAtlString& strData)	const
	{
		bool	ret;
		TCHAR	pszTmp[sizeof(WINDOWPLACEMENT) * 2 + 10];

		ret = GetFormatedData(pszTmp,sizeof(WINDOWPLACEMENT) * 2 + 10);
		if(ret == false)
			return	false;

		strData = pszTmp;

		return	true;

	}



	/*!
	 * \brief
	 * 16進文字列として位置情報を返す
	 * 
	 * \param pszData
	 * 取得した位置情報文字列を格納するバッファ
	 * 
	 * \param cchLength
	 * 取得した位置情報文字列を格納するバッファの文字数
	 * (sizeof(WINDOWPLACEMENT) * 2 + 1)以上必要
	 * 
	 * \remarks
	 * WINDOWPLACEMENT構造体をASCIIのHEX文字列でベタに返している
	 * バッファーサイズの指定ミスを減らすため CDnpWindowPlacement::GetFormatedData() を利用すべき
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	GetFormatedData(TCHAR* pszData,int cchLength)	const
	{
		int		i;
		bool	ret;
		TCHAR*	pszTmp;
		BYTE*	pcbData;

		if(pszData == NULL)
			return	false;

		*pszData = NULL;
		if(cchLength < sizeof(WINDOWPLACEMENT) * 2 + 1)
			return	false;

		if(IsValid() == false)
			return	false;

		pszTmp = pszData;
		pcbData = (BYTE*)&_sWindowPlacement;
		for(i = 0; i < sizeof(WINDOWPLACEMENT); i++)
		{
			ret = BinToHex(pcbData[i],pszTmp[0],pszTmp[1]);
			pszTmp += 2;
			if(ret)
				continue;

			*pszData = NULL;
			return	false;
		}
		*pszTmp = NULL;

		return	true;
	}




	/*!
	 * \brief
	 * 16進文字列の位置情報をクラス内に読み込む
	 * 
	 * \param pszData
	 * 読み込む位置情報
	 * 
	 * \retval	true	成功
	 * \retval	false	失敗
	 * 
	 */
	bool	SetFormatedData(LPCTSTR pszData)
	{
		int		i;
		bool	ret;
		LPCTSTR	pszTmp;
		BYTE*	pcbData;

		if(pszData == NULL)
			return	false;

		if(::_tcslen(pszData) != sizeof(WINDOWPLACEMENT) * 2)
			return	false;

		pszTmp = pszData;
		pcbData = (BYTE*)&_sWindowPlacement;
		for(i = 0; i < sizeof(WINDOWPLACEMENT); i++)
		{
			ret = HexToBin(pcbData[i],pszTmp[0],pszTmp[1]);
			pszTmp += 2;
			if(ret)
				continue;

			::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));
			return	false;
		}

		if(IsValid())
			return	true;

		::ZeroMemory(&_sWindowPlacement,sizeof(WINDOWPLACEMENT));

		return	false;
	}





private:



	//
	//	ウインドウの重複部分の取得
	//
	//	APIと異なりNormalizeRectされていない場合も失敗しない(内部で正規化しているだけ)
	//
	BOOL	IntersectRect(LPRECT lprcDst,const RECT* lprcSrc1,const RECT* lprcSrc2)	const
	{
		if(lprcDst == NULL || lprcSrc1 == NULL || lprcSrc2 == NULL)
		{
			if(lprcDst)
			{
				lprcDst->left	= 0;
				lprcDst->right	= 0;
				lprcDst->top	= 0;
				lprcDst->bottom	= 0;
			}

			return	FALSE;
		}

		RECT	src1;
		RECT	src2;

		src1 = *lprcSrc1;
		src2 = *lprcSrc2;

		NormalizeRect(&src1);
		NormalizeRect(&src2);

		lprcDst->left	= (src1.left > src2.left) ? src1.left : src2.left;
		lprcDst->right	= (src1.right < src2.right) ? src1.right : src2.right;
		lprcDst->top	= (src1.top > src2.top) ? src1.top : src2.top;
		lprcDst->bottom	= (src1.bottom < src2.bottom) ? src1.bottom : src2.bottom;

		if(lprcDst->left >= lprcDst->right || lprcDst->top >= lprcDst->bottom)
		{
			lprcDst->left	= 0;
			lprcDst->right	= 0;
			lprcDst->top	= 0;
			lprcDst->bottom	= 0;

			return	FALSE;
		}

		return	TRUE;
	}


	//
	//	RECTの正規化
	//
	void	NormalizeRect(LPRECT lprc)	const
	{
		if(lprc == NULL)
			return;

		LONG	tmp;

		if(lprc->left > lprc->right)
		{
			tmp = lprc->left;
			lprc->left = lprc->right;
			lprc->right = tmp;
		}

		if(lprc->top > lprc->bottom)
		{
			tmp = lprc->top;
			lprc->top = lprc->bottom;
			lprc->bottom = tmp;
		}
	}



	//
	//	1バイトのバイナリを16進数文字の2文字にする
	//
	bool	BinToHex(BYTE cb,TCHAR& cUpper,TCHAR& cLower)	const
	{
		BYTE	tmp;

		tmp = cb >> 4;
		if(tmp < 10)
			cUpper = _T('0') + tmp;
		else
			cUpper = _T('A') + tmp - 10;

		tmp = cb & 0x0F;
		if(tmp < 10)
			cLower = _T('0') + tmp;
		else
			cLower = _T('A') + tmp - 10;

		return	true;
	}


	//
	//	16進数文字の2文字を1バイトのバイナリにする
	//
	bool	HexToBin(BYTE& cb,const TCHAR& cUpper,const TCHAR& cLower)	const
	{
		BYTE	tmp;

		if(cUpper >= _T('0') && cUpper <= _T('9'))
			tmp = (BYTE)cUpper - _T('0');
		else if(cUpper >= _T('A') && cUpper <= _T('F'))
			tmp = (BYTE)cUpper - _T('A') + 10;
		else if(cUpper >= _T('a') && cUpper <= _T('f'))
			tmp = (BYTE)cUpper - _T('a') + 10;
		else
			return	false;
		cb = tmp << 4;

		if(cLower >= _T('0') && cLower <= _T('9'))
			tmp = (BYTE)cLower - _T('0');
		else if(cLower >= _T('A') && cLower <= _T('F'))
			tmp = (BYTE)cLower - _T('A') + 10;
		else if(cLower >= _T('a') && cLower <= _T('f'))
			tmp = (BYTE)cLower - _T('a') + 10;
		else
			return	false;
		cb += tmp;

		return	true;
	}
};
プロジェクトファイルをダウンロード

カテゴリー「ウインドウ操作」 のエントリー