Reputation: 43
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
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 string
s 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