.NETプログラミング研究 第13号

.NET Tips

フォームの形を変える

注意

この記事の最新版は「フォームやコントロールの形を変える」及び「フォームウィンドウの特定の色を透明にする」で公開しています。

フォームの形を四角(矩形)以外の形に変える方法としてここでは、Control.Regionプロパティを使う方法と、Form.TransparencyKeyプロパティを使う方法の2つを紹介します。まずはControl.Regionプロパティを使った方法から。

形を変えたいフォームのRegionプロパティに、その形状を示したRegionオブジェクトを指定することにより、フォームの形を変えることが出来ます。

次の例はフォームをドーナッツ型にするコードです。ここではフォームのLoadイベントハンドラ内にコードを書いていますが、コンストラクタ内の適当な位置などに記述してもかまいません。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
Private Sub Form1_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    'フォームの大きさを適当に変更
    Me.SetBounds(Me.Left, Me.Top, 301, 301, BoundsSpecified.Size)
    'GraphicsPathオブジェクトの作成
    Dim myGraphicsPath As System.Drawing.Drawing2D.GraphicsPath = _
        New System.Drawing.Drawing2D.GraphicsPath()
    '丸を描く
    myGraphicsPath.AddEllipse(New Rectangle(0, 0, 300, 300))
    '真ん中を丸くくりぬく
    myGraphicsPath.AddEllipse(New Rectangle(100, 100, 100, 100))
    'Regionプロパティの設定
    Me.Region = New Region(myGraphicsPath)
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
private void Form1_Load(object sender, System.EventArgs e)
{
    //フォームの大きさを適当に変更
    this.SetBounds(this.Left, this.Top, 301, 301, BoundsSpecified.Size);
    //GraphicsPathオブジェクトの作成
    System.Drawing.Drawing2D.GraphicsPath myGraphicsPath =
        new System.Drawing.Drawing2D.GraphicsPath();
    //丸を描く
    myGraphicsPath.AddEllipse(new Rectangle(0, 0, 300, 300));
    //真ん中を丸くくりぬく
    myGraphicsPath.AddEllipse(new Rectangle(100, 100, 100, 100));
    //Regionプロパティの設定
    this.Region = new Region(myGraphicsPath);
}

次の例はフォームの形を文字(この例では"DOBON!")にするものです。

  1
  2
  3
  4
  5
  6
  7
  8
