Тема 7. Табличные компоненты и работа с ними

Табличное представление данных повсеместно используется в приложениях. В этой теме рассмотрены основные приёмы работы со следующими компонентами:

·  DataGridView – табличный редактор для отображения данных из файла XML или из БД;

·  DataGrid – базовая табличная компонента для отображения связанных таблиц.

Пример 1. Пишем TableEditor для редактирования таблицы и сохранения её в формате XML

На форму добавим dataGridView1, а объекты DataTable и DataSet создадим программно.

Для этого опишем глобально в классе формы следующие величины:

private: String ^ BaseName;

private: DataTable ^ Table;

private: DataSet ^ Set;

На загрузку формы реализуем такой код (обработчик события Load):

BaseName = "table. xml";

Table = gcnew DataTable();

Set = gcnew DataSet();

if (IO::File::Exists(BaseName) == false) {

dataGridView1->DataSource = Table;

Table->Columns->Add ("Имена");

Table->Columns->Add("Номера телефонов");

Set->Tables->Add(Table);

}

else {

Set->ReadXml (BaseName);

String ^ StringXML = Set->GetXml();

dataGridView1->DataMember = "Название таблицы";

dataGridView1->DataSource = Set;

}

Перед закрытием формы выполним следующий код (обработчик события FormClosing):

Table->TableName = "Название таблицы";

Set->WriteXml(BaseName);

Данные сохраняются в формате XML.

DataSet представляет собой кэш данных, расположенный в оперативной памяти. DataSet состоит из коллекции объектов класса DataTable.

НЕ нашли? Не то? Что вы ищете?

Доступ к ячейкам таблицы можно получить, используя свойства класса DataTable (Rows, Cols, Item) - но запись поля таблицы в файл м. б. некорректной из-за того, что технология предусматривает кэширование данных.

Пример такого кода:

System::Data::DataRow ^ newRow = Table->NewRow();

Table->Rows->Add(newRow);

Поэтому следует пользоваться методами объекта DataSet.

Пример 2. Пишем проект TableEditor2 для связанных таблиц

Компонента DataGrid - решение для показа связанных таблиц в одной компоненте, в DataGridView такой возможности нет. Не все компоненты доступны по умолчанию, в том числе, и DataGrid. Щелкаем правой кнопкой мыши на панели элементов управления, Выбрать элементы, на вкладке "Компоненты. NET Framework" устанавливаем флажок возле элемента DataGrid пространства имен System. Windows. Forms, нажимаем ОК.

После этого DataGrid можно добавить на форму.

Описаны глобально:

private: Boolean ShowClients; //Флажок-переключатель таблиц

private: System::Data::DataSet ^ DataSet1;

На загрузку формы (обработчик события Load):

ShowClients = true;

//Создадим таблицу:

System::Data::DataTable ^ Table = gcnew DataTable("Клиенты");

//Создадим и настроим столбец программно:

DataColumn ^ Column = gcnew DataColumn("Название организации");

Column->ReadOnly = true;

Column->Unique = true;

Table->Columns->Add(Column);

//Добавим столбцы с настройками по умолчанию, указав только названия:

Table->Columns->Add("Контактное лицо");

Table->Columns->Add("Телефон");

//Создадим DataSet и добавим туда таблицу:

DataSet1 = gcnew DataSet();

DataSet1->Tables->Add(Table);

//Добавим в таблицу предустановленные записи об организациях-заказчиках

Table->Rows->Add("НГАСУ", "Иванов Максим", "3234566");

Table->Rows->Add("НГТУ", "Сидорова Ксения", "3630313");

//Создадим вторую таблицу - "Заказы"

System::Data::DataTable ^ Table2 = gcnew DataTable("Заказы");

DataColumn ^ Column2 = gcnew DataColumn("Номер заказа");

Column2->DataType = System::Type::GetType("System. Int32");

Column2->AutoIncrement = true; //Автоматический счётчик заказов

Column2->ReadOnly = true; Column2->Unique = true; //Название организации - уникально!

Table2->Columns->Add(Column2);

