Technics

Last Updated 2011/09/21


このページでは、.NET Framework および C# のテクニックについて取り上げます。

クラスとしてまとめたものは NET コンポーネントのページでも紹介しています。

テクニックやチップを紹介するサイトは多いですから、ここではできるだけほかのサイトが取り上げないテームにこだわってみました。したがって、定番のテクニックについてはほかのサイトをあたってください。

なお、私が公開している .Net Framework クラスライブラリリファレンスを見ながらサンプルコードを見ると、より分かりやすいと思います。


マルチメディア

マルチメディアの正確な定義は知りませんが、常識的にはサウンド(音声)とビデオ(動画)をさすのだろうと思います。.NET Framework 1.0/1.1 にはマルチメディア関係の機能はまったくありません。.NET Framework 2.0 には SoundPlayer クラス、SystemSound クラス、SystemSounds クラスが追加されましたが、WAVE 形式の音声を発声する程度の機能しかありませんので、マルチメディア機能とは程遠いものです。

Windows には Windows Media Player(以後、WMP と称する)が付属しています。サウンド(音声)、イメージ(静止画像)、ビデオ(動画)のすべてをまかなうことができるツールです。現在市場で流通しているほとんどのファイル形式をサポートしていますので、マルチメディアプレーヤの名にふさわしい機能を持っています。

WMP は COM ベースのアプリケーションとして作られていますので、.NET Framework アプリケーションに組み込んで使うことができます。Windows 7 に付属のもののバージョンは 12.0 で、Microsoft のサイトにいけば、SDK をダウンロードできますので、興味のある人は入手しておくといいでしょう。

Note Windows Media Player の機能は、.NET Framework の中に含まれませんが、私が公開している .NET Framework クラスライブラリリファレンス(NETClass)では関連するインターフェースを収録し、Windows Media Player を使う手順について解説しています。

WMP は OCX コントロールなので、Windows フォームに貼り付けて使用することができます。以下は、ツールボックスに WMP のツールボタンを追加する手順です。

  1. プロジェクトを起こしてツールボックスを表示する
  2. ツールボックスの [全般] のページを開く
  3. [全般] のページの内側をマウスで右クリックし、表示されたダイアログボックスの [アイテムの選択] を選択する
  4. 表示されたダイアログボックスの [COM コンポーネント] のページを表示する
  5. Windows Media Player を選択し、[OK] する

以上の手順で、ツールボックスに WMP のツールボタンが表示され、ソリューションエクスプローラの [参照設定] に AxWMPLibe と WMPLib の 2 つの参照が追加されます。以降の操作は、Button コントロールなどと同様の手順でフォームに貼り付けることができます。フォームに適当なサイズと位置にコントロールを配置し、以下に示すコードを実行してください。

 private void button1_Click(object sender, System.EventArgs e)
 {
   // ファイルをロードするとすぐに再生を開始する
   axWindowsMediaPlayer1.URL = @"test.mpeg";
 }

 private void button2_Click(object sender, Sstem.EventArgs e)
 {
   // 一時停止(再度再生は button4 をクリック)
   axWindowsMediaPlayer1.controls.pause();
 }

 private void button3_Click(object sender, System.EventArgs e)
 {
   // 再生を停止する
   axWindowsMediaPlayer1.controls.stop();
 }

 private void button4_Click(object sender, System.EventArgs e)
 {
   // 一時停止中の再生を再開する
   axWindowsMediaPlayer1.controls.play();
 }

なお、サウンドに関しては ActiveX コントロールを使う必要はありませんので、WindowsMediaPlayerClass クラスを使うことができます。このクラスをラップしたクラスを作ってみました。NET コンポーネント のページにあります。

WMI

.NET Framework SDK のヘルプ中に、WMI "Windows Management Instrumentation" に関する大きな項目があります。WMI は .NET Framework の一部ではないので、この例外的な扱い方には意味があるということなんでしょう。

Windows のシステム管理サービスは API 関数を呼び出さなければ利用できません。したがって、スクリプト系言語を使う WEB サーバーからはシステム管理サービスを使うことができませんでした。その解決策として登場したのが WMI です。

