Pere Gil
Pere Gil

Reputation: 43

Keep checkstate of items in checkboxlist on search c# winforms

So I'm working on a textbox based filter for a bunch of items in a Checked List Box in winforms using c#.

So far I have this code:

List<string> liscollection = new List<string>();
        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(textBox1.Text) == false)
            {
                checkedListBox1.Items.Clear();

                foreach (string str in liscollection)
                {
                    if (str.Contains(textBox1.Text, StringComparison.OrdinalIgnoreCase))
                    {
                        checkedListBox1.Items.Add(str);
                    }
                }
            }
            else if (textBox1.Text == "")
            {
                checkedListBox1.Items.Clear();
                foreach (string str in liscollection)
                {
                    checkedListBox1.Items.Add(str);
                }
            }
        }

It works fine, when I enter the text in TextBox1 all items not containing the entered text disappear. The problem comes to the checked state on each item. Every time checkedListBox1.Items.Clear(); is called, the checked state also gets cleared.

Is there a method I could use to keep the filter working but to not clear the checked state of the items?

I have been looking for a while now and can't find anything on this and I am a beginner and can't come up with a solution on my own.

Thank you so much in advance for your time!

And excuse my English, it is not my first language :)

Upvotes: 4

Views: 1369

Answers (1)

Joshua Robinson
Joshua Robinson

Reputation: 3539

CheckListBox.Items is of type ObjectCollection, which means that it will accept any object and not just string. By default, CheckedListBox will display the results of ToString as the item's text.

What this means is that you could write a class to represent the items stored in the CheckedListBox that track their own CheckedState property, then override the ToString method so that it displays the text that you want.

// CheckedListBoxItem.cs
public class CheckedListBoxItem
{
    /// <summary>
    /// The item's text - what will be displayed in the CheckedListBox.
    /// </summary>
    public string Text { get; set; }

    /// <summary>
    /// The item's check state.
    /// </summary>
    public CheckState CheckState { get; set; } = CheckState.Unchecked;

    /// <summary>
    /// Whether the item is checked or not.
    /// </summary>
    public bool Checked
    {
        get
        {
            return (CheckState == CheckState.Checked || CheckState == CheckState.Indeterminate);
        }
        set
        {
            if (value)
            {
                CheckState = CheckState.Checked;
            }
            else
            {
                CheckState = CheckState.Unchecked;
            }
        }
    }

    public bool Contains(string str, StringComparison comparison)
    {
        return Text.IndexOf(str, comparison) >= 0;
    }

    public override string ToString()
    {
        return Text;
    }
}

The behavior of CheckedListBoxItem.Checked is based on CheckedListBox.GetItemChecked which treats CheckState.Interetminate as checked, and CheckedListBox.SetItemChecked which treats true as CheckState.Checked and false as CheckState.Unchecked.

In your Form, you would then change the type of lstcollection to List<CheckedListBoxItem> and set the Text property of each item to the string that you have now.

CheckedListBox doesn't have any way to bind the CheckState of each item, so you'll have to manage that yourself. Fortunately, there is the CheckedListBox.ItemChecked event that will fire whenever the checked state of an item changes. So, you can handle the event with a function like...

private void checkedListBox1_ItemCheck(object sender, ItemCheckEventArgs e)
{
    // Since each item in the CheckedListBox is of type CheckedListBoxItem, we can
    // just cast to that type...
    CheckedListBoxItem item = checkedListBox1.Items[e.Index] as CheckedListBoxItem;

    // Then set the checked state to the new value.
    item.CheckState = e.NewValue;
}

Your filter function remains largely unchanged, but when you're adding the items to the CheckedListBox you have to pass the CheckedState as well...

private List<CheckedListBoxItem> liscollection;

private void textBox1_TextChanged(object sender, EventArgs e)
{
    checkedListBox1.Items.Clear();
    if (string.IsNullOrEmpty(textBox1.Text) == false)
    {
        foreach (var item in liscollection)
        {
            if (item.Contains(textBox1.Text, StringComparison.OrdinalIgnoreCase))
            {
                checkedListBox1.Items.Add(item, item.CheckState);
            }
        }
    }
    else
    {
        foreach (var item in liscollection)
        {
            checkedListBox1.Items.Add(item, item.CheckState);
        }
    }
}

EDIT

For a case like this, I wouldn't add the items to checkedListBox1 at design time. I'd add them to liscollection at runtime, then add liscollection to checkedListBox1.Items.

public class YourForm : Form
{
    public YourForm()
    {
        InitializeComponent();

        // You would add one item to liscollection for each item that you have in the checkedListBox1's designer and set the Text to whatever the item is now...
        liscollection = new List<CheckedListBoxItem>
        {
           new CheckedListBoxItem { Text = "The" },
           new CheckedListBoxItem { Text = "needs" },
           new CheckedListBoxItem { Text = "of" },
           new CheckedListBoxItem { Text = "the" },
           new CheckedListBoxItem { Text = "many" },
           new CheckedListBoxItem { Text = "outweigh" },
           new CheckedListBoxItem { Text = "the" },
           new CheckedListBoxItem { Text = "needs" },
           new CheckedListBoxItem { Text = "of" },
           new CheckedListBoxItem { Text = "the" },
           new CheckedListBoxItem { Text = "few" },
        };
        checkedListBox1.Items.AddRange(liscollection.ToArray());
    }
}

If you really prefer to populate checkedListBox1 from the designer, you can do that too. You just have to call checkedListBox1.Items.Clear() after populating liscollection and before calling checkedListBox1.Items.AddRange(...)

public class YourForm : Form
{
    public YourForm()
    {
        InitializeComponent();

        liscollection = new List<CheckedListBoxItem>();
        foreach(string str in checkedListBox1.Items)
        {
            liscollection.Add(new CheckedListBoxItem { Text = str });
        }
        checkedListBox1.Items.Clear();
        checkedListBox1.Items.AddRange(liscollection.ToArray());
    }
}

The important line there is checkedListBox1.Items.AddRange(liscollection.ToArray()). You want the Items to be instances of CheckedListBoxItem, that way in the ItemCheck event you can cast the item to an instance of CheckedListBoxItem. By populating checkedListBox1 from the designer, you're populating it with strings only, so when you try to cast to a CheckedListBoxItem in the ItemCheck event it fails. That's why you're getting that NullReferenceException.

Upvotes: 4

Related Questions