Table2->Columns->Add("Объем заказа");

Table2->Columns->Add("Организация-заказчик");

//Добавим в DataSet вторую таблицу:

DataSet1->Tables->Add(Table2);

Table2->Rows->Add(1, "100000", "НГАСУ");

Table2->Rows->Add(2, "200000", "НГАСУ");

//Обеспечим отношение 1:N между первой и второй таблицами:

DataColumn ^ Parent = DataSet1->Tables["Клиенты"]->Columns["Название организации"];

DataColumn ^ Child = DataSet1->Tables["Заказы"]->Columns["Организация-заказчик"];

DataRelation ^ Link1 = gcnew DataRelation("Ссылка на заказы клиента", Parent, Child);

// В Parent значения в связываемом столбце должны быть уникальными, в Child - нет

DataSet1->Tables["Заказы"]->ParentRelations->Add(Link1);

dataGrid1->SetDataBinding(DataSet1, "Клиенты");

dataGrid1->CaptionText = "Родительская таблица \"Клиенты\"";

dataGrid1->CaptionFont = gcnew System::Drawing::Font("Consolas", 11);

На нажатие кнопки (переключает между родительской и дочерней таблицами):

if (ShowClients == true) {

dataGrid1->SetDataBinding(DataSet1, "Клиенты");

dataGrid1->CaptionText = "Родительская таблица \"Клиенты\"";

}

else {

dataGrid1->SetDataBinding(DataSet1, "Заказы");

dataGrid1->CaptionText = "Дочерняя таблица \"Заказы\"";

}

ShowClients = !ShowClients;

Пример 3. Делаем всё по-современному - через DataGridView. Подробнее об этом компоненте.

Он представляет собой прямоугольный массив ячеек, который можно рассматривать как коллекцию строк или столбцов.

Rows - коллекция строк, имеет тип DataGridRowCollection

Columns - коллекция столбцов, тип DataGridColumnCollection

Оба свойства индексируются как массивы для доступа к конкретной строке/столбцу, нумерация с нуля

Cells - коллекция ячеек из объекта DataGridRowCollection, приведём пример доступа к ячейке:

try {

MessageBox::Show(dataGridView1->Rows[1]->Cells[1]->Value->ToString());

}

catch (...) { MessageBox::Show("Нет такой ячейки"); }

RowCount, ColumnCount - количество строк и столбцов

В несвязанном режиме компонент может отображать любые табличные данные.

Методы для добавления/удаления/редактирования строк относятся к коллекции Rows и имеют типовые имена: Add, Insert, Clear, AddCopy, InsertCopy, Remove, RemoveAt и могут иметь по несколько перегрузок каждая, например, для Add есть версии Add(), Add(КоличествоНовыхСтрок), Add (DataGridViewRow ^row), Add (... Object ^object)

Настройка внешнего вида компонента также типовая: такие свойства, как BackColor, Alignment, Font и т. д. находятся в объекте типа DataGridViewCellStyle

Каждая ячейка представлена объектом System::Windows::Forms::DataViewCell, за "личный" внешний вид ячейки отвечает свойство InheritedStyle, вид по умолчанию - DefaultCellStyle

Примеры.

1. По нажатию кнопки перекрасим фон таблицы в розовый цвет:

dataGridView1->DefaultCellStyle->BackColor=Color::Pink;

2. ...или только фон выбранной ячейки:

if (cell_y>-1 && cell_x>-1)

dataGridView1->Rows[cell_y]->Cells[cell_x]->Style->BackColor=Color::Green;

Значения cell_y, cell_x описаны глобально в классе формы:

private: int cell_y, cell_x;

инициализируются в обработчике её события Load:

cell_y = cell_x = -1;

и получаются значения в обработчиках событий dataGridView1_KeyUp, dataGridView1_MouseUp

(одинаковым кодом):

cell_y = dataGridView1->CurrentCell->RowIndex;

cell_x = dataGridView1->CurrentCell->ColumnIndex;

3. Обработчик события dataGridView1_CellFormatting для выделения текущей ячейки жёлтым фоном:

