GUI и визуальные компоненты

ComboBox в C# WinForms: руководство

Обновлено: · 7 мин чтения

ComboBox в Windows Forms - выпадающий список, в котором пользователь выбирает один вариант из набора и, при желании, вводит собственный текст. Здесь разобраны добавление элементов, привязка через DataSource, DisplayMember и ValueMember, событие SelectedIndexChanged, режимы DropDownStyle, автодополнение и очистка списка.

Примеры рассчитаны на .NET 8 и проект Windows Forms.

ComboBox в C# WinForms: руководство
Содержание
  1. Что такое ComboBox и из чего он состоит
  2. Добавление элементов
    1. Add, AddRange и Insert
    2. Добавление объектов
  3. Привязка данных: DataSource, DisplayMember и ValueMember
  4. Привязка перечисления enum
  5. Событие SelectedIndexChanged
    1. Зависимые списки
  6. Свойство DropDownStyle
  7. Автодополнение
  8. Поиск и выбор элемента из кода
  9. Очистка и удаление элементов
  10. Типичные ошибки
  11. Частые вопросы

Что такое ComboBox и из чего он состоит

ComboBox объединяет текстовое поле и раскрывающийся список. Он экономит место на форме: вместо десятка переключателей показывается одна строка, а варианты появляются по клику на стрелку. Элемент подходит для выбора страны, категории товара, единицы измерения, режима работы и любых других наборов значений.

Главные свойства: Items хранит коллекцию элементов, SelectedIndex отдаёт номер выбранного пункта (-1, если ничего не выбрано), SelectedItem возвращает сам объект, Text содержит строку в поле, а SelectedValue работает вместе с привязкой данных. Похожий по идее элемент ListBox показывает все варианты сразу, а ComboBox прячет их до раскрытия.

Добавление элементов

Заполнять список можно в конструкторе формы, в обработчике Load или из любого другого места. Есть несколько способов: по одному, пачкой и из готовой коллекции.

Add, AddRange и Insert

Метод Items.Add добавляет значение в конец списка и возвращает его индекс. AddRange принимает массив объектов и работает быстрее при большом числе элементов, а Insert вставляет значение в указанную позицию.

private void Form1_Load(object sender, EventArgs e)
{
    comboBox1.Items.Add("Москва");
    comboBox1.Items.Add("Казань");
    comboBox1.Items.AddRange(new object[] { "Самара", "Тула", "Омск" });
    comboBox1.Items.Insert(0, "Выберите город");

    comboBox1.SelectedIndex = 0;   // показать первый пункт
}

Для заполнения большого списка оберните вызовы в BeginUpdate и EndUpdate: пока обновление приостановлено, элемент не перерисовывается после каждого добавления, и загрузка тысяч строк проходит заметно быстрее.

comboBox1.BeginUpdate();
for (int i = 1; i <= 5000; i++)
    comboBox1.Items.Add($"Пункт {i}");
comboBox1.EndUpdate();

Добавление объектов

В Items можно класть не только строки, но и любые объекты. Для показа ComboBox вызывает у каждого объекта ToString, поэтому переопределите этот метод или укажите DisplayMember. Так в списке видно название, а за ним скрыт целый объект с идентификатором и другими свойствами.

public class City
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public override string ToString() => Name;
}

comboBox1.Items.Add(new City { Id = 1, Name = "Москва" });
comboBox1.Items.Add(new City { Id = 2, Name = "Казань" });

if (comboBox1.SelectedItem is City city)
    MessageBox.Show($"Код города: {city.Id}");

Привязка данных: DataSource, DisplayMember и ValueMember

Когда данные уже лежат в коллекции или таблице, удобнее привязать её целиком, чем добавлять элементы вручную. Свойство DataSource принимает список, массив или DataTable. DisplayMember задаёт имя свойства, текст которого виден пользователю, а ValueMember имя свойства со значением, которое вы получаете через SelectedValue.

var cities = new List<City>
{
    new City { Id = 1, Name = "Москва" },
    new City { Id = 2, Name = "Казань" },
    new City { Id = 3, Name = "Самара" }
};

comboBox1.DataSource = cities;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "Id";

// позже
int id = (int)comboBox1.SelectedValue;
string name = comboBox1.Text;

Порядок присваивания важен: сначала задайте DisplayMember и ValueMember, потом DataSource, либо наоборот, но в рамках одного блока кода и до обращения к SelectedValue. Иначе возможна ситуация, когда SelectedValue возвращает объект целиком, а не значение свойства. Так бывает при первом срабатывании SelectedIndexChanged во время привязки.

С DataTable привязка выглядит так же: в DisplayMember и ValueMember указываются имена столбцов. Подробнее о загрузке таблиц и показе записей рассказано в статьях про DataGridView.

