0

I have a combobox in my application, where items are loaded asynchronously depending on a search text you can enter in the text field.

This works fine, but every time the text of the first item is automatically selected during updating the datasource of the combobox.

This leads to unintended behaviour, because I need to have the search text entered by the user to stay in the textfield of the combobox until a selection is done by the user and not automatically overwrite the text with the first entry.

This is my code:

public partial class ProductGroupDescription : UserControl, Interfaces.TabPages.ITabPageProductGroupDescription
{
    private Services.IProductGroupDescriptionService _ApplicationService;

    public BindingList<ProductGroup> ProductGroups { get; set; } = new BindingList<ProductGroup>();
    public string ProductGroupSearchText { get; set; } = string.Empty;

    public ProductGroupDescription(Services.IProductGroupDescriptionService applicationService)
    {
        InitializeComponent();
        InitialSetupControls();
        _ApplicationService = applicationService;
    }

    public void InitialSetupControls()
    {
        var pgBindingSource = new BindingSource();
        pgBindingSource.DataSource = ProductGroups;
        Cbo_ProductGroup.DataSource = pgBindingSource.DataSource;
        Cbo_ProductGroup.DataBindings.Add("Text", ProductGroupSearchText, "");
    }

    private async void Cbo_ProductGroup_TextChanged(object sender, EventArgs e)
    {
        if (Cbo_ProductGroup.Text.Length >= 2)
        {
            ProductGroupSearchText = Cbo_ProductGroup.Text;
            Cbo_ProductGroup.SelectedIndex = -1;
            bool withStopFlagged = Chk_StopFlag_PGs_Included.Checked;
            List<ProductGroup> list = await _ApplicationService.GetProductGroupBySearchString(ProductGroupSearchText, withStopFlagged);
            if (list != null && list.Count > 0)
            {
                ProductGroups.Clear();
                list.ForEach(item => ProductGroups.Add(item));
                Cbo_ProductGroup.DroppedDown = Cbo_ProductGroup.Items.Count > 0 && Cbo_ProductGroup.Focused;
            }
        }
    }
}

I tried to set Cbo_ProductGroup.SelectedIndex = -1, but it does not solve my issue here.

