Как заполнить один ComboBoxCell из базы данных?

Я использую этот код, чтобы заполнить поле со списком.

MySqlDataAdapter sqlDa = new MySqlDataAdapter(query, bd);
DataTable dtbl = new DataTable();
sqlDa.Fill(dtbl);
box.MinimumWidth = 200;
box.ValueMember = idFromTable;
box.DisplayMember = "Nome";
box.DataSource = dtbl;

Я использую этот код. Поэтому я могу заполнить свой dataGridView всеми данными, которые у меня есть в mysql.

MySqlDataAdapter sqlDa = new MySqlDataAdapter("SELECT * FROM fosseis", bd);
DataTable dtbl = new DataTable();
sqlDa.Fill(dtbl);
FosseisDtGridVw.DataSource = dtbl;

Этот код работает, но проблема в том, что я не могу заполнить поле со списком после заполнения своего DataGridView, а также не могу заполнить только одно поле со списком во всем представлении сетки данных (в определенной строке, например в операторе if).

Я пытался сделать DataSource из comboboxColumn нулевым, прежде чем снова заполнить его, попытался сделать что-то вроде

datagridview.Row[1].Cells[1].DataGridView.DataSource = dtbl;

И другие вещи, которые я не нахожу достаточно мудрыми, чтобы решить эту проблему. Большую часть времени, когда я пытался решить эту проблему, я получал исключение из другого кода.

Этот объект не установлен в экземпляр


person Marto    schedule 19.02.2021    source источник
comment
Что заполняет одну ячейку со списком? значит здесь? Ячейка одной строки во всех имеющихся у вас строках? -- Если вы хотите установить DataSource для DataGridViewComboBoxColumn, просто сделайте это после создания столбцов. Соответствует ли ячейка ComboBox полю, возвращаемому запросом? Столбцы DGV создаются автоматически или вы добавили столбцы в конструкторе? Можете описать компоновку DGV и что собственно хотите сделать? ИМО, это неясно (обычно все строки имеют один и тот же тип ячейки в определенном столбце).   -  person Jimi    schedule 19.02.2021
comment
Джими, да, я хочу заполнить ComboBoxCell в определенной строке во всех строках, которые он заполнил представленным кодом (2-й). Я добавил столбцы в Designer.   -  person Marto    schedule 19.02.2021
comment
Почему только один? Какая от этого польза? Все строки имеют одинаковый тип столбцов. Что особенного в этой конкретной строке? Всегда ли это 2-й ряд? -- Поскольку вы добавили столбцы в дизайнере, какой тип столбца привязан к этому полю? Установлен ли DataPropertyName? Что это за столбец в конструкторе?   -  person Jimi    schedule 19.02.2021
comment
Может быть, мне следует переделать весь вопрос с дополнительной информацией. Но я пока постараюсь объяснить по-простому. Моя программа представляет собой диспетчер базы данных, где я могу вводить больше строк, удалять и вставлять. Но поля со списком должны быть похожи на иерархическую систему. Например: если предыдущий ComboBox заполнен Англией (столбец Country), то следующий просто может быть заполнен городами из Англии (столбец Cities). А пока мой запрос SELECT * FROM locationtable WHERE Coutry_idCountry = 1 (1 присваивается стране Англии). Таблицы связаны друг с другом. Спасибо за усилия.   -  person Marto    schedule 19.02.2021
comment
DataPropertyName установлено, они ComboBoxColumn   -  person Marto    schedule 19.02.2021
comment
Похоже, у вас есть база данных с нетрадиционным дизайном (обычно у вас есть таблица с PK, и этот PK является селектором FK другой таблицы), и вы пытаетесь использовать DataGridView нетрадиционным способом (с ячейками ComboBox, содержащими разные источники данных). Я не говорю, что это невозможно сделать, но чтобы настроить все это, вам нужно настроить поведение по умолчанию с обеих сторон. Имхо, это обязательно вызовет много проблем. Попробуйте перепроектировать свой макет, чтобы он обрабатывал сценарий Master-Detail, или правильно используйте DataGridView и обрабатывайте отношения с помощью выделенных пользовательских элементов управления.   -  person Jimi    schedule 19.02.2021
comment
У вас есть какая-нибудь страница, которую я мог бы увидеть, чтобы я мог лучше расположить? Я не вижу, как правильно.   -  person Marto    schedule 19.02.2021


Ответы (2)


Я уверен, что может быть более чем один способ сделать это. В приведенном ниже примере у меня есть два DataGridViewComboBoxColumns. Один для списка «Штаты», а другой для списка «Города». Поле со списком города заполняется в зависимости от выбранного штата. Было минимальное тестирование, и я предполагаю, что другое событие (я) также может быть лучшим подходом. Но это может сработать, в моем тесте сработало без ошибок.

