きゃべログ

C#:CheckedListBoxのチェックされてる行の文字色を変更

C#

文字色の変更方法はやや工夫が必要

ちょっと知り合いのフォームアプリケーションのプログラムの相談に乗ったところ,「CheckedListBoxの文字色を条件に応じて変更したい」とのことだった.色々ググってみたが,ListBoxにはデザインエディタのプロパティウィンドウから追加できるDrawItemというイベントを使用すれば上手くいくらしいが,CheckedListBoxにはそれが見当たらない.ふしぎ.オーナードローができないということらしいですが,よくわからない.ListBoxの描画設定を変更したいという方は以下を参照してください.

変更方法

さて,ここからが本題になるのですが,CheckedListBoxの特定の行だけ文字色を変更する方法を模索した.困ったときのstackoverflow,困っている人は多いらしくすぐ見つかった. c# - How to dynamically change / set checkedListBox item fore colour - Stack Overflow どうやらCheckedListBoxを継承したカスタムコントロールを作成し,描画に関わるOnDrawItemというメソッドをオーバーライドして解決するらしい.というわけでさっそく実装しようと思ったもののカスタムコントロールというものを作ったことがないのでこれにも悩むことに.

カスタムコントロールの作成 ここで第一の落とし穴にはまるわけですが,色々調べていたら公式にそれらしきことが書いていました. 方法 : 既存の Windows フォーム コントロールから継承する でも画像付きで易しいサイトを見つけたので参照したのはこちら. [C#]テキストボックスのカスタムコントロールを作成する : 忘れる前にメモ プロジェクト → クラスの追加 からCustomCheckedListBox.csを作成し,ソースからCheckedListBoxを継承する.そしたらソースはこんな具合に.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication5.bin
{
    class CustomCheckedListBox : CheckedListBox
    {
    }
}

そしてビルド → ソリューションのビルドを実行する.そしてFormのデザインエディタのツールボックスを開くとCustomCheckedListBoxが追加されているのでそれをドラッグ&ドロップで配置する.綺麗に表示できました.これだけで嬉しい.

Result 1

チェックのついている行の文字色赤色にしてみる

さて自分で作ったコントロールが表示できたところで,次は機能拡張させることにします.今回はサンプルとしてチェックのついている行の文字を赤色にしてみます.とりあえず先ほど調べたstackoverflowのこのAnswerを参考にし,クラス名を変更したりusing System.Drawingを追記したりして実装してみます.この例ではチェックの入った列の文字が緑色になってくれるはずです.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;


namespace WindowsFormsApplication5.bin
{
    class CustomCheckedListBox : CheckedListBox
    {
        public CustomCheckedListBox()
        {
            DoubleBuffered = true;
        }
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            Size checkSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, System.Windows.Forms.VisualStyles.CheckBoxState.MixedNormal);
            int dx = (e.Bounds.Height - checkSize.Width) / 2;
            e.DrawBackground();
            bool isChecked = GetItemChecked(e.Index);//For some reason e.State doesn't work so we have to do this instead.
            CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
            using (StringFormat sf = new StringFormat { LineAlignment = StringAlignment.Center })
            {
                using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : ForeColor))
                {
                    e.Graphics.DrawString(Items[e.Index].ToString(), Font, brush, new Rectangle(e.Bounds.Height, e.Bounds.Top, e.Bounds.Width - e.Bounds.Height, e.Bounds.Height), sf);
                }
            }
        }
        Color checkedItemColor = Color.Green;
        public Color CheckedItemColor
        {
            get { return checkedItemColor; }
            set
            {
                checkedItemColor = value;
                Invalidate();
            }
        }
    }
}

Error1 Error2

いざ実行してみると何やらエラーが.フォームのコントロールの中にエラーの警告が出ていてなかなか迫力がある.「デザイナーでハンドルされていない例外をスローして、無効になりました。例外:‘0’のInvalidArgument= Valueは’index’に対して有効ではありません。パラメーター名:index」とのこと.さっぱりわからんので検索して参考にしたのはこちら. 【C#.NET】ListViewの選択しているインデックスを取得する|ばちブロ どうもindexが0のときにもe.Indexを呼び出しているのが悪さをしているらしいので,要素の個数が0の時には描画処理を行わないようにしてやりました.Items.Countが0でないときのみこの描画処理を行うようにします.ついでにcheckedItemColorの指定をGreenからRedに変えてやります.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing;


namespace WindowsFormsApplication5.bin
{
    class CustomCheckedListBox : CheckedListBox
    {
        public CustomCheckedListBox()
        {
            DoubleBuffered = true;
        }
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            if (Items.Count != 0)
            {
                Size checkSize = CheckBoxRenderer.GetGlyphSize(e.Graphics, System.Windows.Forms.VisualStyles.CheckBoxState.MixedNormal);
                int dx = (e.Bounds.Height - checkSize.Width) / 2;
                e.DrawBackground();
                bool isChecked = GetItemChecked(e.Index);//For some reason e.State doesn't work so we have to do this instead.
                CheckBoxRenderer.DrawCheckBox(e.Graphics, new Point(dx, e.Bounds.Top + dx), isChecked ? System.Windows.Forms.VisualStyles.CheckBoxState.CheckedNormal : System.Windows.Forms.VisualStyles.CheckBoxState.UncheckedNormal);
                using (StringFormat sf = new StringFormat { LineAlignment = StringAlignment.Center })
                {
                    using (Brush brush = new SolidBrush(isChecked ? CheckedItemColor : ForeColor))
                    {
                        e.Graphics.DrawString(Items[e.Index].ToString(), Font, brush, new Rectangle(e.Bounds.Height, e.Bounds.Top, e.Bounds.Width - e.Bounds.Height, e.Bounds.Height), sf);
                    }
                }
            }
        }
        Color checkedItemColor = Color.Red;
        public Color CheckedItemColor
        {
            get { return checkedItemColor; }
            set
            {
                checkedItemColor = value;
                Invalidate();
            }
        }

    }
}

はいできあがり!

まとめ

  • CheckedListBoxの文字色指定は結構大変だった.
  • カスタムコントロールの作り方をお勉強しました.
  • stackoverflowの答えをコピペしたらエラーが出た.
  • if文一つで解決した.
  • オリジナルのコントロールができた.嬉しい.

きゃべ / Masaya Kurahashi
きゃべ / Masaya Kurahashi
Software Engineer, Product Manager