I also saw this on SO: Prevent AutoSelect behavior of a System.Window.Forms.ComboBox (C#)

But is there really no simpler solution to this issue?

Julian
  • 886
  • 10
  • 21
dns_nx
  • 3,651
  • 4
  • 37
  • 66
  • Are you trying to make eg an autocomplete that is driven by a database? – Caius Jard Dec 02 '21 at 07:14
  • *This lead to unintended behaviour* - generally in winforms if we do something like programmatically setting the text of a textbox but then don't want to fire the text changes handler we either remove the event handler, do the thing and reinstate the handler or (my preference) we set some bool "ignoreTextChanged" true and in the TextChanged handler the first thing we do is check the bool and set it false and return, if it's true – Caius Jard Dec 02 '21 at 07:33
  • @CaiusJard To your first question. I don't want to load all data into the control in advance and then use autocomplete, because we have many data in it. It makes sense to load them by given text entry. – dns_nx Dec 02 '21 at 08:02
  • @CaiusJard To your second comment: I'm not sure, if this fits in the situation. The text is being changed first and then the items are loaded asynchronously. And here is exactly when the first item is selected. I am not sure if I have understood you correctly, but in my opinion this has nothing to do with the Text_Changed event itself. It overwrites the text entered by the user with the text of the first loaded entry. – dns_nx Dec 02 '21 at 08:07
  • Capture the text entered by the user, load the items, and restore the text. The f you're triggering loading items from changing the text then set ignoreTextChanged=true before you restore the text (if restoring the txt is triggering the search again). You might also want to capture the caret position so you can restore it too.. it's such a faff that in the past I've done this with two controls eg a textbox and a listbox so they don't fight each other – Caius Jard Dec 02 '21 at 08:20
  • Have a look at https://stackoverflow.com/questions/40984605/c-sharp-adding-filter-to-combobox-dropdown-list – Caius Jard Dec 02 '21 at 08:40
  • That does not work. I already tried to set the text back after updating the items. No chance. Even no event is fired, when it automatically updates the text (no text_changed, text_updated, selected_item_changed, selected_index_changed,...). I tried all relevant events. It is weird. – dns_nx Dec 02 '21 at 09:01
  • I don't recall seeing this work with data binding; every time I've successfully seen someone implement this sort of "typing in the drop down filters the items" it's manifest as manipulating the combo's Items collection. For my money I think I'd have a look at allowing the user to type into a textbox and showing a filtered list of items in a listbox.. – Caius Jard Dec 02 '21 at 09:08
  • @CaiusJard I got it work now. If you like, please have a look at my answer. Many thanks for your hints and help! – dns_nx Dec 03 '21 at 05:19

1 Answers1

1

I got it to work now. It worked, when I removed the binding of the text field of the combobox

Cbo_ProductGroup.DataBindings.Add("Text", ProductGroupSearchText, "");

and set the new (old) value directly to the text field of the combobox.

Cbo_ProductGroup.Text = searchText;

In this case, a new event Text_Changed is fired and so the application has a infinite loop. So I used a property (ShouldTextChangedEventBeIgnored), if the Text_Changed event should be ignored. Thanks to @CaiusJard for many hints in the comments.

This is my final code:

public partial class ProductGroupDescription : UserControl, Interfaces.TabPages.ITabPageProductGroupDescription
{
    private ApplicationLogic.Interfaces.Services.IProductGroupDescriptionService _ApplicationService;

    public BindingList<ProductGroup> ProductGroups { get; set; } = new BindingList<ProductGroup>();
    public bool ShouldTextChangedEventBeIgnored { get; set; } = false;

    public ProductGroupDescription(ApplicationLogic.Interfaces.Services.IProductGroupDescriptionService applicationService)
    {
        _ApplicationService = applicationService;
        InitializeComponent();
        InitialSetupControls();
    }

    public void InitialSetupControls()
    {
        var pgBindingSource = new BindingSource();
        pgBindingSource.DataSource = ProductGroups;
        Cbo_ProductGroup.DataSource = pgBindingSource.DataSource;
    }

    private async Task<List<ProductGroup>> LoadProductGroupItems(string searchText)
    {
        bool withStopFlagged = Chk_StopFlag_PGs_Included.Checked;
        return await _ApplicationService.GetProductGroupBySearchString(searchText, withStopFlagged);
    }

    private async Task SetProductGroupSearchBoxItems(List<ProductGroup> list, string searchText)
    {
        await Task.Run(() =>
        {
            if (list != null && list.Count > 0)
            {
                ShouldTextChangedEventBeIgnored = true;
                Cbo_ProductGroup.Invoke((c) =>
                {
                    ProductGroups.Clear();
                    list.ForEach(item => ProductGroups.Add(item));
                    c.DroppedDown = c.Items.Count > 0 && c.Focused;
                    c.Text = searchText;
                    c.Select(c.Text.Length, 0);
                });
                ShouldTextChangedEventBeIgnored = false;
            }
        });
    }

    private async void Cbo_ProductGroup_TextChanged(object sender, EventArgs e)
    {
        try
        {
            if (Cbo_ProductGroup.Text.Length >= 2 && ShouldTextChangedEventBeIgnored == false)
            {
                string searchText = Cbo_ProductGroup.Text;
                List<ProductGroup> list = await LoadProductGroupItems(Cbo_ProductGroup.Text);
                await SetProductGroupSearchBoxItems(list, searchText);
            }
        }
        catch(Exception ex)
        {
            System.Diagnostics.Trace.Write(ex);
        } 
    }
}
dns_nx
  • 3,651
  • 4
  • 37
  • 66