var table = new DataTable();
table.Columns.Add("Id", typeof(int));
table.Columns.Add("Title", typeof(string));
table.Rows.Add(1, "Новый");
table.Rows.Add(2, "В работе");
table.Rows.Add(3, "Завершён");

comboBox2.DataSource = table;
comboBox2.DisplayMember = "Title";
comboBox2.ValueMember = "Id";

Привязанный список нельзя изменять через Items.Add: при установленном DataSource коллекция Items доступна только для чтения. Добавляйте элементы в сам источник. Если источник - List, то для автоматического обновления используйте BindingList или BindingSource.

Привязка перечисления enum

Для выбора значения из перечисления проще всего передать в DataSource массив его значений. Так список всегда совпадает с кодом и не нужно дублировать варианты вручную.

enum Priority { Низкий, Средний, Высокий }

comboBox3.DataSource = Enum.GetValues(typeof(Priority));
comboBox3.SelectedItem = Priority.Средний;

var chosen = (Priority)comboBox3.SelectedItem!;
Console.WriteLine(chosen);

Если нужно по строке получить значение перечисления, поможет статья о преобразовании строки в enum.

Событие SelectedIndexChanged

Событие SelectedIndexChanged срабатывает, когда пользователь выбирает другой пункт или значение меняется из кода. Именно в нём обычно обновляют зависимые поля: например, после выбора страны загружают список городов. В обработчике проверяйте, что выбор действительно сделан, потому что SelectedIndex может быть равен -1.

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
    if (comboBox1.SelectedIndex < 0) return;

    var selected = comboBox1.SelectedItem;
    label1.Text = $"Вы выбрали: {selected} (индекс {comboBox1.SelectedIndex})";
}

Обработчик можно подключить двойным щелчком по элементу в дизайнере или вручную в конструкторе формы: comboBox1.SelectedIndexChanged += comboBox1_SelectedIndexChanged. Есть и родственные события: SelectionChangeCommitted срабатывает только при выборе пользователем (не из кода), TextChanged при изменении текста, DropDown при раскрытии списка, а Validating при выходе из поля.

Зависимые списки

Классический пример - два связанных ComboBox, где содержимое второго зависит от выбора в первом. Чтобы не получить лишних срабатываний при заполнении, используйте SelectionChangeCommitted или временно отписывайтесь от события.

private readonly Dictionary<string, string[]> _cities = new()
{
    ["Россия"] = new[] { "Москва", "Казань", "Самара" },
    ["Беларусь"] = new[] { "Минск", "Гомель" },
    ["Казахстан"] = new[] { "Астана", "Алматы" }
};

private void comboCountry_SelectionChangeCommitted(object sender, EventArgs e)
{
    comboCity.DataSource = null;
    if (comboCountry.SelectedItem is string country)
        comboCity.DataSource = _cities[country];
}

Свойство DropDownStyle

От режима DropDownStyle зависит, можно ли вводить собственный текст и как выглядит список. Значение выбирается в окне свойств или задаётся из кода.

ЗначениеВвод текстаСписокКогда применять
DropDownРазрешёнРаскрывается по стрелкеВыбор из набора с возможностью ввести своё
DropDownListЗапрещёнРаскрывается по стрелкеСтрогий выбор только из готовых вариантов
SimpleРазрешёнВсегда показан под полемРедко, когда есть место на форме

В режиме DropDownList свойство Text нельзя присвоить произвольной строкой: значение, которого нет в списке, не отобразится. Если нужно показать подсказку вроде "Выберите город", добавьте её первым элементом, как сделано в примере выше. В режиме DropDown пользователь может ввести текст, которого нет в списке, поэтому при сохранении проверяйте SelectedIndex: значение -1 означает ввод вручную.

comboBox1.DropDownStyle = ComboBoxStyle.DropDownList;
comboBox1.DropDownHeight = 200;      // высота раскрытой части
comboBox1.MaxDropDownItems = 8;      // сколько пунктов видно сразу
comboBox1.DropDownWidth = 250;       // ширина списка, может быть шире самого поля

// режим DropDown: проверка, ввёл ли пользователь своё значение
if (comboBox1.SelectedIndex == -1 && !string.IsNullOrWhiteSpace(comboBox1.Text))
    MessageBox.Show($"Введено вручную: {comboBox1.Text}");

Автодополнение

ComboBox умеет подсказывать варианты по мере набора текста. Для этого используются три свойства: AutoCompleteMode определяет способ показа (Suggest показывает выпадающую подсказку, Append дописывает остаток строки, SuggestAppend делает и то и другое), AutoCompleteSource указывает источник данных, а AutoCompleteCustomSource хранит собственный набор строк.

comboBox1.DropDownStyle = ComboBoxStyle.DropDown;
comboBox1.AutoCompleteMode = AutoCompleteMode.SuggestAppend;

// вариант 1: подсказки из самих элементов списка
comboBox1.AutoCompleteSource = AutoCompleteSource.ListItems;

