前回閉じたときと同じ場所にウインドウを開きたいと思うことがしばしばある。そういうときに使うためのクラス。
ウインドウ位置の取得、復元、レジストリへの書き込み、レジストリからの読み込みなどに対応する。
■ウインドウの位置を保存したいときは 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;
}
};
プロジェクトファイルをダウンロード