5360


現在，VisualStudioC#のフォームアプリケーションで，CheckedListBoxを使用してToDoリストを制作しています．

ListBoxの機能に加えて各アイテムがチェックされているかどうかという状態を保存できますが，  
それに加えて各アイテムに対し重要であるかどうかという状態を保存できるようにしたいと考えています．  
CheckedListBoxを継承したカスタムコントロールを作成して，これを実現することは可能でしょうか．この重要かどうかを変更できるメソッドをフォームから呼び出せるようにし，最終的にはOnDrawItemをオーバーライドし，重要な要素のForeColorを変えるということを考えています．

できれば具体的な実装方法を教えていただきたいです．  
よろしくお願いします．


CC BY-SA 3.0
2015-01-26T14:02:29.287
5346
2015-01-28T11:30:56.737
7711
question
c#
CheckedListBoxのItemそれぞれに状態を示すbool型のパラメータを持たせたい


継承ではないですけどデータクラスを定義してリストを作り、`CheckedListBox`にデータバインドするという方法が良いのではないかと思います。

[code]

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    
        public class ListData
        {
            public string Text { get; set; }
            public bool IsImportant { get; set; }
        }
    
        private ListData[] array;
    
        private void Form1_Load(object sender, EventArgs e)
        {
            array = new[] 
            {
                new ListData()
                {
                    Text="AAA"
                },
                new ListData()
                {
                    Text="BBB",
                    IsImportant=true
                },
                new ListData()
                {
                    Text="CCC"
                }
            };
    
            checkedListBox1.DataSource = array;
            checkedListBox1.DisplayMember = "Text";
        }
    }
    
[/code]

リストの要素数が変動する場合は`ListData[]`ではなく`BindingList<ListData>`を使用すればよいです。

## 2015/01/28追記

ご指摘の通り、変更通知インターフェイス付のデータソースを利用した場合はCheckedの値が保持されません。これは[CheckedListBoxのDataSourceが非Browsableになっている](https://msdn.microsoft.com/ja-
jp/library/k86hzt9t%28v=vs.110%29.aspx)理由でもあります。

これに対応するには自分でDataSourceの変更通知を受け取る必要があります。

[code]

    public class CheckedListBoxEx : CheckedListBox
    {
        // 項目の選択イベント
        protected override void OnItemCheck(ItemCheckEventArgs ice)
        {
            var list = DataSource as IList<ListData>;
            if (ice.Index < list.Count)
            {
                list[ice.Index].IsChecked = ice.NewValue == CheckState.Checked;
            }
            base.OnItemCheck(ice);
        }
    
        // DataSourceの変更時
        public new object DataSource
        {
            get
            {
                return base.DataSource;
            }
            set
            {
                // イベント通知の解除
                var currentList = base.DataSource as IBindingList;
                if (currentList != null)
                {
                    currentList.ListChanged -= DataSource_ListChanged;
                }
    
                base.DataSource = value;
    
                // イベント通知の設定
                var newList = base.DataSource as IBindingList;
                if (newList != null)
                {
                    newList.ListChanged += DataSource_ListChanged;
                }
                 
                OnDataChanged();
            }
        }
    
        // DataSourceでのイベント発生時
        private void DataSource_ListChanged(object sender, ListChangedEventArgs e)
        {
            OnDataChanged();
        }
    
        // 表示の更新
        private void OnDataChanged()
        {
            BeginUpdate();
            var list = base.DataSource as IList<ListData>;
            for (var i = 0; i < Items.Count; i++)
            {
                SetItemChecked(i, list != null && i < list.Count && list[i].IsChecked);
            }
            EndUpdate();
        }
    }
    
[/code]

なお上記のコードには

  * DataSourceの型をIBindingListに限定している
  * 変更通知の範囲を見ずに全て更新している
  * 要素のプロパティ変更を受け取っていない
  * 初期表示にチェックが反映されない(原因不明)

などと色々な手抜きや問題点があります。


CC BY-SA 3.0
2015-01-26T14:30:31.477
5352
2015-01-28T11:30:56.737
2020-06-17T08:14:45.997
-1
5750
5346
answer