// вариант 2: собственный набор строк
var source = new AutoCompleteStringCollection();
source.AddRange(new[] { "Москва", "Мурманск", "Магадан", "Минск" });
comboBox1.AutoCompleteSource = AutoCompleteSource.CustomSource;
comboBox1.AutoCompleteCustomSource = source;

Источник ListItems подходит, когда подсказывать нужно то же, что есть в списке. В режиме DropDownList автодополнение не работает, так как ввод текста запрещён. Поиск по началу строки регистронезависим, а искать подстроку из середины стандартный механизм не умеет: для этого придётся фильтровать источник самостоятельно в событии TextChanged.

Поиск и выбор элемента из кода

Чтобы выбрать нужный пункт программно, присвойте SelectedIndex или SelectedItem. Для поиска строки по префиксу служит FindString, для точного совпадения FindStringExact. Оба метода возвращают индекс или -1. Если список привязан к данным, значение по ключу выбирают присвоением SelectedValue.

int idx = comboBox1.FindStringExact("Казань");
if (idx >= 0) comboBox1.SelectedIndex = idx;

int part = comboBox1.FindString("Сам");   // первый, начинающийся с "Сам"

comboBox1.SelectedItem = "Тула";           // выбор по значению
comboBox2.SelectedValue = 2;               // выбор по ValueMember при привязке

bool exists = comboBox1.Items.Contains("Омск");

Очистка и удаление элементов

Для полной очистки вручную заполненного списка вызывают Items.Clear(). Отдельные пункты удаляют методами Remove и RemoveAt. Чтобы просто снять выбор и оставить варианты, достаточно присвоить SelectedIndex = -1. А если нужно сбросить введённый текст в режиме DropDown, выполните Text = string.Empty.

comboBox1.Items.Remove("Тула");        // по значению
comboBox1.Items.RemoveAt(0);           // по индексу
comboBox1.SelectedIndex = -1;          // снять выбор
comboBox1.Text = string.Empty;         // очистить текстовую часть
comboBox1.Items.Clear();               // удалить все элементы

// список с привязкой: сначала сбрасываем источник
comboBox2.DataSource = null;
comboBox2.Items.Clear();

Помните, что при установленном DataSource вызов Items.Clear() приведёт к исключению: "Невозможно изменить коллекцию элементов, если задан DataSource". Сначала присвойте DataSource значение null, а затем очищайте Items или задайте новый источник. Метод ResetText тоже сбрасывает отображаемый текст.

Типичные ошибки

Частая проблема - обращение к SelectedItem.ToString() без проверки: если ничего не выбрано, SelectedItem равен null и возникает NullReferenceException, причины которого разобраны в статье про NullReferenceException. Вторая ошибка - кастинг SelectedValue в int, когда привязка ещё не завершена; в этот момент свойство может вернуть объект целиком или DataRowView. Проверяйте тип через is либо подписывайтесь на SelectionChangeCommitted.

Третья проблема - лишние срабатывания SelectedIndexChanged при начальном заполнении: установка DataSource тоже поднимает событие, и обработчик получает неполные данные. Решение - флаг "идёт загрузка" или отписка на время заполнения. Четвёртая - пользователь в режиме DropDown вводит произвольный текст, а код рассчитывает на выбранный элемент. Если ввод вручную не нужен, сразу ставьте DropDownList. Наконец, не пытайтесь хранить в строках составные значения вида "1|Москва": лучше завести класс и использовать DisplayMember с ValueMember.

Частые вопросы

Как получить выбранное значение ComboBox?

Для строк используйте comboBox1.Text или comboBox1.SelectedItem. Для привязанного списка с ValueMember берите SelectedValue. Если нужен номер позиции, читайте SelectedIndex.

Как запретить пользователю вводить свой текст?

Установите DropDownStyle = ComboBoxStyle.DropDownList. В этом режиме можно только выбрать готовый вариант, а поле ввода становится недоступным для редактирования.

Как выбрать пункт по умолчанию при загрузке формы?

В обработчике Load присвойте SelectedIndex = 0 или нужному индексу. Для привязанных данных подойдёт SelectedValue с известным ключом. Выбор по умолчанию задаётся и в окне свойств через Text.

Почему SelectedIndexChanged срабатывает при запуске формы?

Событие поднимается при любом изменении выбора, включая начальную привязку DataSource. Добавьте проверку SelectedIndex < 0 либо используйте SelectionChangeCommitted, которое реагирует только на действия пользователя.

Как сделать ComboBox с цветами или картинками?

Переключите DrawMode на OwnerDrawFixed и обработайте событие DrawItem. В нём вы сами рисуете текст и значок каждого пункта методами Graphics. Для простого списка этого не требуется.

Как отсортировать элементы в ComboBox?

Для ручного списка включите свойство Sorted = true. Для привязанного источника отсортируйте саму коллекцию заранее, например через OrderBy, потому что Sorted при DataSource не работает.