CAD日記

主にAutoCADのことについて書いているけど、近頃は投資系ネタに注力している。自動売買、仮想通貨、PC関係、プログラミングなど。@caddiary

ソフト開発

最小化したウインドウの位置を保存して復元するとまずい話(C++編)

投稿日:

ウインドウを最小化した状態としてタスクバーから右クリックして閉じるってやると、次回ウインドウを起動したときに画面外に出てしまって、操作不能になるアプリがある。そんなときは、タスクバーの上にカーソルを置いて、その上に出てくるプレビューウインドウの上で右クリックして出たメニューから「移動」として、上下矢印キーのうちのどれかを押して、その後マウスを動かせばマウスにウインドウがくっついてきて画面内でクリックすると正常化することは知っていた。

そんな利用者側の小技の話ではなく、そもそもプログラムがバグってるんじゃねぇのという厳しい言葉にこたえるべく、あれこれと解説してみようじゃないか。

以下は、ウインドウの位置と最大化の状態をレジストリに保存する処理。この部分には何ら問題はなくて、問題はこのRECT情報をどうやって取得&設定しているのかということである。

static void _WritePosition(CRect* pRect, bool maximized)
{
	CPosKeepApp* pApp = (CPosKeepApp*)AfxGetApp();
	pApp->WriteProfileInt(_T("DlgPos"), _T("cx"), pRect->left);
	pApp->WriteProfileInt(_T("DlgPos"), _T("cy"), pRect->top);
	pApp->WriteProfileInt(_T("DlgPos"), _T("Width"), pRect->Width());
	pApp->WriteProfileInt(_T("DlgPos"), _T("height"), pRect->Height());
	pApp->WriteProfileInt(_T("DlgPos"), _T("maximized"), maximized);
}

static void _ReadPosition(CRect* pRect, bool& maximized)
{
	CPosKeepApp* pApp = (CPosKeepApp*)AfxGetApp();
	pRect->left = pApp->GetProfileInt(_T("DlgPos"), _T("cx"), 0);
	pRect->top = pApp->GetProfileInt(_T("DlgPos"), _T("cy"), 0);
	pRect->right = pApp->GetProfileInt(_T("DlgPos"), _T("Width"), 0) + pRect->left;
	pRect->bottom = pApp->GetProfileInt(_T("DlgPos"), _T("height"), 0) + pRect->top;
	maximized = pApp->GetProfileInt(_T("DlgPos"), _T("maximized"), 0);
}

OnDestroyをオーバーライドして以下のようにGetWindowRectして、アプリ終了時のウインドウのレクトを取得しているならヤバい。最小化されている場合は、ウインドウ座標にマイナス値が入っているので復元時に画面外に飛んでしまう。

void CPosKeepDlg::OnDestroy()
{
	CRect rect;
	GetWindowRect(&rect);
	_WritePosition(&rect, IsZoomed() );

	CDialogEx::OnDestroy();
}

ちなみにアプリ起動時の読み込み処理はこんな感じ。

BOOL CPosKeepDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();
	...

	CRect rect;
	bool maximized = 0;
	_ReadPosition(&rect, maximized);
	MoveWindow(rect.left, rect.top, rect.Width(), rect.Height());
	if(maximized)
		ShowWindow(SW_MAXIMIZE);

	return TRUE;
}

【初級編】
手っ取り早く対処するなら以下のようにOnCloseをオーバーライドして、2行足せばよい。最小化されている状態を判定した上で、ウインドウを元に戻すわけで、その際画面上の無駄な動きをなくすためにSW_HIDEモードにもしている。最小化問題はこれでクリア。

void CPosKeepDlg::OnClose()
{
	if (IsIconic())  // 最小化されてたら、元に戻す(非表示にするのは画面上の無駄な動きをなくすため)
		ShowWindow(SW_HIDE|SW_RESTORE);

	CDialogEx::OnClose();
}

【中級編】
最大化して閉じた場合の問題もあって、最大化から元に戻した時のウインドウサイズが最大化状態に近い(8ドットずれあり)が、本来なら最大化した前の状態に戻ってほしいのですよ。ということで、アプリ終了時の処理を以下のように変更。GetWindowRectがGetWindowPlacementに変わっている。その時点のレクト情報を保存するんじゃなくて、通常状態(最小化でも最大化でもない)のレクトを保存しているのが肝要。なお、初級編でやったことは不要になる。

