Лекция 12. Работа с сеткой DBGrid
Мы с вами уже неоднократно применяли этот компонент для вывода на экран информации из наборов данных в виде таблицы. Однако этот компонент способен на большее. Профессиональные программы отличаются большим набором дополнительных возможностей для пользователя. В этой лекции мы и поговорим о дополнительных возможностях сетки DBGrid.
Как мы уже знаем, строки сетки DBGrid соответствуют записям подключенного набора данных, а столбцы - полям. Свойство DataSource содержит ссылку на выбранный набор данных. Изменяя эту ссылку во время работы программы, можно изменять выводимые в сетке данные, отображая то одну, то другую таблицу (или запрос) в одной сетке DBGrid.
Столбцы DBGrid
Столбцы содержат значения полей подключенного к сетке набора данных. Этими значениями можно манипулировать, показывая или скрывая поля НД, меняя их местами или добавляя новые столбцы. Нам уже приходилось это делать в редакторе полей набора данных, однако, это не всегда оправдано - один набор данных может использоваться в различных местах приложения, в различных формах и на различных сетках. Изменение свойств полей набора данных в этом случае коснется и всех сеток DBGrid, которые подключены к нему, а это требуется далеко не всегда. Более разумным вариантом будет добавление всех полей в редактор полей набора данных, а изменение их свойств можно сделать в каждой сетке по-своему.
Если не пользоваться редактором столбцов самой сетки, DBGrid будет выводить значения по умолчанию - будут выведены все поля набора данных, а заголовки столбцов будут соответствовать именам полей. Но стоит только добавить в редактор столбцов хоть один столбец, и сетка DBGrid будет отображать только его. Таким образом, мы можем показывать только те столбцы, которые действительно необходимы.
Создайте новое приложение. Свойству Name формы, как всегда, присвойте значение fMain, свойству Caption - "Изучение свойств DBGrid". Проект сохраните в отдельную папку, модулю дайте имя Main, а проекту в целом - MyDBGrid. В эту же папку скопируйте базу данных ok.mdb из прошлой лекции. На форме нам понадобятся сетка DBGrid с вкладки Data Controls, с вкладки ADO компоненты ADOConnection и ADOTable, с вкладки Data Access - компонент DataSource. Также для красоты и удобства можно добавить компонент DBNavigator.
Из прошлых лекций вы знаете, как подключить к базе данных компонент ADOConnection, а затем подключить к нему таблицу ADOTable. В свойстве TableName таблицы выберите таблицу LichData, и откройте ее. Компонент DataSource подключите к нашей таблице, а сетку DBGrid и навигатор DBNavigator - к DataSource. В результате у вас должна получиться простая форма с сеткой и навигатором по ней, в которой отображаются все поля таблицы LichData:
Рис. 12.1 . Форма проекта
Допустим, в нашем проекте нам нужны не все поля таблицы, а только некоторые из них. Значит, придется поработать с редактором столбцов сетки DBGrid. Вызвать редактор можно тремя способами: дважды щелкнуть по сетке; щелкнуть правой кнопкой по сетке и в контекстном меню выбрать команду Columns Editor и, наконец, щелкнув дважды по свойству сетки Columns в Инспекторе Объектов:
Рис. 12.2 . Редактор столбцов сетки DBGrid
Работа с этим редактором очень похожа на редактор полей набора данных, но есть и отличия. В верхней части окна вы видите четыре кнопки, слева - направо:
Add New (Добавить новый столбец).
Delete Selected (Удалить выделенный столбец).
Add All Fields (Добавить все столбцы из набора данных).
Restore Defaults (Восстановить значения по умолчанию для выделенного столбца).
Если столбцов в редакторе нет, то сетка отображает все поля НД. Добавим один столбец. Для этого нажмем первую кнопку. Сразу же все поля НД исчезли, а сетка отображает пустой столбец. Выделим его в редакторе столбцов, а в Инспекторе объектов в свойстве FieldName выберем поле "Фамилия". Сразу же столбец отобразит это поле. Заголовок столбца будет соответствовать названию поля. В нашей БД имена полей мы задавали русскими буквами, однако это бывает не всегда, особенно если вы работаете с таблицами Paradox или клиент-серверными БД. В этом случае названия полей будут выводиться латиницей, а это не удобно для пользователя. Изменить параметры заголовка столбца можно в раскрывающемся свойстве Title, которое имеет ряд собственных свойств:
Alignment - свойство устанавливает выравнивание заголовка и может быть taCenter (по центру), taLeftJustify (по левому краю) и taRightJustify (по правому краю). По умолчанию, заголовок выровнен по левому краю.
Caption - свойство содержит текст, который отображается в заголовке столбца. Если поле НД имеет имя латинскими буквами, именно здесь можно отобразить его кириллицей.
Color - цвет заголовка, по умолчанию это свойство равно clBtnFace, что обеспечивает стандартный серый цвет. Если вы желаете украсить программу, можете выбрать другой цвет.
Font - шрифт заголовка. Если дважды щелкнуть по этому свойству, откроется диалоговое окно, в котором можно изменить шрифт, начертание, размер и цвет шрифта. То же самое можно сделать, если раскрыть это свойство и непосредственно изменять нужные свойства.
Надо заметить, что свойства Font, Alignment и Color внутри свойства Title меняют шрифт, выравнивание и цвет фона только заголовка столбца, а не его содержимого. Но у столбца имеются эти же свойства, они меняют шрифт, выравнивание и цвет фона выводимых в столбце данных.
Свойство Visible разрешает или запрещает отображение столбца, а свойство Width позволяет изменить его ширину. О других свойствах поговорим чуть позже.
Добавьте в сетку другие поля НД: "Имя", "Отчество", "Пол" и "Военнообязанный".
Обратите внимание, что перетаскивая мышью столбцы в редакторе столбцов, вы можете менять их порядок в сетке. Пользователь же имеет возможность менять их порядок, перетаскивая мышью заголовки столбцов.
У сетки DBGrid имеется свойство Columns, которое содержит столбцы. Щелчок по этому свойству как раз и откроет редактор столбцов сетки. Столбцы хранятся в свойстве Items в виде массива и имеют индексы от 0 до DBGrid1.Columns.Items.Count-1. Заметим, что свойство Items используется по умолчанию, и его можно не указывать:
DBGrid1.Columns[0] = DBGrid1.Columns.Items[0]
Шрифт и цвет можно менять не только в Инспекторе объектов, но и программно. Добавим на форму две кнопки, на которых напишем "Шрифт" и "Цвет". А также два диалога: FontDialog и ColorDialog. Создадим процедуру нажатия на первую кнопку и впишем в нее следующее:
{Меняем шрифт}
procedure TfMain.Button1Click(Sender: TObject);
begin
//считаем в диалог шрифта установленный шрифт
FontDialog1.Font := DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Font;
//установим выбранный шрифт:
if FontDialog1.Execute then
DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Font :=
FontDialog1.Font;
end;
Здесь вначале мы свойству Font диалога FontDialog присвоили тот же шрифт, который был в текущем столбце сетки. Затем вызвали выбор диалога, и если пользователь выбрал другой шрифт (название, начертание, размер, цвет шрифта), то изменяем шрифт всего столбца на выбранный пользователем. Аналогичным образом меняем и цвет столбца. Создайте процедуру обработки второй кнопки, и впишите код:
{Меняем цвет}
procedure TfMain.Button2Click(Sender: TObject);
begin
//считаем в диалог цвета установленный цвет:
ColorDialog1.Color :=
DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Color;
//установим выбранный цвет:
if ColorDialog1.Execute then
DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Color :=
ColorDialog1.Color;
end;
Сохраните проект, скомпилируйте его и проверьте работу кнопок. И шрифт, и цвет текущего столбца будут меняться:
Рис. 12.3 . Изменение шрифта и цвета текущего столбца
Следует заметить, что таким образом можно менять параметры не только содержимого текущего столбца, но и его заголовка, если, например, вместо
DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Color
использовать
DBGrid1.Columns.Items[DBGrid1.SelectedIndex].Title.Color
А если применить цикл, то можно изменить параметры всех столбцов:
for i:= 0 to DBGrid1.Columns.Count-1 do
DBGrid1.Columns.Items[i].Color :=
Пустые столбцы
Если добавить в редактор столбцов сетки DBGrid новый столбец, но в свойстве FieldName не выбирать поле БД, а оставить его пустым, мы получим пустой столбец. Для чего нужны пустые столбцы в сетке? Во-первых, в них можно выводить обработанные данные из других столбцов. К примеру, пользователю неудобно просматривать три столбца "Фамилия", "Имя" и "Отчество". Ему было бы удобней просмотреть один сборный столбец в формате "Фамилия И.О.". Во-вторых, пустое поле может выводить информацию по требованию.
Рассмотрим эти случаи.
Создайте новый столбец, но не назначайте ему поле из НД. Выделите этот столбец, и в его свойстве Title.Caption впишите "Фамилия И.О.", а в свойстве Width укажите ширину в 150 пикселей. Эти сборные данные не будут видны в момент проектирования таблицы, они выйдут только во время работы программы.
Столбцы "Фамилия", "Имя" и "Отчество" нам уже не нужны, скройте их, установив их свойство Visible в False. А новый столбец перетащите мышью наверх, его индекс будет равен 0.
Нам придется написать код, который нужно вписать в событие OnDrawColumnCell сетки. Это событие наступает при прорисовке каждой ячейки столбца. Также имеется событие OnDrawDataCell, которое выполняет схожие функции, но оно оставлено для поддержки старых версий, и использовать его не желательно. Итак, выделяем сетку, генерируем событие OnDrawColumnCell и вписываем код:
{Прорисовка таблицы}
procedure TfMain.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
s: String;
begin
//если это пустой столбец
if Column.Index = 0 then begin
if ADOTable1['Фамилия'] Null then
s:= ADOTable1['Фамилия'] + ' ';
if ADOTable1['Имя'] Null then
s:= s + Copy(ADOTable1['Имя'], 1, 1) + '.';
if ADOTable1['Отчество'] Null then
s:= s + Copy(ADOTable1['Отчество'], 1, 1)+ '.';
DBGrid1.Canvas.TextOut(Rect.Left, Rect.Top, s);
end; //if
end;
Здесь мы вначале проверяем - наш ли это столбец (равен ли индекс нулю)? Если наш, то в переменную s начинаем собирать нужный текст. При этом имеем в виду, что пользователь мог и не заполнить некоторые поля. Чтобы у нас не произошло ошибки, вначале убеждаемся, что поле не равно Null (то есть, текст есть). Если текст есть, добавляем его в переменную s. Причем если это имя или отчество, с помощью функции Copy() получаем только первую букву и добавляем к ней точку. Когда s сформирована, добавляем этот текст в наш столбец с помощью метода TextOut() свойства Canvas сетки. В метод передаются три параметра: координатылевой позиции, верхней позиции и сам текст. Эти координаты мы берем из параметра события OnDrawColumnCell - Rect, который имеет такие свойства, как Left и Top, показывающие, соответственно, левую и верхнюю позиции текущей ячейки.
В результате программа будет иметь вид:
Рис. 12.4 . Заполнение пустого столбца
Теперь о том, как использовать пустой столбец для вывода информации по требованию пользователя. Допустим, в сетке DBGrid нам нужна кнопка, нажатие на которую привело бы к выводу сообщения об образовании сотрудника.
Создайте новый пустой столбец. Перетаскивать его не нужно, пусть будет последним. Свойство Width (ширина) установите в 20 пикселей. Название столбца ( Title.Caption ) вписывать не нужно, пусть будет пустым. В свойстве ButtonStyle выберите значение cbsEllipsis. Это приведет к тому, что при попытке редактировать этот столбец образуется кнопка с тремя точками:
Рис. 12.5 . Кнопка в пустом столбце
Нужный код пишется в событии OnEditButtonClick() сетки DBGrid, которое происходит всякий раз, когда пользователь нажимает на кнопку "…". Сгенерируйте это событие и впишите только одну строку:
ShowMessage(ADOTable1['Образование']);
Теперь, когда пользователь нажмет на эту кнопку, ему будет выведено сообщение с текстом об образовании текущего сотрудника.
Список выбора в столбце
Для организации списков выбора служит компонент ComboBox. Однако сетка DBGrid позволяет устроить такой же список в одном из своих столбцов без использования каких-либо других компонентов. В нашем примере есть поле "Пол". Это текстовое поле из трех символов. Во время конструирования базы данных ok.mdb с помощью программы MS Access мы указывали, что это поле может хранить значения либо "муж", либо "жен". То же самое можно было сделать с помощью сетки. Откройте редактор столбцов сетки и выделите столбец "Пол". Обратите внимание на свойство PickList в Инспекторе объектов. Это свойство имеет тип TStrings, то есть представляет собой набор строк, так же как и свойство Items у компонента ComboBox. Щелкните дважды по PickList, чтобы открыть редактор, и впишите туда
муж
жен
именно так, каждое значение на своей строке. Сохраните проект, скомпилируйте его и попробуйте редактировать этот столбец. При попытке редактирования в ячейке покажется похожий на ComboBox список, в котором можно будет выбрать одно из указанных значений:
Рис. 12.6 . Список выбора в сетке
Следует иметь в виду, что наличие такого списка не препятствует пользователю ввести какое-то иное значение. Этот список нужен не для контроля, а только для облегчения пользователю ввода данных. Если же вы не желаете, чтобы пользователь имел возможность вводить другие данные, контроль следует организовать иным способом.
Еще нужно заметить, что в практике программирования список чаще формируется во время работы программы, а строки списка берутся, как правило, из другой связанной таблицы. Добавить в список новую строку очень просто:
DBGrid1.Columns.Items[4].PickList.Add('абв');
Выделение отдельных строк
Очень часто в практике приходится выделять какие-то строки, изменяя их фон или цвет шрифта. Например, в бухгалтерии обычно выделяют строки, в которых значение меньше нуля. Допустим, ваша программа показывает клиентов, а какой-то столбец содержит их баланс на счету вашей компании. Если этот баланс меньше 0, значит, клиент имеет задолженность перед вашей фирмой. Бухгалтеру будет очень удобно, если дебиторы (должники) будут выделяться в общем списке красным цветом.
Способ прорисовки данных в сетке DBGrid зависит от значения ее свойства DefaultDrawing. По умолчанию свойство равно True, то есть данные прорисовываются автоматически. Если свойство содержит False, то прорисовку придется кодировать самостоятельно в свойствах OnDrawColumnCell или OnDrawDataCell, о которых уже упоминалось в этой лекции.
Если мы написали алгоритм прорисовки, но свойство DefaultDrawing содержит True, то вначале сетка заполнится данными автоматически, а затем будет выполнен наш алгоритм. Другими словами, прорисовка некоторых частей сетки будет выполнена дважды. Это не очень хорошо для быстродействия программы, однако нам придется поступать именно так: ведь мы не все строки и столбцы собираемся выводить другим способом, а только некоторые. Остальные будут заполнены данными по умолчанию.
Разберем этот метод подробней. Если найти его в справке Delphi, то увидим:
property OnDrawColumnCell: TDrawColumnCellEvent;
То есть, этот метод имеет тип TDrawColumnCellEvent. Описание типа такое:
type TDrawColumnCellEvent = procedure (Sender:TObject; const Rect:TRect;
DataCol:Integer; Column:TColumn; State:TGridDrawState) of object;
Разберемся с параметрами.
Rect - координаты прорисовки.
DataCol - порядковый номер текущего столбца (начиная с 0).
Column - данные текущего столбца.
State - состояние ячейки. Может быть:
gdSelected - ячейка выделена
gdFocused - фокус ввода в ячейке
gdFixed - ячейка - заголовок столбца.
Допустим, в нашем примере требуется, чтобы строки с военнообязанными сотрудниками выделялись красным цветом:
Рис. 12.7 . Выделение строк
Заметим сразу, что наличие пустых столбцов создает дополнительные, но решаемые проблемы. Код события OnDrawColumnCell придется переделать, он будет таким:
{Прорисовка таблицы}
procedure TfMain.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
s: String;
begin
with DBGrid1.Canvas do begin
//поле "Военнообязанный" содержит истину?
if (ADOTable1['Военнообязанный'])= True and
not (gdSelected in State) then begin
//выводим все ячейки строки красным по белому:
Font.Color:= clRed;
FillRect(Rect);
end; //if
//если это пустой сборный столбец
if Column.Index = 0 then begin
if ADOTable1['Фамилия'] Null then
s:= ADOTable1['Фамилия'] + ' ';
if ADOTable1['Имя'] Null then
s:= s + Copy(ADOTable1['Имя'], 1, 1) + '.';
if ADOTable1['Отчество'] Null then
s:= s + Copy(ADOTable1['Отчество'], 1, 1)+ '.';
DBGrid1.Canvas.TextOut(Rect.Left, Rect.Top, s);
end //if
//если это пустой столбец с кнопкой "..."
else if Column.Index = 6 then begin
DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State);
Exit;
end //if
//все остальные столбцы
else
TextOut(Rect.Left+2, Rect.Top+2, Column.Field.Text);
end; //with
end;
Разберемся с кодом. Вначале с помощью with мы указываем, что будем работать непосредственно со свойством DBGrid1.Canvas, которое отвечает за стиль прорисовки ячейки. Далее мы смотрим, содержится ли True в поле "Военнообязанный" текущей записи. Если да, то указываем, что цвет шрифта должен быть красным, а затем функцией FillRect(Rect) мы стираем стандартный вывод.
Далее мы определяем, прорисовывается ли в данный момент пустой сборный столбец с "Фамилия И.О.". Если это он, то мы формируем переменную s с нужными данными и выводим их, как в прошлый раз.
Если же это пустой столбец с кнопкой "…", то мы делаем стандартный вывод и выходим из процедуры. Если мы этого не сделаем, то получим ошибку программы.
Все остальные столбцы мы выводим строкой
TextOut(Rect.Left+2, Rect.Top+2, Column.Field.Text);
Обратите внимание, что мы добавили по два пикселя к крайней левой и крайней верхней координате ячейки. Если этого не сделать, то новая прорисовка не будет целиком закрашивать старую:
Рис. 12.8 . Некорректная прорисовка
Количество добавляемых пикселей зависит от формата данных и размера шрифта. Обычно это определяется путем проб. Например, ячейка может содержать цифры, которые обычно прижимаются к правому краю, и тут двумя пикселями не обойтись.
Сделать проверку на тип данных можно, например, так:
//если текст, сдвинем только на 2 пикселя
if Column.Field.DataType = ftString then
TextOut(Rect.Left, Rect.Top+2, Column.Field.Text)
//если цифры, сдвинем их вправо на 28 пикселей
else TextOut(Rect.Left+28, Rect.Top+2, Column.Field.Text);
Выделение строки красным текстом может оказаться недостаточным для заказчика. Что, если он потребует, чтобы эти строки выделялись красной строкой с белым шрифтом? Тогда вместо
Font.Color:= clRed;
FillRect(Rect);
вам придется написать
//выводим все ячейки строки белым текстом по красному фону:
Brush.Color:= clRed;
Font.Color:= clWhite;
FillRect(Rect);
Как видите, подсвойство
DBGrid1.Canvas.Brush.Color
отвечает за цвет заливки ячейки, а
DBGrid1.Canvas.Font.Color
за цвет выводимого в ней шрифта. Список доступных цветов вы можете открыть в любом свойстве Color любого компонента. Теперь вы сможете создать сетку со сложными элементами прорисовки.