Search code examples
c#winformscheckedlistbox

Safe data from checkedlistbox checked unchecked?


I have the checkedlistbox(cbl1) which is changing his items while user searching username in the textbox. The problem is that when function for display result of the searching from dynamic array on cbl1. The checking states "checked or unchecked" he throws OutOfRangeException'cause the size of items is changing. So I tried to use temporary checkedlistbox(cbl2) to save previous data of cbl1 but it is not working. Here is example:

 private Task DisplayFound(HashSet<string> foundValues)
        {
            CheckedListBox temp = new CheckedListBox();
            temp.Sorted = true;
            temp.Items.Clear();

            CheckedListBox.ObjectCollection newItems = temp.Items;

            for (int i = 0; i < foundValues.Count; i++)
            {
               // here is problem
                if (checkedlistbox_users.GetItemChecked(i) == true)
                {
                    newItems.Add(foundValues.ElementAt(i));
                    temp.SetItemChecked(i, true);
                    continue;
                }
                newItems.Add(foundValues.ElementAt(i));
                temp.SetItemChecked(i, false);
            }

            checkedlistbox_users.Items.Clear();

            checkedlistbox_users.Items.AddRange(temp.Items);

            return Task.CompletedTask;
        }
        private async void searchUpdater_Tick(object sender, EventArgs e)
        {
            if (textbox_username.TextLength > 0)
            {
                string text = textbox_username.Text.ToLower();

                if (CB_botsFilter.Checked)
                {
                    HashSet<string> foundBots = usernames.FindAll(x => x.StartsWith(text) && x.ToLower().Contains("bot")).ToHashSet();
                    await DisplayFound(foundBots);
                    return;
                }
                if (CB_peopleFilter.Checked)
                {
                    HashSet<string> foundPeople = usernames.FindAll(x => x.StartsWith(text) && !x.ToLower().Contains("bot")).ToHashSet();
                    await DisplayFound(foundPeople);
                    return;
                }

                HashSet<string> found = usernames.FindAll(x => x.ToLower().StartsWith(text)).ToHashSet();
                await DisplayFound(found);
            }
        }


Solution

  • I agree with both JohnG and Jimi and will use their comments to provide suggested improvements to your posted code. The first change is to eliminate the Timer. Consider an update method that is triggered by any change of the textbox or the filter checkboxes. Subscribe to those events by overriding OnHandleCreated in the main form:

    protected override void OnHandleCreated(EventArgs e)
    {
        base.OnHandleCreated(e);
        if (!DesignMode)
        {
            // Add notifications for text and checkbox changes.
            textbox_username.TextChanged += searchUpdater;
            CB_botsFilter.CheckedChanged += searchUpdater;
            CB_peopleFilter.CheckedChanged += searchUpdater;
    
            // Populate the checked list with all users.
            foreach (var user in usernames)
            {
                checkedlistbox_users.Items.Add(user);
            }
        }
    }
    

    ...where the searchUpdater handler method is defined as:

    private void searchUpdater(object sender, EventArgs e)
    {
        var found = new List<string>();
        if (!string.IsNullOrWhiteSpace(textbox_username.Text))
        {
            if (CB_botsFilter.Checked && CB_peopleFilter.Checked)
            {
                found.AddRange(usernames.FindAll(x =>
                    x.StartsWith(textbox_username.Text)));
            }
            else
            {
                if (CB_botsFilter.Checked)
                {
                    found.AddRange(usernames.FindAll(x =>
                        x.StartsWith(textbox_username.Text) &&
                        x.Contains("bot", StringComparison.OrdinalIgnoreCase)));
                }
                if (CB_peopleFilter.Checked)
                {
                    found.AddRange(usernames.FindAll(x =>
                        x.StartsWith(textbox_username.Text) &&
                        !x.Contains("bot", StringComparison.OrdinalIgnoreCase)));
                }
            }
        }
        DisplayFound(found.ToHashSet());
    }
    

    Your code appears to be trying to place check marks next to the users found by the filter. That outcome could be achieved by iterating the Items collection and setting the check true if foundValues contains the string and otherwise false. I have added a multiline textbox to display the filter result for diagnostic purposes.

    private void DisplayFound(HashSet<string> foundValues)
    {
        textBoxTmp.Text = string.Join(Environment.NewLine, foundValues);
        for (int i = 0; i < checkedlistbox_users.Items.Count; i++)
        {
            checkedlistbox_users.SetItemChecked(
                i, 
                foundValues.Contains(checkedlistbox_users.Items[i]));
        }
    }
    

    This might not be the exact outcome you want, but taking into account the feedback given by the three of us it should give you enough ideas to solve your problem especially with the OutOfRangeException.

    This graphic shows what the result of various searches would be.

    enter image description here