Nick_F
Nick_F

Reputation: 1117

ComboBox functionality

I want to use a combobox to select items from a CSV file. When I type a string into the combobox, the dropdown should display the elements that are matching what I type. If any part of a string from the CSV file matches my text, it should be displayed in the dropdown. I have to be able to select with Arrow Up/Down any string from the dropdown and when I press Enter, the text property of the combobox should get filled with the selected string from dropdown. The catch is that if I type a string that does not match any elements from the dropdown and then I press Enter, the program should inform me that no matches were found. At the moment the code raises an exception caught in the try...catch loop. Below is the code. The CSV content are these terms, one per line: CONA, CONB, CONC, DAB, DAC. The error that shows up is "InvalidArgument = Value of '0' is not valid for 'index' Parameter name: index".

using System;
using System.Collections.Generic;
using System.Data;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace ComboBoxDB
{
    public partial class Form1 : Form
    {
        // In-memory list to hold the CSV data.
        private List<string> items = new List<string>();
        // Path to your CSV file.
        private string csvFilePath = "data.csv";

        // Flags to track state
        private bool _isUpdating = false;
        private string _userTypedText = string.Empty;
        private bool _isNavigating = false;
        // Flag to suppress dropdown reopening after selection (via Enter)
        private bool _suppressDropdownAfterSelection = false;

        public Form1()
        {
            InitializeComponent();
            LoadCSV();
            SetupComboBox();
        }

        // Load the CSV file into the 'items' list.
        private void LoadCSV()
        {
            if (File.Exists(csvFilePath))
            {
                items = File.ReadAllLines(csvFilePath).ToList();
            }
            else
            {
                items = new List<string> { "CONA", "CONB", "CONC", "DAB", "DAC" };
                File.WriteAllLines(csvFilePath, items);
            }
        }

        // Set up the ComboBox with all needed event handlers.
        private void SetupComboBox()
        {
            cbManPN.AutoCompleteMode = AutoCompleteMode.None;
            cbManPN.AutoCompleteSource = AutoCompleteSource.None;
            cbManPN.DropDownStyle = ComboBoxStyle.DropDown;

            cbManPN.TextChanged += cbManPN_TextChanged;
            cbManPN.KeyDown += cbManPN_KeyDown;
            cbManPN.KeyUp += cbManPN_KeyUp;
            cbManPN.SelectedIndexChanged += cbManPN_SelectedIndexChanged;
            cbManPN.DropDownClosed += cbManPN_DropDownClosed;
            cbManPN.SelectionChangeCommitted += cbManPN_SelectionChangeCommitted;
        }

        private void cbManPN_TextChanged(object sender, EventArgs e)
        {
            // Don't process if we're already updating or navigating.
            if (_isUpdating || _isNavigating)
                return;

            _isUpdating = true;
            _userTypedText = cbManPN.Text ?? string.Empty;

            // We save these values in case we restore them below.
            int selectionStart = cbManPN.SelectionStart;
            if (selectionStart > _userTypedText.Length)
                selectionStart = _userTypedText.Length;

            this.BeginInvoke(new Action(() =>
            {
                try
                {
                    var currentText = _userTypedText;
                    // Filter items (case-insensitive substring).
                    var filteredItems = items
                        .Where(i => i.IndexOf(currentText, StringComparison.OrdinalIgnoreCase) >= 0)
                        .OrderBy(i => i)
                        .ToArray();

                    cbManPN.BeginUpdate();
                    // Clear items and ensure no leftover selection.
                    cbManPN.Items.Clear();
                    cbManPN.SelectedIndex = -1;

                    if (filteredItems.Length > 0)
                    {
                        // Add new items and reset selected index.
                        cbManPN.Items.AddRange(filteredItems);
                        cbManPN.SelectedIndex = -1;

                        // Only open the dropdown if we have text and not suppressing.
                        cbManPN.DroppedDown = (currentText.Length > 0 && !_suppressDropdownAfterSelection);

                        // Update the ComboBox text and caret position to preserve user’s input.
                        cbManPN.Text = currentText;
                        if (cbManPN.Focused)
                            cbManPN.SelectionStart = selectionStart;
                    }
                    else
                    {
                        // If no matches, close the dropdown (but don't reset the text).
                        cbManPN.DroppedDown = false;
                    }

                    cbManPN.EndUpdate();
                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error in TextChanged update: " + ex.Message,
                        "TextChanged Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally
                {
                    _suppressDropdownAfterSelection = false;
                    _isUpdating = false;
                }
            }));
        }

        private void cbManPN_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
            {
                _isNavigating = true;
                if (!cbManPN.DroppedDown && cbManPN.Items.Count > 0)
                {
                    cbManPN.DroppedDown = true;
                }

                if (cbManPN.SelectedIndex == -1 && cbManPN.Items.Count > 0)
                {
                    // If no item is selected, select first or last depending on arrow.
                    cbManPN.SelectedIndex = (e.KeyCode == Keys.Down) ? 0 : cbManPN.Items.Count - 1;
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.Enter)
            {
                // Prevent reopening the dropdown in TextChanged.
                _suppressDropdownAfterSelection = true;
                this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));

                _isNavigating = false;
                string finalText = cbManPN.Text;

                if (!string.IsNullOrWhiteSpace(finalText))
                {
                    // Check for partial matches with the same logic as TextChanged.
                    var filteredMatches = items
                        .Where(i => i.IndexOf(finalText, StringComparison.OrdinalIgnoreCase) >= 0)
                        .ToArray();

                    if (filteredMatches.Length == 0)
                    {
                        MessageBox.Show("No matching elements in the database.",
                            "No Match", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    }
                }

                e.Handled = true;
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.Escape)
            {
                this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));
                _isNavigating = false;

                // Restore the original user-typed text.
                _isUpdating = true;
                cbManPN.Text = _userTypedText;
                if (cbManPN.Focused)
                    cbManPN.SelectionStart = _userTypedText.Length;
                _isUpdating = false;

                e.Handled = true;
                e.SuppressKeyPress = true;
            }
            else
            {
                _isNavigating = false;
            }
        }

        private void cbManPN_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode != Keys.Down && e.KeyCode != Keys.Up)
                _isNavigating = false;
        }

        private void cbManPN_SelectedIndexChanged(object sender, EventArgs e)
        {
            // Only update text if user is navigating with arrow keys.
            if (_isNavigating && cbManPN.SelectedIndex != -1)
            {
                _isUpdating = true;
                string selectedItem = cbManPN.SelectedItem.ToString();
                cbManPN.Text = selectedItem;
                if (cbManPN.Focused)
                    cbManPN.SelectionStart = selectedItem.Length;
                _isUpdating = false;
            }
        }

        private void cbManPN_SelectionChangeCommitted(object sender, EventArgs e)
        {
            // Handle mouse-based selection.
            if (cbManPN.SelectedItem != null)
            {
                _isUpdating = true;
                string selectedItem = cbManPN.SelectedItem.ToString();
                cbManPN.Text = selectedItem;
                if (cbManPN.Focused)
                    cbManPN.SelectionStart = selectedItem.Length;
                _isUpdating = false;

                this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));
                _isNavigating = false;
            }
        }

        private void cbManPN_DropDownClosed(object sender, EventArgs e)
        {
            // If the dropdown closed while navigating, decide what to do with the text.
            if (_isNavigating)
            {
                _isNavigating = false;
                if (cbManPN.SelectedIndex == -1 && !string.IsNullOrEmpty(_userTypedText))
                {
                    _isUpdating = true;
                    cbManPN.Text = _userTypedText;
                    if (cbManPN.Focused)
                        cbManPN.SelectionStart = _userTypedText.Length;
                    _isUpdating = false;
                }
            }
        }
    }
}