Я предполагаю, что у вас может возникнуть одна проблема, когда код «загружает» данные. Вы должны иметь в виду, как вы сказали, что вы не можете «настроить» каждую ячейку поля со списком, пока не узнаете, какое значение имеет «Состояние» (первое поле со списком). И вы не будете знать, какое «состояние» имеет эта строка, пока ПОСЛЕ загрузки данных.

Учитывая это, должно показаться очевидным, что если вы хотите, чтобы поля со списком были установлены правильно при первоначальной загрузке данных… тогда вам лучше убедиться, что в поле со списком «Штат/Город» есть правильные элементы «Штат/Город». Либо так, либо добавляйте строки в сетку по одной, что не очень хорошая идея по многим причинам.

Кроме того, в отличие от обычного поля со списком, DataGridViewComboBoxCell/Column — это другой монстр, и он печально известен тем, что выбрасывает DataError, когда в ячейках со списком сетки установлены недопустимые элементы. Сетка выдает DataError каждый раз, когда код пытается установить значение ячейки в значение, которое НЕ находится в списке элементов поля со списком/столбца. Пример… когда данные загружены. Когда это происходит, если его не поймать… это событие сбоя приложения, и его следует избегать.

Следовательно, при загрузке данных с использованием DataSource для сетки ОБЯЗАТЕЛЬНО должен быть «полный» список «Города», поскольку мы не знаем, какой «Штат» находится в какой строке. Таким образом, «полный» список городов решит эту проблему при «загрузке» данных, и мы можем что-то сделать «после» загрузки данных для фильтрации вновь добавленных строк.

Чтобы установить индивидуальное поле со списком, мы просто «фильтруем» исходный список со ВСЕМИ «Городами», чтобы он содержал только «Города» из этого выбранного штата. ЕСЛИ мы «ФИЛЬТРИРУЕМ» исходный список, а не создаем новый, то шансы получить DataError значительно снижаются.

Другими словами, источник данных столбцов со списком содержит полный список… тогда каждая ячейка поля со списком в этом столбце может быть «подмножеством» исходного списка. Сетка не выдаст ошибку, если отображаются только НЕКОТОРЫЕ элементы в списке, ей важно только то, что их НЕТ в списке.

Другой основной вопрос, который вы не описали, — это «как» организованы данные. Если данные хранятся по-другому, то вам может потребоваться внести некоторые коррективы, однако основная идея все равно должна применяться. Итак, давайте посмотрим, как это будет работать в общих чертах, используя шесть (6) шагов.

Общий подход из шести (6) шагов...

  1. Во-первых, нужно загрузить данные для обоих полей со списком (данные о штатах и ​​данных о городах). В этом примере есть два DataTables под названием «Штаты» и «Города». Эти данные предназначены «строго» для источников данных полей со списком… а не для самой сетки.
  2. Создайте два (2) DataGridViewComboBoxColumns под названием «Штаты» и «Города». Используйте таблицу «Штаты» на шаге 1 в качестве источника данных для поля со списком «Штаты» и, очевидно, таблицу «Города» в качестве источника данных для столбца поля со списком «Города». Кроме того, установите в полях со списком необходимые значения свойств, чтобы сопоставить каждый столбец с источником данных сетки.
  3. Добавьте столбцы из шага 2 в сетку «ДО» установки источника данных сетки.
  4. Установите источник данных сетки. При правильной настройке столбцы поля со списком должны соответствовать загруженным данным. Однако в поле со списком городов будут отображаться ВСЕ города.
  5. После загрузки данных нам нужно вызвать метод, который перебирает каждую строку в сетке и «фильтрует» каждый список со списком «Города» по значениям выбранного состояния.
  6. После этого это все пользовательский интерфейс. Для этого нам нужно подписаться на несколько событий сетки, чтобы поддерживать «отфильтрованное» состояние каждого поля со списком, например, когда пользователь изменяет значение «Состояние» или добавляет новую строку.

Ниже приведен полный пример. Создайте новое решение winforms, поместите DataGridView в форму и следуйте инструкциям. У нас должно получиться что-то похожее на…

введите здесь описание изображения

Для начала давайте посмотрим на образцы данных. Будет два DataTables под названием «Штаты» и «Города».

Схема «Состояния» будет состоять из двух (2) столбцов…

  1. уникальный int ID «StateID»
  2. string «Имя состояния»