e->CellStyle->SelectionBackColor = Color::Yellow;

e->CellStyle->SelectionForeColor = Color::Black;

4. Сделаем в dataGridView1 таблицу со значениями функции. Код по нажатию кнопки:

dataGridView1->Columns->Clear();

dataGridView1->ColumnCount=2;

dataGridView1->Rows->Add (10); //Добавили 10 строк

dataGridView1->Columns[0]->Name = "X";

dataGridView1->Columns[1]->Name = "Y(X)";

float x; int i;

for (x=1.5,i=0; i<10; x+=0.1,i++) {

dataGridView1->Rows[i]->Cells[0]->Value = Convert::ToString (x);

dataGridView1->Rows[i]->Cells[1]->Value = Math::Round(x*x, 2).ToString();

//или dataGridView1->Rows[i]->Cells[1]->Value = (x*x).ToString("f");

}

5. Есть также множество событий, связанных с редактированием ячейки: CellBeginEdit, CellEndEdit, CellParsing, CellValidating, CellValidated и т. д.

Например, по умолчанию наша таблица редактируется. Чтобы разрешить в первом столбце (Y(X)) ввод только числовых значений, напишем следующий код, выполняемый по событию CellValueChanged:

String ^Val =

dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value->ToString();

if (e->ColumnIndex==1) {

float val;

bool A = Single::TryParse(Val,

System::Globalization::NumberStyles::Number,

System::Globalization::NumberFormatInfo::CurrentInfo, val);

if (A==false) {

dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value = lastValue;

MessageBox::Show("Неверное число "+val. ToString(),"Ошибка");

}

}

Величина lastValue описана в классе формы:

private: float lastValue;

и, по событию CellBeginEdit, сохраняет предыдущее значение, хранимое в ячейке:

if (e->ColumnIndex==1) lastValue =

Convert::ToDouble(dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value);

Пример разработки приложения:

Написать табличный редактор ведомости студенческой группы со столбцами: Фамилия, 1, 2, ..., 17 (номера недель), итого (отметка о зачете)

Данные автоматически загружаются из файла и сохраняются в файле формата xml.

Поддерживаются редактирование, в том числе, произвольное добавление и удаление строк, проверка правильности ввода данных.

Способы решения:

Можно сделать таблицу dataGridView, не связанную с DataSet и работать непосредственно с ячейками через методы класса dataGridView (как в последнем примере). Сложности – придётся «вручную» писать поддержку сохранения и загрузки таблицы.

Второй вариант – связать таблицу с DataSet, чтобы легко загружать и сохранять данные XML (как в примере 1), но тогда работа с добавлением/удалением ячеек делается через методы кэша DataSet, иначе компилятор и не разрешит.

Решение: Создав новый проект Windows Forms с главной формой Form1, добавим на неё компоненту DataGridView и растянем на всю форму (свойство Dock = Fill). Также добавим к проекту контекстное меню contextMenuStrip с пунктами "Добавить" и "Удалить" и укажем его в свойстве ContextMenuStrip компоненты dataGridView1.

Столбцы таблицы (фамилия, 17 граф для оценок или иных отметок, графа "зачёт") создадим программно по событию Load формы:

BaseName = "table1.xml"; Table = gcnew DataTable(); Set = gcnew DataSet();

dataGridView1->DataSource = Table; Table->Columns->Add ("ФИО");

for (int i=1; i<=17; i++) Table->Columns->Add(""+i);

Table->Columns->Add("Итого");

Set->Tables->Add(Table); dataGridView1->DataSource = Set;

Table->TableName = "Успеваемость";

if (IO::File::Exists(BaseName) == true) {

Set->ReadXml (BaseName);

}

dataGridView1->DataMember = "Успеваемость";

for(int i=1; i<=17; i++) dataGridView1->Columns[i]->Width = 25;

Перед этим пропишем в классе формы глобальные величины:

private: String ^ BaseName;

private: DataTable ^ Table;

private: DataSet ^ Set;

private: String ^Val; //предыдущее значение из текущей ячейки

private: int Column; //текущий столбец