Upvotes: 0

Views: 42

Answers (1)

Nick_F
Nick_F

Reputation: 1117

This code works as intended:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;

namespace ComboBoxDB
{
    public partial class Form1 : Form
    {
        // CSV data.
        private List<string> items = new List<string>();
        private string csvFilePath = "data.csv";

        // State flags.
        private bool isUpdating = false;
        private bool isNavigating = false;
        private bool suppressDropdown = false;
        private bool enterMessageShown = false;
        private string userText = string.Empty;

        public Form1()
        {
            InitializeComponent();
            LoadCSV();
            SetupComboBox();
        }

        private void LoadCSV()
        {
            if (File.Exists(csvFilePath))
                items = File.ReadAllLines(csvFilePath).ToList();
            else
            {
                items = new List<string> { "CONA", "CONB", "CONC", "COND", "DAR", "DAT" };
                File.WriteAllLines(csvFilePath, items);
            }
        }

        private void SetupComboBox()
        {
            cbManPN.AutoCompleteMode = AutoCompleteMode.None;
            cbManPN.AutoCompleteSource = AutoCompleteSource.None;
            cbManPN.DropDownStyle = ComboBoxStyle.DropDown;

            cbManPN.TextChanged += cbManPN_TextChanged;
            cbManPN.KeyDown += cbManPN_KeyDown;
            cbManPN.KeyUp += cbManPN_KeyUp;
            cbManPN.SelectedIndexChanged += cbManPN_SelectedIndexChanged;
            cbManPN.DropDownClosed += cbManPN_DropDownClosed;
            cbManPN.SelectionChangeCommitted += cbManPN_SelectionChangeCommitted;
        }

        // Helper: Set the ComboBox's text and caret position.
        private void SetTextAndCaret(string text, int caretPosition)
        {
            cbManPN.Text = text;
            if (cbManPN.Focused)
                cbManPN.SelectionStart = caretPosition;
        }

