最大化できちゃうの

ウィンドウが最大化できちゃうの。というお話。


C#のFormクラスにはMaximizeBoxというプロパティがあって、
それをfalseにすればウィンドウの最大化ボタンを無効にして最大化を出来ないようにするという機能があります。
こうすればキャプション部分をダブルクリックした場合などでも最大化しません。


しかし、最大化しないはずのウィンドウを最大化させる方法があります。


最近のマウスには5つボタンなどのタイプがあり、自由に機能を選択できるものがあります。
そのボタンの機能に最大化を割り当て、MaximizeBoxがfalseになっているウィンドウがアクティブになっているときに押します。
すると、見るも無残にウィンドウは最大化されてしまいます。


予想だにしない事態です。
なのでこれはと思い少し思案、そして実験してみました。
実験内容は簡単。MaximizeBoxがfalseになっているウィンドウに対して最大化のメッセージをPostMessageで送るだけ。


結果は思ったとおりです。最大化されてしまいました。
この結果から想像するに、MaximizeBoxをfalseにするということは、
『自分自身で最大化のメッセージを生成しない』
ということを意味するのだと思います。
つまり、意図的にもしくは自分自身ではない外から直接最大化のメッセージを投げられた場合最大化してしまうということだということです。(あくまで想像ですが)


正直重箱の隅をつつくような話ですが、わざわざMaximizeBoxをfalseにして最大化をさせたくないのですからこういう場合にも対処ができたほうが幸せなような気がします。
なので作りました。


少し長いですが以下にコードの全体を載せます。

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.ComponentModel;

namespace aharisu.Component
{
	class MaximizingNotifyWindow : NativeWindow 
	{
		private const int WM_SYSCOMMAND = 0x0112;
		private const int SC_MAXIMIZE = 0xf030;
		private const int WS_MAXIMIZEBOX = 0x00010000;

		protected Form targetForm_;
		private bool maximumBoxEnabled_ = true;
		public event CancelEventHandler Maximuming;

		#region Propeties

		public bool MaximumBoxEnabled
		{
			get { return maximumBoxEnabled_; }
			set { ChangeEnabled(value); }
		}

		#endregion

		public MaximizingNotifyWindow(Form form)
		{
			this.targetForm_ = form;

			if (form.IsHandleCreated)
				AssignHandle(form.Handle);
			else
				form.HandleCreated += new EventHandler(form_HandleCreated);
			form.HandleDestroyed += new EventHandler(form_HandleDestroyed);
		}

		#region Override & Event Handler

		void form_HandleDestroyed(object sender, EventArgs e)
		{
			ReleaseHandle();
		}

		void form_HandleCreated(object sender, EventArgs e)
		{
			AssignHandle(((Form)sender).Handle);
		}

		protected override void WndProc(ref Message m)
		{
			if (m.Msg == WM_SYSCOMMAND)
			{
				if (((int)m.WParam & 0xfff0) == SC_MAXIMIZE)
				{
					if (this.maximumBoxEnabled_)
					{
						CancelEventArgs e = new CancelEventArgs();
						if (Maximuming != null)
							Maximuming(targetForm_, e);
						if (e.Cancel)
							m.WParam = (IntPtr)(((int)m.WParam) ^ SC_MAXIMIZE);
					}
					else
						m.WParam = (IntPtr)(((int)m.WParam) ^ SC_MAXIMIZE);
				}
			}
			base.WndProc(ref m);
		}

		#endregion

		#region Helper Method

		private void ChangeEnabled(bool enable)
		{
			if (enable && !this.maximumBoxEnabled_)
			{
				SetWindowLong(targetForm_.Handle, -16,
					GetWindowLong(targetForm_.Handle, -16) | WS_MAXIMIZEBOX);
				DrawMenuBar(targetForm_.Handle);
			}
			else if (!enable && this.maximumBoxEnabled_)
			{
				SetWindowLong(targetForm_.Handle, -16, 
					GetWindowLong(targetForm_.Handle, -16) & ~WS_MAXIMIZEBOX);
				DrawMenuBar(targetForm_.Handle);
			}

			this.maximumBoxEnabled_ = enable;
		}

		#endregion

		#region DllImport

		[DllImport("user32.dll")]
		private static extern Int32 GetWindowLong(IntPtr hWnd, int nIndex);
		[DllImport("user32.dll")]
		private static extern Int32 SetWindowLong(IntPtr hWnd, int nIndex, Int32 dwNewLong);
		[DllImport("user32.dll")]
		[return:MarshalAs(UnmanagedType.Bool)]
		static extern bool DrawMenuBar(IntPtr hWnd);

		#endregion

	}
}

次にこのクラスを利用するテストコードです。これは適切なデザイナのコードがあると仮定しています。

public partial class Form1 : Form
{
	MaximizingNotifyWindow form_;

	public Form1()
	{
		InitializeComponent();
		form_ = new MaximizingNotifyWindow(this);
		form_.Maximuming += new CancelEventHandler(Maximuming);
	}

	void Maximuming(object sender, CancelEventArgs e)
	{
		if (MessageBox.Show("最大化しますか?", "Infomation", MessageBoxButtons.YesNo) == DialogResult.No)
			e.Cancel = true;
	}
}

このように、コンストラクタの引数に最大化の際知らせてほしいFormの参照を渡します。
次にイベントを登録します。
最大化をキャンセルする方法はFormクラスのFormClosingイベントなどと同じです。


こうすれば対話的に最大化をキャンセルできます。
しかし無条件でキャンセルしたい時もあります。
そういう時はMaximumBoxEnabledをfalseにすれば無条件でキャンセルされるようになります。


MaximizingNotifyWindowクラスのChangeEnabledメソッドの中で何やら怪しいことをしていますが、
そのメソッドの役割は、キャプションバー部分にある最大化ボタンの有効化と無効化です。
これはFormのほうのMaximizeBoxをtrueにしたりfalseにしたりすることで同じことができます。
こんな方法もあるよということで残しているだけです。


しかしこの方法では、最大化ボタンが無効化状態でもMaximizeBoxはtrueと同期がとれていないので、直したほうがいいかもしれません。
完全に同期させるようにするには、MaximizeBoxの状態が変わった時もキャプチャできないといけないのですが、そっちは単純にはいかないようです(しっかり確かめてないけど)。
少し調べてみたところ、MaximizeBoxの状態を変えたときにWM_STYLECHANGINGやWM_STYLECHANGEDというメッセージが飛んできているのでその辺を突き詰めて調べてみるとできるようになるかもしれません。


でも面倒くさいので今はしません。
いつかするかもしれません。
しないかもしれません。


ちなみにこのコードは書いて出しの状態なのでいつものようにほとんど検証されていません。
なので思いもよらないことが起きても僕は何の保証はできません。
あくまでも自己責任、参考程度ということでお願いします。


単純に最大化をキャンセルしたいだけならオーバライドしたWndProc内をまねるだけでできるしね。


#追記
09年4月17日 MaximizingNotifyWindowのコンストラクタで引数のFormのHandleが既に作られていたらすぐにAssignHandleするように修正。すでにDisposeされているかの判断もいるかな?検証中。