あるところにC# / .NET Frameworkで作られている業務システムありけり。
ある日ユーザーから「メインメニューから子画面を開いた後、メインメニューがずっと残りっぱなしで移動もできないのは邪魔すぎる何を考えとるんか誰じゃこんなシステムを作ったやつは(以下略)」とお叱りを受けた。
※修正後、しばらくして今度は「何で子画面を開いたらメインメニューが消えるんだ何を考えとるんか誰じゃこんなシステムを作ったやつは(以下略)」とお叱りを受けたので元に戻した。何なんだ一体。
調べてみると子画面をモーダルウィンドウとして表示しており、ほんならモードレスに変えましょうと相成った。
モーダルからモードレスに変える
モーダルの時はこんな感じだった。
FormA hoge = new FormA();
hoge.ShowDialog();
教科書通りのド定番パターン。
定義済みのFormAをモーダルウィンドウとして表示する。一番簡単なやり方。
これをモードレスにする場合はhoge.Show()とすればいいんだけど、
FormA hoge = new FormA();
hoge.Show();
モーダルで開いた場合はフォームを閉じるまでコードの実行が止まる一方、モードレスの場合はフォームを表示したらすぐ続きのコードが実行される。
また、モーダルの場合は使用後にDisposeが必要(大抵の場合はGCが上手いこと解放してくれるみたいだけど)だったり、モーダレスだと複数のウィンドウを同時に開けてしまったり、モーダルとの違いはたくさんあるため、単純にコードを置き換えればいいというわけではない。
専用関数を作る
色々検討した結果、専用の関数を作ることでモーダルとモードレスの違いを吸収しつついい感じに仕上げることにした。
FormA hoge = null;
FormB fuga = null;
//任意の場所で呼び出し
ShowModeless(ref hoge);
//任意の場所で呼び出し
ShowModeless(ref fuga);
//作成した関数
private void ShowModeless<T>(T form) where T : Form, new()
{
//子画面が既に開いているなら処理終了
if (this.OwnedForms.Any()) return;
//未作成 or 廃棄済ならnewして開く
if (form is null || form.IsDisposed)
{
form = new T();
form.Show();
this.AddOwnedForm(form);
}
else
{
//作成済のフォームがあるなら表示する
form.Activate();
}
// メインメニュー(自身)を非表示にする
//this.Hide();
}
子画面を同時に1つしか開かせたくなかったので当初は子画面にを開くとメインメニューが操作できないようHide()するようにしていた。
後に関数の頭で子画面の有無を確認すればいいと気づき、先頭でメインフォームのOwnedForms配列の要素数をチェックするよう変更した。
明示的にしなくてもDisposeされる謎
何となく各フォームが作成済かとかDispose済かとかを確認するためにフォーム毎に変数を作ってrefで渡してたけど、結局子フォームを閉じたらIsDisposed=trueになってた。
※後で調べたらモードレスの場合はClose時にDisposeされるから不要らしい。
それだったら明示的にDisposeするのをサボってて、結局それだったら使いっぱなしでも良いじゃんとも思ってはいる。
//任意の場所で呼び出し
ShowModeless(new FormA());
//任意の場所で呼び出し
ShowModeless(new FormB());
//作成した関数
private void ShowModeless<T>(T form) where T : Form, new()
{
//子画面が既に開いているなら処理終了
if (this.OwnedForms.Any()) return;
form.Show();
this.AddOwnedForm(form);
}
みたいな。
ここまでスカスカになると逆に関数にする必要はあるのかという問題もある。
本当は親フォーム側で子フォームのFormClosedイベントをキャッチしてDisposeとかしないといけないんだけど、前回の話でもあったようにIDisposableだからと言って絶対に必ず死んでもDisposeしないといけないかというとそんなことはなく、GCがうまいことやってくれるみたい。
(もちろんファイル操作でDisposeしなかったらファイルが開きっぱなしになっちゃうとか副作用を生じることもあるので、ケースバイケース。やるに越したことはないのは大前提)
本題のタスクバーからアイコンが消える話
ということで最終的には(課題はあるものの)一応動くものができたんだけど、まだメインメニューを非表示にしてた頃、子フォームをShow()した後にメインメニューをHide()するとタスクバーからアイコンが消えることが発覚。
メインメニューに戻ってくると復活するし、子フォーム内で別のモーダルウィンドウを開いても復活する。
どういうこと。
ググってみたらこちらの記事に
form.Show(this);
とすればいいと書いてあった。
MicrosoftのForm.Showのドキュメントを見ると、引数にownerを指定できるようだ。
public void Show (System.Windows.Forms.IWin32Window owner);
なるほど、ownerを登録する前にformを生成してしまってたから、Windowsにきちんと親子関係が伝わっていなかったのか。
ん、ということは...先にAddOwnedFormを呼んでOwner登録すればよかっただけ?
this.AddOwnedForm(form);
form.Show();
これもやってることは一緒だよね?と思って実行してみたら予想通りアイコンは消えなくなった。それだけの話だったのか...。
(まぁform.Show(this);なら一行で済むしこっちでいいんだけど)
結果としてHide()するのは辞めたのだが、思わぬところに落とし穴があって調べてみたらなんとも単純な話だったというオチ。
コメントする