gfache
gfache

Reputation: 616

How to manage ListBox/CheckedListBox bound with List<> in WinForms?

How to bind a ListBox or a CheckedListBox with a List<> of programmer defined object? How to edit the data after the binding? Through which object could I modify the data? How to make the listbox view updating when the data change? How to retrieve the check state of an item's checkbox when the user click on it for a CheckedListBox?

I wanted to share the few knowledge I have because I saw many threads on this subject but none was showing clearly how to deal with data binding. Don't misunderstand my attempt, I only want to show a lightened approach among others.

Feel free to comment. Hope that will help!

Upvotes: 1

Views: 3295

Answers (1)

gfache
gfache

Reputation: 616

In the following, 'ListBox' is different from 'listbox' which is the generalized term used for ListBox and CheckedListBox.


Firstly, the bound is done in the testForm_Load event handler. One must tell which object is to be bound and what property/field to be shown in the listbox.

((ListBox)m_checkedListBox).DataSource = m_underList;
((ListBox)m_checkedListBox).DisplayMember = "PropToDisplay";

As I understand the process, the bound listbox behave like a read-only unidirectional (datasource => listbox) mirror view. We can only affect the listbox and retrieve its data by the way of handling the underlying data object or the events mechanism of the control (forget Items & co.).

For CheckListBox,we can retrieve the checked items by adding an ItemCheckEventHandler method to the ItemCheck event and then store it into a property's/field's object programmer-defined.

m_checkedListBox.ItemCheck += new ItemCheckEventHandler(this.ItemCheck);

But we can't define the state of an internally checked item in the datasource (underlying list) to be shown as checked in the CheckListBox. The CheckListBox is seemingly not designed to behave that way.

Then, one may add or remove Userclass object at will through the underlying list or call any method you want. Just don't care about the listbox.

m_underList.Add(new UserClass("Another example", 0, true));

Finally, the view refreshing of the listbox. I saw many article talking about setting DataSource to null and then re-assigning it back to its previous object. I looked for a better way to do it (better or prettier?). The method below do it pretty simply.

void refreshView(ListBox lb, object dataSource);

References:

For complementary information about retrieving user selection from the listbox, visit MSDN ListControl Class Examples

Below is the entire code of a simple example of a CheckedListBox being bound, filled by data and cleared. Note for simplicity that I started from the principle that either the listbox nor the data list are sorted.


UserClass.cs: Class used to store displayed/check states/hidden data

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

namespace TestForm
{
    class UserClass
    {
        private int m_underProp;

        // ctors
        public UserClass(string prop2Disp, int anotherObj, bool isChecked = false)
        {
            PropToDisplay = prop2Disp;
            IsChecked = isChecked;
            AnotherProp = anotherObj;
        }

        public UserClass()
        {
            PropToDisplay = string.Empty;
            IsChecked = false;
            AnotherProp = 0;
        }

        //  Property to be displayed in the listbox
        public string PropToDisplay
        {
            get;
            set;
        }

        //  For CheckedListBox only!
        //  Property used to store the check state of a listbox
        //  item when a user select it by clicking on his checkbox
        public bool IsChecked
        {
            get;
            set;
        }

        //  Anything you want
        public int AnotherProp
        {
            get
            {
                return m_underProp;
            }

            set
            {
                m_underProp = value;
                //  todo, processing...
            }
        }

        //  For monitoring
        public string ShowVarState()
        {
            StringBuilder str = new StringBuilder();

            str.AppendFormat("- PropToDisplay: {0}", PropToDisplay);
            str.AppendLine();
            str.AppendFormat("- IsChecked: {0}", IsChecked);
            str.AppendLine();
            str.AppendFormat("- AnotherProp: {0}", AnotherProp.ToString());

            return str.ToString();
        }
    }
}

TestForm.Designer.cs: Design of the form