データバインディングで解決できます。  
Infoクラスは、1つの項目の状態を保持するためのクラスとなります。

CheckedListBoxクラスを継承したCheckedListBoxExを使用し、  
OnDrawItemメソッドをオーバーライドし、InfoクラスのImportantプロパティで見た目を変えています。

ObservableCollectionを使用していますがListでも、この場合は、構いません。

  * フォームでは、データバインディングに限界があります、できればWPFに移行を推奨します。 
  * 下記のコードは、参考に程度にしてください。適当実装なため…

* * *
[code]

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace WindowsFormsApplication1 {
        public partial class Form1 : Form {
            private readonly BindingSource binder = new BindingSource();
            private readonly ObservableCollection<Info> items = new ObservableCollection<Info>() {
                new Info() {
                    Checked = true,
                    Name = "hoge"
                }
            };
            public Form1() {
                InitializeComponent();
            }
    
            private void Form1_Load( object sender , EventArgs e ) {
                this.binder.DataSource = items;
                this.checkedListBox1.DataSource = this.binder;
            }
    
            private void button1_Click( object sender , EventArgs e ) {
                this.items[0].Important = !this.items[0].Important;
    
                this.binder.Add( new Info() {
                    Checked = true ,
                    Important = true ,
                    Name = "hoge"
                } );
            }
        }
    
        public sealed class Info : INotifyPropertyChanged {
            public bool Checked {
                get {
                    return this._Checked;
                }
                set {
                    if( this._Checked == value )
                        return;
                    this._Checked = value;
                    this.OnPropertyChanged( "Checked" );
                }
            }
            private bool _Checked;
    
            public string Name {
                get {
                    return this._Name;
                }
                set {
                    if( this._Name == value )
                        return;
                    this._Name = value;
                    this.OnPropertyChanged( "Name" );
                }
            }
            private string _Name;
    
            public bool Important {
                get {
                    return this._Important;
                }
                set {
                    if( this._Important == value )
                        return;
                    this._Important = value;
                    this.OnPropertyChanged( "Important" );
                }
            }
            private bool _Important;
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            private void OnPropertyChanged( string name ) {
                if( this.PropertyChanged != null )
                    this.PropertyChanged.Invoke( this , new PropertyChangedEventArgs( name ) );
            }
    
            public override string ToString() {
                return this.Name;
            }
        }
    
        public class CheckedListBoxEx : CheckedListBox {
            protected override void OnDrawItem( DrawItemEventArgs e ) {
                var item = this.Items[e.Index] as Info;
                if( item != null ) {
                    e.Graphics.FillRectangle( item.Important ? Brushes.Red : Brushes.White , e.Bounds );
                }
                base.OnDrawItem( e );
            }
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-26T14:56:57.670
5360
2015-01-26T14:56:57.670
2207
5346
answer


「重要な要素のForeColorを変える」は以下のような感じでCheckedBoxListを継承します。  
（Form側は @pgrhoさんの回答で実装されている前提）

ForeColorを変えるというよりは、独自に背景や文字を描画します。

[code]

    class CustomCheckedListBox : CheckedListBox
    {
        protected override void OnDrawItem(DrawItemEventArgs e)
        {
            var listData = this.Items[e.Index] as Form1.ListData;
            if (listData == null)
            {
                // item が Form1.ListData でない場合は普通に描画
                base.OnDrawItem(e);
                return;
            }
    
            e.DrawBackground();
            var brush = new SolidBrush(this.ForeColor);
            if (listData.IsImportant)
            {
                // 重要だったら文字は赤色にする
                brush = new SolidBrush(Color.Red);
            }
            e.Graphics.DrawString(this.GetItemText(listData), e.Font, brush, e.Bounds);
            e.DrawFocusRectangle();
        }
    }
    
[/code]


CC BY-SA 3.0
2015-01-26T14:58:14.693
5361
2015-01-26T23:15:57.647
2015-01-26T23:15:57.647
3925
3925
5346
answer