void CPosKeepDlg::OnDestroy()
{
	// ウィンドウ位置・状態取得
	WINDOWPLACEMENT wndPlace;
	wndPlace.length = sizeof(WINDOWPLACEMENT);
	GetWindowPlacement(&wndPlace);

	// ウィンドウ位置と最大化状態の保存
	CRect rect(wndPlace.rcNormalPosition);
	_WritePosition(&rect, wndPlace.showCmd==SW_SHOWMAXIMIZED);

	CDialogEx::OnDestroy();
}

【上級編】
最小化とか最大化の問題はこの上までの対応で済む中、おまけとしてアプリ起動時の処理も改良。急にややこしくなっているのは、モニタの解像度変更でウインドウが外側にでちゃったときにウインドウ内におさめる処理であり、またマルチモニターにも対応している。ここまでやる必要あんのかってことだけど、プロのプログラマーならやんなきゃいけないのかね。。

BOOL CPosKeepDlg::OnInitDialog()
{
	CDialogEx::OnInitDialog();
	...

	CRect rect;
	bool maximized = 0;
	_ReadPosition(&rect, maximized);

	// 解像度変更によって、ウインドウが画面の外にでてしまった場合の調整)
	// 対象モニタの情報を取得(マルチモニタ対応、どのモニタに表示されているかを判定した上で情報取得)
	HMONITOR hMonitor = MonitorFromRect(&rect, MONITOR_DEFAULTTONEAREST);
	MONITORINFO mi;
	mi.cbSize = sizeof(MONITORINFO);
	GetMonitorInfo(hMonitor, &mi);

	// 位置補正(画面から外れていた場合に画面内に収める)
	if (rect.right > mi.rcMonitor.right)
	{
		rect.left -= rect.right - mi.rcMonitor.right;
		rect.right = mi.rcMonitor.right;
	}
	if (rect.left < mi.rcMonitor.left)
	{
		rect.right += mi.rcMonitor.left - rect.left;
		rect.left = mi.rcMonitor.left;
	}
	if (rect.bottom > mi.rcMonitor.bottom)
	{
		rect.top -= rect.bottom - mi.rcMonitor.bottom;
		rect.bottom = mi.rcMonitor.bottom;
	}
	if (rect.top < mi.rcMonitor.top)
	{
		rect.bottom += mi.rcMonitor.top - rect.top;
		rect.top = mi.rcMonitor.top;
	}

	// ウインドウ位置と最大化状態の復元
	MoveWindow(rect.left, rect.top, rect.Width(), rect.Height());
	if(maximized)
		ShowWindow(SW_MAXIMIZE);

	return TRUE;  // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

-ソフト開発

執筆者:


comment

メールアドレスが公開されることはありません。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

no image

アプリでzip圧縮と解凍をやりたくてzlibを組み込んでみたんだけど。。

でかいファイルは、圧縮してコンパクトに扱いたくなる。 テキスト形式で定義されているファイルをアプリで読み書きする。 テキストは圧縮すれば1/5くらいになるので、その結果小さくなった ファイルをアプリの …

no image

プロパティシート

ソフト開発の話です。 私はVC6でソフトを作ってます。 ダイアログボックスの作りこみは、おもしろいものです。 プログラマーにとっては、デザイン的センスを問われる分野ですかね。 プロパティシートとは、ペ …

zipcopy Ver1.06リリース

zipcopyをリビジョンアップして、Ver1.06とした。 詳細は以下の通り。 Ver1.06 2020/2/24 ・ファイル削除時のタイムアウト(秒)を設定可能として、初期値を180秒(3分)とし …

no image

BMPをJPEGに

ビットマップ(BMP)ファイルをJPEGファイルに変換できないものか。 そんなテーマが頭の中で渦巻いていた。 既存のアプリ(フリーソフトなど)を使えば簡単だが、 プログラム的にやるとなると、なかなか。 …

no image

WinMergeのフィルタ

【2018/11/24追記】 この書きっぷりではあまりにもわかりずらいので、整理して追加情報を加えて新たな記事とした。 WinMegeのフィルタについて今すぐ知りたいならココを読め プログラマにとって …