Private Sub Form1_Load(ByVal sender As System.Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    Dim myGraphicsPath As New System.Drawing.Drawing2D.GraphicsPath()
    '文字のサイズは50
    myGraphicsPath.AddString("DOBON!", New FontFamily("Arial"), _
        FontStyle.Bold, 50, New Point(0, 0), StringFormat.GenericDefault)
    Me.Region = New Region(myGraphicsPath)
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
private void Form1_Load(object sender, System.EventArgs e)
{
    System.Drawing.Drawing2D.GraphicsPath myGraphicsPath =
        new System.Drawing.Drawing2D.GraphicsPath();
    //文字のサイズは50
    myGraphicsPath.AddString("DOBON!", new FontFamily("Arial"),
        (int) FontStyle.Bold, 50, new Point(0, 0),
        StringFormat.GenericDefault);
    this.Region = new Region(myGraphicsPath);
}

そのほかの形に変える例につきましては、下のURLをご覧ください。

次にForm.TransparencyKeyプロパティを使ってフォームの形を変える方法を紹介します。

Windows2000以降のOSであれば、Form.TransparencyKeyプロパティにより、フォームで透明にしたい色を指定できます。このTransparencyKeyを使うことにより、自由な形のフォームを簡単に作れそうです。つまり、フォームの形としたい形が描かれた画像ファイルを用意し、フォームにその画像を描画し、画像の背景色をフォームのTransparencyKeyに指定する訳です。

しかし残念なことに実際にはそう簡単にはいきません。私の試したところでは、Windowsの設定で「画面のプロパティ」の「画面の色」が「High Color(16ビット)」以下になっている時はうまく行きますが、「True Color(32ビット)」の時はTransparencyKeyに指定した色でも透明になりませんでした。(環境により違いがあるかもしれません。)

いろいろ試してみたところ、少なくとも私の環境では次のような方法により、True Colorの時でも成功するようになりました。まずフォームの背景色を透明にしたい色にし、フォームのTransparencyKeyにもその色を指定します。フォームの形に使う画像ファイルはBitmapオブジェクトとして読み込み、MakeTransparentメソッドにより、透明にしたい色(背景色)を透明色にします。(透明色を指定して保存したGif画像を使うという手もあります。)

画像をフォームに描画するには、BackgroundImageプロパティを使わずに、フォームのPaintイベントハンドラで描画するようにします。(この方法がすべての環境でうまく行くか分かりませんので、情報をいただければ助かります。)

次の例ではフォームの形を"form.bmp"という画像ファイルに保存し、これを使ってフォームForm1の形を作っています。ここでは白を透明色としています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
Dim _formBitmap As Bitmap
 
Private  Sub Form1_Load(ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load
    'TransparencyKeyプロパティを指定する前に画像を読み込む
    'フォームの形の画像を読み込む
    _formBitmap = New Bitmap("form.bmp")
    '画像の透明色を指定する
    _formBitmap.MakeTransparent(Color.White)
 
    'フォームの境界線をなくす
    Me.FormBorderStyle = FormBorderStyle.None
    '大きさを適当に変更
    Me.Size = New Size(100, 100)
    '透明を指定する
    Me.TransparencyKey = Color.White
    'フォームの背景色を透明色にする
    Me.BackColor = Color.White
End Sub
 
Private  Sub Form1_Paint(ByVal sender As Object, _
        ByVal e As System.Windows.Forms.PaintEventArgs) _
        Handles MyBase.Paint
    'フォームの形の画像を描画する
    e.Graphics.DrawImage(_formBitmap, 0, 0)
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
Bitmap _formBitmap;
 
private void Form1_Load(object sender, System.EventArgs e)
{
    //TransparencyKeyプロパティを指定する前に画像を読み込む
    //フォームの形の画像を読み込む
    _formBitmap = new Bitmap(@"form.bmp");
    //画像の透明色を指定する
    _formBitmap.MakeTransparent(Color.White);
 
    //フォームの境界線をなくす
    this.FormBorderStyle = FormBorderStyle.None;
    //大きさを適当に変更
    this.Size = new Size(100, 100);
    //透明を指定する
    this.TransparencyKey = Color.White;
    //フォームの背景色を透明色にする
    this.BackColor = Color.White;
}
 
private void Form1_Paint(object sender,
    System.Windows.Forms.PaintEventArgs e)
{
    //フォームの形の画像を描画する
    e.Graphics.DrawImage(_formBitmap, 0, 0);
}

.NET質問箱

メールのサブジェクトをデコードするには?

注意

この記事の最新版は「メールのサブジェクトをデコードする」で公開しています。

質問:

受信したメールのSubjectをデコードしたいのですが、どのようにすればよいのでしょうか?

例「=?ISO-2022-JP?B?GyRCJCIkMSReJDckRiQqJGEkRyRIJCYhKhsoSg==?=」

答え:

ちゃんとしたものを作るにはRFC2047の知識が必要になると思いますが、「とりあえず」的なもの(Base64形式のみ対応)であれば、次のような簡単なコード(メソッド)で実現できます。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
'/ <summary>
'/ メールのサブジェクトをデコードする
'/ </summary>
'/ <param name="subject">デコードするメールサブジェクト</param>
'/ <returns>デコードされた文字列</returns>
Private Shared Function DecodeMailSubject(ByVal subject As String) As String
    '要素を分解する
    Dim s As String() = subject.Split("?"c)
    Dim b() As Byte
    If s(2) = "B" Then
        'Base64形式の時
        b = System.Convert.FromBase64String(s(3))
    Else
        'Base64形式のみ対応
        Throw New Exception("未対応のエンコード形式です。")
    End If
    's(1)をEncoding名として、デコードする
    Return System.Text.Encoding.GetEncoding(s(1)).GetString(b)
End Function
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
/// <summary>
/// メールのサブジェクトをデコードする
/// </summary>
/// <param name="subject">デコードするメールサブジェクト</param>
/// <returns>デコードされた文字列</returns>
private static string DecodeMailSubject(string subject)
{
    //要素を分解する
    string[] s = subject.Split('?');
    byte[] b;
    if (s[2] == "B")
    {
        //Base64形式の時
        b = System.Convert.FromBase64String(s[3]);
    }
    else
    {
        //Base64形式のみ対応
        throw new Exception("未対応のエンコード形式です。");
    }
    //s[1]をEncoding名として、デコードする
    return System.Text.Encoding.GetEncoding(s[1]).GetString(b);
}

フォームが閉じられる時その原因を知るには?

注意

この記事の最新版は「フォームが閉じられる時その原因を知る」で公開しています。

質問:

VB6のQueryUnloadイベントにおけるUnloadModeのように、フォームが閉じられる時にどうしてフォームが閉じられようとしているのか(ウィンドウのCloseボタンのクリックにより閉じられようとしているのか、コードのCloseメソッドにより閉じられようとしているのか等)知るにはどのようにすればよいのでしょうか?

答え:

これにはいくつかの方法があるようです。

一つ目は、フォームのWndProcメソッドをオーバーライドし、送られてくるメッセージを調べるという方法です。次の例では、WM_ENDSESSION、WM_SYSCOMMAND、WM_CLOSEが送られてきたか調べ、フォームが閉じられる原因がOSのシャットダウンによるか、Xボタンやコントロールメニューによるか、コードによるか判断しています。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
    Const WM_CLOSE As Integer = &H10
    Const WM_ENDSESSION As Integer = &H16
    Const WM_SYSCOMMAND As Integer = &H112
    Const SC_CLOSE As Integer = &HF060
 
    Select Case m.Msg
        Case WM_ENDSESSION
            'OSのシャットダウンなどで閉じられようとしている
            Console.WriteLine("WM_ENDSESSION")
        Case WM_SYSCOMMAND
            If m.WParam.ToInt32() = SC_CLOSE Then
                'Xボタン、コントロールメニューの「閉じる」、
                'コントロールボックスのダブルクリック、
                'Atl+F4などにより閉じられようとしている
                Console.WriteLine("SC_CLOSE")
            End If
        Case WM_CLOSE
            'Application.Exit以外などで閉じられようとしている
            Console.WriteLine("WM_CLOSE")
    End Select
 
    MyBase.WndProc(m)
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
protected override void WndProc(ref Message m)
{
    const int WM_CLOSE = 0x0010;
    const int WM_ENDSESSION = 0x16;
    const int WM_SYSCOMMAND = 0x112;
    const int SC_CLOSE = 0xF060;
 
    switch (m.Msg)
    {
        case WM_ENDSESSION:
            //OSのシャットダウンなどで閉じられようとしている
            Console.WriteLine("WM_ENDSESSION");
            break;
        case WM_SYSCOMMAND:
            if (m.WParam.ToInt32() == SC_CLOSE)
                //Xボタン、コントロールメニューの「閉じる」、
                //コントロールボックスのダブルクリック、
                //Atl+F4などにより閉じられようとしている
                Console.WriteLine("SC_CLOSE");
            break;
        case WM_CLOSE:
            //Application.Exit以外などで閉じられようとしている
            Console.WriteLine("WM_CLOSE");
            break;
    }
 
    base.WndProc (ref m);
}

二番目の方法は、StackFrameを使うというちょっと変わった方法で、これは GotDotNet Message Boards で紹介されています。

ここで紹介されているYeahIGotDotNetさん、MikeWill34さんの書いたコード及び、CodeProjectで紹介されているEvilDoctorSmithさんのコードを参考にさせていただき、次のようなコードを書いてみました。詳しくは上記URLをご覧ください。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
Private Sub Form1_Closing(ByVal sender As Object, _
        ByVal e As System.ComponentModel.CancelEventArgs) _
        Handles MyBase.Closing
    Dim stack As New System.Diagnostics.StackTrace(True)
    Dim frame7 As System.Diagnostics.StackFrame = stack.GetFrame(7)
    Select Case frame7.GetMethod().Name
        Case "DispatchMessageW"
            Console.WriteLine("タスクマネージャーによる")
        Case "SendMessage"
            Console.WriteLine("コードによる")
        Case "CallWindowProc"
            If stack.FrameCount > 14 Then
                Dim frame14 As System.Diagnostics.StackFrame = _
                    stack.GetFrame(14)
                If frame14.GetMethod().Name = "WmSysCommand" Then
                    Console.WriteLine( _
                        "Xボタンまたはコントロールメニューによる")
                Else
                    If frame14.GetMethod().Name = "WndProc" Then
                        Console.WriteLine("OSのシャットダウンによる")
                    End If
                End If
            End If
        Case "DefMDIChildProc"
            Console.WriteLine( _
                "MDI子フォームのXボタンまたはコントロールメニューによる")
        Case "DefFrameProc"
            Console.WriteLine("MDI親フォームが閉じられたことによる")
        Case "ShowDialog"
            Console.WriteLine("モーダルダイアログが閉じられたことによる")
        Case Else
            Console.WriteLine("原因不明")
    End Select
End Sub
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
private void Form1_Closing(object sender,
    System.ComponentModel.CancelEventArgs e)
{
    System.Diagnostics.StackTrace stack =
        new System.Diagnostics.StackTrace(true);
    System.Diagnostics.StackFrame frame7 = stack.GetFrame(7);
    switch (frame7.GetMethod().Name)
    {
        case "DispatchMessageW":
            Console.WriteLine("タスクマネージャーによる");
            break;
        case "SendMessage":
            Console.WriteLine("コードによる");
            break;
        case "CallWindowProc":
            if (stack.FrameCount > 14)
            {
                System.Diagnostics.StackFrame frame14 =
                    stack.GetFrame(14);
                if (frame14.GetMethod().Name == "WmSysCommand")
                    Console.WriteLine(
                        "Xボタンまたはコントロールメニューによる");
                else if (frame14.GetMethod().Name == "WndProc")
                    Console.WriteLine("OSのシャットダウンによる");
            }
            break;
        case "DefMDIChildProc":
            Console.WriteLine(
                "MDI子フォームのXボタンまたはコントロールメニューによる");
            break;
        case "DefFrameProc":
            Console.WriteLine("MDI親フォームが閉じられたことによる");
            break;
        case "ShowDialog":
            Console.WriteLine("モーダルダイアログが閉じられたことによる");
            break;
        default:
            Console.WriteLine("原因不明");
            break;
    }
}

最後に紹介するのは、hidden windowを使った方法です(一番目の方法を拡張したような感じです)。この方法につきましては、下記URLで紹介されていますので、興味のある方はご覧ください。

コメント



ページ情報
  • カテゴリ : .NET
  • 作成日 : 2003-07-29 (火) 06:00:00
  • 作成者 : DOBON!
  • 最終編集日 : 2010-03-21 (日) 00:31:27
  • 最終編集者 : DOBON!
[ トップ ]   [ 新規 | 子ページ作成 | 一覧 | 単語検索 | 最終更新 | ヘルプ ]