        // Helper: Update the dropdown items based on current text.
        private void UpdateDropdown()
        {
            string currentText = userText;
            int caretPos = Math.Min(cbManPN.SelectionStart, currentText.Length);
            var filtered = items
                .Where(i => i.IndexOf(currentText, StringComparison.OrdinalIgnoreCase) >= 0)
                .OrderBy(i => i)
                .ToArray();

            cbManPN.DroppedDown = false;
            cbManPN.BeginUpdate();
            cbManPN.Items.Clear();
            cbManPN.SelectedIndex = -1;

            if (filtered.Length > 0)
            {
                cbManPN.Items.AddRange(filtered);
                cbManPN.SelectedIndex = -1;
                cbManPN.DroppedDown = (currentText.Length > 0 && !suppressDropdown);
                SetTextAndCaret(currentText, caretPos);
            }
            else
            {
                cbManPN.DroppedDown = false;
                SetTextAndCaret(currentText, currentText.Length);
            }
            cbManPN.EndUpdate();
        }

        private void cbManPN_TextChanged(object sender, EventArgs e)
        {
            if (isUpdating || isNavigating)
                return;

            isUpdating = true;
            userText = cbManPN.Text ?? string.Empty;

            // Use BeginInvoke so this runs after the event completes.
            this.BeginInvoke(new Action(() =>
            {
                try { UpdateDropdown(); }
                catch (Exception ex)
                {
                    MessageBox.Show(this, "Error in TextChanged update: " + ex.Message,
                        "TextChanged Exception", MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
                finally { suppressDropdown = false; isUpdating = false; }
            }));
        }

        private void cbManPN_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Down || e.KeyCode == Keys.Up)
            {
                if (cbManPN.Items.Count == 0)
                {
                    e.Handled = true;
                    e.SuppressKeyPress = true;
                    return;
                }
                isNavigating = true;
                if (!cbManPN.DroppedDown)
                    cbManPN.DroppedDown = true;
                if (cbManPN.SelectedIndex == -1)
                {
                    cbManPN.SelectedIndex = (e.KeyCode == Keys.Down) ? 0 : cbManPN.Items.Count - 1;
                    e.Handled = true;
                }
            }
            else if (e.KeyCode == Keys.Enter)
            {
                if (!enterMessageShown)
                {
                    enterMessageShown = true;
                    suppressDropdown = true;
                    this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));
                    isNavigating = false;
                    string finalText = cbManPN.Text;
                    if (!string.IsNullOrWhiteSpace(finalText))
                    {
                        var matches = items
                            .Where(i => i.IndexOf(finalText, StringComparison.OrdinalIgnoreCase) >= 0)
                            .ToArray();
                        if (matches.Length == 0)
                        {
                            MessageBox.Show($"No matching elements for \"{finalText}\" in the database.",
                                "No Match", MessageBoxButtons.OK, MessageBoxIcon.Information);
                        }
                        else
                        {
                            MessageBox.Show($"You selected \"{finalText}\" from the database");
                        }
                    }
                }
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
            else if (e.KeyCode == Keys.Escape)
            {
                this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));
                isNavigating = false;
                isUpdating = true;
                SetTextAndCaret(userText, userText.Length);
                isUpdating = false;
                e.Handled = true;
                e.SuppressKeyPress = true;
            }
            else
            {
                isNavigating = false;
            }
        }

        private void cbManPN_KeyUp(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
                enterMessageShown = false;
            if (e.KeyCode != Keys.Down && e.KeyCode != Keys.Up)
                isNavigating = false;
        }

        private void cbManPN_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (isNavigating && cbManPN.SelectedIndex != -1 && cbManPN.Items.Count > 0)
            {
                isUpdating = true;
                string sel = cbManPN.SelectedItem.ToString();
                SetTextAndCaret(sel, sel.Length);
                isUpdating = false;
            }
        }

        private void cbManPN_SelectionChangeCommitted(object sender, EventArgs e)
        {
            if (cbManPN.Items.Count > 0 && cbManPN.SelectedItem != null)
            {
                isUpdating = true;
                string sel = cbManPN.SelectedItem.ToString();
                SetTextAndCaret(sel, sel.Length);
                isUpdating = false;
                this.BeginInvoke(new Action(() => cbManPN.DroppedDown = false));
                isNavigating = false;
            }
        }

        private void cbManPN_DropDownClosed(object sender, EventArgs e)
        {
            if (isNavigating)
            {
                isNavigating = false;
                if (cbManPN.SelectedIndex == -1 && !string.IsNullOrEmpty(userText))
                {
                    isUpdating = true;
                    SetTextAndCaret(userText, userText.Length);
                    isUpdating = false;
                }
            }
        }
    }
}

Upvotes: -2

Related Questions