namespace TestForm
{
    partial class testForm
    {
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary>
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            this.m_checkedListBox = new System.Windows.Forms.CheckedListBox();
            this.m_toggle = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // m_checkedListBox
            // 
            this.m_checkedListBox.CheckOnClick = true;
            this.m_checkedListBox.FormattingEnabled = true;
            this.m_checkedListBox.Location = new System.Drawing.Point(13, 13);
            this.m_checkedListBox.Name = "m_checkedListBox";
            this.m_checkedListBox.Size = new System.Drawing.Size(171, 109);
            this.m_checkedListBox.TabIndex = 0;
            // 
            // m_toggle
            // 
            this.m_toggle.Location = new System.Drawing.Point(190, 53);
            this.m_toggle.Name = "m_toggle";
            this.m_toggle.Size = new System.Drawing.Size(75, 23);
            this.m_toggle.TabIndex = 1;
            this.m_toggle.Text = "Fill";
            this.m_toggle.UseVisualStyleBackColor = true;
            this.m_toggle.Click += new System.EventHandler(this.m_toggle_Click);
            // 
            // testForm
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(275, 135);
            this.Controls.Add(this.m_toggle);
            this.Controls.Add(this.m_checkedListBox);
            this.Name = "testForm";
            this.Text = "Form";
            this.Load += new System.EventHandler(this.testForm_Load);
            this.ResumeLayout(false);

        }

        #endregion

        private System.Windows.Forms.CheckedListBox m_checkedListBox;
        private System.Windows.Forms.Button m_toggle;
    }
}

TestForm.cs: Behavior of the form

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;
using System.Diagnostics;
using System.IO;

namespace TestForm
{
    public partial class testForm : Form
    {
        //  a List which will contain our external data. Named as the underlying list
        private List<UserClass> m_underList;

        public testForm()
        {
            InitializeComponent();

            m_underList = new List<UserClass> (3);
        }

        private void testForm_Load(object sender, EventArgs e)
        {
            //  Bind the CheckedListBox with the List
            //  The DataSource property is hidden so cast the object back to ListBox
            ((ListBox)m_checkedListBox).DataSource = m_underList;

            //  Tell which property/field to display in the CheckedListBox
            //  The DisplayMember property is hidden so cast the object back to ListBox
            ((ListBox)m_checkedListBox).DisplayMember = "PropToDisplay";

            /*
             * The CheckedListBox is now in "read-only" mode, that means you can't add/remove/edit
             * items from the listbox itself or edit the check states. You can't access
             * the underlying list through the listbox. Considers it as a unidirectionnal mirror
             * of the underlying list. The internal check state is disabled too, however
             * the ItemCheck event is still raised...
             */

            //  Manually set the ItemCheck event to set user defined objects
            m_checkedListBox.ItemCheck += new ItemCheckEventHandler(this.ItemCheck);
        }

        private void ItemCheck(object sender, ItemCheckEventArgs evnt)
        {
            if (sender == m_checkedListBox)
            {
                if (!m_checkedListBox.Sorted)
                {
                    //  Set internal object's flag to remember the checkbox state
                    m_underList[evnt.Index].IsChecked = (evnt.NewValue != CheckState.Unchecked);

                    //  Monitoring
                    Debug.WriteLine(m_underList[evnt.Index].ShowVarState());
                }
                else
                {
                    //  If sorted... DIY
                }
            }
        }

        private void m_toggle_Click(object sender, EventArgs e)
        {
            if (sender == m_toggle)
            {
                if (m_toggle.Text == "Fill")
                {
                    //  Fill the checkedListBox with some data
                    //  Populate the list with external data.
                    m_underList.Add(new UserClass("See? It works!", 42));
                    m_underList.Add(new UserClass("Another example", 0, true));
                    m_underList.Add(new UserClass("...", -7));
                    m_toggle.Text = "Clear";
                }
                else
                {
                    //  Empty the checkedListBox
                    m_underList.Clear();
                    m_toggle.Text = "Fill";
                }

                //  Refresh view
                //  Remember CheckedListBox inherit from ListBox
                refreshView(m_checkedListBox, m_underList);
            }
        }

        //  Magic piece of code which refresh the listbox view
        void refreshView(ListBox lb, object dataSource)
        {
            CurrencyManager cm = (CurrencyManager)lb.BindingContext[dataSource];
            cm.Refresh();
        }
    }
}

Here is the views of the form. The first image is the form when loaded and the second one is when the "Fill" button is clicked. One may note that the second item in the listbox is not checked despite the IsChecked property set to true when added to the datasource.

Form loadedFill button clicked

Upvotes: 3

Related Questions