private: bool Changed; //признак изменения текущей ячейки

и будем автоматически сохранять данные при выходе из программы (событие FormClosing):

Table->TableName = "Успеваемость";

Set->WriteXml(BaseName);

По выбору пункта меню "Добавить" выполняется следующее:

DataRow ^newR = Set->Tables["Успеваемость"]->NewRow();

newR[0] = "Студент";

try {

int i=dataGridView1->CurrentCell->RowIndex;

Set->Tables["Успеваемость"]->Rows->InsertAt(newR, i);

Set->Tables["Успеваемость"]->AcceptChanges();

} catch (...) {}

А пункт "Удалить" проще всего запрограммировать так:

try {

int i=dataGridView1->CurrentCell->RowIndex;

Set->Tables["Успеваемость"]->Rows[i]->Delete();

Set->Tables["Успеваемость"]->AcceptChanges();

}

catch (...) { }

Применение AcceptChanges нужно, чтобы изменение данных немедленно отобразилось в таблице.

Контроль правильности ввода (не более 1 символа в графы отметок, не более 20 символов в графы "ФИО" и "Итого") сделаем следующими обработчиками:

dataGridView1_CellBeginEdit(на начало редактирования):

Column = e->ColumnIndex; //запоминаем столбец

Val = dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value->ToString();

//что было в ячейке

Changed = false; //ячейка не изменена

dataGridView1_CellValueChanged (по изменению ячейки)

String ^newVal =

dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value->ToString();

if (e->ColumnIndex>0 && e->ColumnIndex<18) { if (newVal->Length>1) newVal = Val; }

else if (newVal->Length>20) newVal = Val;

dataGridView1->Rows[e->RowIndex]->Cells[e->ColumnIndex]->Value = newVal;

dataGridView1_EditingControlShowing //для установки своих обработчиков в дополнение

//к стандартным обработчиками событий компоненты

if (Column > 0 && Column < 18) {

TextBox ^tb = (TextBox^)e->Control;

tb->MaxLength = 1;

tb->KeyPress += gcnew KeyPressEventHandler(this,&Form1::tb_KeyPress);

}

//наша функция – дополнение к обработке KeyPress - разрешаем буквы, цифры, Bacspace и пробел

void tb_KeyPress(System::Object^ sender, System::Windows::Forms::KeyPressEventArgs^ e) {

char c = e->KeyChar;

if (!(Char::IsLetterOrDigit(c) || c==(char)Keys::Back || c==(char)Keys::Space)) e->Handled = true;

//а вот обработчик KeyDown так не сделать

}

dataGridView1_CellLeave //покидая ячейку, изменим правила перехода к следующей

//(по умолчанию – вниз, а мы хотим вправо

//конечно, "костыль", по идее, надо писать класс-наследник

//DataGridView и там управлять клавиатурой

if (!Changed) {

Changed = true;

int c = dataGridView1->CurrentCell->ColumnIndex;

if (c == dataGridView1->Columns->Count-1) {

SendKeys::Send("{Home}");

}

else {

SendKeys::Send("{Up}");

SendKeys::Send("{Right}");

}

}

Проблема состоит в том, что DataGridView обрабатывает многие клавиши своими собственными событиями и "не пускает" коды до уровня KeyPress или KeyDown (KeyUp выполняется).

Задание: реализовать табличный редактор в соответствии с вариантом задания. Предусмотреть в приложении следующие возможности:

·  загрузка табличных данных из файла и их сохранение в файл;

·  редактирование, добавление, удаление записей;

·  при необходимости – поиск и выделение (или отображение в новом окне) записей, отвечающих заданным условиям;

·  реализация расчётов, указанных в варианте задания.

Варианты задания.

1. Таблица со столбцами «Название товара», «Количество на складе», «Цена за единицу», «Итого». Кнопки «Добавить», «Удалить», «Вычислить»

2. Таблица со столбцами «Название магазина», «Район», «Сумма выручки». Кнопка «Вычислить» (сумма выручки по магазинам выбранного из списка района)

3. Таблица со столбцами «Фамилия», «Телефон». Функции «Добавить», «Найти» (по номеру телефона)

