Взяться за подробное рассмотрение возможностей по расширенному
использованию DataGrid элемента управления для построения
пользовательского интерфейса в GUI приложениях меня натолкнули вопросы
из форумов на сайте http://www.ishodniki.ru . Действительно, табличная форма для
представления и редактирования данных – одна из самых часто
используемых. .NET Framework содержит достаточно мощный элемент
управления DataGrid предназначенный именно для решения этой задачи. Но
каким бы богато функциональным не был элемент пользовательского
интерфейса, разработчик всегда захочет что-нибудь улучшить, расширить,
настроить (хорошо, если это желание возникает в ответ на запросы
пользователя, а не из стремления программиста освоить новые приемы, что
называется ”из любви к искусству” :-) ). DataGrid, с одной стороны,
предлагает удобную объектную модель для расширения своей
функциональности. В то же время, взявшись за изучение вопроса,
столкнулся с тем, что в документации и в online MSDN Library явно мало
описаний ”как это сделать”, относящихся к работе с DataGrid. В данной
статье мы посмотрим, как решать задачу ввода в ячейку из списка
текстовых значений.
DropDownListBox: Ввод данных в ячейку через ComboBox
Не самый простой пример, но, пожалуй, самый наболевший. Поэтому начнем
именно с него.
Все примеры будем строить на базе данных Northwind, входящей в состав
поставки любой версии Microsoft SQL Server 2000.
Итак, перед нами задача – вводить данные в ячейку из ниспадающего
списка возможных значений. Попытаемся найти соответствующий стиль
колонки и обнаружим, что .NET Framework предлагает только два типа
колонок – DataGridTextBoxColumn , для представления и редактирования
текстовых строк, и DataGridBoolColumn , для представления и
редактирования булевых значений.
Придется немного потрудиться. Реализуем следующий подход: для
отображения значений в колонке предоставим работать классу
DataGridTextBoxColumn, а при получении фокуса на ячейку, наложим на нее
ComboBox элемент управления, получим значение от пользователя, и
передадим его в оригинальную ячейку DataGrid.
Итак,
1 Создадим новый Windows Application проект на C#.
2 Поместим DataGrid на форму.
3 Добавим вспомогательные элементы
public class Form1 : System.Windows.Forms.Form
{
// Добавим объекты в класс формы для получения
// DataSet из базы данных
static SqlConnection cn =
new SqlConnection("server=(local);Integrated Security=SSPI;" +
"Persist Security Info=false;database=Northwind;");
SqlDataAdapter daEmpoyees =
new SqlDataAdapter("Select LastName,FirstName, TitleOfCourtesy From
Employees", cn);
DataSet ds = new DataSet();
// создаем элемент ComboBox - через него будем получать
// значения для колонки TitleOfCourtesy
// Поскольку будем передвигать ComboBox поверх активной ячейки,
// получившей фокус для пользовательского ввода - СomboBox
// потребуется один.
ComboBox customCombo = new ComboBox();
4. Проинициализируем объекты при загрузке формы
private void Form1_Load(object sender,
System.EventArgs e)
{
// заполним DataSet
daEmpoyees.Fill(ds, "Employees");
// свяжем DataGrid с DataSet
dataGrid1.DataSource = ds;
dataGrid1.DataMember = "Employees";
customCombo.Name = "customCombo";
// сделаем ComboBox пока невидимым - подождем пока ячейка
// не получит фокус
customCombo.Visible = false;
// заполним возможными значениями для
// поля TitleOfCourtesy - форма обращения
customCombo.Items.Clear();
customCombo.Items.Add("Ms.");
customCombo.Items.Add("Dr.");
customCombo.Items.Add("Mrs.");
customCombo.Items.Add("Mr.");
// добавим обработчик события изменения содержимого ComboBox
// в этой процедуре будем значение,
// выбранное пользователем в ComboBox
// записывать в ячейку DataGrid
customCombo.TextChanged+= new EventHandler(this.combo_TextChanged);
// установим высоту строки для DataGrid в высоту нашего ComboBox
dataGrid1.PreferredRowHeight = customCombo.Height;
// Добавим ComboBox к коллекции
// подчиненных элементов управления DataGrid
dataGrid1.Controls.Add(customCombo);
}
5. Сохраним значение, введенное пользователем через ComboBox
private void combo_TextChanged(object obj,System.EventArgs e)
{
if (dataGrid1.CurrentCell.ColumnNumber == 2 )
{
// фокус с ComboBox перемещается - спрячем его
customCombo.Visible = false;
// запомним значение, выбранное пользователем, в текущей ячейке
dataGrid1[dataGrid1.CurrentCell] = customCombo.Text;
}
}
6. Осталось позаботиться, чтобы наш ComboBoх позиционировался
поверх текущей ячейки DataGrid и был видимым в момент ввода значений
private void dataGrid1_Paint(object sender,
System.Windows.Forms.PaintEventArgs e)
{
if( dataGrid1.CurrentCell.ColumnNumber == 2)
// растянем ComboBox на ширину ячейки
customCombo.Width = dataGrid1.GetCurrentCellBounds().Width;
}
private void dataGrid1_CurrentCellChanged(object sender,
System.EventArgs e)
{
// ячейка получила фокус и если это наша колонка, нужно
//позиционировать ComboBox поверх ячейки и сделать его видимым
if( dataGrid1.CurrentCell.ColumnNumber == 2)
{
// убедимся, что ComboBox невидим
customCombo.Visible = false;
// установим ширину ComboBox позже в событии Paint
// это гарантирует, что ComboBox будет всегда
// растягиваться правильно
customCombo.Width = 0;
// позиционируем ComboBox поверх активной ячейки
customCombo.Left = dataGrid1.GetCurrentCellBounds().Left;
customCombo.Top = dataGrid1.GetCurrentCellBounds().Top;
customCombo.Text = dataGrid1[dataGrid1.CurrentCell] + "";
// теперь ComboBox готов получать ввод от пользователя
customCombo.Visible = true;
}
else
{
// это не наша колонка
customCombo.Visible = false;
customCombo.Width = 0;
}
}
private void dataGrid1_Click(object sender, System.EventArgs e)
{
// фокус не на ячейке – спрячем ComboBox
customCombo.Visible = false;
customCombo.Width = 0;
}
private void dataGrid1_Scroll(object sender, System.EventArgs e)
{
// фокус не на ячейке – спрячем ComboBox
customCombo.Visible = false;
customCombo.Width = 0;
}
7. Запускаем проект и наслаждаемся видом нашего DataGrid
Полный исходный текст примера можно загрузить из раздела
Примеры на сайте
http://www.gotdotnet.ru .
Давайте добавим теперь код для сохранения наших изменений,
произведенных через расширенный dataGrid в базу данных. И что бы не было
совсем скучно, расширим наш пример.
DbLookup: Использование ComboBox для получения значений из
справочника
Итак, усложняем задачу – хотим использовать ComboBox для заполнения
ячейки DataGrid, когда возможные значения хранятся в отдельной таблице,
а поле содержит идентификатор соответствующей строки справочника. Говоря
коротко – хотим получить так называемый dblookup. Колонка ReportsTo,
моделирующая иерархию подчиненности сотрудников - подходящий кандидат
для примера.
Используем тот же подход, что и в первом примере – позиционировать
ComboBox поверх редактируемой ячейки. Но колонка ReportsTo содержит
идентификаторы менеджеров, а пользователь хочет видеть на экране
фамилии. И это его вполне законное право. Тогда будем использовать две
колонки – первая, вычисляемая, для отображения фамилий менеджеров.
Именно она и будет видна в DataGrid. Вторая – поле ReportsTo, будет
невидима в DataGrid но будет использоваться для изменения данных в
DataSet.
Итак,
1. изменим оператор Select в конструкторе SqlDataAdapter и
добавим две колонки, чтобы работать с полем ReportsTo и ID для
идентификации обновляемой записи:
SqlDataAdapter daEmpoyees = new SqlDataAdapter(
"select e.LastName as LastName,e.FirstName as FirstName, " +
" e.TitleOfCourtesy as TitleOfCourtesy, s.LastName as Manager," +
"e.ReportsTo as ReportsTo,e.EmployeeID as EmployeeID " +
" from Employees as e " +
" LEFT OUTER JOIN Employees as s on e.ReportsTo = s.EmployeeID", cn);
2. Теперь наш DataSet, a потом и DataGrid будет содержать
дополнительные три колонки. Поскольку мы не хотим, чтобы пользователь
видел ID служащего и ID его менеджера, мы удалим эти колонки из DataGrid
в обработчике события Form_Load. Коллекция колонок для таблицы в
DataGrid доступна через свойство GridColumnStyles соответствующей
таблице. При добавлении DataGridTableStyle() с пустой коллекцией колонок
к коллекции стилей таблиц DataGrid генерирует коллекцию колонок
самостоятельно на основе связанного DataSet.
//
заставим DatsGrid сгенерировать коллекцию стилей колонок
// для нашего DataSet
dataGrid1.TableStyles.Add(new DataGridTableStyle());
dataGrid1.TableStyles[0].MappingName = "Employees";
// удалим последние две колонки - EmployeeID и ReportsTo
dataGrid1.TableStyles["Employees"].GridColumnStyles.RemoveAt(5);
dataGrid1.TableStyles["Employees"].GridColumnStyles.RemoveAt(4);
3. Теперь займемся нашей DbLookup ячейкой. Добавим еще один
ComboBox – customDbLookUp – для фокусирования поверх колонки с
отображаемой фамилией менеджера. Заполнять ее будем объектами,
сопоставляющим для каждого служащего его ID с LastName. Эти объекты
будут экземплярами класса Employee:
public class Employee
{
// хранит EmployeeID и lastName
public Employee(){}
public Employee(string s,int i)
{
LastName = s;
EmployeeID = i;
}
public override string ToString()
{
// строковое свойство по умолчанию как LastName
return ((LastName == null)?"NULL":LastName ) ;
}
public string LastName;
public int EmployeeID;
}
4. Завершим инициализацию второго ComboBox. Мы формируем
коллекцию элементов ComboBox из наших объектов Employee. В дальнейшем
это позволит просто получить ID менеджера по его фамилии и запомнить
этой ID в поле ReportsTo
customDbLookUp.Items.Clear();
// прочтем значения EmployeeID и LastName через SqlDataReader
SqlCommand cmdDbLookUp = new SqlCommand("Select EmployeeID, LastName From
Employees",cn);
SqlDataReader rdLastNames;
cn.Open();
rdLastNames = cmdDbLookUp.ExecuteReader();
// заполним ComboBox значениями
while (rdLastNames.Read())
{
customDbLookUp.Items.Add(new
Employee(rdLastNames.GetString(1),rdLastNames.GetInt32(0)));
}
rdLastNames.Close();
cn.Close();
// Добавим ComboBox к коллекции
// подчиненных элементов управления DataGrid
dataGrid1.Controls.Add(customDbLookUp);
// добавим обработчик события изменения содержимого ComboBox
// в этой процедуре будем значение, выбранное пользователем в ComboBox
// записывать в ячейку DataGrid
customDbLookUp.TextChanged += new
EventHandler(this.customDbLookUp_TextChanged);
Управление видимостью осуществляется в соответствующих событиях DataGrid
таким же образом, как и для первого ComboBox.
5. Теперь обработаем ввод пользователя через ComboBox. Помимо
изменения значения в DataGrid, ,будем менять значения поля ReportsTo в
DataSet
private void customDbLookUp_TextChanged(object obj,System.EventArgs e)
{
if (dataGrid1.CurrentCell.ColumnNumber == 3 )
{
// фокус с ComboBox перемещается - спрячем его
customDbLookUp.Visible = false;
// запомним значение, выбранное пользователем, в текущей ячейке
dataGrid1[dataGrid1.CurrentCell] = customDbLookUp.Text;
Employee emp = (Employee)customDbLookUp.SelectedItem ;
if( emp != null)
{
// изменим в DataSet значение ReportsTo
ds.Tables["Employees"].Rows[dataGrid1.CurrentCell.RowNumber]
["ReportsTo"] = emp.EmployeeID;
}
}
}
6. Осталось совсем немного – сохранить наши изменения в базе
данных и проверить результат. Не забудем создать UpdateCommand для
DataAdapter (удалять и добавлять строки в нашем примере мы не будем)
private void Form1_Load(object sender,
System.EventArgs e)
{
daEmpoyees.UpdateCommand =
new SqlCommand("update Employees set LastName= @LastName,
FirstName = @FirstName, TitleOfCourtesy = @TitleOfCourtesy ,
ReportsTo = @ReportsTo where EmployeeID = @EmployeeID",cn);
daEmpoyees.UpdateCommand.Parameters.Add("@LastName",
System.Data.SqlDbType.VarChar, 20, "LastName");
daEmpoyees.UpdateCommand.Parameters.Add("@FirstName",
System.Data.SqlDbType.VarChar, 10, "FirstName");
daEmpoyees.UpdateCommand.Parameters.Add("@ReportsTo",
System.Data.SqlDbType.Int, 4, "ReportsTo");
daEmpoyees.UpdateCommand.Parameters.Add("@TitleOfCourtesy",
System.Data.SqlDbType.VarChar, 25, "TitleOfCourtesy");
daEmpoyees.UpdateCommand.Parameters.Add("@EmployeeID",
System.Data.SqlDbType.Int, 4, "EmployeeID");
7. Добавим две кнопки и код для обработки события нажатия на
кнопки.
private void update_Click(object
sender, System.EventArgs e)
{
// сохраним изменения в базе данных
this.daEmpoyees.Update(ds,"Employees");
}
private void refresh_Click(object sender, System.EventArgs e)
{
// уберем строки из DataSet
ds.Clear();
// и заполним его заново
daEmpoyees.Fill(ds,"Employees");
dataGrid1.DataMember = "Employees";
dataGrid1.DataSource = ds;
}
Итак, мы рассмотрели один из подходов модификации интерфейса ввода
DataGrid. Есть и другие пути достижения похожих задач. Наиболее типичным
представляется подход использования DataGridColumnStyle класса. Когда
для создания стиля колонки, отличного от текстовой строки или булевого
значения, нужно создать класс, наследуемый из DataGridColumnStyle и
реализующий логику отображения и редактирования значений в ячейке. Но об
этом уже в следующей статье.