Таблица «Города» будет иметь три (3) столбца…

  1. уникальный int ID «CityID»
  2. int «StateID и
  3. a string «Название города»

Пример… Состояние данных с использованием CSV… Я вставил это, чтобы вы могли использовать это для тестовых данных в приведенном ниже коде. Код считывает эти данные из простого CSV-файла.

1,Alaska
2,California
3,Texas
4,Colorado

И данные города с использованием CSV

1,1,Anchorage
2,1,Bethel
3,1,Fairbanks
4,1,Soldotna
5,2,Los Angeles
6,2,San Francisco
7,2,San Diego
8,2,Sacramento
9,3,San Antonio
10,3,Austin
11,3,Dallas
12,3,Houston
13,4,Denver
14,4,Colorado Springs
15,4,Aurora
16,3,El Paso
17,4,Boulder
18,2,Pleasanton
19,3,Lubbock
20,1,Ketchikan

Шаг 1

Используя эти данные, код FillDataSet прочитает каждый файл «Штаты» и «Города» и создаст DataSet с двумя таблицами с именами States и Cities. Ниже приведен код, который прочитает указанные выше файлы и вернет DataSet с двумя таблицами.

DataSet StateCityDS;

private void FillDataSet() {
  DataTable dt1 = new DataTable();
  dt1.TableName = "States";
  dt1.Columns.Add("StateID", typeof(int));
  dt1.Columns.Add("StateName", typeof(string));
  DataTable dt2 = new DataTable();
  dt2.TableName = "Cities";
  dt2.Columns.Add("CityID", typeof(int));
  dt2.Columns.Add("StateID", typeof(int));
  dt2.Columns.Add("CityName", typeof(string));
  string line;
  string[] splitArray;
  using (StreamReader sr = new StreamReader(@"D:\Test\CSV\StatesDemo_1.txt")) {
    while ((line = sr.ReadLine()) != null) {
      splitArray = line.Split(',');
      if (splitArray.Length >= 2) {
        dt1.Rows.Add(splitArray[0], splitArray[1]);
      }
    }
  }
  using (StreamReader sr = new StreamReader(@"D:\Test\CSV\CitiesDemo_1.txt")) {
    while ((line = sr.ReadLine()) != null) {
      splitArray = line.Split(',');
      if (splitArray.Length >= 3) {
        dt2.Rows.Add(splitArray[0], splitArray[1], splitArray[2]);
      }
    }
  }
  StateCityDS = new DataSet();
  StateCityDS.Tables.Add(dt1);
  StateCityDS.Tables.Add(dt2);
}

Шаг 2 и 3

Теперь у нас есть некоторые тестовые данные. Затем нам нужен метод для добавления двух (2) DataGridViewComboBoxColumns.. Ниже следует отметить, что DataPropertyName каждого столбца настроено на соответствие соответствующему столбцу в таблице источника данных «GRIDS». DataSource каждого столбца устанавливается в одну из таблиц из возвращенного DataSet выше. Обратите внимание, что DisplayMember и ValueMember. мы хотим отображать название штата/города, и мы хотим, чтобы значение было идентификатором штата/города.

private void AddCascadeComboBoxes() {
  DataGridViewComboBoxColumn col1 = new DataGridViewComboBoxColumn();
  col1.Name = "States";
  col1.HeaderText = "State";
  col1.DataSource = StateCityDS.Tables["States"];
  col1.DisplayMember = "StateName";
  col1.ValueMember = "StateID";
  col1.DataPropertyName = "States";
  dataGridView1.Columns.Add(col1);

  col1 = new DataGridViewComboBoxColumn();
  col1.Name = "Cities";
  col1.HeaderText = "City";
  col1.DataSource = StateCityDS.Tables["Cities"];
  col1.DisplayMember = "CityName";
  col1.ValueMember = "CityID";
  col1.DataPropertyName = "Cities";
  dataGridView1.Columns.Add(col1);
}

Это должно позаботиться о столбцах поля со списком. Если мы вызовем FillDataSet, а затем AddCascadeComboBoxes из события загрузки форм, в сетке должно быть два столбца поля со списком. В первом будут перечислены четыре (4) штата, а во втором поле со списком будут перечислены ВСЕ города для всех штатов. С этой конфигурацией загрузка данных в сетку теперь должна работать успешно. Итак, давайте создадим несколько тестовых данных для сетки и проверим это.

Шаг 4

Ниже GetGridDataSource имитирует данные, которые будут загружены в саму сетку. В этом примере это просто DataTable с тремя столбцами под названием «Имя», «Штаты» и «Города». Каждая строка будет иметь разные имена с разными идентификаторами штата и города. Пример: Штат 1 = Аляска, Город 1 = Анкоридж. Этот метод может выглядеть примерно так…