システム管理サービスの多くは .NET Framework でもサポートしますので、WMI がものすごく役に立つとは言えませんが、.NET Framework がサポートしない機能を持っていることも事実です。たとえば、ちょっと前までは BIOS に関する情報とか、CPU などの情報を取得するにはアセンブリ言語のお世話になるほかありませんでしたが、WMI を利用すればプロパティを参照するだけで済みます。

WMI を .NET Framework プロジェクトに組み込む手順を説明します。

  1. [プロジェクト]-[参照の追加] の [.NET] のページを開く
  2. System.Management にフォーカスをあてて、[OK]する

これで DLL への参照がプロジェクトに追加されます。コンポーントの名前空間名は次のとおりです。

  using System.Management;

コモンダイアログボックスのカスタマイズ

下図は、Borland C++ Builder の TOpenPictureDialog コントロールを使って、画像ファイルの一つを選択したところです。

TOpenPictureDialog

画像ファイルを選択するとき、このようにプレビュ機能があると便利ですね。VCL では標準のコモンダイアログボックスをカスタマイズすることが簡単にできますが、.NET Framework のコモンダイアログをカスタマイズすることは容易ではありません。その最大の理由は、OpenFileDialog クラスが sealed 属性になっているためです。Microsoft はコモンダイアログをカスタマイズしてほしくないということかというと、そうではないようです。カスタマイズする方法を公式の情報として公開しているぐらいですから。

Extend the Common Dialog Boxes Using Windows Forms 1.x

サンプルコードもダウンロードできますので、試してください。カスタマイズというより、0 から作っています。もし、これが VCL であれば、比較的簡単に実現できます。

コモンダイアログクラスに限りませんが、当然、public にすべきと思うクラスを sealed 属性にしているケースがたくさんあります。私はこの一件だけを見ても、Microsoft のクラスの開発者の中に、オブジェクト指向プログラミングという概念を理解していないものがいるのではないかと疑っています。

ColorDialog コントロールを任意の位置に表示する

.Net Framework のコモンダイアログボックスはすべて表示位置を指定することができません。常に画面中央に表示されます。これは、CommonDialog クラスの HookProp メソッドにおいて常に画面中央に表示する設定になっているからです。そこで、ColorDialog コントロールを任意の位置に表示するテクニックを紹介します。

このテクニックの要点は、ColorDialog クラスが奇跡的に sealed 属性でないことです。そこで、ColorDialog クラスから派生するクラスを作成し、HookProc メソッドをオーバーライドします。

using System.Runtime.InteropServices; // DllImport

namespace ColorDialogPosition
{
  public partial class Form1 : Form
  {
    public Form1()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      ColorDialogEx dlg = new ColorDialogEx();

      // button1 の左上隅にダイアログボックスの左上隅の座標を合わせて表示する
      Point p = this.PointToScreen(new Point(button1.Left, button1.Top));
      dlg.Position = p;

      if (dlg.ShowDialog(button1) == DialogResult.OK)
        this.BackColor = dlg.Color;

      dlg.Dispose();
    }
  } // end of Form1 class

  //**************************************************************************************
  // ColorDialogEx(表示位置を設定できる ColorDialog クラス)
  //**************************************************************************************
  internal class ColorDialogEx : ColorDialog
  {
    private Point FPosition;
    public Point Position { set { FPosition = value; } }

    public ColorDialogEx() : base()
    {
    }

    protected override IntPtr HookProc(IntPtr hWnd, int msg, IntPtr wparam, IntPtr lparam)
    {
      if (msg == 0x110) // WM_INITDIALOG
      {
        RECT r = new RECT();

        // ダイアログボックスの位置とサイズを取得する
        GetWindowRect(hWnd, ref r);

        // ダイアログボックスの位置を変更する
        MoveWindow(hWnd, FPosition.X, FPosition.Y, r.right - r.left, r.bottom - r.top, true);

        return IntPtr.Zero; // HookProc メソッドでメッセージを処理済みにする
      }

      // WM_INIDIALOG 以外のメッセージに対しては元のコントロールにまかせる
      return base.HookProc(hWnd, msg, wparam, lparam);
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool GetWindowRect(IntPtr hWnd, [In, Out] ref RECT lpRect);

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    private static extern bool MoveWindow(IntPtr hWnd, int X, int Y,
        int nWidth, int nHeight, bool bRepaint);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT
    {
      public int left;
      public int top;
      public int right;
      public int bottom;
    }
  } // end of ColorDialogEx class
} // end of namespace

