- •В ведение в ado.Net
- •Потребитель данных
- •Поставщики данных
- •Источник данных — xml-файл
- •Элементы управления на форме Меню
- •Как устроены планки меню, статуса и панелей инструментов
- •Планка инструментов (ToolStrip)
- •Навигатор связей
- •Контейнер, расщепляющий форму
- •Создание таблиц и внедрение их в DataSet
- •Пояснения
- •Запись и чтение данных
- •Отображение данных связанной таблицы
- •Добавляем ограничение
- •Вторая, подчиненная таблица
- •Автоматическая навигация по записям связанной таблицы
- •Элемент управления BindingNavigator
- •Реакции на события в DataTable
- •Канонизация имен студентов
- •Вычисляемые колонки DataTable (Expression-Based DataColumn Objects)
- •Поиск в таблице DataTable и в компоненте DataGridView
- •Коррекция пользовательского интерфейса
- •Результат поиска
- •Поиск в DataGridView
- •Отбор данных из DataTable и отображение их в ListView
- •Как легко вносятся ошибки
- •Образ таблицы DataView
- •Методы Find и FindRows класса DataView
- •Задание
- •Элемент управления, который позволяет управлять отображением колонок DataGridView
Канонизация имен студентов
В данный момент мы можем ввести двух студентов с именами: "Joe Doe" и " Joe Doe " (второе содержит массу лишних пробелов) и они будут считаться разными, хотя семантически они одинаковы. Для того, чтобы придать полю Name стандартный формат, давно напрашивается вставка метода Trim(string), который убирает пробелы не только в начале и конце имени, но и лишние пробелы между отдельными словами. Рассмотрим, как можно сделать это с помощью класса StringBuilder.
string Trim (string sOld)
{
StringBuilder sNew = new StringBuilder();
bool wasSpace = true;
for (int i=0; i<sOld.Length; i++)
{
bool isSpace = sOld[i] == ' ';
if (!(wasSpace && isSpace))
sNew.Append (sOld[i]);
wasSpace = isSpace;
}
return sNew.ToString().TrimEnd();
}
В методе Trim используется объект класса StringBuilder. Он позволяет работать со строкой текста, как с коллекцией символов. Сначала коллекция пуста, затем в цикле прохода по старой строке мы добавляем в нее только те символы, которые считаем нужными. Здесь работает метод Append, который меет 18 перегруженных версий и, поэтому, позволяет очень гибко работать с вновь генерируемой строкой. Обязательно просмотрите справку по этому методу.
Задействуйте процесс унификации имен, добавив в начало обработчика события RowChanged следующий код:
ds.Tables[0].RowChanged -= OnStudsRowChanged;
e.Row["Name"] = Trim(e.Row["Name"].ToString());
ds.Tables[0].RowChanged += OnStudsRowChanged;
На лекции мы осуждали необходимость выключения (см. операции –= и +=) и повторного включения задания делегата, реагирующего на изменения в строке таблицы студентов. Что будет, если пренебречь этим?
Временное выключение делегата необходимо, чтобы не получить бесконечный цикл обработки события RowChanged, так как присвоение e.Row["Name"] вновь генерирует событие RowChanged. Теперь коррекция имени будет производиться не только при добавлении новой строки, но и при изменении существующей. Проверьте это и объясните.
Вспомнив о возможностях класса Regex, разработаем новую версию метода Trim, которая состоит из одной строки кода. Замените существующую версию на новую.
string Trim(string s) { return new Regex(@"\s{2,}").Replace(s.Trim(), " "); }
При создании объекта класса Regex мы задаем шаблон регулярного выражения ("\s{2,}"). Мета-символ \s означает space (пустые символы). Шаблон говорит классу Regex, что он должен искать подстроки, которые содержат 2 или более пустых символов. Метод Replace класса Regex осуществляет замену всех найденных в строке s вхождений подстроки, определенной шаблоном, на строку " ", которая содержит только один пробел. Строка s предварительно обрабатывается методом Trim() класса string, что необходимо для уничтожения крайних пробелов.
Еще лучшим решением рассматриваемой проблемы будет добавление в проект класса Helper с методом GetName, который выуживает имя из строки символов с помощью механизма регулярных выражений. Мы использовали такой метод в предыдущей части курса.
Вычисляемые колонки DataTable (Expression-Based DataColumn Objects)
В множество колонок объекта DataTable легко добавить вычисляемые колонки, то есть, поля данных, которых нет в источнике данных, но они вычисляются и добавляются в таблицу на ходу. Это, в частности, можно сделать с помощью объекта DataRelation. Каждый студент сдал какие-то экзамены и они хранятся в связанной таблице Exams. Их количество может отличаться (если нет, то вы сделаете его разным, работая с формой и XML-файлом данных). С помощью объекта DataRelation мы вычислим количество экзаменов для каждого студента и покажем его в отдельной колонке таблицы DataTable, а, следовательно, (благодаря механизму DataBinding) и DataGridView. Добавим в таблицу студентов колонку, отображающую количество сданных ими экзаменов. Это делается удивительно просто.
ds.Tables[0].Columns.Add("ExamsNo", typeof(int), "Count(Child.StudID)");
Эту строку кода следует вставить после того, как в DataSet была добавлена связь между таблицами студентов и экзаменов, но до вызова метода AddStyle (иначе наш стиль не подействует на новую колонку). Секрет ее работы состоит в строковой константе "Count(Child.StudID)" (вспомните про роль имен в механизме DataBinding). Она определяет свойство Expression (вычисляемое выражение), которое имеется в каждом объекте класса DataColumn. Рассмотрим части выражения.
Count() определяет функцию SQL-запроса,
Child определяет множество строк, таблицы Exams, связанных с текущей строкой таблицы студентов,
StudID определяет поле внешнего ключа связи (FK).
Обратите внимание на то, что DataGridView не позволяет редактировать поля данных этой колонки и это правильно. Но, вот недостаток! Если вы теперь вызовите метод Save, то данные вычисляемой колонки попадут в XML-файл, что не правильно. Предотвратить это можно, взяв в свои руки процесс записи в файл. Одним из способов может быть удаление колонки перед записью в файл и вставка ее после чтения из файла. Другим способом может быть работа с методами класса XmlDocument. Вместо вызова методов WriteXml класса DataSet можно вызвать метод Serialize класса XmlSerializer, но для этого придется ввести промежуточный слой: сериализуемые классы или класс, производный от DataTable. В промежуточном слое надо пометить исключаемое свойство атрибутом [XmlIgnore], или не иметь его вовсе.
Оказалось, что для решения этой проблемы есть простой способ — установить свойство ColumnMapping для вычисляемой колонки в значение Hidden. Данные такой колонки не попадают в XML-файл.
Добавьте следующую установку в метод RelateAndBind после привязки DataGridView к данным таблиц.
ds.Tables[0].Columns[4].ColumnMapping = MappingType.Hidden;
Добавьте колонку, которая вычисляет средний балл студента и для нее также установите свойство ColumnMapping. Тип данных для этой колонки не должен быть целочисленным.
Форматирование данных колонки производится в одной из ветвей метода AddStyle (определите ее самостоятельно). Для форматирования использован такой код:
col.DefaultCellStyle.Format = "f2";
Если вы добавили на форму BindingNavigator, то целесообразно включить его в работу таким изящным способом.
gridStud.CellEnter += (s, e) => bn.BindingSource = bs[0];
gridExam.CellEnter += (s, e) => bn.BindingSource = bs[1];
