Из описания видно, что вы хотите создать отношения типа «родители-дети» (от 1 до многих) между двумя полями со списком. IE выбирает Провидение / Штат из поля со списком 1 и заполняет поле со списком 2 Городами в этом выбранном Провидении / Штате.
Из опубликованного кода в событии SelectedIndexChanged
кажется, что комбо «город» Поле очищается, затем заполняется в зависимости от того, какое значение выбрано в поле со списком «Providence». Это должно / будет работать, однако у этого подхода есть как минимум один недостаток. Один поддерживает код. Даже с довольно «статичными» данными ... если вам когда-нибудь понадобится добавить новый провиденс или город ... вам придется вносить изменения в код и перекомпилировать. Это не очень гибко.
Лучше было бы получить данные из вашей базы данных или файла и поместить их в таблицу (таблицы) или, возможно, List<T>
. Затем используйте эти таблицы / List в качестве источника данных для комбинированных списков. Переключение DataSource
для поля со списком «города» должно быть относительно простым путем простого изменения источника данных с помощью нескольких строк кода…
City_ComboBox.DataSource = SelectedState.Cities;
Это уменьшит отправленный код SelectedIndexChanged
до несколько строк кода, и мы можем легко добавлять новые провинции / штаты или города без изменения кода.
Учитывая это, ниже представлены два различных подхода… 1) использование «Класса» с List<T>
2) использование DataSet
с двумя (2) DataTables
.
классом и списком
В первом подходе используется Class
с именем StateCityData
, и этот класс может выглядеть примерно так ...
public class StateCityData {
public string StateName { get; set; }
public List<string> Cities { get; set; }
public string ShortName { get; set; }
public StateCityData () {
StateName = "";
Cities = new List<string>();
ShortName = "";
}
public StateCityData(string state, string shortName) {
StateName = state;
Cities = new List<string>();
ShortName = shortName;
}
public override bool Equals(object obj) {
return obj is StateCityData data &&
ShortName == data.ShortName;
}
public override int GetHashCode() {
return -1319491066 + EqualityComparer<string>.Default.GetHashCode(ShortName);
}
Объект StateCityData
содержит основную c информацию о состоянии. Как показано, свойство Cities
является List<string>
, и эта информация может находиться в другом файле или таблице. При этом предполагается, что данные «Состояния» находятся в одной таблице / файле базы данных, а данные «Города» - в другой таблице / файле, где в каждом городе будет поле с именем «ShortName», соответствующее «какому» СОСТОЯНИЮ, к которому принадлежит город .
Идея состоит в том, чтобы сначала получить все свойства basi c StateCityData
(StateName, ShortName), где список «Cities» каждого объекта пуст. Затем проведите l oop через данные «городов» и поместите каждый город в соответствующий объект StateCityData
, сопоставив переменную ShortName
. Переопределение равно используется, чтобы определить, к какому штату принадлежит город, сопоставив ShortName
из города с ShortName
штата.
Если мы создадим List<StateCityData>
, мы могли бы использовать это List<StateCityData>
как DataSource
для поля со списком 1, используя StateName
в качестве поля со списком DisplayMember
. Это должно позаботиться о первом поле со списком «Providence / State».
Для второго поля со списком «Cities» мы хотим использовать данные из переменной «Cities» в «selected» StateCityData
Объект в первом поле со списком DataSource
для второго поля со списком «Города». Поэтому, когда первое поле со списком «Состояния» меняет значение, нам также необходимо изменить значения поля со списком городов. Это должно быть относительно легко, если просто взять «выбранный» объект StateCityData
из первого поля со списком и установить во втором поле со списком «Города» DataSource
значение Cities
List<string>
из выбранного объекта StateCityData
. Первое поле со списком «State» SelectedIndexChanged
событие может выглядеть примерно так: *
private void State_ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
int selectedIndex = State_ComboBox.SelectedIndex;
if (StatesData[selectedIndex].Cities.Count > 0) {
City_ComboBox.DataSource = StatesData[selectedIndex].Cities;
}
else {
City_ComboBox.DataSource = null;
}
}
Соединение всего этого может выглядеть примерно так, как показано ниже. Метод GetListFromCSV
получает данные из файла с разделителями-запятыми, однако не имеет значения, «как» вы получаете данные. Этот List<StateCityData>
(StateData
) используется как DataSource
для первого поля со списком «States», используя «StateName» в качестве DisplayMember
. Поскольку это происходит в событии загрузки форм, мы знаем, что в поле со списком будет выбрано «первое» состояние, поэтому мы можем установить второе поле со списком «Города» DataSource
, используя первое состояние в списке… StatesData[0].Cities
.
private List<StateCityData> StatesData;
private void Form1_Load(object sender, EventArgs e) {
StatesData = GetListFromCSV();
State_ComboBox.DisplayMember = "StateName";
State_ComboBox.DataSource = StatesData;
City_ComboBox.DataSource = StatesData[0].Cities;
}
Ниже приведен метод для чтения данных из CSV-файла и возврата заполненного List<StateCityData>
- StatesData
.
private List<StateCityData> GetListFromCSV() {
List<string> curCities = new List<string>();
List<StateCityData> statesData = new List<StateCityData>();
StreamReader sr = new StreamReader(@"D:\Test\States.csv");
string curLine;
StateCityData curSCD;
string[] splitArray;
while ((curLine = sr.ReadLine()) != null) {
splitArray = curLine.Split(',');
if (splitArray.Length >= 2) {
curSCD = new StateCityData {
StateName = splitArray[0],
ShortName = splitArray[1]
};
statesData.Add(curSCD);
}
else {
MessageBox.Show("array less than 2: " + curLine);
}
}
sr.Close();
sr = new StreamReader(@"D:\Test\Cities.csv");
int targetIndex;
while ((curLine = sr.ReadLine()) != null) {
splitArray = curLine.Split(',');
if (splitArray.Length >= 2) {
curSCD = new StateCityData("", splitArray[1]);
targetIndex = statesData.IndexOf(curSCD);
if (targetIndex >= 0) {
curSCD = statesData[targetIndex];
curSCD.Cities.Add(splitArray[0]);
}
else {
MessageBox.Show("Target not found: " + curLine);
}
}
else {
MessageBox.Show("Less than 2: " + curLine);
}
}
return statesData;
}
DataSet с двумя таблицами DataTable.
Во втором подходе используется DataSet
, который содержит два простых DataTables
. Первые DataTable
«Штаты» могут выглядеть примерно так:
DataTable dt = new DataTable("States");
dt.Columns.Add("StateName", typeof(string));
dt.Columns.Add("ShortName", typeof(string));
Вторые DataTable
«Города» имеют город и сокращенное название штата, которому оно принадлежит. Это может выглядеть примерно так: *
dt = new DataTable("Cities");
dt.Columns.Add("City", typeof(string));
dt.Columns.Add("ShortName", typeof(string));
В отличие от использования подхода «Класс», где каждый объект StateCityData
содержал свой «собственный» список городов, этот подход имеет одну таблицу со ВСЕМИ городами и должен будет «Отфильтруйте» таблицу, чтобы она содержала только города, соответствующие выбранному состоянию ShortName
. Есть возможность полностью исключить таблицу «Города» и просто запросить базу данных для вейлов, однако я не рекомендую этого. В этом случае «все» города находятся в одной таблице, и мы будем «фильтровать» таблицу на основе выбранного состояния, а затем использовать «отфильтрованные» результаты в качестве DataSource
для поля со списком «Города».
Поэтому поле со списком «States» SelectedIndexChanged
может выглядеть примерно так, как показано ниже. Сначала нам нужно получить короткое имя для выбранных состояний (target
). Затем создайте новый DataView
из таблицы «Города». Отфильтруйте это представление на основе выбранного target
, а затем используйте это представление в качестве DataSource
в поле со списком «Города». Поскольку мы заменяем источник данных, нам также необходимо сбросить поля со списком DisplayMember
.
private void State_ComboBox_SelectedIndexChanged(object sender, EventArgs e) {
string target = ((DataRow)StatesDataSet.Tables["States"].Rows[State_ComboBox.SelectedIndex])["ShortName"].ToString();
dv = new DataView(StatesDataSet.Tables["Cities"]);
dv.RowFilter = String.Format("ShortName = '{0}'", target);
if (dv.Count > 0)
City_ComboBox.DataSource = dv;
else
City_ComboBox.DataSource = null;
City_ComboBox.DisplayMember = "City";
}
Объединение этого в событии загрузки формы может выглядеть примерно так, как показано ниже. Подобно первому подходу, я использую простой CSV-файл для получения данных. Метод GetDataSetFromCSV
просто заполняет таблицы данных данными. Опять же, неважно, как вы получаете данные.
private DataSet StatesDataSet;
private DataView dv;
private void Form1_Load(object sender, EventArgs e) {
GetDataSetFromCSV();
State_ComboBox.DataSource = StatesDataSet.Tables["States"];
State_ComboBox.DisplayMember = "StateName";
dv = new DataView(StatesDataSet.Tables["Cities"]);
string targetState = ((DataRow)StatesDataSet.Tables["States"].Rows[0])["ShortName"].ToString();
dv.RowFilter = String.Format("ShortName = '{0}'", targetState);
City_ComboBox.DataSource = dv;
City_ComboBox.DisplayMember = "City";
}
Метод получения данных из файла CSV.
private void GetDataSetFromCSV() {
StatesDataSet = new DataSet("States");
DataTable dt = new DataTable("States");
dt.Columns.Add("StateName", typeof(string));
dt.Columns.Add("ShortName", typeof(string));
StreamReader sr = new StreamReader(@"D:\Test\States.txt");
string curLine = "";
string[] splitArray;
while ((curLine = sr.ReadLine()) != null) {
splitArray = curLine.Split(',');
if (splitArray.Length >= 2) {
dt.Rows.Add(splitArray[0], splitArray[1]);
}
else {
MessageBox.Show("array less than 3: " + curLine);
}
}
StatesDataSet.Tables.Add(dt);
dt = new DataTable("Cities");
dt.Columns.Add("City", typeof(string));
dt.Columns.Add("ShortName", typeof(string));
sr.Close();
sr = new StreamReader(@"D:\Test\Cities.csv");
while ((curLine = sr.ReadLine()) != null) {
splitArray = curLine.Split(',');
if (splitArray.Length >= 2) {
dt.Rows.Add(splitArray[0], splitArray[1]);
}
else {
MessageBox.Show("Less than 3: " + curLine);
}
}
sr.Close();
StatesDataSet.Tables.Add(dt);
}
Надеюсь, это имеет смысл и поможет.