4. Таблица со столбцами «Номер школы», «Кол-во выпускников», «Кол-во поступивших в ВУЗы», «%». Кнопки «Добавить», «Удалить», «Вычислить %» «Вычислить Итого»

5. Таблица со столбцами «Фамилия», «Зарплата» «Стаж работы», «Премия». Кнопки «Добавить», «Удалить», «Начислить премию» (имеющим стаж более N лет)

6. Таблица со столбцами «Страна», «Золото», «Серебро», «Бронза», «Очки». Кнопки «Добавить», «Удалить», «Вычислить» (3*золото+ 2*серебро + 1*бронза), «Сортировать» (по очкам)

7. Таблица «Название издания», «Цена подписки», «Число экземпляров», «Сумма». Кнопки «Добавить», «Удалить», «Вычислить сумму», «Вычислить итого».

8. Список результатов метеорологических наблюдений по месяцам. Элемент списка хранит следующие данные: период наблюдения(месяц или год), количество дней месяца, количество выпавших осадков, количество облачных дней, количество дней с переменной облачностью. Расчёты по заданным критериям: подсчёт количества дней, отвечающих заданным условиям (например, с количеством осадков выше указанного).

9. Редактор набора записей вида «Имя=Значение», функции редактирования, добавления, удаления, проверки записей.

10. Список работников, содержащий следующие поля: фамилия и инициалы работника, название занимаемой должности, год поступления на работу. Функции поиска работников по заданным критериям.

11. Записная книжка с полями Ф. И.О., номер телефона, дата рождения. Проверка корректности записей, поиск записей по заданным критериям, в том числе, частичному совпадению фамилии или номера.

12. Список вещественных чисел, возможности удаления, добавления, редактирования, сортировки, проверки элемента на уникальность, статистической обработки элементов, например, вычисления мат. ожидания и дисперсии

13. Список книг, хранящихся в библиотеке. О каждой книге хранятся следующие сведения: инвентарный номер, название, Ф. И.О. автора, место и год издания. Поиск по заданному критерию.

14. Список поставок. Описание поставки включает в себя номер поставки, дату, Ф. И.О поставщика, наименование и объем поставки товара. Выбор записей о заданном товаре.

15. Таблица со следующей информацией: наименование продукции, план выпуска (в шт.),фактическое выполнение плана (в шт.), процент выполнения плана (вычисляется).

16. Создайте таблицу с данными о выполнении плана товарооборота по магазинам, содержащую следующую информацию: наименование магазина, дата, реализовано продукции по отделу1 на сумму,…, реализовано продукции по отделу N на сумму, сумма по магазину (вычисляется).

17. Создайте таблицу поступления товаров на склад, содержащую следующую информацию: наименование, дата, количество, цена за единицу, общая стоимость (вычисляется).

18. Создайте таблицу учета расхода горючего автотранспортом предприятия. Таблица должна содержать следующую информацию: номер автомобиля, ФИО водителя, общее расстояние в км, расход горючего (литр) на 1 км, стоимость 1 литра( руб.), общая стоимость (вычисляется).

19. Создайте таблицу с данными о сессии. Таблица должна содержать следующую информацию: номер группы, ФИО студента, дисциплина, оценка. Выбор данных по группе и студенту.

20. Создайте таблицу, содержащую следующую информацию: город, наименование ВУЗа. Выбор данных по нужному городу.

21. Таблица с данными о странах мира. Структура записи: название страны, столица, часть света, население (тыс. чел.), площадь(тыс. кв. м). Выбор записей по заданным критериям.

22. Список Web-адресов, с возможностями редактирования записей и перехода по выбранным адресам.

23. Список адресов E-mail, с возможностями редактирования записей и перехода по выбранным адресам.

24. Таблица успеваемости (grid) со столбцами «Фамилия», «Математика», «Информатика», «История», «Средний балл». Кнопки «Добавить», «Удалить», «Вычислить»

25. Таблица значений функции (grid). Кнопки «Вычислить», «Очистить», поля ввода A, B,dX