Вопрос или проблема
У меня есть DataGridView на форме, в котором есть колонка ComboBox.
При загрузке формы для грида нет источника данных, так как он заполняется другим методом, вызываемым пользователем.
Когда форма загружается (создается экземпляр формы), я получаю ошибку:
Индекс находится вне диапазона. Должен быть неотрицательным и меньше размера коллекции.
Я выяснил, что проблема заключается в строке в файле конструктора формы, когда устанавливается текст заголовка для колонки. Это строка кода, вызывающая ошибку в файле Form1.Designer.cs:
this.gridComboBox1.HeaderText = "Options";
Если я закомментирую эту строку, форма загружается без проблем, но заголовок колонки комбобокса в гриде тогда просто “gridComboBox1“, что явно не идеально.
Я использую Visual Studio для создания своих форм, поэтому попробовал просто добавить совершенно новый элемент управления datagridview в приложение из панели инструментов и настроить колонки, не меняя никаких настроек, кроме типа колонки и текста заголовка. Но я получаю ту же проблему, что наводит меня на мысль, что это общая проблема с winforms, так как она предлагает функциональность, которая не работает.
Я также получаю ту же ошибку, если пытаюсь программно изменить текст заголовка в событии загрузки формы.
Я все еще довольно нов в C# и не смог выяснить, почему это происходит. Сама колонка добавляется ранее в код в файле конструктора следующим образом:
this.dgv1.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
this.select,
this.gridComboBox1,
this.DataType});
Так что колонка должна быть доступна для последующего взаимодействия с кодом.
Может кто-то прояснить, почему это происходит, так как для меня это не имеет смысла.
Я провел еще некоторые исследования и, похоже, исправил проблему.
Я подписался на событие CellValueChanged, чтобы запустить код, который заполняет другую колонку в гриде в зависимости от выбранного варианта из колонки ComboBox.
Тем не менее, это событие вызывается, когда текст заголовка устанавливается кодом конструктора.
Вот код, который вызывает вопрос:
private void dgv1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
try
{
if (e.ColumnIndex == 1) // индекс колонки комбобокса
{
string mappedField = dgv1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
string sql = @$"SELECT
UPPER(DATA_TYPE)
+ CASE
WHEN DATA_TYPE IN ('nvarchar', 'varchar') AND CHARACTER_MAXIMUM_LENGTH = -1 THEN '(MAX)'
WHEN DATA_TYPE IN ('nvarchar', 'varchar') THEN '('+CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10)) + ')'
ELSE ''
END AS DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Report'
AND COLUMN_NAME = '{mappedField}';";
string targetDataType = clsRepeatableMethods.SQLOutputToStringVariable(sql, clsConnections.ConnStrStg);
dgv1.Rows[e.RowIndex].Cells["RepDataType"].Value = targetDataType;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Поскольку в гриде нет строк, кроме заголовка, когда грид создается конструкцией конструктора, возникает ошибка “индекс вне диапазона”.
Исправление заключалось в добавлении проверки для индекса строки.
Я изменил эту строку кода:
if (e.ColumnIndex == 1)
На следующую:
if (e.RowIndex != -1 && e.ColumnIndex == 1)
Теперь все работает. Как я уже говорил ранее, я новичок в C#, и не думал, что событие CellValueChanged будет срабатывать из-за изменения в строке заголовка.
Спасибо @Sinatr, чьи комментарии заставили меня взглянуть шире на то, какой другой код мог быть вызван.
Ответ или решение
Проблема, с которой вы столкнулись, связана с тем, что событие CellValueChanged
для вашего DataGridView
вызывается и при изменении заголовка колонки. Когда ваше приложение загружается, в DataGridView
еще нет строк, кроме заголовка, и это приводит к ошибке "Индекс вне диапазона", когда вы пытаетесь получить доступ к e.RowIndex
.
Ваша первоначальная логика проверки в обработчике события была недостаточной, что и вызывало проблему. Как вы правильно заметили, необходимо добавить дополнительную проверку для e.RowIndex
, чтобы убедиться, что она не равна -1, прежде чем производить какие-либо операции с элементами строк.
Вот корректный фрагмент кода:
private void dgv1_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
try
{
if (e.RowIndex != -1 && e.ColumnIndex == 1) // Проверяем индекс строки и столбца
{
string mappedField = dgv1.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
string sql = @$"SELECT
UPPER(DATA_TYPE)
+ CASE
WHEN DATA_TYPE IN ('nvarchar', 'varchar') AND CHARACTER_MAXIMUM_LENGTH = -1 THEN '(MAX)'
WHEN DATA_TYPE IN ('nvarchar', 'varchar') THEN '('+CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10)) + ')'
ELSE ''
END AS DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'Report'
AND COLUMN_NAME = '{mappedField}';";
string targetDataType = clsRepeatableMethods.SQLOutputToStringVariable(sql, clsConnections.ConnStrStg);
dgv1.Rows[e.RowIndex].Cells["RepDataType"].Value = targetDataType;
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
Теперь ваше приложение должно работать должным образом без возникновения ошибок, связанных с индексами. Также важно помнить, что при работе с событиями, такими как CellValueChanged
, всегда стоит учитывать возможность случайного вызова обработчика при изменениях, не касающихся ценностей в строках данных, как в данном случае.
Если вы будете следовать этой практике проверки индексов в других подобных сценариях, это поможет избежать подобных ошибок в будущем.
Рекомендуется также изучить другие события DataGridView
, которые могут работать аналогично, чтобы иметь возможность предвидеть и обрабатывать возможные ошибки на ранних этапах разработки.