ところで、このコードをテストしていたときに奇妙なことに気付きました。ColorDialog コントロールの Color プロパティのデフォルトは Color.Black ですが、この状態でコントロールを表示し、[色の作成] ボタンをクリックして色を自由設定できるようにしても色を選択することができません。さらに奇妙なことに、Color プロパティに黒色以外の色を指定すると、先に説明した色の自由選択が可能になります。

試しに、Delphi でも同じことをやってみましたが、同じ問題が発生しました。しかも、黒色だけでなく、白色や黒色に近い暗い色でも発生します。つまり、.Net Framework の問題ではなく、Windows の機能のほうに原因があるよぅです。これが Windows のバグなのか、仕様なのかは分かりません。ちなみに、Windows Vista および XP の両方でテストしましたが同じ結果でした。

この問題の対処法はコントロールを表示する前に Color プロパティに黒色以外の色を指定するか、[色の作成] 部分を表示できないようにすることです。


ガベージコレクタと Dispose メソッド

Dispose メソッドはオブジェクトを開放するときに呼び出すメソッドですが、.NET Framework にはガベージコレクタ(以後、GC と称する)が装備されましたので、Dispose メソッドを呼び出されなければ必ずメモリリークを起こすというわけではありません。多くの WEB サイトにおいて、Dispose メソッドを呼び出すべきかどうかの議論が百出しています。C# の場合、Dispose メソッドではなく、using ステートメントを使うべきだ、いやいや GC にまかせるべきだ、Finalize メソッドはどうするのか、とまさに仁義なき戦いが繰り広げられています。

GC に関しては Books のページで紹介する Jeffrey Richter の「プログラミング Microsoft .NET Framework」にかなりのページ数を使っての解説があります。しかし、どちらかというと、GC がどのうように設計されたかの説明に主眼をおいているため、GC を使う立場からの解説ではありません。そこで、WEB サイトをウロウロしていたら、比較的短くまとめた記事を見つけましたので、紹介します。ホームページの管理者の名前は不明ですが、次のサイトに入ってください。

http://studiodragoonnet.main.jp/tech/

メインメニューの Tips を選択し、さらに .NET のページに入ります。目的のページは「ガベージコレクションの仕組み」です。このページの記事程度のことをプログラマは知っていればいいのではないかと思います。ただし、一つだけ補足するすると、Finalaize メソッドはアンマネージリソースの開放に使うべきです。

さて、本サイトでは .NET Framework のクラスライブラリは VCL をベースに作ったものだと説明しています。事実、VCL の中にも Dispose メソッドと Finalize メソッドがあります。そこで、VCL におけるそれらはどのような機能を果たしているかを調べてみました。「Finalize 手続き」という項目の中にヒントらしきものを見つけました。「手続き」は "procedure" にあてた訳語ですが、Finalize メソッドと解釈してください。以下はその部分を抜き出したものです。

Note 「動的に割り当てられた変数の割り当てを Dispose 手続き以外の方法で解除する Delphi コードでのみ Finalize 手続きを使います。動的配列は Dispose 手続きで割り当てを解除できませんが,Finalize に渡すことで解放できます。

Dispose を使って割り当てを解除するグローバル変数,ローカル変数,オブジェクト,動的変数では,インスタンスを廃棄するときにその変数が保持するすべての長い文字列とバリアントの終了処理を行うコードがコンパイル時に生成されます。」

詳しい説明は避けますが、上記の文章を .NET Framework 的に言い換えると、これらのメソッドの役割が浮き彫りになってくるように思います。

Note 「マネージコードによるメモリ領域の割り当ては Dispose メソッドで解放する。Dispose メソッドではアンマネージコードによるメモリ領域の開放はできないが、Finalize メソッドを使うことで開放できる。」

C# の場合、Finalize メソッドというものは存在せず、デストラクタがその機能を代行します。要するに、アンマネージコードにより確保したメモリ領域は特殊な手順を踏む必要があるため、デストラクタがあると、C# のコンパイラがしかるべきコードを自動的に生成する、と理解できないでしょうか。つまり、意味もなく、デストラクタを作ってはいけないというふうにも解釈できます。GC の動作との整合性が問題なのかもしれません。

