リストボックスの行をユーザー独自色で塗りつぶす
基本的には「オーナードロー」という技術を使って、リストボックスの描画イベントを独自定義して描画を行います。といっても特に難しいことはないのでご安心ください。
リストボックスにそういった機能を求めるプログラマが多いわりに、それが基本機能として提供されていません。したがって、おのずとネットのFAQ記事が多くなります。しかし、それらで例示されるサンプルソースは、実際のところ現場で使えるかどうかは微妙です。
よくあるのは、強調行の色は変えているけれども、選択行は普通に青色に白抜き文字色で、少しガッカリな見た目のものです。
このガッカリを具体的に述べます。
Fig.001 の場合は問題ありませんが、Fig. 002 のようになると選択色が強調色を隠してしまい、その行は重要な行なのかどうかわかりません。ソフトの使い心地としては最悪です。
この記事は必ず現場で満足して使ってもらえるサンプルソースを提示します。
具体的には、下記 Fig. 003 のように5の倍数の時は赤色で強調し、それ以外のときは Windows のデフォルト選択色で項目を塗りつぶします。ただし、5の倍数を選択した場合は Fig. 004 に示したように強調色より少し暗くした色で塗りつぶします。
下記の例では、強調色は赤色で、選択色はレンガ色にしています。これでバッチリです!
下記のサンプルソースは、例として、64回の検査(テスト)結果がそれぞれ合格か不合格かを示すリストボックスを表示します。不合格の場合は行を赤色の背景で表示します。
Form1 に listBox1 と button1, button2, button3, button4, button5 を追加してください。
また listBox1 のプロパティエディタで、DrawItem イベントを追加してください。これが listBox1_DrawItem イベントになります。
続いてフォーム設計画面で listBox1 自体をダブルクリックして listBox1_SelectedIndexChanged を追加してください。実行時に、ユーザが選択したリストボックスのインデックスをウインドウ上部タイトルバーに表示します。
一番重要なのは49行目です。listBox1.DrawMode = DrawMode.OwnerDrawFixed; これを忘れてはいけません。この行がないと listBox1_DrawItem イベントがコールされないので、いつまでたっても色が変わらないと悩むことになります。
強調色からトーンを落とした暗い色を取得するのは、独自メソッドである get_dark_color() を定義し、これで取得しています。
104行目のコメントにも書いてあるように、違った色も試してみてください。
コードをコピペするにも、リストボックスやボタンのイベントの登録とかその他もろもろが面倒でなかなかビルド成功までたどりつけないと思います。下記のページでサンプルソースをダウンロードできるようにしておきました。よろしければご利用くださいませ。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace aaa
{
public partial class Form1 : Form
{
// 仮想的に64回の検査結果を格納する配列を宣言する.
const int NUM_ALLOC_RESULT = 64;
int [] ArrResult;
const int RESULT_PASS = 0; // 検査合格.
const int RESULT_FAIL = 1; // 検査不合格.
public Form1()
{
InitializeComponent();
}
private void Form1_Load( object sender, EventArgs e )
{
// 検査結果を格納する配列を確保する.
ArrResult = new int[ NUM_ALLOC_RESULT ];
// 5回に1回、検査不合格のデータ(1)をいれる.合格のときは0とする.
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
if (( k % 5 ) == 0 )
{
ArrResult[k] = RESULT_FAIL; // 1.
}
else
{
ArrResult[k] = RESULT_PASS; // 0.
}
}
// !!!これがすごく重要!!! オーナードローにすることをシステムに伝える.
listBox1.DrawMode = DrawMode.OwnerDrawFixed;
// リストボックスにとりあえずの項目を追加する.
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
String s = String.Format( "{0:d3}", k );
listBox1.Items.Add( s );
}
}
private void listBox1_SelectedIndexChanged( object sender, EventArgs e )
{
int idx = listBox1.SelectedIndex;
this.Text = String.Format( "You selected {0}", idx );
}
// 暗めの色を取得する独自メソッド.
private void get_dark_color( ref Color color_want, Color color )
{
int a = (int)( color.A );
int b = (int)( color.B );
int g = (int)( color.G );
int r = (int)( color.R );
const int OFFSET = 100;
b -= OFFSET;
g -= OFFSET;
r -= OFFSET;
if ( b < 0 ){ b = 0; }
if ( g < 0 ){ g = 0; }
if ( r < 0 ){ r = 0; }
color_want = Color.FromArgb( a, r, g, b );
}
private void listBox1_DrawItem( object sender, DrawItemEventArgs e )
{
// 背景を描画する.
e.DrawBackground(); // コメントアウトしても問題なさそう.
// リストボックスの現在の選択インデックスを取得する.
int idx = e.Index;
if ( idx > -1 )
{
SolidBrush brs_bak = null; // 背景の描画色.
SolidBrush brs_txt = null; // 文字の描画色.
// 基本背景色を決める.
Color color_fail = Color.Red; // Color.Purple なども試してみよう.
// 暗い色を取得する.
Color color_fail_selected = Color.Transparent;
get_dark_color( ref color_fail_selected, color_fail );
// 行が選択されているか、いないか、で場合分けする.
if (( e.State & DrawItemState.Selected ) != DrawItemState.Selected )
{
// 選択されていない行の描画の場合.
if ( ArrResult[idx] != 0 )
{
// 不良結果の場合.
brs_bak = new SolidBrush( color_fail );
brs_txt = new SolidBrush( Color.White );
}
else
{
// 良品結果の場合.
brs_bak = new SolidBrush( e.BackColor );
brs_txt = new SolidBrush( e.ForeColor );
}
}
else
{
// 選択されている行の描画の場合.
if ( ArrResult[idx] != 0 )
{
// 不良結果の場合.
brs_bak = new SolidBrush( color_fail_selected );
brs_txt = new SolidBrush( Color.White );
}
else
{
// 良品結果の場合.
brs_bak = new SolidBrush( e.BackColor );
brs_txt = new SolidBrush( e.ForeColor );
}
}
//描画する文字列の取得
ListBox listbox = (ListBox)( sender );
String str = listbox.Items[ idx ].ToString();
// 背景を塗りつぶす.
Rectangle rct = e.Bounds;
e.Graphics.FillRectangle( brs_bak, rct );
// 背景の上に文字列を描画する.
Font font = e.Font;
e.Graphics.DrawString( str, font, brs_txt, rct );
// ソリッドブラシを破棄する.
if ( brs_bak != null ) { brs_bak.Dispose(); brs_bak = null; }
if ( brs_txt != null ) { brs_txt.Dispose(); brs_txt = null; }
}
// 選択された項目の外枠を示す矩形を描画する.
e.DrawFocusRectangle(); // コメントアウトしても問題なさそう.
}
private void button1_Click( object sender, EventArgs e )
{
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
ArrResult[k] = 0;
}
// 再描画要求を忘れるな.
listBox1.Invalidate( true );
}
private void button2_Click( object sender, EventArgs e )
{
int tmp = 2;
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
if (( k % tmp ) == 0 )
{
ArrResult[k] = RESULT_FAIL;
}
else
{
ArrResult[k] = RESULT_PASS;
}
}
// 再描画要求を忘れるな.
listBox1.Invalidate( true );
}
private void button3_Click( object sender, EventArgs e )
{
int tmp = 3;
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
if (( k % tmp ) == 0 )
{
ArrResult[k] = RESULT_FAIL;
}
else
{
ArrResult[k] = RESULT_PASS;
}
}
// 再描画要求を忘れるな.
listBox1.Invalidate( true );
}
private void button4_Click( object sender, EventArgs e )
{
int tmp = 4;
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
if (( k % tmp ) == 0 )
{
ArrResult[k] = RESULT_FAIL;
}
else
{
ArrResult[k] = RESULT_PASS;
}
}
// 再描画要求を忘れるな.
listBox1.Invalidate( true );
}
private void button5_Click( object sender, EventArgs e )
{
int tmp = 5;
for ( int k = 0; k < ( ArrResult.Length ); k++ )
{
if (( k % tmp ) == 0 )
{
ArrResult[k] = RESULT_FAIL;
}
else
{
ArrResult[k] = RESULT_PASS;
}
}
// 再描画要求を忘れるな.
listBox1.Invalidate( true );
}
}
}