private DataTable GetGridDataSource() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Name", typeof(string));
  dt.Columns.Add("States", typeof(int));
  dt.Columns.Add("Cities", typeof(int));
  dt.Rows.Add("John", 1, 1);
  dt.Rows.Add("Sally", 2, 5);
  dt.Rows.Add("Mary", 3, 10);
  dt.Rows.Add("Dean", 4, 13);
  dt.Rows.Add("Charlie", 2, 8);
  dt.Rows.Add("Nancy", 3, 12);
  return dt;
}

Если мы установим указанное выше DataTable как DataSource в сетке, то каждый столбец поля со списком должен иметь правильное значение «Штат» и «Город», установленное в соответствии с исходным источником данных. Кроме того, вы должны увидеть дополнительный столбец «Имя», как показано на предыдущем рисунке. Если вы видите дополнительный столбец, например штат или город, с номерами, значит DataGridViewComboBoxColumn настроен неправильно.

Шаг 5

Похоже, это решает проблему «загрузки», однако, когда мы нажимаем на любое из полей со списком «Город», список не фильтруется, и мы по-прежнему видим все города. Итак, нам нужно добавить шаг 5 из общего описания выше. После загрузки данных нам нужен дополнительный шаг для «фильтрации» каждого поля со списком «Город» в каждой «существующей» строке. Это не должно изменить текущее значение ячейки и относительно прямолинейно… что-то вроде…

private void SetCityComboBoxesDataSource() {
  foreach (DataGridViewRow row in dataGridView1.Rows) {
    if (!row.IsNewRow) {
      DataRowView dr = (DataRowView)row.DataBoundItem;
      int stateID = (int)dr["States"];
      DataView dv = new DataView(StateCityDS.Tables["Cities"]);
      dv.RowFilter = string.Format("StateID = {0}", stateID);
      DataGridViewComboBoxCell cityCell = (DataGridViewComboBoxCell)(row.Cells["Cities"]);
      cityCell.DataSource = dv;
      cityCell.DisplayMember = "CityName";
      cityCell.ValueMember = "CityID";
    }
  }
}

Выше код перебирает каждую строку, берет значение из ячейки «Штаты» и «фильтрует» список городов со списком. Нам нужно вызвать этот код только ОДИН РАЗ после загрузки данных сетки.

Если мы запустим этот код как есть, то поля со списком будут отображаться правильно, и если вы нажмете на поле со списком «Город», он должен отобразить «отфильтрованный» список. Однако это подводит нас только к шагу 5 в общем описании. Все это необходимо для «загрузки» данных без ошибок и фильтрации добавленных строк, однако нам все еще нужна часть пользовательского интерфейса. Данные загружены, и теперь нам нужно подписаться на пару событий, чтобы правильно управлять «фильтрацией» полей со списком, когда пользователь взаимодействует с сеткой (UI).

Шаг 6

Бьюсь об заклад, есть несколько способов сделать это, и может быть лучший подход к этому. Однако в этом примере я подключил (подписался) на два (2) события сетки.

Событие grids CellEndEdit сработает, когда пользователь закончит «редактирование» ячейки и попытается покинуть ячейку. Когда он сработает, мы проверим, находится ли «отредактированная» ячейка в столбце «Состояние». Если это так, то мы отфильтруем ячейку «Города» в этой строке, чтобы отобразить города этого штата.

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e) {
  if (e.RowIndex >= 0 && e.ColumnIndex >= 0) {
    if (dataGridView1.Columns[e.ColumnIndex].Name == "States") {
      if (dataGridView1.Rows[e.RowIndex].Cells["States"].Value != null) {
        if (int.TryParse(dataGridView1.Rows[e.RowIndex].Cells["States"].Value.ToString(), out int stateID)) {
          DataView dv = new DataView(StateCityDS.Tables["Cities"]);
          dv.RowFilter = string.Format("StateID = {0}", stateID);
          DataGridViewComboBoxCell cityCell = (DataGridViewComboBoxCell)(dataGridView1.Rows[e.RowIndex].Cells["Cities"]);
          cityCell.Value = DBNull.Value;
          cityCell.DataSource = dv;
          cityCell.DisplayMember = "CityName";
        }
      }
    }
  }
}

