┏第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.
===============================