在 C# WinForms 中搜索时保持 CheckboxList 中项目的选中状态

4

我正在使用C#为WinForms中的一组带有复选框的项目创建基于文本框的筛选器。

目前我有以下代码:

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);
                }
            }
        }

当我在TextBox1中输入文本时,所有不包含输入文本的项目都会消失,它能很好地工作。 问题出在每个项目的选中状态上。每次调用checkedListBox1.Items.Clear();时,选中状态也会被清除。
有没有一种方法可以使筛选器继续工作,但不清除项目的选中状态呢?
我已经找了一段时间,无法找到任何关于此的内容,我是一个初学者,无法自己想出解决方案。
非常感谢您的时间!
请原谅我的英语,这不是我的母语 :)

使用 Dictionary<string, bool> 来跟踪集合中每个项目的状态。假设您的字符串是唯一的。 - LarsTech
一个被过滤掉的项目是否需要保持其选中状态呢?例如,如果字符串“Stack”在CheckedListBox中被选中但因为过滤而被移除,当过滤条件改变并且“Stack”重新添加到CheckedListBox时,它是否应该保持选中状态呢? - Joshua Robinson
@JoshuaRobinson 是的,这正是我想做的! - Pere Gil
1个回答

4

CheckListBox.Items 属于 ObjectCollection 类型,这意味着它可以接受任何类型的 object 而不仅仅是 string 类型。默认情况下,CheckedListBox 将使用 ToString 方法的结果作为该项的文本。

这意味着,您可以编写一个代表存储在 CheckedListBox 中的项的 class,该类会跟踪它们自己的 CheckedState 属性,并重写 ToString 方法以显示所需的文本。

// 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;
    }
}
CheckedListBoxItem.Checked的行为基于CheckedListBox.GetItemChecked,它将CheckState.Interetminate视为已选中,并且CheckedListBox.SetItemCheckedtrue视为CheckState.Checked,将false视为CheckState.Unchecked
在您的Form中,您需要将lstcollection的类型更改为List<CheckedListBoxItem>,并将每个项目的Text属性设置为现在的字符串。 CheckedListBox没有任何方法来绑定每个项目的CheckState,因此您必须自己管理。幸运的是,有一个CheckedListBox.ItemChecked事件,每当项目的选中状态发生变化时都会触发该事件。因此,您可以使用以下函数处理该事件...
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;
}

您的筛选函数基本上没有变化,但是在将项添加到 CheckedListBox 时,您必须同时传递 CheckedState ...

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);
        }
    }
}

编辑

对于这种情况,我不会在设计时将项目添加到checkedListBox1中。相反,我会在运行时将它们添加到liscollection,然后将liscollection添加到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());
    }
}

如果你更喜欢从设计师中填充checkedListBox1,那也可以。在调用checkedListBox1.Items.AddRange(...)之前,你只需要在填充liscollection后调用checkedListBox1.Items.Clear()即可。

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());
    }
}

重要的一行是 checkedListBox1.Items.AddRange(liscollection.ToArray())。你需要将Items变为CheckedListBoxItem实例,这样在ItemCheck事件中可以将项目强制转换为CheckedListBoxItem的实例。通过从设计器填充checkedListBox1,您仅用string进行填充,因此在ItemCheck事件中尝试转换为CheckedListBoxItem时会失败。这就是为什么会出现NullReferenceException错误的原因。

你好!非常感谢您的回答和清晰的解释!我尝试按照您的建议操作,这很有道理。但不幸的是,每当我尝试在列表框中检查一个项目时,就会出现错误“System.NullReferenceException:对象引用未设置为对象的实例”在item.CheckState = e.NewValue;处。 - Pere Gil
嗨,@PereGil。你把liscollection成员改成了List<CheckListBoxItem>吗?还是它仍然是一个List<string>?如果它仍然是一个List<string>,那么在checkedListBox1_ItemCheck事件处理程序内部转换将失败,而item将为空。 - Joshua Robinson
你好!非常感谢您的时间。新代码不再出现错误,并且像原始代码一样过滤文本,但是项目的检查状态又被重置了。我尝试了两种方式,一种是在设计器中继续填充 checklistbox,另一种是在运行时添加它们。 - Pere Gil
嗯...我从未调用liscollection.Clear,而是在比较textBox1.Text之前调用checkedListBox1.Items.Clear();,如果我删除它,函数将无法工作,每次输入内容时它将重复匹配。 - Pere Gil
1
我刚刚在一个新项目中尝试了我现在拥有的内容,它完美地运行了。我没有看到任何不同之处,但也许我错过了什么......无论如何,非常感谢您的时间,特别是为向我解释一切,我学到了很多,最终它终于成功了! :) - Pere Gil
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接