┏第42号━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃         .NETプログラミング研究         ┃ ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ ──<メニュー>─────────────────────── ■.NET Tips ・フォームが一つしか表示されないようにする  VB6と同様にフォームにアクセスできるようにする ・フォームのリサイズが終了するまでコントロールの大きさを変えない ・MDI親フォームの背景色を変更する ・MDI親フォームの背景を描画する ─────────────────────────────── ─────────────────────────────── ■.NET Tips ─────────────────────────────── ●フォームが一つしか表示されないようにする  VB6と同様にフォームにアクセスできるようにする Visual Basic 6.0以前のユーザーにとって.NETプログラミングの第一 の関門はWindowsフォームの表示に関する問題でしょう。VB6では、デ ザイナでフォームを作成し、コードで (フォーム名).Show とするだけでフォームを表示できました。しかも、同じフォームの Showメソッドを何回呼び出してもフォームが何枚も表示されるという ことがありません。 これに対して.NETでは、newによりフォームのインスタンスを作成し てからShowメソッドを呼び出す必要があり、さらに、「newしてShow」 を繰り返していると、それだけ次々とフォームが開かれることになり ます。 このような事態を避け、VB6のようにフォームを扱えるようにするた めには、静的プロパティを使用するようにします。フォームのインス タンスは静的フィールドに保持しておき、静的プロパティでこのフィー ルドがnullまたは破棄(Dispose)されているときに新しいインスタン スを作成するようにします。 次に簡単なサンプルを示します。Form2というフォームクラスがある ものとし、以下のような_instanceフィールドとInstanceプロパティ を加えます。(これに加え、Form2クラスをシールクラスとし、Form2 のコンストラクタをprivateに変更すればさらに確実です。) ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'ただ一つのフォームのインスタンスを保持するフィールド Private Shared _instance As Form2 'ただ一つのフォームにアクセスするためのプロパティ Public Shared ReadOnly Property Instance() As Form2 Get '_instanceがnullまたは破棄されているときは、 '新しくインスタンスを作成する If _instance Is Nothing OrElse _instance.IsDisposed Then _instance = New Form2 End If Return _instance End Get End Property ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //ただ一つのフォームのインスタンスを保持するフィールド private static Form2 _instance; //ただ一つのフォームにアクセスするためのプロパティ public static Form2 Instance { get { //_instanceがnullまたは破棄されているときは、 //新しくインスタンスを作成する if (_instance == null || _instance.IsDisposed) _instance = new Form2(); return _instance; } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ Form2のインスタンスにアクセスするには、Form2.Instance静的メソ ッドを使用します。例えば、Form2をモードレスで表示するには、 Form2.Instance.Show() とするだけです(名前空間が異なる場合は、適当な名前空間を付加し てください)。Form2.Instance.Show()を何回呼び出しても、フォー ムが何枚も表示されることはありません。(このプロパティはスレッ ドセーフではありませんが、それほど問題にならないでしょう。しか しどうしてもスレッドセーフにしたいということであれば、下の例を 参考にしてください。) ところで、この問題の解決に、シングルトン(Singleton)デザイン パターンがそのまま使えるのではないかと考える方もいらっしゃるか もしれません。シングルトンとは、クラスのインスタンスがただ一つ であることを保障するためのデザインパターンで、フォームのインス タンスがただ一つであることを保障するということであれば、これは まさに「ビンゴ」と言えるでしょう。 シングルトン及び、C#におけるシングルトンの実装に関しては、次の ページが参考になります。 [URL]Microsoft patterns & practices Patterns - シングルトン http://www.microsoft.com/japan/msdn/practices/type/Patterns/enterprise/DesSingleton.asp [URL]C# でのシングルトンの実装 http://www.microsoft.com/japan/msdn/practices/type/Patterns/enterprise/ImpSingletonInCsharp.asp [URL]Implementing the Singleton Pattern in C# http://www.yoda.arachsys.com/csharp/singleton.html ここでは「C# でのシングルトンの実装」の「静的な初期化」で紹介 されている方法を使用します。この方法によりインスタンスが一つで あるForm2フォームを作成するには、まずForm2クラスをシールクラス とし、Form2のコンストラクタをprivateに変更し、以下のような _instanceフィールドとInstanceプロパティを加えます。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '基本クラスとして使用できないようにする Public NotInheritable Class Form2 Inherits System.Windows.Forms.Form 'コンストラクタをPrivateにする Private Sub New() '(省略) End Sub '(省略) 'フォームのインスタンスを保持するフィールド Private Shared _instance As New Form2 'フォームにアクセスするためのプロパティ Public Shared ReadOnly Property Instance() As Form2 Get Return _instance End Get End Property End Class ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //基本クラスとして使用できないようにする public sealed class Form2 : System.Windows.Forms.Form { //コンストラクタをPrivateにする private Form2() { //(省略) } //(省略) //フォームのインスタンスを保持するフィールド private static readonly Form2 _instance = new Form2(); //フォームにアクセスするためのプロパティ public static Form2 Instance { get { return _instance; } } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ところが上記のようにフォームクラスにシングルトンパターンをその まま適用すると、うまく行きません。フォームを閉じてから再びフォー ムを開こうとすると、エラーが発生するのです。というのは、フォー ムのインスタンスが一つであっても、そのフォームが破棄されてしま ったらそのインスタンスを使ってフォームを開くことができなくなっ てしまうからです。 この問題を解決するには、一つの方法として、フォームが破棄されな いようにフォームが閉じられないようにするといった対策が考えられ ます。 例えば、Form2クラスに次のコードを書き加えることにより、フォー ムを閉じようとした時に閉じずに、隠すようになります。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'フォームを閉じずに隠すようにする Protected Overrides Sub OnClosing( _ ByVal e As System.ComponentModel.CancelEventArgs) e.Cancel = True Me.Hide() End Sub ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //フォームを閉じずに隠すようにする protected override void OnClosing(CancelEventArgs e) { e.Cancel = true; this.Hide(); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ─────────────────────────────── ●フォームのリサイズが終了するまでコントロールの大きさを変えない Windowsフォームのサイズがユーザーにより変更でき、フォームに配 置されたコントロールがDockやAnchorプロパティによりそのサイズ(ま た配置)がフォームのサイズとともに変更されるようになっている時、 フォームの境界線をマウスでドラッグすることによりフォームのサイ ズを変えると、ドラッグしている間絶え間なくコントロールのサイズ が変化します。しかし、ドラッグしている間はコントロールの配置が 変化せずに、ドラッグが完了してマウスボタンを放したときに初めて コントロールの配置が変化するようにしたいというケースもあるでし ょう。 そのような場合の対策について、ニュースグループに適切な投稿があ りますので、ここではそれを紹介します。 [URL]Newsgroups:microsoft.public.dotnet.framework.windowsforms Subject:Re: resize finished event From:Shawn Burke [MSFT] http://groups.google.co.jp/groups?hl=ja&lr=&ie=UTF-8&inlang=ja&selm=uRawvh9vBHA.1572%40tkmsftngp07 ここでは2つの方法が紹介されています。まず一つ目が、Application. Idleイベントを使用する方法です。フォームのサイズを変更している 最中はApplication.Idleイベントが発生せず、変更完了してから発生 するという特徴を利用しています。 この方法によるコードは次のようになります。フォームクラス内に記 述してください。(ニュースグループに紹介されている方法を多少変 えてあります。良くなっているかは分かりませんが...。) ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'OnResizeのEventArgsを保持する Dim resizeEA As EventArgs = Nothing 'フォームのサイズが変更した時 Protected Overrides Sub OnResize(ByVal e As EventArgs) If resizeEA Is Nothing Then resizeEA = e AddHandler Application.Idle, AddressOf OnIdle End If End Sub 'アプリケーションがアイドル状態になった時 Private Sub OnIdle(ByVal s As Object, ByVal e As EventArgs) If Not (resizeEA Is Nothing) Then '基本クラスのOnResizeを呼び出す MyBase.OnResize(resizeEA) resizeEA = Nothing RemoveHandler Application.Idle, AddressOf OnIdle End If End Sub ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //OnResizeのEventArgsを保持する EventArgs resizeEA = null; //フォームのサイズが変更した時 protected override void OnResize(EventArgs e) { if (resizeEA == null) { resizeEA = e; Application.Idle += new EventHandler(OnIdle); } } //アプリケーションがアイドル状態になった時 private void OnIdle(object s, EventArgs e) { if (resizeEA != null) { //基本クラスのOnResizeを呼び出す base.OnResize(resizeEA); resizeEA = null; Application.Idle -= new EventHandler(OnIdle); } } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 2つ目の方法は、フォームのWndProcをオーバーライドし、 WM_EXITSIZEMOVEメッセージを待ち、送られてきた時にコントロール を配置するという方法です。 ニュースグループで紹介されているコードはそのまま使用しても何も 起こりません。私が思うに、例えば、OnResizeメソッドをオーバーラ イドし、何もしないという処理が必要であるにもかかわらず、説明さ れていないようです(リサイズが普通に行われてしまっては、全く意 味がありませんので)。 よって、正しく動作するコードは、例えば次のようになるでしょう。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'OnResizeをオーバーライドし、何もしない Protected Overrides Sub OnResize(ByVal e As EventArgs) End Sub Private Const WM_EXITSIZEMOVE As Integer = &H232 Protected Overrides Sub WndProc(ByRef m As Message) If m.Msg = WM_EXITSIZEMOVE Then 'リサイズ終了後、コントロールを配置する Invalidate() PerformLayout() End If MyBase.WndProc(m) End Sub ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //OnResizeをオーバーライドし、何もしない protected override void OnResize(EventArgs e) { } private const int WM_EXITSIZEMOVE = 0x232; protected override void WndProc(ref Message m) { if (m.Msg == WM_EXITSIZEMOVE) { //リサイズ終了後、コントロールを配置する Invalidate(); PerformLayout(); } base.WndProc(ref m); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ─────────────────────────────── ●MDI親フォームの背景色を変更する WindowsフォームのIsMdiContainerプロパティをtrueにすることによ りMDI親フォームとすると、フォームのBackColorプロパティは無視さ れ、子フォームが表示されるくぼんだクライアント領域はシステムで 指定された色(大抵は濃い灰色)になります。 このクライアント領域には、実はMdiClientというコントロールがあ り、MdiClientコントロールのBackColorプロパティを変更することに より、クライアント領域の色を変えることができます。(ただし、ヘ ルプによると、MdiClientクラスは「独自に作成したコード内で直接 使用することはできません。」とありますので、この方法が適切であ るかは保障できません。) まずフォームにあるMdiClientコントロールを探す次のような静的メ ソッドを作成します。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ''' ''' フォームのMdiClientコントロールを探して返す ''' ''' MdiClientコントロールを探すフォーム ''' 見つかったMdiClientコントロール Public Shared Function GetMdiClient( _ ByVal f As System.Windows.Forms.Form) _ As System.Windows.Forms.MdiClient Dim c As System.Windows.Forms.Control For Each c In f.Controls If TypeOf c Is System.Windows.Forms.MdiClient Then Return CType(c, System.Windows.Forms.MdiClient) End If Next c Return Nothing End Function ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ /// /// フォームのMdiClientコントロールを探して返す /// /// MdiClientコントロールを探すフォーム /// 見つかったMdiClientコントロール public static System.Windows.Forms.MdiClient GetMdiClient(System.Windows.Forms.Form f) { foreach (System.Windows.Forms.Control c in f.Controls) if (c is System.Windows.Forms.MdiClient) return (System.Windows.Forms.MdiClient) c; return null; } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ このメソッドを使って、MDI親フォームのクライアント領域にフォー ムのBackColorプロパティで指定された色を適用するコードは次のよ うになります。 (MDI親フォームのクラス内に記述するものとします。) ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ 'MdiClientを探す Dim mc As System.Windows.Forms.MdiClient = GetMdiClient(Me) If Not (mc Is Nothing) Then '背景色を変更し、再描画する mc.BackColor = Me.BackColor mc.Invalidate() End If ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //MdiClientを探す System.Windows.Forms.MdiClient mc = GetMdiClient(this); if (mc != null) { //背景色を変更し、再描画する mc.BackColor = this.BackColor; mc.Invalidate(); } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ─────────────────────────────── ●MDI親フォームの背景を描画する MDI親フォームの背景に画像を表示するには、フォームのBackGround プロパティが使えます。先に紹介したBackColorプロパティとは違い、 BackGroundプロパティに指定された画像はクライアント領域に正常に 表示されます(画像はタイル状に表示されます)。 MDI親フォームの背景を独自に描画する方法は、"vbAccelerator"で紹 介されています。 [URL]vbAccelerator - Painting in the MDI Client Area http://vbaccelerator.com/home/NET/Code/Libraries/Windows/MDI_Client_Area_Painting/article.asp vbAcceleratorで紹介されている方法は、Win32 APIを使う方法で、か なり複雑です。 このように難しい方法を使わなくても、先に紹介したMdiClientコン トロールのPaintイベントを使ってMDI親フォームのクライアント領域 を描画すれば、もっと簡単に実現できます。 以下に紹介するサンプルでは、MDI親フォームのクライアント領域に 画像("back.bmp")をクライアント領域の大きさに合わせて描画するよ うにしています。なお、このコードはMDI親フォームに記述されてお り、ParentForm_LoadはフォームのLoadイベントハンドラとして登録 されているものとします。 ‥‥▽VB.NET ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ '背景に表示する画像 Dim back As New Bitmap("back.bmp") 'MDI親フォームのLoadイベントハンドラ Private Sub ParentForm_Load(sender As Object, _ e As System.EventArgs) Handles MyBase.Load Me.IsMdiContainer = True 'MdiClientの取得 Dim mc As System.Windows.Forms.MdiClient = GetMdiClient(Me) 'MdiClientのPaintとResizeイベントハンドラを追加 AddHandler mc.Paint, AddressOf MdiClient_Paint AddHandler mc.Resize, AddressOf MdiClient_Resize End Sub Private Sub MdiClient_Paint(sender As Object, e As PaintEventArgs) Dim mc As System.Windows.Forms.MdiClient = _ CType(sender, System.Windows.Forms.MdiClient) '画像をクライアント領域にあわせて描画する e.Graphics.DrawImage(back, mc.ClientRectangle) End Sub Private Sub MdiClient_Resize(sender As Object, e As EventArgs) Dim mc As System.Windows.Forms.MdiClient = _ CType(sender, System.Windows.Forms.MdiClient) 'Paintイベントを呼び出す mc.Invalidate() End Sub ''' ''' フォームのMdiClientコントロールを探して返す ''' ''' MdiClientコントロールを探すフォーム ''' 見つかったMdiClientコントロール Public Shared Function GetMdiClient( _ ByVal f As System.Windows.Forms.Form) _ As System.Windows.Forms.MdiClient Dim c As System.Windows.Forms.Control For Each c In f.Controls If TypeOf c Is System.Windows.Forms.MdiClient Then Return CType(c, System.Windows.Forms.MdiClient) End If Next c Return Nothing End Function ‥‥△VB.NET ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ ‥‥▽C# ここから▽‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ //背景に表示する画像 Bitmap back = new Bitmap("back.bmp"); //MDI親フォームのLoadイベントハンドラ private void ParentForm_Load(object sender, System.EventArgs e) { this.IsMdiContainer = true; //MdiClientの取得 System.Windows.Forms.MdiClient mc = GetMdiClient(this); //MdiClientのPaintとResizeイベントハンドラを追加 mc.Paint += new PaintEventHandler(MdiClient_Paint); mc.Resize += new EventHandler(MdiClient_Resize); } private void MdiClient_Paint(object sender, PaintEventArgs e) { System.Windows.Forms.MdiClient mc = (System.Windows.Forms.MdiClient) sender; //画像をクライアント領域にあわせて描画する e.Graphics.DrawImage(back, mc.ClientRectangle); } private void MdiClient_Resize(object sender, EventArgs e) { System.Windows.Forms.MdiClient mc = (System.Windows.Forms.MdiClient) sender; //Paintイベントを呼び出す mc.Invalidate(); } /// /// フォームのMdiClientコントロールを探して返す /// /// MdiClientコントロールを探すフォーム /// 見つかったMdiClientコントロール public static System.Windows.Forms.MdiClient GetMdiClient(System.Windows.Forms.Form f) { foreach (System.Windows.Forms.Control c in f.Controls) if (c is System.Windows.Forms.MdiClient) return (System.Windows.Forms.MdiClient) c; return null; } ‥‥△C# ここまで△‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥‥ =============================== ■このマガジンの購読、購読中止、バックナンバー、説明に関しては  次のページをご覧ください。  http://www.mag2.com/m/0000104516.htm ■発行人・編集人:どぼん!  (Microsoft MVP for Visual Basic, Oct 2003-Oct 2004)  http://dobon.net  dobon_info@yahoo.co.jp ■ご質問等はメールではなく、掲示板へお願いいたします。  http://dobon.net/vb/bbs.html ■上記メールアドレスへのメールは確実に読まれる保障はありません  (スパム、ウィルス対策です)。メールは下記URLのフォームメール  から送信してください。  http://dobon.net/mail.html Copyright (c) 2003 - 2004 DOBON! All rights reserved. ===============================