最後に、私なりの結論を述べておきます。

「Dispose メソッドが public な場合は必ず、Dispose メソッドを呼び出す。」です。

クラスの作成者が Dispose メソッドを public 宣言したということは必ず、呼び出してほしいとのメッセージだと思うからです。また、Dispose メソッドを呼び出すと、何か悪影響があるかといえば何もありません。ようするに、わずか 1 行のコードを書くか、書かないかの違いでしかありません。

【補足】

public な Dispose メソッドを持たないクラスがあります。この場合は GC にまかせるほかありませんが、オブジェクトがいつは破棄されるかまでは分かりません。このような場合は、using ステートメントを使うことで、using ブロックを抜けるときにオブジェクトが破棄されるようにしておくほうが安全です。

変数の命名規則

先に取り上げた Dispose メソッドと並んで、変数の命名規則に関する議論も盛んですね。これについては、好みの問題がからんでくるため、よけいに感情的な議論になる傾向があります。Microsoft もガイドラインらしきものを提唱してはいるのですが、Microsoft が提供するサンプルコードでさえガイドラインを守っていないぐらいですから、それほど根が深いということなんでしょう。

私の考え方はシンプルです。「変数名は見ただけでその性質が分かるほうがよい」ということにつきます。たとえば、Windows メッセージなどの定数名は WM_KEYDOWN などのようにすべて大文字で構成していますので、定数であることがすぐに理解できます。

ここで、変数の命名規則に関する私なりのルールをリストアップします。

1. メンバ変数はフィールド "Field" をあらわす "F" を先立たせる
Visual C++ ではクラスのメンバ変数に対して m_XXX のように、メンバをあらわす "m" をプリフィックスとして使う人が多いようです。VCL は Object Pascal で作られているため、必然的に Pascal 的な発想をするからでしょうか、フィールドをあらわす "F" を使います。たとえば、こんな感じです。
       private ArrayList FDataList;
私も最初は奇妙な感じを持ちましたが、今では慣れました。"m_" より 1 文字で済むメリットもあります。 一つハッキリしていることは、メンバ変数であるかどうかを明らかにすべきだということです。

2. コントロール変数名にはコントロールをあらわす略称を付ける
私は元々、Visual Basic プログラマであったことに起因すると思いますが、テキストボックスコントロールであれば、txtFileName とか、ボタンコントロールなら btnStart などと、コントロールの略称をプリフィックスとして付けています。FileNameTextBox や StartButton でもいいのでしょうが、変数名のコンパクトさの点ですぐれていると思います。Visual Basic プログラマなら賛成してもらえることでしょう。

3. ローカル変数名はキャメル形にする
キャメル形とは imageWidth とか、redPen のように、先頭の語句は小文字で始まり、次の語句の先頭は大文字にする、 つまり、キャメル "camel"(ラクダ)のコブのような形式をさします。語句が一つだけの場合はすべて小文字にします。こうすることで、メンバ変数やメソッド名との区別ができます。伝統的な C プログラマは変数名やメソッド名にすべて小文字を使いたがる傾向がありますが、新しいコーディングスタイルを取り入れることをおすすめします。

4. 定数名はすべて大文字にする
こうする理由は先に説明したとおりです。

GZipStream クラス

GZipStream クラスは .NET Framework 2.0 に追加されたものです。詳しくは、ここをご覧ください。

ファイルの暗号化

.NET Framework にはファイルを暗号化するクラスがたくさんあります。いえ、多すぎます。暗号化アルゴリズムが古くてもはや使っていないようなものまで含んでいることにはそれなりの理由があるのでしょうが、スッキリしません。

さて、RC2CryptoServiceProvider クラスの実用的な使い方について解説するページをもうけました。ここでは、PasswordDeriveBytes クラスの使い方についても触れています。

フォームを印刷する

フォームを印刷したいとの要望が多いようですね。Visual Basic には PrintForm メソッドというそのものズバリのメソッドがありますが、.Net Framework にはそのようなメソッドはありません。しかし、手はあります。Control クラスの DrawToBitmap メソッドと Graphics クラスの CopyFromScreen メソッドの 2 つです。

