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 を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

関連記事

PDFアレコレはこんなところが落としどころかね

この3連休はPDFiumを使ったサンプルアプリ作成に取り組んでいて、まぁそこそこの感じにできあがったのでここらでいったん完成としておく。

no image

テキストファイルの最大行数

【2018/12/24追記】以下の記事も参考になるかな。 扱えるテキストファイルの最大行数は1億行 バカでかいテキストファイルを作る必要があって、結果約2GBにもなった。 CADから出力するんだけど、 …

マルチスレッドプログラミング(VB.net編)

マルチスレッドプログラミング(C++編)に続き、今度はVB.net編を公開。マルチスレッドプログラミング(C#編)はこちら。

no image

環境変数TEMPトラブルについて振り返る

プログラムから環境変数TEMPで指定されたフォルダにファイルが作れないことがわかった時点で、この問題は解決したも同じだった。TEMPの値を「%USERPROFILE%\AppData\Local\Te …

no image

ベクタープロレジ大賞

Vectorがやってる、もっとも人気のあったダウンロードソフトを決める賞です。 Vectorと言えば、フリーウェアやシェアウェアをダウンロードできるところで一番有名なところです。窓の杜というところもあ …