Событие grids EditingControlShowing связано с проверкой того, меняет ли пользователь значение «State», которое уже было выбрано. Это установит для ячейки «Города» для этой строки значение «ноль». Это делается для того, чтобы значение поля со списком «Город» не становилось «недействительным», если пользователь меняет значение «Штат», когда оба значения уже установлены. Без этой проверки и установки ячейки в нулевое значение пользователь мог бы выбрать другое состояние, и город остался бы из предыдущего состояния. Это предотвратит несогласованное состояние данных.

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
  int colIndex = dataGridView1.CurrentCell.ColumnIndex;
  int rowIndex = dataGridView1.CurrentCell.RowIndex;
  if (dataGridView1.Columns[colIndex].Name == "States") {
    dataGridView1.Rows[rowIndex].Cells["Cities"].Value = DBNull.Value;
  }
}

Подключение сеток DataError пригодится при отладке.

private void dataGridView1_DataError(object sender, DataGridViewDataErrorEventArgs e) {
  MessageBox.Show("Error: R" + e.RowIndex + "C" + e.ColumnIndex + " --> " + e.Exception.Message);
}

Чтобы собрать все это вместе, ниже приведена остальная часть кода. Обратите внимание, что вам нужно будет сохранить два CSV-файла выше в нужном месте, чтобы получить тестовые данные.

DataSet StateCityDS;
DataTable GridDT;

public Form1() {
  InitializeComponent();
}

private void Form1_Load(object sender, EventArgs e) {
  GridDT = GetGridDataSource();
  FillDataSet();
  AddCascadeComboBoxes();
  dataGridView1.DataSource = GridDT;
  SetCityComboBoxesDataSource();
}

Наконец, следует отметить, что если пользователь выбирает поле со списком «Города» из «новой» строки сетки, то будут отображаться ВСЕ города. Предоставлено, что пользователь «может» выбрать любой город, однако, как только пользователь нажмет на поле со списком «Штат», значение выбранного города будет удалено, и фильтр будет применен.

Особое примечание к шагу 1 сверху и установке начальных значений поля со списком. ЕСЛИ вы правильно настроили поля со списком, И при загрузке данных вы получаете сообщение об ошибке данных о том, что элемент не принадлежит к списку элементов со списком... ТОГДА... гарантируется, что данные содержат что-то, чего нет. в выпадающем списке список элементов.

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

Проблема, с которой вы столкнулись бы в этом случае, заключается в том, что делать с оскорбительным значением… вы НЕ можете просто игнорировать его. Вам придется либо удалить эту строку из самих данных, либо добавить этот новый элемент в список элементов со списком. Ядовитая пилюля, с которой нужно как-то справиться.

Извините за длинное сообщение, я надеюсь, что это имеет смысл.

person JohnG    schedule 20.02.2021
comment
Это не сработало, я даже не могу заполнить весь ComboBoxColumn после того, как заполнил DGV данными. И если я сделаю код до этого, он не будет работать, потому что у меня нет строк в DGV - person Marto; 20.02.2021
comment
@Marto… Без дополнительного кода трудно понять, в чем может быть проблема. Я обновлю свой ответ, чтобы показать полный пример в ближайшее время. Можете ли вы уточнить, что каждая строка в сетке имеет два (2) DataGridViewComboBoxColumns,, и вы хотите, чтобы после того, как первое поле со списком изменило свое значение, вы хотите, чтобы второе поле со списком было заполнено данными на основе того, что выбрано в первом поле со списком . - person JohnG; 20.02.2021
comment
Например, первое поле со списком содержит названия штатов, и когда пользователь выбирает штат, второе поле со списком должно содержать только названия городов из этого штата. В этом случае КАЖДОЕ поле со списком «город» в каждой строке может иметь «разные» значения. - person JohnG; 20.02.2021
comment
@Marto ... обновите полное решение для тестирования. - person JohnG; 20.02.2021
comment
Эй, спасибо за усилия, я уверен, что ваш ответ работает во многих проектах, но в моем не работает. Я не знаю, должен ли я использовать наборы данных или нет, но я этого не делаю. Я уже решил эту проблему, и достаточно скоро я опубликую awnser. - person Marto; 23.02.2021
comment
@Marto … Использование DataSet для этого не имеет значения. С тем же успехом вы могли бы использовать два (2) DataTables. Ваш комментарий… «Я уверен, что ваш ответ работает во многих проектах, но в моем не работает». … можете ли вы сказать мне, «почему» он не работает в вашем проекте. Все, что у нас есть, это код, который вы разместили, и описание, которое вы предоставили. Я с нетерпением жду вашего решения/ответа. - person JohnG; 24.02.2021

проверяет DataPropertyName свойство элемента DataGridViewComboBoxColumn, если оно содержит указанное поле в таблице, которая является источником данных представления данных.

person stito550    schedule 21.02.2021