DrawToBitmap メソッドにはいくつかの制限があります。たとえば、RichTextBox コントロールや WebBrowser コントロールがフォーム上に配置されている場合、正常に取り込めないようです。

private void button1_Click(object sender, EventArgs e)
{
  Bitmap bmp = new Bitmap(this.Width, this.Height);
  this.DrawToBitmap(bmp, new Rectangle(0, 0, this.Width, this.Height));
  bmp.Save("form2.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
  bmp.Dispose();
}

CopyFromScreen メソッドはフォームを現状のイメージのまま、つまり、画面上のイメージのまま取り込むことができます。以下のコードは、フォームのビットマップを作成し、ファイルに保存します。

private void button2_Click(object sender, EventArgs e)
{
  Bitmap bmp = new Bitmap(this.Width, this.Height);
  Graphics g = Graphics.FromImage(bmp);
  g.CopyFromScreen(new Point(this.Left, this.Top), new Point(0, 0), this.Size);
  bmp.Save("form.bmp", System.Drawing.Imaging.ImageFormat.Bmp);
  g.Dispose();
  bmp.Dispose();
}

RichTextBox コントロールをフォームに配置し、Text プロパティを設定して、上記の 2 のコードを実行すると、両者の違いが分かります。

相対パス・絶対パスの相互変換

相対パスから絶対パスへの変換は、Path クラスの GetFullPath メソッドを使えば可能になります。しかし、絶対パスを相対に変換する機能はありません。

Uri クラスの MakeRelativeUri メソッドを使えばそれらしいことはできますが、ズバリの解答ではありません。そこで、絶対パスから相対パスに変換する機能をクラス化してみました。

FileNameEx.lzh (2,319 bytes)

型変換クラス(TypeConverter クラス)

System.ComponentModel 名前空間の中に TypeConverter クラスがあり、TypeConverter クラスから派生するクラスがたくさんあります。TypeConverter クラスの役割を一言で言うと、オブジェクト型を文字列型、またオブジェクトの文字列表現をオブジェクト型に変換するものです。

Note このクラスの役割はこれにとどまらないところがヤヤコシイのですが、後日、記事を追加する予定です。

さて、型変換クラスはどういう場面で使うかです。

アプリケーションを作成するとき、カスタマイズ情報などを初期化ファイルとか、Windows レジストリに記録し、次回のアプリケーション起動時に記録しておいた情報を読み込んで各種の設定をするものです。情報を初期化ファイルやレジストリに書き出すときは、オブジェクト型を文字列表現に変換し、読み込むときは文字列表現からオブジェクト型に変換しなければなりません。これが型変換クラスの第一義的な使い方です。

たとえば、Rectangle 型を初期化ファイルに記録するとき、"10, 20, 30, 40" のような文字列で記録し、それを読み込むとき  Rectangle 型として情報を得ることができます。以下にコード例を示します。

private void button1_Click(object sender, EventArgs e)
{
  Rectangle r = new Rectangle(10, 20, 30, 40);

  // Rectangle 型に対応する型変換オブジェクトを取得する
  // TypeDescriptor クラスの GetConverter メソッドを通じて型変換オブジェクトを取得しなさいと
  // いうことになっている
  // 将来、TypeConverter クラスの実装が変更された場合にもこうしておけばコードを変更する
  // 必要がないという意味はある
  TypeConverter converter = TypeDescriptor.GetConverter(typeof(Rectangle));

  // ConvertTo メソッドは通常、オブジェクト型から文字列に変換するときに使う
  object obj = converter.ConvertTo(r, typeof(string));

  Text = obj.ToString();
}
//----------------------------------------------------------------------------------
private void button2_Click(object sender, EventArgs e)
{
  string s = "10, 20, 30, 40";

  TypeConverter converter = TypeDescriptor.GetConverter(typeof(Rectangle));

  // ConvertForm メソッドは通常、文字列表現からオブジェクト型に変換するときに使う
  object obj = converter.ConvertFrom(s);

  Text = obj.ToString();
}

以下に、.Net Framework 標準の型変換クラスをリストアップしておきます。個々のクラスの使い方までは示しませんが、Rectangle 型の例と基本的な手順はまったく同じですので、各自で確かめてください。

型変換クラス変換可能な文字列表現
System.ComponentModel.BooleanConverter"True" または "False"
System.ComponentModel.CharConverter設定した文字のまま
System.ComponentModel.DateTimeConverter"2006/03/16 15:16"
System.ComponentModel.EnumConverterAnchorStyles.Left | AnchorStyles.Top に対して "Top, Left"
System.ComponentModel.GuidConverterGUID 型の文字列表現のまま
System.ComponentModel.StringConverter設定した文字列のまま
System.ComponentModel.TimeSpanConverter"02:20:50"
System.Drawing.ColorConverterColor.Red に対して "Red"
System.Drawing.FontConverter"MS UI Gothic, 9pt"
System.Drawing.ImageFormatConverterImageFormat.Bmp に対して "Bmp"
System.Drawing.PointConverter"20, 30"
System.Drawing.RectangleConverter"20, 30, 583, 358"
System.Drawing.SizeConverter"583, 358"
System.Windows.Forms.CursorConverterCursors.Cross に対して "Cross"
System.Windows.Forms.KeysConverterKeys.A | Keys.Control に対して "Ctrl+A"
System.Windows.Forms.LinkArea.LinkAreaConverter "2, 3"
System.Windows.Forms.OpacityConverterdouble 型の変換と同じ

基になる RCW から...

Visual Studio を使っている人は以下に示すメッセージを見たことはありませんか。

Note 「ページを読み込もうとしているときにエラーが発生しました。
基になる RCW から分割された COM オブジェクトを使うことはできません。」

COM オブジェクトを使っていないのにもかかわらずです。

RCW は "Runtime Callable Wrapper" の略ですが、詳しいことは依然として不明でした。そこで、WEB サイトをあたったところ、以下のサイトに解説があることが分かりました。

http://msdn.microsoft.com/ja-jp/library/aa159887.aspx

このサイトを要約すると、COM オブジェクトを開放したあと、RCW を経由せずに COM オブジェクトにアクセスしたときに発生するということらしい。

Visual Studio が COM オブジェクトを使っていることは理解していますが、どう対処すればいいのかとウロウロしていたところ、以下のサイトで "Microsoft Source Analysys Tool for C#" に問題があるという記事を見つけました。

http://code.msdn.microsoft.com/sourceanalysis/WorkItem/View.aspx?WorkItemId=9

このツールをインストロールしている場合、Visual Studio の IDE のメニュー [ツール] を開くと、ツール名がリストアップされるはずです。そこで、コントロールパネルの [プログラムと機能] を開いて、上記のツールを削除すると問題は解決しました。

なお、このツールの最新バージョンではバグを修正したようですが、新しいバージョンがリリースされたのかどうかは分かりません。

スプラッシュウインドウ

スプラッシュウインドウ "splash window" とは、アプリケーションを起動したときにごあいさつとして表示するフォームです。その役割は単なるごあいさつの場合もありますが、スプラッシュウインドウを表示中にバックグラウンドでアプリケーションを初期化する処理を実行する場合もあります。

Note ちなみに、"splash" の英語としての意味は、「やらに目立つ新聞の大見出しや写真などの画像」です。

ここでは Visual Studio 2005 C# を使って .Net Framework アプリケーションにスプラッシュウインドウを組み込む手順を説明します。スプラッシュウインドウを組み込む方法はいくつかありますが、.Net Framework 的な方法を取り上げることにします。.Net Framework 的とは、イベントを利用するという意味です。

メインフォーム

メインフォームという言葉は .Net Framework の中で定義されているわけではありませんが、.Net Framework SDK のアチコチにはでてきます。たとえば、ApplicationContext クラスです。いずれにしろ、メインフォームはアプリケーションを起動したときに最初に表示されるフォームです。Visual Studio で Windows フォームアプリケーションを作成するとき、デフォルトの状態で追加されるフォーム(デフォルトのオブジェクト名は Form1)が自動的にメインフォームとなります。

ところで、複数のフォームを持つアプリケーションの場合、プロジェクトの [プロパティ] の中に、どのフォームをメインフォームにするかを設定する手段がありませんね。

Note Microsoft が忘れたのでしょうか。

しかし、手はあります。以下は、プロジェクト内に自動的に作成される Program.cs のコードです。

namespace SplashTest
{
  static class Program
  {
    [STAThread]
    static void Main()
    {
      Application.EnableVisualStyles();
      Application.SetCompatibleTextRenderingDefault(false);
      Application.Run(new Form1());
    }
  }
}

この中の Application.Run(new Form1()); の "Form1" をメインフォームとしたいフォームオブジェクトに変更すれば OK です。

スプラッシュウインドウの形式

スプラッシュウインドウの形式は決まっているわけではありませんが、通常は以下に示す条件によることが多いようです。

フォームのタイトルバーをなくすには、FormBorderStyle プロパティを None にまた、スクリーン中央に表示するには、StartPosition プロパティを CenterScreen に設定します。これらは必須の条件ではありませんが、通常はこうします。

フォームを閉じるタイミングはアプリケーションによって異なると思いますが、ここではタイマーを組み込んで一定時間を経過した時とします。

さて、問題はどうやってメインフォームより先に表示するかです。ここからは実際にプロジェクトを作成しながら説明しましょう。

スプラッシュウインドウを表示する

いつもの手順で Windows フォームアプリケーションプロジェクトを起こします。最初に表示されるフォームが自動的にメインフォームとなります。[プロジェクト]-[Windows フォームの追加] で Form2 をプロジェクトに追加します。Form2 をスプラッシュウインドウとして表示することにします。

Form2 に timer1 を追加し、その Interval プロパティを 2000(2 秒)にしておきましょう。

namespace SplashTest
{
  public partial class Form2 : Form
  {
    public Form2()
    {
      InitializeComponent();

      timer1.Interval = 2000;
      timer1.Enabled = true;
    }

    private void timer1_Tick(object sender, EventArgs e)
    {
      timer1.Enabled = false;
      this.Close();
    }
  }
}

以下は、Form1 の Load イベントハンドラですが、ここがスプラッシュフォームを表示するポイントです。

private void Form1_Load(object sender, System.EventArgs e)
{
  // スプラッシュフォームを作成し、表示する
  Form2 splashForm = new Form2();

  // Form2 をモーダル表示するが timer1 が 2 秒たつと、
  // Form2 はクローズする
  splashForm.ShowDialog();

  // スプラッシュフォームを破棄する
  if (!splashForm.IsDisposed)
    splashForm.Dispose();

  // メインフォームをアクティブにする
  this.Activate();
}

メインフォームの初期化終了までスプラッシュウインドウを表示する

前項の方法ではスプラッシュウインドウを表示している間、アプリケーションは完全に停止してしまいます。ごあいさつだけが目的であればこれでもいいですが、アプリケーションを初期化するための時間稼ぎとしてスプラッシュウインドウを表示する場合には不都合です。

メインフォームの Load イベントでアプリケーションを初期化するためのコードを実行する場合に、その間、スプラッシュフォームを表示する手順について説明します。なお、前の項と同様、メインフォームは Form1 で、スプラッシュフォームは Form2 とします。

今回は Form2 には何もしません。タイマーも使いません。以下は、Form1 の Load イベントハンドラです。

private void Form1_Load(object sender, System.EventArgs e)
{
  // スプラッシュフォームを作成し、表示する
  Form2 splashForm = new Form2();
  splashForm.Show(); // Form2 をモードレス表示する
  Application.DoEvents();

  // アプリケーションの初期化のかわりに c:\windows ディレクトリ内の
  // ファイルを列挙する(ただし、ここでは何もしない)
  string[] files = Directory.GetFiles(@"C:\Windows");

  foreach (string file in files)
  {
    Thread.Sleep(100); // CPU の性能に応じて調整のこと
  }

  // スプラッシュフォームを破棄する
  splashForm.Dispose();

  // メインフォームをアクティブにする
  this.Activate();
}

【注意】

タイトルバーを持たないフォームを使う場合は、プログラムに不都合があったときにフォームを終了することができなくなる可能性があります。そこで、フォームのどこかをクリックするとフォームを閉じる仕組みを作っておくことをすすめます。

アプリケーションドメイン

アプリケーションドメイン(以後、"AppDomain")とは何かについて、一言で言えば、1 つのプロセス内で複数のアセンブリ(実行可能ファイル)を起動するための仕組みです。

.Net Framework SDK、市販の参考書、WEB サイトなどをあたってもそのように解説しています。ところで、この仕組みのメリット、つまりなぜ、AppDomain を .Net Framework に導入したかについて説明するものは多くはありません。多くないというより皆無といっていいと思います。

ある WEB サイトでは、「FireBird というデータベースは単一プロセスでしかデータベースにアクセスできないため、複数の EXE を単一プロセスとして動作させている」、あるいは「単一プロセスにすると CLR などが 1 つで済むため、消費メモリが少なくなる」との記述があります。

また、別のサイトでは、「アプリケーションドメインはセキュリティ管理のための境界であって、実行の管理とは関係ない」との記述もありました。

次に、書籍のほうを見てみましょう。以下の本は Microsoft の正式解説書で、著者の Jeffrey Richter は内部構造の分析を得意とする人です。

「プログラミング Microsoft .Net Framework」 Jeffrey Richter, Microsoft Press

この本でさえ、AppDomain をどう使うかについては触れていません。

最後に、Microsoft の意見を聞いてみましょう。「.Net Framework FAQ」の中の「アプリケーションドメインとは?」の記事がもっとも期待できる記事です。少し長いですが、全文を収録します。

「アプリケーション ドメイン (AppDomain と表記することがあります) は、アプリケーションを隔離する役割を果たす仮想プロセスです。同じアプリケーション スコープ内で (言い換えると、アプリケーションのエントリ ポイントから始まるオブジェクト アクティベーションのシーケンスの中の任意のポイントで) 作成されたすべてのオブジェクトは、同じアプリケーション ドメイン内に作成されます。1 つのオペレーティング システム プロセス内に複数のアプリケーション ドメインが存在することができるため、アプリケーションを隔離するための低コストの手段として利用できます。

OS プロセスは、互いに独立したメモリ アドレス空間を持つことで隔離を行います。これは効果的ですが、コストが高く、大規模な Web サーバーに必要な数に対応できるだけのスケーラビリティは持っていません。一方、共通言語ランタイムはアプリケーション ドメイン内で実行されるコードが使用するメモリを管理することで、アプリケーションの隔離を行います。これにより、アプリケーションはドメインの外のメモリにはアクセスできなくなります。この方法で管理できるのはタイプ セーフなコードだけであることに注意してください (ランタイムは、アプリケーション ドメインにアンセーフなコードがロードされた場合には、隔離を保証することはできません)。」

要約すると、次の 2 点にしぼられると思います。

「低コストの手段」はメモリ消費量を節約できるという意味だと思います。しかし、Windows フォームアプリケーションであればメモリ消費量を心配する必要はありませんから(もちろん、通常は、ですが)、この理由はオマケみたいなものだと思います。結局、WEB サーバーにおいて、OS レベルで大量のプロセスを管理することは負荷が大きいので、プロセスに代わるものとして、AppDomain を導入した、という理解でいいのではないかと思います。

DoubleBuffered プロパティ

グラフィックスに関係するコントロールを作成するとき、その OnPaint メソッド内で必要な処理を実行することになりますが、メソッドの引数として戻る Graphics オブジェクトには無効化領域を取得する機能がないので、処理を高速化するため、Windows API 関数を直接呼び出す人がいます。しかし、この問題を .Net Framework が無視するはずがありません。

Control クラスの中に DoubleBuffered プロパティがあります。これは BufferedGraphics オブジェクトを有効にするもので、true に設定するとコントロールを再描画するときにグラフィックスバッファを利用でき、コントロールを再描画するときのチラツキを防止する効果があります。

DoubleBuffered プロパティを true に指定したときの BufferedGraphics オブジェクトは Control クラスから派生するクラスを作成する場合に、OnPaint メソッドの引数として戻る Graphics オブジェクトのみを対象とする点に注意してください。それ以外の手段で取得した Graphics オブジェクトには効果がありません。

なお、BufferedGraphicsContext クラスと BufferedGraphicsManager クラスとを利用すると、独自のダブルバッファリングを構築することができます。これについては私が公開している .Net Framework クラスライブラリリファレンスを参照してください。

−以上−