.NET ROOM.NET ROOMトップへCOMRADE総合トップへ
「6:コントロール実行時の動作を制御する」へ

描画時のちらつきを抑える

 きちんと再描画されるようになったのはよいのですが、今度は描画のちらつきが気になるかと思います。
特に、OSの「画面のプロパティ」の「効果」で、「ドラッグ中にウィンドウの内容を表示する」が
チェックされていると、サイズを変更中に一定のタイミングでPaintイベントが発生し、
広い範囲で描画しようとするために、ちらつきが発生してしまいます。

 描画のちらつきを抑えるには、「ダブルバッファ」というアルゴリズムを使います。
これは簡単に説明すると、描画のためのバッファを2つ取り、先に背面のバッファに描画したものを、
前面に描画することにより、描画中の線などが見えなくなるようにする方法です。

ControlStyles.DoubleBufferを使う方法


■サンプル:SimpleDoubleBuffer■

 まずは.NET Frameworkにあらかじめ用意されているクラスを使って、
簡単にダブルバッファを実現する方法を説明します。
この方法では先ほどと同様に、SetStyleメソッドを使用します。
フォーム、もしくはコントロールのコンストラクタに以下のコードを追加します。

   SetStyle(ControlStyles.ResizeRedraw | ControlStyles.DoubleBuffer |
         ControlStyles.AllPaintingInWmPaint, true);

 SetStyleの第1引数のControlStylesはFlagsAttributeがTrue
のenumであるため、上記のようにビット演算が可能です。
ここでは、ビットOrで指定したすべてのフラッグをTrueに変えることができます。
ControlStylesのそのほかの値と、フォーム、ユーザーコントロール、
コントロールでのデフォルトの値を表1に示します。

表1:ControlStylesのデフォルトの値
ControlStyles コントロール ユーザー
コントロール
フォーム
AllPaintingInWmPaint True False False
CacheText False False False
ContainerControl False True True
DoubleBuffer False False False
EnableNotifyMessage False False False
FixedHeight False False False
FixedWidth False False False
Opaque False False False
ResizeRedraw False False False
Selectable True True True
StandardClick True True True
StandardDoubleClick True True True
SupportsTransparent
BackColor
False False False
UserMouse False False False
UserPaint True True True


 ControlStyles.DoubleBufferを有効にするためには、
必ず「ControlStyles.AllPaintingInWmPaint」を有効にしてください。
これで、ダブルバッファが実現できました。
バッファの設定など細かい作業は、基底クラスで行なってくれます。

バッファを自作する方法


 続いて、自分でバッファを作成する方法を説明します。
まずはじめにSetStyleですが、以下のように指定します。

   SetStyle(ControlStyles.ResizeRedraw |ControlStyles.Opaque, true);

 次に、Paintイベントハンドラを、以下のように記述します。
 
    private void NormalDoubleBuffer_Paint(
    object sender, PaintEventArgs e) {
    Bitmap local_bmp = new Bitmap(ClientRectangle.Width,
                        ClientRectangle.Height);
    Graphics bmp_graphics = Graphics.FromImage(local_bmp);
    bmp_graphics.Clear(BackColor);
    DrawAster(bmp_graphics);
    e.Graphics.DrawImage(local_bmp, 0, 0);
   }

 先ほどダブルバッファのアルゴリズムには、2つのバッファを用意すると解説しましたが、
ここでは、元のGraphics以外に、Bitmapクラスからもうひとつの
Graphics(bmp_graphics)を作成しています。
以降では説明の便宜上、元のGrapihicsを「表Graphics」と呼び、
Bitmapクラスから作成したもうひとつのGraphicsを「裏Graphics」と呼びます
(裏画像ではありません…)。

 裏Graphicsに対して、先ほど同様、DrawAsterメソッドで描画を行ない、
Graphics.DrawImageメソッドで描画済みのビットマップを表Graphicsに描画します。

どちらの方法を使えばいい?


 先ほどあんなに簡単に実現できたダブルバッファをなぜこんなに
ややこしくする必要があるかと思っていらっしゃる方もいると思いますので、
両者のメリット/デメリットを簡単にあげてみましょう。


■ControlStyles.DoubleBufferを使う方法■
メリット 実装が簡単
デメリット 裏Graphicsのサイズが必ず表Graphicsと同じになる


■自分でバッファを指定する方法■
メリット 裏Graphicsのサイズを自分で制御できる
デメリット 実装が比較的面倒


 「ControlStyles.DoubleBufferを使う方法」のデメリットであげたように、
裏Graphicsのサイズが必ず表Graphicsと同じになるため、
大きな画面ではそれだけメモリが必要になります。
そのため、意図的に異常に大きなサイズに設定すると、
エラー(System.ComponentModel.Win32Exception)が発生します。

 後ほど説明する、ScrollableControlでも同様の問題が起こります。
現在画面に表示されているサイズの分だけ裏バッファがあれば十分だと考えられますが、
ScrollableControlで現在見えていない領域のサイズを大きくした場合でも同様のエラーが出ることから、
表示サイズではなく、論理サイズの分のメモリが確保されてしまうと考えられます。

 異常に大きなサイズを指定することはコーディングで防げますが、
それでも見えていない部分までバッファを作成してしまうのはもったいという場合には、
細かく制御するために「自分でバッファを指定する方法」で実装するほうがメモリの節約になります。

PaintBackGroundイベントを無効化

■サンプル:NormalDoubleBuffer2■

ここで再び、ちらつきの原因に関して説明しますが、
通常の描画は背景色でいったん描画領域をクリアして、そこに描画したいものを描画するために、
描画に時間がかかる場合に、一瞬背景が見えてしまうために起こります。

 「ControlStyles.DoubleBufferを使う方法」では、Control Styles.Opaqueを“True”にして、
背景の描画を行なわない ようにして、ちらつきを予防しています。
そのため、

   protected override void OnPaintBackground(
    PaintEventArgs e) { }

というように、PaintBackGroundイベントを無効化することでも同様に実装することが可能です。
この場合は、SetStyleでは、ResizeRedrawだけ設定すれば十分です。

 サンプルNormalDoubleBufferとサンプルNormalDoubleBuffer2の両方を実行してみると、
両者が同じ動きをしていることがわかります。


前へ | ページトップへ | 次へ