C#でListBoxの項目が無い時の背景描画処理を変更する

C#でListBoxの描画処理を変更したい場合、当然ながらOwnerDrawを使います。

DrawMode = DrawMode.OwnerDrawFixed;

これで

protected override void OnDrawItem(DrawItemPaintArgs e) {}

を定義すれば、項目は自由に描画できます。

背景描画も変更したい場合、ウィンドウプロシージャをオーバーライドして、

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0x14) //0x14:WM_ERASEBKGND
  {
    PaintBackground();//任意の描画処理
    return;
  }
  base.WndProc(ref m);
}

とすれば良いです。

ただしこれだと、項目が0個の時に正しく背景が描画されません。どんな処理を書いても、必ずBackColorの単色塗りつぶしになってしまいます。

メッセージを追っていくと、どうやら項目が0個の時にはWM_ERASEBKGNDが呼ばれないみたいです。
ListBoxは内部的に複数のコントロールから出来ているようで、項目が0個でも、ウィンドウを移動しているとたまにWM_ERASEBKGNDが呼ばれたり、よく分からない挙動をします。

WM_PAINTは再描画時に必ず呼ばれるので、このときに背景描画処理をしてしまえば良さそうです。

しかし、単にWM_PAINT時に描画しても、その後に上から単色塗りつぶしが行われて、期待した結果は得られません。

WM_PAINT時に背景描画を行ってからreturn;してしまうと、描画は上手くいくのですが、無限に再描画が発生してCPUを食い尽くしてしまいます。これは、無効領域が有効化されないためです。

解決

要するに、WinAPIで直接描画するのと同じように、BeginPaintをやって、EndPaintで無効領域を有効化してしまえば良いわけです。

[StructLayout(LayoutKind.Sequential)]
unsafe public struct PAINTSTRUCT
{
  public IntPtr hdc;
  public bool fErase;
  public RECT rcPaint;
  public bool fRestore;
  public bool fIncUpdate;
  public fixed byte rgbReserved[32];
}

[StructLayout(LayoutKind.Sequential)]
public struct RECT
{
  public int left;
  public int top;
  public int right;
  public int bottom;
}

[DllImport("user32.dll")]
public static extern IntPtr BeginPaint(IntPtr hWnd, out PAINTSTRUCT ps);

[DllImport("user32.dll")]
public static extern IntPtr EndPaint(IntPtr hWnd, ref PAINTSTRUCT ps);

protected override void WndProc(ref Message m)
{
  if (m.Msg == 0xf && Items.Count == 0) //0xf:WM_PAINT
  {
    PAINTSTRUCT ps;
    BeginPaint(Handle, out ps);

    PaintBackground(); //任意の描画処理

    EndPaint(Handle, ref ps);
    return;
  }
  base.WndProc(ref m);
}

注意

注意点としては、PAINTSTRUCT構造体が固定長の配列を持つため、unsafeブロックで囲み、fixedキーワードで配列を静的確保しています(この記述は古いC#ではだめみたいです)。

当然、ビルドオプションで「unsafeコードを許可する」にチェックを入れておいてください。

この手法を使う副作用として、VistualStudioのフォームデザイナが使えなくなります。unsafeコードを使うと、例外を吐いてだめになるみたいです。

しかたないので、これらのコードを全部#ifで囲み、条件付きコンパイルで普段は無効にしておき、リリース時に有効にすることで回避できます。これらのコードを無効にしても、背景が単色になるだけで、開発には支障がないはずです。

ここまでやるとC#で書く意味があるのか疑問になりますが、どうしてもリストボックスの背景を変更したいときの参考になれば幸いです。

デザインも頼めるシステム開発会社をお探しならBPS株式会社までどうぞ 開発エンジニア積極採用中です! Ruby on Rails の開発なら実績豊富なBPS

この記事の著者

baba

ゆとりプログラマー。 高校時代から趣味でプログラミングを初め、そのままコードを書き続けて現在に至る。慶應義塾大学環境情報学部(SFC)卒業。BPS設立初期に在学中から参加している最古参メンバーの一人。Ruby on Rails、PHP、Androidアプリ、Windows/Macアプリ、超縦書の開発などを気まぐれにやる。軽度の資格マニアで、情報処理技術者試験(16区分17回 + 情報処理安全確保支援士試験)、技術士(情報工学部門)、Ruby Programmer Gold、AWSソリューションアーキテクト(アソシエイト)、日商簿記2級、漢検準1級などを保有。

babaの書いた記事

週刊Railsウォッチ

インフラ

ActiveSupport探訪シリーズ

BPSアドベントカレンダー