Справочник от Автор24
Поделись лекцией за скидку на Автор24

ADO. Связь с таблицей MS Access

  • 👀 248 просмотров
  • 📌 219 загрузок
Выбери формат для чтения
Загружаем конспект в формате pdf
Это займет всего пару минут! А пока ты можешь прочитать работу в формате Word 👇
Конспект лекции по дисциплине «ADO. Связь с таблицей MS Access» pdf
ADO. Связь с таблицей MS Access С самого появления технологии баз данных программисты испытывали потребность в механизмах доступа к этим самым данным. Различные компании по-своему пытались предоставить им такую возможность. Например, для работы с таблицами типа dBase была создана Система Управления Базами Данных ( СУБД ) Clipper. Для времен операционной системы MS-DOS - превосходное решение. Однако Clipper не мог работать ни с какими другими типами таблиц. И чем больше типов данных появлялось, тем острее вставала необходимость разработать универсальный инструмент доступа, который мог бы работать с любым типом данных. Механизм доступа к данным - это программный инструмент, позволяющий получить доступ к базе данных и ее таблицам. Как правило, это драйвер в виде *.dll файлов, который устанавливается на ПК разработчика (и клиента), и который используется программой для связи с БД. Сравнение BDE и ADO Borland Database Engine (BDE) - первая такая разработка фирмы Borland. Этот механизм доступа к данным позволяет обращаться к локальным и файл-серверным форматам баз данных dBase, FoxPro и Paradox, к различным серверам SQL и ко многим другим источникам данных, доступ которых поддерживался при помощи драйверов ODBC. Например, с помощью BDE можно напрямую работать с табличными файлами MS Excel. Увы, механизм доступа BDE признается устаревшим даже самой компанией Borland. В данный момент многие инструменты Delphi являются кросс платформенными, то есть, программы с небольшими доработками можно переносить на другие операционные системы. Корпорация Borland выпустила новую среду быстрой разработки программ - Kylix, на которой создаются приложения для операционных систем семейства Linux. Часто говорят, что Kylix - это Delphi для Linux. Так и есть - если вы умеете программировать на Delphi, сумеете и на Kylix. Большинство инструментов Delphi были унаследованы Kylix, но, увы, не BDE. Дальнейшее развитие этого механизма доступа к данным корпорацией Borland прекращено. Тем не менее, хоронить его рано. Многие программисты до сих пор используют данный инструмент в разработке приложений для небольших компаний. Да что там говорить, китайская компания Huawei, разрабатывающая современнейшие электронные АТС как для городских, так и для мобильных телефонов, до сих пор использует BDE для доступа к настройкам и статистическим данным этих АТС! Кроме того, BDE имеет множество простых и удобных возможностей для программиста, таких например, как создание таблиц программно. Удобство работы с BDE трудно переоценить, однако нельзя не сказать и о минусах. Основной минус - распространение приложений. Если ваше приложение использует для доступа к данным компоненты BDE, то и у клиента, который будет пользоваться вашей программой, должен быть установлен BDE. Причем если вы использовали алиасы (псевдонимы базы данных), то настройка на эти же алиасы должна быть и у клиента. Впрочем, создание инсталляционного пакета при помощи стандартной утилиты Install Shield Express снимает эту проблему. Эта утилита позволяет включать настроенный механизм BDE в состав инсталляционного пакета вашей программы. Конечно, за это приходится расплачиваться большими размерами инсталляционного файла. Другой минус касается не только BDE, но и любого другого универсального механизма доступа к данным. Универсальность такого механизма подразумевает сложность его реализации. Программисту предоставляется уже готовый инструмент, с которым удобно работать, однако этот инструмент достаточно "тяжелый" - используя его, вы довольно существенно увеличиваете размеры своего приложения. На предыдущем курсе "Введение в программирование на Delphi " мы затрагивали работу с базами данных посредством BDE. Больше к этим темам мы возвращаться не будем, хотя изредка будем обращаться к BDE, чтобы продемонстрировать те или иные возможности, отсутствующие в других механизмах доступа к данным, или отличающиеся от них. Поэтому если вы пропустили этот курс, то хотя бы бегло просмотрите работу с BDE в лекциях 29-32. ActiveX Data Object (ADO) - это механизм доступа к данным, разработанный корпорацией Microsoft. Если точнее, то ADO - это надстройка над технологией OLE DB, посредством которой можно связываться с различными данными приложений Microsoft. В середине 1990-х годов большое развитие получила технология COM, и корпорация Microsoft в связи с этим объявила о постепенном переходе от старой технологии ODBC к новой OLE DB. Однако технология OLE DB достаточно сложная, использование этой технологии происходит на системном уровне и требует от программиста немало знаний и труда. Кроме того, технология OLE DB очень чувтвительна к ошибкам, и "вылетает" при первом удобном случае. Чтобы облегчить программистам жизнь, корпорация Microsoft разработала дополнительный прикладной уровень ADO, который мы будем изучать на этом курсе. По своим возможностям ADO напоминает BDE, хотя конечно, является более мощным инструментом. Компания Borland разработала набор компонентов для доступа к ADO и первоначально назвала его ADOExpress. Однако корпорация Microsoft упорно противится использованию своих обозначений в продуктах сторонних разработчиков, поэтому, начиная с Delphi 6, этот набор компонентов стал именоваться dbGo. Эти компоненты вы можете увидеть на вкладке ADO палитры компонентов. Технология ADO, как и BDE, независима от конкретного сервера БД, имеет поддержку как локальных баз данных различных типов, так и некоторых клиентсерверных БД. Плюсов у этой технологии много. Драйверы, разработанные корпорацией Microsoft для собственных нужд, более надежные, чем драйверы сторонних производителей. Поэтому если вам требуется работать с базами данных MS Access или для архитектуры клиент-сервер использовать MS SQL Server, то использование ADO будет наиболее предпочтительным. Кроме того, имеется плюс и в вопросе распространения программ - во всех современных Windows встроены драйверы ADO. Другими словами, ваша программа будет работать на любом ПК, где установлен Windows. Как ни странно, но основной минус так же заключается в вопросе распространения программ. Корпорация Microsoft поступает довольно хитро. Каждые пару-тройку лет появляются новые версии Windows. Рядовому пользователю обычно нет нужды переходить на свежую ОС, тем более что каждая новая система становится все требовательней к ресурсам ПК. Для того чтобы заставить пользователя перейти на новую версию, корпорация Microsoft обязательно вводит несколько новых стандартов или технологий, несовместимых со старыми. А для старых версий доработок не предусматривается. Вот и приходится бедному пользователю скрепя зубы тратиться на новые версии операционной системы и пакета MS Office. Поэтому при использовании технологии ADO приходится думать о том, какая версия Windows стоит у конечного пользователя, будет ли ваша программа работать у него на ПК. Технология ADO на самом деле является частью Microsoft Data Access Components (MDAC). Компания Microsoft распространяет MDAC как отдельный продукт, к счастью, бесплатный. При этом поддерживается только самая последняя версия MDAC. Например, в состав Delphi 7 входит MDAC 2.6. При распространении собственных программ следует учитывать, что у клиента с большей долей вероятности уже установлена эта самая MDAC, причем самой последней версии. Однако если он пользуется старыми версиями Windows (Win95, 98, ME, NT), то вам потребуется позаботиться об установке MDAC на его компьютер. Если же у него установлена ОС Win2000, WinXP или более новая, то MDAC у него уже есть, и вам беспокоиться не о чем. Еще один серьезный минус ADO в том, что он для подключения к БД использует довольно медлительную технологию COM. Если ваша база данных будет содержать несколько тысяч записей, то скорость работы с таблицами может стать в сотни раз более медленной, чем если бы вы использовали BDE! На современных ПК, имеющих частоту процессора до 2 ГГц и выше, эти замедления могут быть и незаметны, но работа с огромной базой данных на более медленных ПК превратится в сплошное ожидание. Основными компонентами, с которыми нам предстоит работать, являются TADOConnection (для подключения к БД), TADOTable (аналог TTable из BDE ), TADOQuery (аналог TQuery из BDE, для выполнения запросов и получения набора данных) и TADODataSet (предназначенный для набора данных, полученных через SQLзапрос). Создание базы данных MS Access Базы данных MS Access имеют много плюсов, часто программисты предпочитают использовать именно их. Во-первых, база данных MS Access - это один файл. Сколько бы таблиц и индексов она не содержала, все это хранится в одном единственном файле. А значит, такую базу данных легче обслуживать - переносить на новое место, делать резервные копии и так далее. Еще один плюс - имена полей в такой БД можно давать русскими буквами. Лучше всего изучать новый материал на практике. Для примера создадим базу данных для отдела кадров какого-нибудь предприятия. Какие данные на сотрудника нам понадобятся? Прежде всего, фамилия, имя и отчество. Затем укажем пол (мужской или женский), семейное положение (холост или женат/замужем), количество детей. Также понадобятся дата рождения и дата поступления на работу. Стаж работы в годах. Образование. Военнообязанный сотрудник, или нет. Телефоны, по которым можно связаться с сотрудником в любое время. Должность сотрудника и отдел (если есть), в котором он числится. А также его домашний адрес. При этом учитываем, что сотрудник не обязательно является жителем города, где он работает. А вдруг он приехал на заработки из другого города? Или даже из другой страны? Следовательно, придется вводить и страну, и город - вдруг потребуется делать отчет по сотрудникам, прописанным в Украине, например? Вот сколько данных нужно будет вводить для отдела кадров! А ведь мы еще немного упростили их. Стаж работы подразделяется на общий и непрерывный. Эти данные учитываются при расчете больничных листов. Но для учебной базы данных такими деталями можно пренебречь. Итак, первым делом оптимизируем данные, исходя из правил трех нормальных форм. В результате получаем целых четыре таблицы: Рис. 1. Оптимизированные таблицы Главной здесь будет таблица LichData, которая содержит основные данные о сотруднике. Она имеет релятивные связи с другими таблицами. Поле "Ключ" будет автоинкрементным, то есть автоматически будет прибавляться на единицу, гарантируя нам уникальность ключа. В подчиненных таблицах имеется поле "Сотрудник" целого типа, по которому будет обеспечиваться связь. Причем ключевых полей в дочерних таблицах не будет. Главная таблица поддерживает связь один-к-одному с таблицами Doljnost и Adres, и связь один-ко-многим с таблицей Telephones, ведь у сотрудника наверняка есть и домашний, и рабочий телефоны, а в карманах, возможно, лежит пару мобильников. То есть, один сотрудник может иметь много телефонов. С полями и связями определились, пора создавать базу данных. Для этого загрузите программу MS Access. Если в правой части окна у вас нет панели "Создание файла", то выберите команду "Файл -Создать". Затем выберите команду "Новая база данных". Сразу же выйдет запрос с именем этой базы данных. Создайте папку, которая все равно нам понадобится для нового проекта, укажите эту папку, а базу данных назовите ok (отдел кадров). Как только вы нажмете кнопку "Создать", появится окно этой базы данных: Рис. 2. Создание БД Сейчас нам потребуется сделать четыре таблицы. Поэтому дважды щелкаем по команде "Создание таблицы в режиме конструктора", и переходим к конструктору. В левой части мы вводим имя поля, причем русскими буквами. В поле "Тип данных выбираем тип", а на вкладке "Общие" делаем настройки поля. Описание поля заполнять необязательно. Итак, создаем поля: 1. "Ключ". Разумеется, имя поля пишем без кавычек. Выбираем тип данных "Счетчик", это автоинкрементный тип данных. В настройках убедитесь, что поле индексированно - Да (Совпадения не допускаются). Правой кнопкой щелкните по этому полю и выберите команду "Ключевое поле". Слева от поля появится значок ключа. 2. "Фамилия". Тип поля текстовый, размер 25 символов. Индексированное поле - Да (Совпадения допускаются). Ведь могут же попасться родственники или однофамильцы! 3. "Имя". Тип поля текстовый, размер 25 символов. Индексированное поле - Да (Совпадения допускаются). 4. "Отчество". Тип поля текстовый, размер 25 символов. Индексы не нужны. 5. "Пол". Текстовый, размер 3 символа. В формате поля укажите "муж/жен", конечно, без кавычек. 6. "Сем_Полож". Логический тип, формат поля "Да/Нет". Здесь мы будем указывать, состоит ли сотрудник (сотрудница) в браке. 7. "Детей". Числовой тип, размер поля Байт (трудно представить, что у кого-то будет более 255 детей!). 8. "Дата_Рожд". Тип поля - Дата/Время. Выберите формат "Краткая форма даты". Затем выберите тот же формат для поля "Маска". При попытке выбора маски выйдет запрос на подтверждение сохранения таблицы. Ответьте утвердительно, а вместо имени таблицы по умолчанию "Таблица 1" впишите " LichData ", так будет называться наша первая таблица. После этого появится окно создания маски ввода. Выберите "Краткий формат даты", нажмите "Далее", после чего в окне "Маска ввода" наберите "00.00.0000". В результате мы будем иметь маску в виде "дд.мм.гггг". 9. "Дата_Пост". Все то же самое, что и в №8. 10. "Стаж". Тип поля числовой, размер - байт. 11. "Образование". Текстовый, размер поля 30 символов. Ведь здесь может быть и длинный текст, например "неоконченное высшее техническое". 12. "Военнообязанный". Логический тип, формат "Да/Нет". В результате получим такую картину: Рис. 3. Поля таблицы LichData При попытке закрыть это окно, выйдет запрос о сохранении таблицы " LichData ". Ответьте утвердительно. Главная таблица сделана, осталось еще три. Снова щелкаем "Создание таблицы в режиме конструктора". Вводим такие поля: 1. "Сотрудник". Тип поля - числовой, размер поля - длинное целое. Делать это поле ключевым не нужно, даже после того, как при попытке закрыть таблицу Access предложит вам сделать поле ключевым. 2. "Отдел", Текстовое, 15 символов. 3. "Должность", Текстовое, 20 символов. Закрываем таблицу, даем ей имя " Doljnost ", отказываемся от создания ключа. Делаем следующую таблицу. Поля: 1. "Сотрудник". Тип поля - числовой, размер поля - длинное целое. Не ключевое. 2. "Страна". Тип текстовый, размер 15. 3. "Город". Тип текстовый, размер 20. 4. "Дом_Адрес". Тип текстовый, размер 100. Закрываем таблицу, даем имя " Adres ", отказываемся от создания ключа. Делаем следующую таблицу. Поля: 1. "Сотрудник". Тип поля - числовой, размер поля - длинное целое. Не ключевое. 2. "Телефон". Тип текстовый, размер 17. Желательно задать маску. Сразу же выйдет запрос о сохранении таблицы, сохраните ее под именем " Telephones ". Для этого выбираем маску (дважды щелкаем по ней), в окне нажимаем кнопку "Список". Настраиваем маску, как на рисунке: Рис. 4. Маска для телефона 3. "Примечание". Тип текстовый, размер 10. Формат "Рабочий/Домашний/Мобильный". Закрываем таблицу " Telephones ", отказываясь от создания ключевого поля. Все, база данных готова. Программу MS Access можно закрыть, больше она не понадобится. Пока база данных еще пустая, желательно сделать резервную копию файла ok.mdb, который и является полученной базой данных. Как видите, никаких связей между таблицами мы не делали - проще будет сделать их в проекте программы. Практика работы с БД MS Access из Delphi Базу данных мы спроектировали, таблицы сделали. Осталась еще половина работы - проект Delphi, работающий с этой базой данных. Загружаем Delphi, делаем новый проект. Главная форма нашей программы будет выглядеть так: Рис. 5. Главная форма Здесь я поместил три обычных панели. Свойству Align верхней панели присвоил значение alTop (весь верх). Затем свойству Align нижней панели присвоил значение alBottom. Затем поместил компонент Splitter с вкладки Additional панели инструментов, и его свойству Align также присвоил alBottom, после чего он прижался к нижней панели. Splitter - это разделитель между панелями. С его помощью пользователь мышью сможет передвигать нижнюю панель, меняя ее размеры. И, наконец, свойству Align средней панели присвоил значение alClient, чтобы она заняла все оставшееся место на форме. Не забудьте очистить свойство Caption всех трех панелей. Далее на верхнюю панель я поместил три компонента RadioButton с вкладки Standard палитры компонентов. В их свойствах Caption я написал, соответственно, "Адрес", "Телефоны" и "Должность". Переключаясь между ними, пользователь сможет выводить в нижнюю, подчиненную сетку DBGrid нужные данные. Свойству Checked первой радиокнопки присвоил значение True, чтобы включить ее. Раздел с переключателями я разделил компонентом Bevel с вкладки Additional палитры компонентов. Его ширину (свойство Width ) сделал равным 2 пикселям, превратив его в вертикальную разделительную полосу. Далее я сделал раздел поиска, поместив в него обычные Label, Edit и кнопку BitBtn. Этот раздел понадобится на следующей лекции. В последнем разделе верхней панели находятся еще две кнопки BitBtn. Одна из них предназначена для редактирования текущей записи, другая - для добавления новой. Вторая и третья панели содержат только по одному компоненту DBGrid из вкладки DataControls палитры компонентов, свойствам Align которых присвоено значение alClient. Свойству Name формы присвоено значение fMain, свойство Caption формы имеет текст "Отдел кадров", модуль сохранен под именем Main.pas, а проект в целом называется ok (отдел кадров). Далее в проект добавлен модуль данных (File -> New -> Data Module). Модуль данных - это не визуальный контейнер для размещения на нем невизуальных компонентов. В основном, он предназначен для размещения в нем компонентов подключения к данным ( TDataBase, ADOConnection и т.п.), компонентов - наборов данных ( TTable / ADOTable, TQuery / ADOQuery, TStoredProc / ADOStoredProc ) и компонентов DataSource, которые обеспечивают связь наборов данных и компонентов отображения/редактирования данных. Также модуль данных часто используют и для хранения глобальных переменных, общих функций и процедур, которые должны быть видны по всей программе. Модуль данных не имеет формы, но сохраняется как модуль в файле *.pas. Свойству Name модуля данных мы присвоим имя fDM, а модуль сохраним как DM.pas. Теперь самое интересное. Добавляем в модуль компонент ADOConnection с вкладки ADO палитры компонентов. Этот компонент обеспечит связь других компонентов с базой данных при помощи механизма ADO. Связь обеспечивается свойством компонента ConnectionString. В общем-то, у таких компонентов, как ADOTable тоже есть это свойство, однако, имея четыре таблицы, придется четыре раза устанавливать связь. Проще единожды соединиться компонентом ADOConnection и использовать его для связи других компонентов. Приступим к делу. Щелкните дважды по свойству ConnectionString компонента ADOConnection. Откроется окно подключения компонента к ADO: Рис. 6. Окно подключения к ADO. Здесь мы можем подключиться тремя способами: 1. 2. 3. Использовать для связи созданный ранее link-файл. Вписать в поле "Use Connection String" строку для связи с ADO. Сгенерировать эту строку, нажав кнопку Build. Воспользуемся третьим способом - нажмем кнопку Build. Открывается новое окно, содержащее настройки подключения: Рис. 7. Настройки подключения Вначале нам предлагается выбрать поставщика OLE DB, или иначе, указать нужный для подключения драйвер. Для связи с базой данных MS Access больше всего подходит " Microsoft Jet 4.0 OLE DB Provider ". Jet - это название механизма работы с СУБД, встроенного в MS Access. Этот механизм поддерживает как собственные БД MS Access, имеющие расширение *.mdb, так и ODBC. Его и выделяем в списке. Нажимаем на кнопку "Далее", либо переходим к вкладке "Подключение". Здесь нам нужно выбрать или ввести базу данных. Тут есть одно замечание. Если мы выберем базу данных, то есть, нажмем на кнопку с тремя точками, откроем диалог выбора и найдем там наш файл, то база данных будет привязана к указанному адресу. Если вы желаете поместить базу данных в какой-то определенной папке, то так и поступите. Однако если вы поместили файл с базой данных (в нашем случае ok.mdb) там же, где находится программа, и не желаете зависеть от определенной папки (ведь пользователь может переместить вашу программу), то нужно вручную вписать только имя файла с БД, без всякого адреса. В этом случае вы не сможете проверить подключение, нажав на кнопку "Проверить подключение". Ну и не надо, обойдемся без проверки. Укажите только имя файла - ok.mdb (вы ведь уже поместили этот файл в папку с проектом?). Нажмите на кнопку "ОК". Закрываем окно редактора связей, и нам остается открыть подключение. Однако перед этим переведите свойство LoginPrompt компонента ADOConnection в False. Если этого не сделать, то при каждой попытке соединиться с базой данных будет выходить запрос на пользовательское имя и пароль, нам это не нужно, наша база данных без пароля. Теперь свойство Connected переведите в True. Если вам удалось это сделать, и не вышло никаких сообщений об ошибке, то подключение состоялось. Пойдем дальше. Установите в модуль данных четыре компонента ADOTable, по одному на каждую таблицу из нашей базы данных. Компонент ADOTable (также как и TTable из вкладки BDE ) предназначен для создания набора данных. Набором данных (НД) называется группа записей, полученных такими компонентами, как TTable/ADOTable, TQuery/ADOQuery, TStoredProc/ADOStoredProc из одной или нескольких таблиц базы данных. Все компоненты наборов данных являются потомками класса TDBDataSet, и имеют много общих свойств, методов и событий. Эти компоненты также называют наборами данных. Табличные компоненты (TTable/ADOTable) являются наборами данных, которые получают из базы данных полную копию одной из таблиц, и предоставляют полученный набор данных визуальным компонентам отображения данных ( DBGrid, DBEdit, DBMemo и проч.). Компоненты запросов ( TQuery/ADOQuery ) для получения набора данных из базы данных используют SQL -запрос. Компоненты позволяют получить из одной или нескольких таблиц только те данные, которые удовлетворяют запросу. Выделите все четыре ADOTable (удерживая клавишу ), и в их свойстве Connection выберите нашу связь ADOConnection1. Таким образом, все четыре ADOTable мы подключили к базе данных. Выделите первый компонент ADOTable. Переименуйте его свойство Name в TLichData, а в свойстве TableName выберите главную таблицу базы - LichData. Буква "Т" в начале названия компонента укажет нам в дальнейшем, что это таблица. Рядом с компонентом установите компонент DataSource из вкладки Data Access палитры компонентов. Компонент DataSource предназначен для организации связи с наборами данных, и служит посредником между такими компонентами НД, как ADOTable, ADOQuery и между компонентами отображения данных, например, DBGrid, DBEdit и т.п. Свойство Name компонента DataSource переименуйте в DSLichData ( DS - DataSource ). В свойстве DataSet выберите таблицу TLichData. То же самое нужно проделать еще три раза, подключая аналогичным образом компоненты DataSource к другим таблицам: Рис. 8. Модуль данных с установленными компонентами. Затем свойство Active таблиц переведите в True, открыв их. Для тех, кто пропустил предыдущий курс, напомню, что таблицы можно открывать и закрывать не только в Инспекторе Объектов, но и программно. Как открыть, так и закрыть таблицы можно двумя абсолютно равноценными способами: //Открываем таблицы: fDM.TLichData.Open; fDM.TDoljnost.Active := True; //Закрываем их: fDM.TLichData.Close; fDM.TDoljnost.Active := False; Пойдем далее. Перейдите на главную форму. Выберите команду File -> Use Unit и подключите к ней модуль DM. Теперь мы сможем видеть таблицы из главной формы. На вкладке DataControls сосредоточены визуальные (видимые пользователю) компоненты отображения данных, такие как DBGrid (сетка, отображающая все данные НД в виде таблицы, и позволяющая редактировать их), DBEdit (поле редактирования данных, предназначенная для ввода или редактирования одного поля записи, то есть, ячейки таблицы), DBMemo (для редактирования MEMO -полей) и т.д. Единственным исключением является компонент DBNavigator. Этот компонент предназначен не для отображения данных, а для перемещения по записям набора данных, для вставки новой записи или удаления старой, для перевода НД в режим редактирования или для подтверждения сделанных изменений в наборе данных. Выделите верхнюю сетку DBGrid, в ее свойстве DataSource выберите fDM.DSLichData. В таком же свойстве нижней сетки выберите fDM.DSAdres. Сетки среагировали, и вы можете видеть названия полей. Разумеется, таблица еще пуста, данных пока нет. Кстати, выделите обе сетки, и установите в True их свойства ReadOnly - только чтение. Таблицы ведь будут связаны, и нам не нужно, чтобы пользователь вводил данные фрагментарно. Мы для этого сделаем отдельную форму, а эти сетки нужны только для просмотра. Теперь нужно между таблицами установить связь. Это требуется не только для того, чтобы в нижней сетке выходили данные только на сотрудника, выделенного в верхней сетке, но и для того, чтобы мы смогли в дальнейшем вводить связанные данные в окне редактора. Снова выделите модуль данных. Щелкните дважды по первой таблице, чтобы открыть редактор полей. Правой кнопкой щелкните по этому редактору и выберите команду Add all fields (добавить все поля). В окне редактора полей появились все поля таблицы: Рис. 9. Редактор полей Редактор полей предназначен для настройки параметров каждого поля, для добавления новых полей или удаления имеющихся. Если в редакторе полей нет ни одного поля, то в компоненте DBGrid будут отображены все поля таблицы, имеющие параметры по умолчанию. Если же мы добавили в редактор полей хотя бы одно поле, то сетка DBGrid его и отобразит. В редакторе мы можем для каждого поля изменить различные параметры, например, ширину колонки, название колонки, видимое это поле или нет, и т.п. Кроме того, редактор полей предоставляет возможность добавлять в набор данных новые поля, например вычисляемые или просматриваемые ( lookup ). Но эту возможность мы будем рассматривать в других лекциях. Поле "Ключ" у нас автоинкрементное, предназначено для связи с другими таблицами. Пользователю его видеть не обязательно. Выделите его, и в свойстве Visible установите False. Теперь для пользователя оно будет невидимым. Здесь у нас есть два логических поля - "Сем_Полож" и "Военнообязанный". Чтобы True и False выходили на экране так, как нам нужно, свойству DisplayValues первого из этих полей присвойте значение "Женат;Холост" (разумеется, без кавычек), а второго - "Да;Нет". Первым здесь идет значение, которое будет обозначать True, вторым - False. Эти значения разделяются точкой с запятой, пробелы не нужны. Таким же образом добавьте все поля в остальные три таблицы. У них невидимым следует сделать поле "Сотрудник" - этому полю автоматически будет присвоено такое же число, как у поля Ключ соответствующей записи. Логических полей у них нет. Однако для поля "Телефон" таблицы Telephones следует изменить свойство EditMask. Щелкните по нему дважды, открыв редактор маски, и в поле Input Mask введите маску "#(###)-###-####". Сохраните ее, нажав кнопку ОК. Для полей типа Дата в этом свойстве (в таблице LichData два таких поля) введите маску "##.##.####". Далее кнопкой перейдите в редактор кода. В нижней части окна вы можете увидеть вкладку Diagram, перейдите на нее. Нам с вами потребуется сделать такие связи: Рис. 10. Связи базы данных Для начала в окно диаграмм нужно добавить наши таблицы. Найдите их в окне дерева объектов Object TreeView. Если у вас это окно закрыто, откройте его клавишами либо командой меню View -> Object TreeView. Ухватитесь в этом окне мышью за название главной таблицы LichData {TLichData} и перетащите ее в окно диаграмм. Таблица вместе с полями отобразится в окне. Если бы ранее мы не добавили все поля в окне редактора полей компонента ADOTable, то здесь мы не увидели бы полей. Точно также перетащите остальные таблицы, как на рисунке. Связи главная - подчиненная делают кнопкой Master / Detail Connector, которую вы можете увидеть в верхней части окна диаграмм (предпоследняя). Нажмите на кнопку, затем подведите указатель мыши к боковой границе главной таблицы, нажмите левую кнопку и, удерживая ее, проведите линию к боковой границе таблицы Adres. Как только вы отпустите кнопку, появится окно связей: Рис.11. Окно связей Здесь в поле Detail Fields нужно выбрать поле, по которому будет осуществляться связь, в нашем случае это поле "Сотрудник". В поле Master Fields выбираем ключевое поле "Ключ". Затем нажимаем кнопку Add и кнопку OK. Связь установлена. При установке связей главный / подчиненный важно начинать вести линию с главной таблицы к подчиненной. Если бы мы сделали иначе, то главной таблицей стала бы TAdres. Такую же связь установите и с остальными таблицами. Просто, не правда ли? Сохраните проект, скомпилируйте его и запустите на выполнение. Если в сетках главного окна вы видите открытые таблицы, то все хорошо. Если нет, возможно, при изменении настроек ваши таблицы закрылись. В таком случае закройте программу (но не проект!), выделите таблицы, и их свойству Active снова присвойте значение True. Таблицы должны появиться в сетках главного окна, даже на этапе проектирования. Пойдем дальше. Теперь нам нужно сделать окно редактора данных. Создайте новую форму (File -> New -> Form). Ее свойство Name переименуйте в fEditor, а при сохранении формы дайте модулю имя Editor. Командой File -> Use Unit подключите к форме модуль данных DM. Теперь нам нужно установить на форму такие компоненты: Рис. 12. Окно редактора данных Здесь я поступил следующим образом: установил на форму четыре панели GroupBox с вкладки Standard, на каждую таблицу свой GroupBox. Почему я так поступил, станет понятно позже. Займемся первой таблицей. В свойстве Caption компонента GroupBox впишите "Личные данные", это название отразится в заголовке панели. Далее на эту панель следует установить восемь компонентов DBEdit с вкладки DataControls палитры компонентов, два DBCheckBox для редактирования логических данных, и один компонент DBComboBox для списка. Поясняющие компоненты Label установите и настройте самостоятельно. Немного доработаем компонент DBComboBox. Щелкните дважды по его свойству Items, открыв редактор. В нем введите две строки: муж жен Сохраните текст, нажав кнопку ОК. Теперь пользователь сможет указать пол сотрудника, выбрав нужную строку из списка. Для таблицы Doljnost все еще проще: на панели GroupBox всего два компонента DBEdit и два поясняющих Label. Для таблицы Adres используйте три DBEdit. А вот для таблицы Telephones понадобится один DBEdit, один DBComboBox, сетка DBGrid и кнопка BitBtn. Сетка нужна для контроля введенных телефонов, ведь здесь связь один-ко-многим, и телефонов может быть несколько. В редакторе Items компонента DBComboBox введите три строки: Рабочий Домашний Мобильный Теперь займемся подключением компонентов контроля. Удерживая , выделите все компоненты контроля на первой панели (все компоненты, кроме Label ). В их свойстве DataSource выберите fDM.DSLichData, подключив компоненты к нужному набору данных (таблице). Снимите общее выделение, и выделите первый DBEdit. В его свойстве DataField выберите поле "Фамилия". Это свойство подключает выбранный компонент к определенному полю таблицы. Таким же образом подключите к соответствующим полям остальные компоненты. Затем подключайте компоненты других таблиц, каждое к своей таблице и к соответствующему полю. Сетка DBGrid подключается к fDM.DSTelephones, и не имеет поля, разумеется. Она отображает все видимые поля таблицы. В правой нижней части для удобства пользователя я установил навигационный компонент DBNavigator с вкладки Data Controls. Этот компонент предназначен для перемещения по записям, включения режима редактирования записи, сохранения или отмены сделанных изменений, добавления новой записи или удаления существующей. В его свойстве DataSource я выбрал fDM.DSLichData, чтобы подключить компонент к главной таблице. Нам нужна от этого компонента только возможность перехода на начало или конец таблицы, на следующую или предыдущую запись. Поэтому раскройте его свойство VisibleButtons (видимость кнопок компонента) и переведите в False все кнопки, кроме nbFirst, nbPrior, nbNext и nbLast. Нажатие на эти кнопки приведет к вызову соответствующих методов компонента ADOTable. Эти методы делают следующее: First - переход на первую запись таблицы. Prior - переход на предыдущую запись. Next - переход на следующую запись. Last - переход на последнюю запись. Когда у DBNavigator останется всего четыре кнопки, эти кнопки окажутся вытянутыми. Уменьшите ширину компонента, чтобы кнопки приняли более привычный вид. Теперь пришло время объяснить, почему я поместил компоненты на панели GroupBox, и почему для каждой таблицы сделал отдельную панель. Если вы прошли предыдущий курс "Введение в программирование на Delphi", то знаете, что измененная запись в таблице сохраняется в трех случаях: 1. Применением метода Post. 2. При переходе на другую запись. 3. При добавлении новой записи. Когда мы, заполнив одну таблицу, перейдем к другой, то в первой таблице запись еще не будет сохранена. Поле "Ключ" у нас автоинкрементное, на него завязаны остальные таблицы. До тех пор, пока мы не сохраним запись, в этом поле не будет никакого значения. Следовательно, данные в других таблицах не смогут привязаться к какой-то записи главной таблицы. Поэтому выделите первый GroupBox, и дважды щелкните по событию onExit на вкладке Events инспектора объектов. Это событие происходит всякий раз, когда пользователь перейдет к другой панели GroupBox, либо к кнопкам, расположенным в нижней части окна. В сгенерированной процедуре впишите код: {Вышли из редактирования LichData} procedure TfEditor.GroupBox1Exit(Sender: TObject); begin if fDM.TLichData.Modified then fDM.TLichData.Post; end; Свойство Modified компонента ADOTable имеет логический тип - в нем содержится True, если данные были изменены, и False в противном случае. Метод Post этого компонента, как уже упоминалось, сохраняет измененную запись таблицы. При этом в поле "Ключ" попадет присвоенное автоматически значение. Таким образом, введенный код означает, что если запись была изменена, то следует ее сохранить. Сгенерируйте событие onExit для оставшихся панелей GroupBox и таким же образом сохраните изменения записей в соответствующих таблицах. Далее сгенерируйте событие нажатия на кнопку "Добавить" в GroupBox с телефонными данными. Этой кнопкой мы будем добавлять новые записи в таблицу, ведь один сотрудник может иметь более одного телефона. Код в процедуре будет такой: if fDM.TTelephones.Modified then fDM.TTelephones.Post; fDM.TTelephones.Append; DBEdit14.SetFocus; Вначале мы сохраняем измененные значения, если они были. Затем методом Append мы добавляем в таблицу новую запись. Добавить новую запись можно двумя методами: 1. Append - добавляет новую запись в конец таблицы. 2. Insert - добавляет новую запись в текущее положение курсора. После добавления новой записи таблица уже будет в режиме редактирования, поэтому можно не вызывать метод Edit, который переводит таблицу в этот режим. Далее мы переводим фокус ввода на DBEdit с телефонными номерами, чтобы пользователю не пришлось делать это самому. В процедуре нажатия на кнопку "Сохранить и выйти" код простой: if fDM.TLichData.Modified then fDM.TLichData.Post; if fDM.TDoljnost.Modified then fDM.TDoljnost.Post; if fDM.TAdres.Modified then fDM.TAdres.Post; if fDM.TTelephones.Modified then fDM.TTelephones.Post; Close; Здесь мы лишь сохраняем изменения во всех таблицах, если они были, и закрываем окно. Напоследок у нас осталась кнопка "Добавить сотрудника". Что мы должны сделать, если пользователь нажмет на эту кнопку? Добавить новую запись в каждую таблицу и перевести курсор в первый DBEdit, в котором редактируется фамилия. Это и делаем: fDM.TLichData.Append; fDM.TDoljnost.Append; fDM.TAdres.Append; fDM.TTelephones.Append; DBEdit1.SetFocus; С этой формой мы закончили, переходим к главной форме. Не забывайте время от времени сохранять проект. Если вы еще не подключили модуль Editor к главной форме командой File -> Use Unit, то сделайте это сейчас, чтобы можно было вызывать окно редактора из главной формы. Начнем с кнопки "Новый сотрудник". Как и в предыдущем примере, нам потребуется добавить новую запись в каждую таблицу, после чего открыть окно редактора: fDM.TLichData.Append; fDM.TDoljnost.Append; fDM.TAdres.Append; fDM.TTelephones.Append; fEditor.ShowModal; Сгенерируйте процедуру onClick для кнопки "Редактировать". Тут будет лишь одна строчка кода: fEditor.ShowModal; В результате откроется окно редактора, и компоненты будут отображать данные текущей записи. Предположим, пользователю будет удобней дважды щелкнуть по записи в верхней сетке DBGrid, чем нажимать кнопку. Поэтому выделите сетку с главной таблицей и сгенерируйте для нее событие onDBLClick. Там введите такую же строчку кода. Блок поиска по фамилии оставим на следующую лекцию и перейдем к программированию радиокнопок. По нашему замыслу, при открытии программы в верхней сетке DBGrid будут отображаться данные из главной таблицы, а в нижней - из таблицы Adres. Также будет выделена радиокнопка с надписью "Адрес". Если пользователю захочется посмотреть должность или телефоны текущего сотрудника, он будет щелкать соответствующую радиокнопку, и эти данные должны быть отображены в нижней DBGrid. Выделите первую радиокнопку с надписью "Адрес" и сгенерируйте для нее событие onClick, которое будет возникать, когда пользователь щелкнет по ней. В процедуре этого события впишите следующий код: if RadioButton1.Checked then DBGrid2.DataSource := fDM.DSAdres; Здесь мы проверили, включена ли данная радиокнопка. Если да, то мы меняем связь нижней сетки DBGrid и подключаем ее к таблице Adres. Ведь связь сетки с таблицей осуществляется через соответствующий компонент DataSource, а у нас их четыре. Подключаясь то к одному, то к другому DataSource, мы можем программно менять отображенную в сетке таблицу. Для события onClick радиокнопки с надписью "Телефоны" код будет таким: if RadioButton2.Checked then DBGrid2.DataSource := fDM.DSTelephones; А для события onClick радиокнопки с надписью "Должность", соответственно, код будет следующим: if RadioButton3.Checked then DBGrid2.DataSource := fDM.DSDoljnost; Таким образом, в нижней сетке мы отображаем то одну, то другую подчиненную таблицу, и всякий раз в этих таблицах будут показаны данные текущего сотрудника. Вот и весь код! Сохраните проект и скомпилируйте его. На следующей лекции мы будем изучать методы поиска и фильтрации, поэтому введите в базу данные десятка на два - три сотрудников, чтобы было, что искать. Поиск, фильтрация и индексация таблиц Последовательный перебор В программах, работающих с базами данных, часто используют поиск данных. Для чего еще нужны базы данных, как не для этого? Самый простой, но в то же время и самый медленный, "тяжеловесный" поиск, это, пожалуй, последовательный перебор. Вы переходите на первую запись таблицы, создаете цикл, который длится до последней записи, и внутри этого цикла проверяете необходимое условие. Также можно делать и обратный перебор, от последней записи к первой. В таблице 3.1 приведены все свойства и методы наборов данных ( TTable/ADOTable, TQuery/ADOQuery ), которые могут быть использованы при организации последовательного перебора: Таблица 1. Свойства и методы набора данных, которые могут быть задействованы при последовательном переборе Свойства методы и Описание Eof Свойство логического типа. Принимает значение True, если достигнут конец таблицы, или если таблица пуста, и False в противном случае. Bof Свойство логического типа. Принимает значение True, если достигнуто начало таблицы, и False в противном случае. Next Метод. Делает текущей следующую запись набора данных. Prior Метод. Делает текущей предыдущую запись набора данных. First Метод. Делает текущей первую запись набора данных. Last Метод. Делает текущей последнюю запись набора данных. Пример: //перешли на первую запись: fDM.TLichData.First; //делать, пока не конец таблицы: while not fDM.TLichData.Eof do begin if fDM.TLichData['Фамилия'] = 'Иванов' then break; //нашли нужную запись, и вышли из цикла fDM.TLichData.Next; //иначе перешли на следующую запись end; //while Как видно из примера, мы делаем прямой последовательный перебор от первой записи до последней. Получить или изменить значение нужного поля можно, указав имя поля в квадратных скобках после имени набора данных. Например: Edit1.Text := fDM.TLichData['Фамилия']; //получили значение fDM.TLichData['Фамилия']:= Edit1.Text; //изменили значение Приведенный пример поиска нужной записи допустим, если в таблице имеется не более сотни-другой записей, а условная проверка достаточно сложна. Но обычно программисты этот способ не используют, или используют только в крайнем случае. Далее рассмотрим другие способы поиска. Метод Locate Метод Locate ищет первую запись, удовлетворяющую условию поиска. Если запись найдена, метод делает ее текущей и возвращает True. В противном случае метод возвращает False и курсор не меняет положения. Поле, по которому ведется поиск, не обязательно должно быть индексировано. Однако если поле индексировано, то метод ищет запись по индексу, что значительно ускоряет поиск. Поиск может вестись как по одному полю, так и по нескольким полям. Метод имеет три параметра: function Locate (const KeyFields: String; const KeyValues: Variant; Options: TLocateOptions) : Boolean; Параметр KeyFields задает поле или список полей, по которым ведется поиск. Если имеется несколько полей, их разделяют точкой с запятой. Параметр KeyValues является вариантным массивом, в котором задаются критерии поиска. При этом первое значение KeyValues ставится в соответствие с первым полем, указанным в KeyFields. Второе - со вторым, и так далее. Третий параметр Options позволяет задать некоторые опции поиска: loCaseInsensitive - поиск ведется без учета высоты букв, то есть, считаются одинаковыми строки "строка", "Строка" или "СТРОКА". loPartialKey - запись будет удовлетворять условию, если ее часть содержит искомый текст. То есть, если мы ищем "ст", то удовлетворять условию будут "строка", "станция", "стажер" и т.п. Пустой набор [] указывает, что настройки поиска игнорируются. То есть, строка ищется "как есть". Примеры использования метода Locate: Table1.Locate('Фамилия', Edit1.Text, []); Table1.Locate('Фамилия;Имя', VarArrayOf(['Иванов', 'Иван']), [loCaseInsensitive]); Как видно из примера, если для поиска вы используете одно поле, то значение может передаваться напрямую из компонента Edit. Если же вы используете список полей, то должны передать в метод массив вариантов, в которых содержатся искомые значения, по одному на каждое поле. При установке компонента ADOTable в раздел uses прописывается модуль ADODB, который содержит описания всех свойств, методов и событий компонента. Желательно использовать метод в том модуле, где установлен этот компонент. Рассмотрим применение этого метода на примере. Откройте проект. Перейдите на модуль DM, где у нас хранятся компоненты доступа к базе данных. Процедуру поиска реализуем в этом модуле, а чтобы с ней можно было работать из других форм, опишем ее в разделе public: public { Public declarations } procedure MyLocate(s: String); Как видите, в процедуру передается параметр - строка. В ней мы будем передавать искомую фамилию. Если курсор находится на описании нашей процедуры, то нажмите , чтобы сгенерировать процедуру автоматически. Процедура будет иметь следующий код: procedure TfDM.MyLocate(s: String); begin TLichData.Locate('Фамилия', s, [loPartialKey]); end; Таким образом, при нахождении подходящей записи курсор будет перемещаться к ней. На главной форме выделите компонент Edit, предназначенный для поиска по фамилии. Создайте для него событие onChange, которое наступает при изменении текста в поле компонента. В созданной процедуре пропишите вызов поиска: fDM.MyLocate(Edit1.Text); Сохраните пример, скомпилируйте и опробуйте результаты поиска. Метод Locate рекомендуется использовать везде, где это возможно, поскольку он всегда пытается применить наиболее быстрый поиск. Если поле индексировано, и использование индекса ускорит процесс поиска, Locate использует индекс. Если поле не имеет индекса, Locate все равно ищет данные наиболее быстрым способом. Это делает вашу программу независимой от индексов. Метод Lookup Метод Lookup, в отличие от Locate, не меняет положение курсора в таблице. Вместо этого он возвращает значения некоторых ее полей. Причем в отличие от Locate, этот метод осуществляет поиск лишь на точное соответствие. Такой способ поиска востребован реже, однако в иных случаях этим методом очень удобно пользоваться. Рассмотрим синтаксис этого метода. function Lookup (const KeyFields: String; const KeyValues: Variant; const ResultFields: String) : Variant; Как вы видите, первые два параметра такие же, как у Locate. А вот третий параметр и возвращаемое значение отличаются. В строке ResultFields через точку с запятой перечисляются поля таблицы, значения которых метод должен вернуть. Возвращаются эти значения в виде вариантного массива. Проблема в том, что вернуться может значение Null, то есть, ничего, или Empty (пустой) и это нужно проверять. Рассмотрим работу метода Lookup на примере нашей программы. Прежде всего, вспомним, как работает тип данных Variant. В переменную типа Variant можно поместить любое значение, в том числе и массив. Этот тип данных обычно используют, когда не известно заранее, данные какого типа нам понадобятся на этапе выполнения программы. Когда переменной типа Variant присвоено значение, имеется возможность проверить тип данных этого значения. Для этого служит функция VarType(): function VarType(const V: Variant): TVarType; В качестве параметра в функцию передается переменная вариантного типа. Функция возвращает значение типа TVarType. Это значение указывает, какого типа данные содержатся в переменной. Значение может быть varSmallint (короткое целое), varInteger (целое), varCurrency (денежный формат) и так далее. Чтобы увидеть полный список возвращаемых функцией значений, в редакторе кода установите курсор на название функции и нажмите , вызвав контекстный справочник. Нас же в данном примере интересуют всего два значения: varNull (записи нет) и varEmpty (запись пустая). Если в программе мы заранее не проведем проверку на эти значения, то вполне можем вызвать ошибку программы. Если же поиск прошел успешно, то будет возвращен массив вариантных значений, элементы которого начинаются с нуля. Каждый элемент массива будет содержать данные одного из указанных полей. Загрузите проект программы. Для поиска воспользуемся кнопкой с надписью "Найти", расположенной в верхней части главной формы. Идея такова: пользователь вводит в поле Edit1 какую то фамилию и нажимает кнопку "Найти". Событие onClick этой кнопки собирает в строковую переменную значения четырех указанных полей найденной записи. Причем после каждого значения в строку добавляется символ "#13" (переход на новую строку), формируя многострочный отчет. Затем эту строку мы выведем на экран функцией ShowMessage(). Итак, в окне главной формы дважды щелкните по кнопке "Найти", генерируя событие onClick. Полный листинг процедуры приведен ниже: {щелкнули по кнопке Найти} procedure TfMain.BitBtn1Click(Sender: TObject); var myLookup: Variant; //для получения результата s : String; //для отчета begin //получаем результат: myLookup := fDM.TLichData.Lookup('Фамилия', Edit1.Text, 'Фамилия;Имя;Отчество;Образование'); //проверяем, не Null ли это: if VarType(myLookup) = varNull then ShowMessage('Сотрудник с такой фамилией не найден!') else if VarType(myLookup) = varEmpty then ShowMessage('Запись не найдена!') //если это массив, то из его элементов собираем //многострочную строку: else if VarIsArray(myLookup) then begin s := myLookup[0] + #13 + myLookup[1] + #13 + myLookup[2] + #13 + myLookup[3]; //и выводим ее на экран: ShowMessage(s); end; //else if end; Комментарии достаточно подробны, чтобы вы разобрались с кодом. Сохраните проект, скомпилируйте его и запустите. Опробуйте этот способ поиска. Фильтрация данных Фильтрацию данных применяют не реже а, пожалуй, даже чаще, чем поиск. Разница в том, что при поиске данных пользователь видит все записи таблицы, при этом курсор либо переходит к искомой записи, либо он получает данные этой записи в виде результата работы функции. При фильтрации дело обстоит иначе. Пользователь в результате видит только те записи, которые удовлетворяют условиям фильтра, остальные записи становятся скрытыми. Конечно, таким образом искать нужные данные проще. Можно указать в условиях фильтра, что требуется вывести всех сотрудников, чья фамилия начинается на "И". Пользователь увидит только их. А можно и по-другому: вывести всех сотрудников, которые поступили на работу в период между 2000 и 2005 годом. Короче говоря, удобство работы пользователя с вашей программой зависит от вашей фантазии. Рассмотрим основные способы фильтрации записей. Свойство Filter Свойство Filter - наиболее часто используемый способ фильтрации записей, имеет тип String. Вначале программист задает условия фильтрации в этом свойстве, затем присваивает логическому свойству Filtered значение True, после чего таблица будет отфильтрована. Условия фильтрации должны входить в строку, например: fDM.TLichData.Filter := 'Фамилия =''Иванов'''; По правилам синтаксиса, если внутри строки встречается апостроф, его нужно дублировать. Приведенный выше пример в результате содержит условие: Фамилия = 'Иванов' Применяя это свойство, достаточно сложных условий задать невозможно, но если условия фильтрации просты, то данный способ незаменим. Опробуем фильтрацию записей на примере нашего приложения. Откройте событие onChange компонента Edit, изменим его немного. Закомментируйте или удалите вызов процедуры поиска MyLocate, и впишите следующий код: //fDM.MyLocate(Edit1.Text); - закомментировали fDM.TLichData.Filter := 'Фамилия >=' + QuotedStr(Edit1.Text); fDM.TLichData.Filtered := True; Откомпилируйте проект и запустите его на выполнение. При введении только первой буквы фамилии записи уже начинают фильтроваться. К примеру, если мы ввели букву "Л", то остаются записи с фамилиями, начинающимися от буквы "Л" до конца алфавита. Можно также улучшить поиск, если при этом еще отсортировать записи по индексу, но об этом чуть позже. Функция QuotedStr() возвращает переданный ей текст, заключенный в апострофы. Условие фильтра можно было бы описать и так: fDM.TLichData.Filter := 'Фамилия >=''' + Edit1.Text + ''''; Сложность заключается в том, что в этом случае приходится считать апострофы. Функция QuotedStr() помогает решить эту проблему. Событие onFilterRecord Это событие возникает при установке значения True в свойстве Filtered. Применение этого способа имеет большой плюс, и большой минус. Плюс в том что, сгенерировав это событие, программист получает возможность задать гораздо более сложные условия фильтрации. Минус же заключается в том, что проверка заключается перебором всех записей таблицы. Если таблица содержит очень много записей, процесс фильтрации может затянуться. В событие передаются два параметра. Первый параметр - набор данных DataSet. С ним можно обращаться, как с именем фильтруемой таблицы. Второй параметр логическая переменная Accept. Этой переменной нужно передавать результат условия фильтра. Если условие возвращает False, то запись не принимается, и не будет отображаться. Соответственно, если возвращается True, то запись принимается. Рассмотрим этот способ на примере. Суть примера в следующем: необходимо отфильтровать записи по начальным (или всем) буквам фамилии, вводимым пользователем в поле Edit1. В предыдущем примере, если бы мы ввели букву "И", то вышли бы фамилии, первой буквой которых были бы "И" - "Я". Это не так удобно. Сделаем так, чтобы если пользователь введет букву "И", то останутся только фамилии, начинающиеся на "И". Если пользователь введет еще букву "в", то останутся только фамилии, начинающиеся на "Ив", и так далее. Поочередно вводя начальные буквы, пользователь доберется до нужных фамилий. Для начала подготовим модуль данных. В нем нам потребуется создать глобальную переменную ed, чтобы мы могли передавать в нее текст из компонента Edit1: var fDM: TfDM; ed: String; //текст из Edit1 Этого действия можно было бы избежать, если бы компонент ADOTable, подключенный к таблице LichData, располагался на главной форме. Но поскольку он находится в модуле данных, то и событие onFilterRecord будет сгенерировано в нем. А в этом событии нам нужно будет знать, что в данный момент находится в поле ввода Edit1. Именно для этого и нужна глобальная переменная ed. Далее выделяем TLichData, то есть, компонент ADOTable, подключенный к таблице LichData. На вкладке Events (События) инспектора объектов найдите событие onFilterRecord и дважды щелкните по нему, сгенерировав процедуру. Полный листинг процедуры: {onFilterRecord главной таблицы} procedure TfDM.TLichDataFilterRecord(DataSet: TDataSet; var Accept: Boolean); var s : String; //для значения поля begin //получаем столько начальных букв из поля Фамилия, //сколько букв имеется в переменной ed: s := Copy(DataSet['Фамилия'], 1, Length(ed)); //делаем проверку на совпадение значений: Accept := s = ed; end; Здесь в переменную s попадает столько начальных букв из поля "Фамилия", сколько букв содержит в данный момент компонент Edit1 на главной форме (эти буквы мы передадим в переменную ed чуть позже). Если текст в переменной s совпадает с текстом из поля Edit1, то переменной Accept присваивается True, и запись принимается. Иначе запись отфильтровывается. Не забудьте сохранить проект. Далее перейдем в главную форму. Нужно удалить весь текст из события onChange компонента Edit1, и вписать новый: {Изменение поиска по фамилии} procedure TfMain.Edit1Change(Sender: TObject); begin //если в поле Edit1 есть хоть одна буква, if Edit1.Text <> '' then begin fDM.TLichData.Filtered := False; //отключаем фильтр ed := Edit1.Text; //передаем в fDM новый текст fDM.TLichData.Filtered := True; //включаем фильтр end //если букв нет, фильтрацию отключаем: else fDM.TLichData.Filtered := False; end; Вот и все. Что же тут у нас происходит? Как только пользователь введет хоть одну букву, срабатывает событие onChange компонента Edit1. Если в Edit1 есть хоть одна буква, то мы вначале отключаем фильтрацию, отменяя прошлый фильтр, если он был. Затем мы передаем в глобальную переменную ed, расположенную в модуле данных, текст из Edit1. Далее снова включаем фильтр. При этом срабатывает событие onFilterRecord нашей таблицы, и в этом событии сравнивается текущее значение переменной ed и записей поля "Фамилия". Сохраните проект, скомпилируйте и запустите программу. Проверьте, как фильтруются записи. Имея воображение, в событии onFilterRecord можно устраивать сколь угодно сложные проверки. Ведь в этом событии можно сравнивать не одно поле, а несколько, причем поля не обязательно должны быть индексированы. Вы можете проверять на совпадение хоть все поля таблицы, и поскольку фильтрация происходит путем перебора записей, то усложнение условных проверок заметно не замедлит этот процесс. Использование индексов Создание индексных полей обеспечивает сортировку данных по этим полям, что также облегчает поиск данных - ведь найти нужную фамилию или имя проще, если они отсортированы по алфавиту. Причем имеется возможность сортировать записи не только по возрастанию, но и по убыванию, хотя в большинстве руководств по Delphi эта возможность не описывается. При создании в базе данных таблицы LichData мы указали поля "Фамилия" и "Имя", как индексированные. Этим и воспользуемся. Чтобы включить сортировку записей по полю "Фамилия", достаточно указать название поля в свойстве IndexFieldNames таблицы: fDM.TLichData.IndexFieldNames := 'Фамилия'; Если требуется отключить сортировку, этому свойству присваивается пустая строка: fDM.TLichData.IndexFieldNames := ''; Существует еще одна хитрость, о которой мало где можно прочитать. При индексировании таблицы к имени поля можно прибавить строку " ASC ", если мы желаем сортировать в возрастающем порядке (по умолчанию), или " DESC ", если сортируем в убывающем порядке. Сортировка " ASC " используется по умолчанию. Добавим возможность сортировки по фамилии и имени в нашу программу. Для этого на главную форму установим компонент TPopupMenu с вкладки Standard палитры компонентов. Дважды щелкните по компоненту, чтобы открыть редактор меню. Создадим следующие пункты: Сортировать по фамилии Сортировать по имени Не сортировать Обратная сортировка В редакторе меню выделите пункт "Сортировать по фамилии" и измените свойство Name этого пункта на NFam. Пункт "Сортировать по имени" переименуйте в NImya. Пункт "Не сортировать" - в NNet, а пункт "Обратная сортировка" - в NObrat. Вначале создайте обработчик событий для пункта "Не сортировать" (дважды щелкните по пункту). Тут все просто: {Не сортировать} procedure TfMain.NNetClick(Sender: TObject); begin fDM.TLichData.IndexFieldNames := ''; end; Для обработчика событий пункта "Сортировать по фамилии" код немного сложней: {Сортировать по фамилии} procedure TfMain.NFamClick(Sender: TObject); var stype : String; begin //выбираем направление сортировки: if NObrat.Checked then stype := ' DESC' //обратная сортировка else stype := ' ASC'; //прямая сортировка //сортируем fDM.TLichData.IndexFieldNames := 'Фамилия' + stype; end; Здесь, в зависимости от состояния свойства Checked пункта "Обратная сортировка" мы присваиваем строковой переменной stype либо значение ' ASC ' (прямая сортировка), либо ' DESC ' (обратная сортировка). Обратите внимание, что перед строкой имеется пробел, он нужен, чтобы строка не "прилепилась" к названию поля. Далее мы устанавливаем индекс, указывая имя поля и добавляя к нему значение переменной stype. Таким образом, если Checked пункта "Обратная сортировка" имеет значение True (галочка установлена), мы добавляем ' DESC ', или ' ASC ' в противном случае. В результате имя индексного поля может быть либо "Фамилия ASC ", либо "Фамилия DESC ". Сортировку по имени кодируем аналогичным образом: {Сортировать по имени} procedure TfMain.NImyaClick(Sender: TObject); var stype : String; begin //выбираем направление сортировки: if NObrat.Checked then stype := ' DESC' else stype := ' ASC'; //сортируем fDM.TLichData.IndexFieldNames := 'Имя' + stype; end; Нам осталось указать код пункта всплывающего меню "Обратная сортировка". Тут нам нужно не просто установить галочку, если ее не было, но также проверить - есть ли сортировка по какому либо полю? Если таблица отсортирована, требуется ее пересортировать по этому же полю, но уже в обратном порядке. Вот код: {Команда "Обратная сортировка"} procedure TfMain.NObratClick(Sender: TObject); begin //изменяем направление сортировки NObrat.Checked := not NObrat.Checked; //если сортировка по фамилии, пересортируем if Pos('Фамилия',fDM.TLichData.IndexFieldNames)>0 then fMain.NFamClick(Sender); //если сортировка по имени, пересортируем if Pos('Имя',fDM.TLichData.IndexFieldNames)>0 then fMain.NImyaClick(Sender); end; Как видите, мы использовали функцию Pos(), которая возвратит ноль, если в строке не найдено указанной подстроки, или номер символа, с которого эта подстрока начинается, если она есть. Нам нужно определить, не входит ли в имя индексного поля "Фамилия" или "Имя". Ведь к имени поля добавлена строка ' ASC ' или ' DESC ', так что прямая проверка if fDM.TLichData.IndexFieldNames = 'Фамилия' then результата не даст, в любом случае результатом было бы False. Ну а для пересортировки мы вызываем соответствующий пункт меню, чтобы не писать код сортировки еще раз, например: fMain.NFamClick(Sender); Следует заметить, что при большом количестве записей в таблице смена индексного поля будет несколько замедлять работу приложения. Тем не менее, индексация таблицы - очень удобный и часто применяемый способ организации вывода записей. В свойстве PopupMenu верхней сетки DBGrid1 выберите созданное только что всплывающее меню, чтобы оно открывалось только над этой сеткой, сохраните проект, скомпилируйте его и опробуйте сортировку данных. Напоследок заметим, что мы имеем возможность применить одновременно и фильтрацию записей, и их индексацию. Это позволяет нам создать достаточно мощный и удобный для пользователя механизм поиска записей в нашей программе. Наборы данных. Основные свойства, методы и события До сих пор мы работали с таблицами с помощью компонента TADOTable. На самом деле мы работали не с самими таблицами, а с Набором данных (DataSet). Набор данных - это коллекция записей из одной или нескольких таблиц базы данных. Наборы данных можно получить с помощью компонент TADOTable, TADOQuery или TADOStoredProc, который необходим для архитектуры клиент-сервер. Каким образом получаются наборы данных? Когда мы открываем таблицу, то есть, присваиваем True свойству Active компонента TADOTable, например, специальный механизм делает выборку записей в соответствии с заданными параметрами, и возвращает нам эти записи в виде таблицы. Можно сказать, что наборы данных - это прослойка между нашим приложением и реальными таблицами, хранящимися в базе данных. Все указанные выше компоненты являются наборами данных, имеют общего предка - класс TDataSet и заимствовали от него свойства, методы и события, добавляя собственные возможности. Об этом и поговорим на этой лекции. Свойства Active - Свойство имеет логический тип и позволяет открыть или закрыть набор данных, если свойству присвоить True или False соответственно. В зависимости от свойства CanModify данные можно либо только просматривать, либо можно также редактировать их. AutoCalcFields - Свойство логического типа. Если установить значение False, то возникновение события OnCalcFields будет подавляться, вычисляемые поля обрабатываться не будут. Значение True разрешает расчет вычисляемых полей. Bof - Свойство имеет логический тип и содержит True, если курсор находится на первой записи набора данных, и False в противном случае. Bof содержит True, когда: Не пустой набор данных открывается. При вызове метода First. При вызове метода Prior, если курсор при этом на первой записи набора данных. При вызове метода SetRange в пустом наборе данных или диапазоне. Bookmark - Свойство позволяет установить закладку на текущей записи набора данных. Количество закладок может быть неограниченно, работа с закладками рассматривалась на курсе "Введение в программирование на Delphi ". Свойство имеет тип TBookmarkStr. CanModify - Свойство имеет логический тип, и показывает, можно ли редактировать полученный набор данных, или он доступен только для чтения. При открытии набора данных автоматически запрашивается доступ для редактирования. В таком доступе может быть отказано по разным причинам, например, таблица открыта другим пользователем в эксклюзивном режиме. В этом случае CanModify получает значение False, и мы можем только просматривать данные, но не вносить в них изменения. DatabaseName - Свойство строкового типа, содержит адрес базы данных или ее псевдоним. Однако это справедливо к наборам данных BDE. В случае использования механизма ADO, это свойство недоступно - вместо него для подключения к базе данных следует использовать свойство Connection или ConnectionString. DataSource - Свойство используется в наборах данных для указания детального набора данных в отношениях один-ко-многим. DefaultFields - Свойство логического типа, содержит True, если программист не создал ни одного поля в редакторе полей набора данных. В этом случае все поля определяются автоматически, в соответствии с данной таблицей. Eof - Свойство, противоположное свойству Bof. Имеет логический тип, и имеет значение True в случаях, когда: Открыт пустой набор данных. Вызван метод Last. Вызван метод Next, если указатель при этом находится на последней записи таблицы. При вызове метода SetRange в пустом наборе данных или диапазоне. FieldCount - Свойство целого типа, содержит количество полей в наборе данных. Fields - Свойство позволяет получить значение нужного поля по его индексу. Поля при этом индексируются с нуля. Например, получить значение седьмого по счету поля набора данных можно так: Edit1.Text := CustTable.Fields[6].Value; FieldValues - Свойство позволяет получить значение нужного поля по его имени. Это свойство используется по умолчанию, поэтому его можно не указывать. Примеры: Edit1.Text := CustTable. FieldValues ['Order']; Edit1.Text := CustTable['Order']; Filter - Свойство строкового типа. Содержит строку, которая определяет правила фильтрации набора данных. Filtered - Свойство логического типа. Если в свойстве Filter имеется строка, определяющая порядок фильтрации, то присвоение значения True свойству Filtered приводит к фильтрации набора данных. Присвоение этому свойству False отменяет фильтрацию. FilterOptions - Свойство имеет тип TFilterOptions и применяется для строковых или символьных полей. Свойству можно присвоить значение foCaseInsensitive или foNoPartialCompare. В первом случае фильтрация будет учитывать регистр букв, во втором учитывается лишь точное совпадение образцу. Modified - Очень важное свойство логического типа. Содержит True, если набор данных был изменен, и False в противном случае. Часто применяется для проверок: если набор данных изменен, то вызвать метод Post, чтобы сохранить изменения. RecNo и RecordCount - Свойства целого типа. Первое содержит номер текущей записи в наборе данных, второе - общее количество записей. State - Очень важное свойство, определяющее состояние набора данных. Может иметь следующие значения: dsInactivate - набор данных закрыт. dsBrowse - режим просмотра. dsEdit - режим редактирования. dsInsert - режим вставки. dsSetKey - поиск записи. dsCalcFields - состояние установки вычисляемых полей. dsFilter - режим фильтрации записей. dsNewValue - режим обновления свойства TField.NewValue. dsOldValue - режим обновления свойства TField.OldValue. dsCurValue - режим обновления свойства TField.CurValue. dsBlockRead - состояние чтения блока записей. dsInternalCalc - обновление полей, у которых свойство FieldKind соответствует значению fkInternalCalc. Методы Append - Метод добавляет новую запись в конец набора данных. При этом набор данных автоматически переходит в режим редактирования. AppendRecord (const Values: array of const) - Метод добавляет новую запись в конец набора данных, и заполняет поля этой записи значениями из массива, переданного в метод как параметр. Cancel - Отменяет все изменения набора данных, если они еще не сохранены методом Post или переходом на другую запись. ClearFields - Метод очищает все поля текущей записи. Close - Закрывает набор данных. Метод является альтернативой присваивания False свойству Active набора данных. Delete - Метод удаляет текущую запись. Следует заметить, что во многих форматах данных удаляемая запись лишь помечается, как удаленная, и скрывается от пользователя. Физически же такая запись из файла не удаляется. В этом случае обычно время от времени приходится "паковать" таблицы, избавляясь от таких записей. Edit - Метод переводит набор данных в состояние редактирования. Если этого не сделать, изменение записи будет невозможным. FieldByName - Еще один способ получить значение поля или изменить его, указывая имя поля. При этом можно использовать явное преобразование данных в нужный тип, например, AsInteger, AsString и т.п. Пример: Table1. FieldByName ('QUANTITY').AsInteger := StrToInt(Edit1.Text); FindFirst , FindLast , FindNext и FindPrior - Методы пытаются установить курсор соответственно, на первую, на последнюю, на следующую и на предыдущую запись. В случае успеха методы возвращают True. Переход к другой записи приводит к автоматическому сохранению изменений, если изменения были. First , Last , Next и Prior - просто устанавливают указатель соответственно на первую, последнюю, следующую и предыдущую запись. Переход к другой записи приводит к автоматическому сохранению изменений, если изменения были. FreeBookmark - Метод освобождает память, связанную с закладкой Bookmark. Обычно вместо вызова этого метода достаточно присвоить закладке пустую строку (см. лекцию 30 курса "Введение в программирование на Delphi"). GotoBookmark - Метод обеспечивает переход на закладку Bookmark, переданную в качестве параметра. Insert - Метод вставляет новую запись в указанную в параметре позицию набора данных. При этом набор данных автоматически переходит в режим редактирования. InsertRecord (const Values: array of const) - Метод вставляет новую запись в набор данных, и заполняет поля этой записи значениями из массива, переданного в метод как параметр. Пример: Customer. InsertRecord ([CustNoEdit.Text, CoNameEdit.Text, AddrEdit.Text, Null, Null, Null, Null, Null, Null, DiscountEdit.Text]); Обратите внимание, что в некоторые поля были вставлены значения Null, то есть, ничего. То же самое происходит, когда пользователь при редактировании записи вносит значения не во все поля. IsEmpty - Метод возвращает True, если в наборе данных нет записей. Применяется для проверки - не пуста ли таблица? Locate - Метод ищет запись в наборе данных (см. предыдущую лекцию). Lookup - Метод ищет запись в наборе данных (см. предыдущую лекцию). В отличие от Locate не переводит указатель на найденную запись, а лишь возвращает значения ее полей. Open - Метод открывает набор данных. То же самое происходит, если свойству Active набора данных присвоить значение True. Post - Метод сохраняет сделанные изменения в наборе данных. Refresh - Метод заново перечитывает таблицу и обновляет набор данных. Имеет смысл использовать в приложениях, где несколько пользователей работают с одной базой данных. События After … - События, возникающие после вызова соответствующего метода: AfterCancel - Событие возникает после отмены изменений в текущей записи. AfterClose - Событие возникает после закрытия набора данных. AfterDelete - Событие возникает после удаления текущей записи. AfterEdit - Событие возникает после перехода набора данных в режим редактирования. AfterInsert - Событие возникает после вставки новой записи. AfterOpen - Событие возникает после открытия набора данных. AfterPost - Событие возникает после вызова метода Post. AfterScroll - Событие возникает после перехода на другую запись. Before … - События, возникающие перед вызовом соответствующего метода: BeforeCancel - Событие возникает перед отменой изменений в текущей записи. BeforeClose - Событие возникает перед закрытием набора данных. BeforeDelete - Событие возникает перед удалением текущей записи. BeforeEdit - Событие возникает перед переходом набора данных в режим редактирования. BeforeInsert - Событие возникает перед вставкой новой записи. BeforeOpen - Событие возникает перед открытием набора данных. BeforePost - Событие возникает перед вызовом метода Post. BeforeScroll - Событие возникает перед переходом на другую запись. OnCalcFields - Событие возникает при необходимости переопределения вычисляемых полей. Такое событие возникает всякий раз, когда программа должна сформировать значения для вычисляемых полей. Событие возникает также при открытии набора данных, и при любом его изменении. Если алгоритм вычислений достаточно сложен, база данных большая, а пользователь интенсивно с ней работает, событие OnCalcFields может значительно замедлить работу с базой данных. В этом случае следует отключать это событие. Для этого достаточно присвоить значение False свойству AutoCalcFields текущего набора данных. OnFilterRecord - Событие возникает при включении фильтрации записей. OnNewRecord - Событие возникает при вызове методов Append или Insert. Блокировка таблиц в архитектуре файл-сервер При работе в архитектуре файл-сервер с единой сетевой базой данных работают несколько клиентских приложений. При этом нередко возникает ситуация, когда один пользователь вносит изменения в базу данных. В этот момент, во избежание потери или порчи данных, следует запретить внесение изменений другими пользователями. Такая блокировка достигается методом LockTable (). При этом значение свойства LockType этого набора данных определяет вид запрета. Значение ltReadLock запрещает чтение, а ltWriteLock - запись в набор данных. Можно запретить и чтение, и запись, но для этого следует вызвать метод LockTable () дважды. Блокировка таблиц методом LockTable () справедлива для таблиц Paradox или dBase, если вы используете механизм BDE. Когда изменения внесены, и необходимость блокировки пропадает, можно снять блокировку методом UnlockTable (), указав в параметре тип снимаемого запрета ( ltWriteLock, ltReadLock ). Свойство LockType набора данных ADO имеет тип TADOLockType: type TADOLockType = (ltUnspecified, ltOptimistic, ltBatchOptimistic); ltReadOnly, ltPessimistic, Это свойство позволяет определить тип блокировки при открытии набора данных. Как видно из описания типа, свойство может иметь следующие значения: ltUnspecified - тип блокировки не определен. ltReadOnly - блокировка записи, читать данные можно. ltPessimistic - пессимистическая блокировка. Свойство указывает, что если вы редактируете запись, то другие пользователи не смогут редактировать ее, пока вы не сохраните изменения. ltOptimistic - оптимистическая блокировка. Блокировка подразумевает, что возникновение конфликта маловероятно. В связи с этим любой пользователь в любое время может редактировать любую запись. Проверка на наличие конфликтов производится только в момент сохранения изменений. ltBatchOptimistic - свойство устанавливает блокировку на пакет записей, а не на отдельную запись. При этом все обновления, сделанные пользователем, не записываются сразу, а накапливаются в оперативной памяти. Позже они сохраняются одним пакетом. Такой подход увеличивает производительность приложения, но также увеличивается риск возникновения конфликтов. Курсоры в наборах данных ADO Наборы данных ADO имеют два специфичных свойства, неразрывно связанные друг с другом: CursorLocation и CursorType. Курсоры оказывают большое влияние на то, каким образом извлекаются данные из таблиц, каким образом вы можете перемещаться по ним и т.д. Фактически, курсор - это механизм перемещения по записям набора данных. От того, какой курсор используется в многопользовательской среде, зависит способ перемещения по записям: только вперед или в обе стороны. Будете ли вы видеть изменения, сделанные другими пользователями, также зависит от типа применяемого курсора. CursorLocation (положение курсора) Это свойство определяет, каким образом извлекаются и модифицируются данные. Значений только два: clUseClient - курсор на стороне клиента. clUseServer - курсор на стороне сервера. Клиентский курсор обслуживается механизмом ADO Cursor Engine. В момент открытия набора данных все данные перекачиваются с сервера на клиентский компьютер. После этого данные хранятся в оперативной памяти. Перемещения по данным и их модификация происходит значительно быстрее, кроме того, клиентский курсор обладает более широкими возможностями. Серверный курсор обслуживается операционной системой. Благодаря тому, что курсор находится на стороне сервера, приложению нет смысла перекачивать все данные разом, это повышает скорость работы с БД. Серверные курсоры больше подходят для обслуживания больших наборов данных. Следует заметить, что если вы работаете с локальной базой данных (например, Access ), то серверный курсор будет обслуживаться программой, обслуживающей эту базу данных. CursorType (тип курсора) Имеется пять типов курсора: Unspecified - не указанный. В Delphi такой тип не используется, он присутствует только потому, что имеется в ADO. Forward-only (только вперед). Этот тип курсоров обеспечивает самую высокую производительность, однако он позволяет перемещаться по записям только в одном направлении - от начала к концу, что делает его малопригодным для создания пользовательского интерфейса. Однако он хорошо подходит для программных операций, таких как перебор записей, формирование отчета и т.п. Static (статический) - пользователь имеет возможность перемещаться в обоих направлениях, однако изменения записей, выполненные другими пользователями, не видны таким курсором. Keyset (набор ключей). При открытии набора данных с сервера читается полный список всех ключей. Этот набор ключей хранится на стороне клиента. Если приложение нуждается в данных, провайдер OLE DB читает строки таблицы. Однако после открытия набора данных в этот набор нельзя добавлять новые ключи, или удалять имеющиеся. То есть, если другой пользователь добавил новую запись, текущий клиент ее не увидит. Однако он увидит изменения существующих записей, сделанные другими пользователями. Dynamic (динамический). Самый мощный тип курсора, но при этом и самый ресурсоемкий. Он позволяет видеть все изменения, все добавления или удаления, сделанные другими пользователями, но при этом больше других замедляет работу с БД. Поля (TField) Каждая база данных состоит из таблиц, каждая таблица - из записей. Каждая запись в свою очередь представляет собой набор полей. Поле набора данных - это экземпляр достаточно мощного класса TField, о котором мы и поговорим на этой лекции. Изучение свойств полей будем проводить на примере приложения из прошлой лекции. Подстановочные (Lookup) поля Подстановочное поле Lookup изначально в набор данных не входит, его нужно создавать самостоятельно. Такое поле отличается от обычного тем, что показывает данные из другого набора данных. Для использования такого поля два набора данных обязательно должны иметь релятивную связь. На прошлой лекции мы применяли компонент DBLookupComboBox, который является аналогом подстановочного поля, но который нельзя показать в сетке DBGrid. При создании подстановочного поля также необходимо указать набор данных, откуда поле будет просматривать значения, ключевые поля для релятивной связи и поле со значениями, которые нужно подставлять. Открываем проект из "Таблицы Paradox в ADO" , открываем окно модуля данных. Дважды щелкаем по компоненту FoodT, чтобы открыть редактор полей. В этом редакторе у нас уже присутствуют пять полей, имеющихся в таблице, добавим шестое, подстановочное. Для этого щелкните правой кнопкой по редактору полей и выберите команду New Field (Новое поле): Рис. 13. Создание подстановочного (Lookup) поля В разделе Field type (Тип поля) вы можете выбрать один из трех вариантов. Нас сейчас интересует тип Lookup. Заполните необходимые поля значениями, как на рисунке 13 и нажмите кнопку "ОК". Новое подстановочное поле будет добавлено в набор данных. В списке полей его можно переместить мышью на другое место, установите его сразу под FName. Перейдите на окно главной формы и убедитесь, что новое поле появилось. Однако оно пока еще не содержит данных - данные будут доступны только во время прогона программы. Сохраните проект, скомпилируйте и посмотрите, как работает программа. Как мы видим, теперь на главной форме два поля, которые ни к чему показывать пользователю - FKey с номерами записей, и FType - с номерами типов блюд, которые нам уже не нужны, поскольку мы показываем сами типы. Уберем их, точнее, сделаем невидимыми. Снова откройте редактор полей набора данных FoodT. Установите свойство Visible этих полей в False. Вычисляемые (Calculated) поля Как и подстановочное, вычисляемое поле изначально не входит в набор данных, а добавляется в процессе проектирования приложения. Вычисляемые поля предназначены для показа данных, которые автоматически вычисляются в процессе работы программы, используя одно или несколько полей набора данных. К примеру, в таблице имеется поле стоимости товара и количество, которое купил какой-то клиент. Вычисляемое поле, перемножив значения этих полей, может показать общую стоимость товара. В нашем примере мы создадим вычисляемое поле для показа стоимости блюда в долларах США. Для этого в модуле данных создадим глобальную переменную dollar: var fDM: TfDM; dollar: Currency = 30.36; Вы можете указать текущий курс доллара к рублю, он так быстро меняется, что едва ли будет таким, как в моем примере. Итак, дважды щелкаем по набору данных FoodT, чтобы открыть редактор полей. Щелкаем правой кнопкой по этому редактору и выбираем команду " New field ". В поле " Name " впишите название нового поля FDCena. В поле " Component " автоматически отобразится имя нового объекта-поля " FoodTFDCena ", по которому в дальнейшем мы сможем к нему обращаться. Это имя составное - имя набора данных плюс имя нового поля, без всяких пробелов и разделителей. В поле " Type " выберите тип Float, так как у нас могут быть копейки, вернее, центы. Затем убедитесь, что переключатель установлен на " Calculated " и нажмите "ОК". В редакторе полей появилось новое поле. Чтобы мы не получили сумму с кучей цифр после запятой, выделите в редакторе полей поле FDCena, и в его свойстве DisplayFormat укажите маску "#.## $US" (разумеется, без кавычек). К слову сказать, при создании вычисляемого поля мы могли бы выбрать тип Currency (денежный), но тогда к цифре добавлялось бы "р.", если ваша Windows имеет российские настройки. Вещественные поля набора данных наряду с полями целого типа имеют четыре свойства, которые могут вам пригодиться: DisplayFormat - Определяет формат отображения числа. DisplayEdit - Определяет формат числа при редактировании. MaxValue - Определяет максимально возможное для поля число. MinValue - Определяет минимально возможное число. Свойства MaxValue и MinValue по умолчанию имеют значение 0, что указывает на отсутствие ограничений. Однако, это еще полдела. Поле мы сделали, осталось сделать вычисления. Код необходимых вычислений прописывается в свойстве OnCalcFields набора данных. Закройте редактор полей и выделите НД FoodT. Сгенерируйте для него обработчик события OnCalcFields и в этом обработчике пропишите следующую строчку: FoodTFDCena.Value := FoodTFCena.Value / dollar; Как видно из примера, мы используем значения одного или нескольких полей текущего набора данных, производим над ними какие то вычисления, и результат этих вычислений присваиваем вычисляемому полю. Сохраните проект, скомпилируйте и посмотрите, как работает программа. Если вы все сделали правильно, у вас получится подобная картина: Рис. 14. Подстановочное и вычисляемое поле в программе Поле данных (Data) Если вы помните, при создании нового поля мы имеем три переключателя Field type. Переключатель " Data " предназначен для создания поля данных - пустого поля, которое программист использует по своему усмотрению. Наполнение этого столбца можно прописать в обработчике события OnGetText полученного объекта-поля. На практике такие поля используют редко, чаще они применяются для программного создания таблиц, о чем мы поговорим на одной из следующих лекций. Свойство DisplayValues Свойство DisplayValues объекта-поля предназначено для отображения данных логического поля в нужном формате. Для примера изменим отображение данных поля FVeget. Откройте редактор полей компонента FoodT, выделите поле FVeget. В его свойстве DisplayValues укажите значение "Да;Нет". У логического поля вместо True и False здесь можно указать свою пару значений. Значение до точки с запятой считается истинным, значение после - ложным. Примеры: Да;Нет Муж;Жен Yes;No Y;N Д;Н и т.п. Указанные в свойстве значения пары "Истина;Ложь" будут отображаться в компонентах вывода данных, таких как DBGrid, DBEdit и т.п. Кроме того, эти же значения будут отображены, если вы будете получать значения этого поля с использованием свойства AsString, чтобы преобразовать значение в строковый тип. Для облегчения пользователю ввода данных немного изменим проект. Откройте окно fEditor и удалите DBEdit, предназначенный для ввода логического значения в поле FVeget. Вместо него установите компонент DBComboBox. Дважды щелкните по свойству Items этого компонента и в открывшемся редакторе значений впишите две строки: Да Нет В свойстве DataSource компонента выберите таблицу FoodT, а в свойстве DataField - поле FVeget. Сохраните проект, скомпилируйте и посмотрите, как он работает. Теперь пользователю не нужно вписывать значение - он может выбрать его из списка. Для полей других типов свойство DisplayValues недоступно. Вместо него предлагается использовать свойство DispalyFormat, которое доступно только для числовых полей и полей типа TDataTime. При этом формат задается так же, как в функциях формата, например, FormatFloat() и FormatDateTime(), применение которых подробно рассматривалось на курсе "Введение в программирование на Delphi ". Например, для поля типа Дата формат: dddd dd mmm yyyy выведет дату в формате "Понедельник 04 Янв 2010" Другие наиболее важные свойства класса TField Aligment - Определяет выравнивание выводимого значения. Может иметь следующие значения: taLeftJustufy - выравнивание по левому краю taRightJustify - выравнивание по правому краю taCenter - выравнивание по центру. AsXXXX - Группа свойств этого типа преобразует значение поля к нужному типу. Вместо XXXX могут быть использованы: BCD - двоично-десятичный тип. Boolean - логический тип. Currency - денежный тип. DataTime - тип дата-время. Float - вещественный тип. Integer - целый тип. String - строка. Variant - variant. Пример: DBText1.Field.AsString := 'Santa Cruz Wharf'; Calculated - Содержит True, если значение поля вычисляется в обработчике OnCalcFields набора данных, и False в противном случае. CanModify - Содержит True, если значение поля можно изменить, и False в противном случае. Currency - Свойство доступно у вещественных полей. Если свойству при проектировании приложения присвоить True, то значения будут выходить в денежном формате. DataSize - Содержит размер данных. DataType - Содержит тип данных, определяемый перечислением TFieldType, например, ftString - строка, ftBoolean - логический тип, ftFloat - вещественный тип, и так далее. Класс TFieldType содержит достаточно большой список типов полей, более подробные данные вы можете посмотреть в справочной системе Delphi. DisplayLabel - Позволяет ввести строку - заголовок отображаемого столбца. Если заголовок не задан, по умолчанию будет использоваться имя поля. EditMask - Позволяет указать строку - маску для ввода данных. FieldName - Имя поля. Lookup - Содержит True, если поле подстановочное. Origin - Содержит имя поля в физической таблице. ReadOnly - Если содержит True, значение поля нельзя менять. Required - Если содержит True, значение поля не может быть пустым. Size - Если поле имеет запись переменной длины, свойство указывает текущий размер данных. Value - Содержит значение поля. Visible - Если содержит True (по умолчанию), поле отображается в таких компонентах, как DBGrid. Наиболее важные методы класса TField AssignValue() - Преобразует вариантное значение поля Value с помощью метода AsXXXX и помещает результат в переменную Value, переданную в метод как параметр. Create() - Создает поле-объект и инициализирует его. Destroy() - Уничтожает поле-объект. Наиболее важные события класса TField OnChange - возникает после изменения данных поля и их успешной записи. OnGetText - в обработчике этого события можно подготовить текст для свойств DisplayText и Text. OnSetText - возникает при записи данных из параметра Text в свойство Text. OnValidate - возникает после изменения значения но до записи в буфер. Этот обработчик удобно использовать для проверки на правильность введенных данных. Обращение к значению поля К значению поля можно обратиться через свойства Value или AsXXXX, например: Edit1.Text := FoodTFName.Value; Edit1.Text := FoodTFName.AsString; Применение свойства AsXXXX приводит к преобразованию значения в нужный тип. Разумеется, типы должны быть совместимыми. Например, целое число можно преобразовать в вещественное, но не наоборот. Если вы не вызывали редактор полей и не создавали для набора данных ни одного объекта-поля, то значение поля можно получить через свойство FieldByName, например: FoodT.FieldByName('FName').AsString := Edit1.Text; Кроме того, доступ к значению поля можно получить через свойства набора данных Fields или FieldValues: FoodT.Fields[1].AsString := Edit1.Text; FoodT.FieldValues['FName'] := Edit1.Text; Как уже упоминалось, свойство FieldValues в наборах данных применяется по умолчанию, так что последний пример можно записать и так: FoodT['FName'] := Edit1.Text; Если для доступа к полю вы используете свойство Fields, имейте в виду, что индексация полей начинается с 0, то есть индекс 1 соответствует второму полю набора данных. Свойство FieldValues обладает еще одной особенностью: оно имеет вариантный тип и позволяет использование списка полей, таким образом, единственным оператором можно записать сразу несколько полей: var v : Variant; begin //создаем вариантный массив: v := VarArrayCreate([0, 2], varVariant); //читаем значения полей: v := FoodT['FName;FType;FCena']; Edit1.Text := v[0]; Edit2.Text := v[1]; Edit3.Text := v[2]; Запросы Запросы (TQuery, TADOQuery) Запросы (TQuery, TADOQuery) - это такие же наборы данных, как и таблицы ( TTable, TADOTable ). Запросы, как и таблицы, происходят от общего предка - TDBDataSet, в связи с этим они имеют схожие свойства, методы и события. Но имеются и существенные различия. Прежде всего, если табличный набор данных TTable ( TADOTable ) получает точную копию данных из таблицы базы данных, то запрос TQuery ( TADOQuery ) получает этот набор, основываясь на запросе, сделанном на специальном языке SQL (Structured Query Language - Язык Структурированных Запросов). С помощью этого языка программист создает запрос, который передается параметру TQuery.SQL ( TADOQuery .SQL ). При открытии набора данных этот запрос обрабатывается используемым механизмом BDE, ADO или др. и в набор данных передаются запрошенные данные. Заметили разницу? Не копия таблицы, а именно запрошенные данные, причем в указанном порядке! Используя запросы, в одном наборе данных можно получить взаимосвязанные данные из разных физических таблиц. Отпадает надобность в подстановочных полях. Имеется два варианта работы с SQL -запросами. В первом случае, SQL -запрос запрашивает нужные данные из таблицы (таблиц) базы данных. При этом формируется временная таблица, созданная в каталоге запуска программы, и компонент-запрос становится ее владельцем. Работа с такими данными очень быстрая, но пользователь при этом не может изменять данные, он лишь просматривает их. Такой подход идеален для составления отчетности. Если же пользователю требуется вносить изменения в таблицу (таблицы), то с помощью специальных операторов SQL ( INSERT, UPDATE, DELETE ) формируется запрос, уведомляющий механизм доступа к данным изменить данные БД. В этом случае никаких временных таблиц не создается. Запрос передается механизму доступа, обрабатывается им, выполняются изменения, и механизм доступа уведомляет программу о благополучном (или нет) изменении данных. Сравним табличные наборы данных с запросами. При работе с локальными или файл-серверными БД, табличные наборы данных имеют преимущество в скорости доступа к данным, поскольку запросы создают и используют для этого временные таблицы, а табличные НД напрямую обращаются к физическим таблицам БД. Однако при этом, запросы позволяют формировать гибкие наборы данных, которые невозможно было бы получить с помощью табличных НД. При работе в архитектуре клиент-сервер, всякое преимущество табличных наборов данных пропадает. Ведь они должны получить точную копию запрошенной таблицы, в которой могут быть десятки и сотни тысяч записей. Если учесть, что все эти данные передаются по сети, и передаются не одному, а множеству клиентов, то мы получим очень медленную систему, постоянно перегружающую сеть. Компонент TADOQuery Для демонстрации работы компонента TADOQuery создадим совсем маленькое приложение - простейший SQL -монитор (у Delphi имеется встроенный SQL -монитор, но ведь всегда приятно сделать что-то своими руками!). Итак, создайте папку для нового приложения. В эту папку скопируйте базу данных ok.mdb, с которой мы работали в четвертой лекции. Если вы еще помните, там у нас имеется четыре таблицы, предназначенные для программы отдела кадров. Создайте новый проект в Delphi, форму переименуйте в fMain, сохраните ее модуль под именем Main, а проект в целом как SQLMon. В свойстве Caption формы пропишите "Простой SQL монитор". Далее на форму установите панель. В свойстве Align панели выберите alTop, чтобы панель заняла весь верх, а ее высоту растяните примерно на полформы. Очистите свойство Caption. На эту панель установите компонент Memo, именно в нем мы будем писать наши SQL -запросы. Дважды щелкните по свойству Lines этого компонента, чтобы вызвать редактор текста, и очистите весь текст. Также не помешает дважды щелкнуть по свойству Font и изменить размер шрифта на 12 для лучшего восприятия текста. В свойстве Align выберите alLeft, чтобы компонент Memo занял всю левую часть панели. В правой части панели установите две простые кнопки и компонент TDBNavigator с вкладки Data Controls панели инструментов. Для улучшения внешнего вида интерфейса ширину кнопок сделайте такой же, как у навигатора базы данных. В свойстве Caption первой кнопки напишите "Выполнить SQL -запрос", на второй кнопке напишите "Очистить компонент Memo ". Собственно, мы могли бы очищать Memo сразу при выполнении SQL -запроса, и обойтись без второй кнопки. Но многие запросы похожи, и проще изменить часть текста запроса, чем писать весь запрос заново. На нижнюю, свободную половину формы установите компонент TDBGrid с вкладки Data Controls для отображения данных. В свойстве Align сетки выберите alClient, чтобы сетка заняла все оставшееся место. У вас должна получиться такая картина: Рис. 15. Внешний вид приложения Еще нам потребуются три компонента: TADOConnection и TADOQuery с вкладки ADO для получения набора данных, и TDataSource с вкладки Data Access для связи сетки DBGrid и навигатора DBNavigator с этим набором данных. Дважды щелкните по ADOConnection1, чтобы вызвать редактор подключений. Нажмите кнопку " Build ", выберите поставщика Microsoft Jet 4.0 OLE DB Provider, и нажмите "Далее". В поле "Выберите или введите имя базы данных" укажите нашу БД ok.mdb и нажмите "ОК". И еще раз "ОК", чтобы закрыть окно редактора подключений. Сразу же свойство LoginPrompt переводим в False, чтобы при каждом запуске программы у нас не запрашивался логин и пароль, а Connected в True. Подключение к базе данных произошло. В свойстве Connection компонента TADOQuery выберем ADOConnection1, а в свойстве DataSet компонента DataSource1 выберем наш НД ADOQuery1. Теперь набор данных ADOQuery1 соединен с базой данных, а DataSource1 - с этим набором данных. В свойстве DataSource компонентов DBGrid1 и DBNavigator1 выберем DataSource1, чтобы они могли взаимодействовать с набором данных. Нам осталось лишь запрограммировать обработчик события onClick для обеих кнопок. Щелкните дважды по кнопке "Выполнить SQL -запрос", чтобы сгенерировать это событие, и пропишите в нем такой код: //проверим - есть ли текст в Memo. Если нет, выходим: if Memo1.Text = '' then begin ShowMessage('Вначале введите запрос!'); Memo1.SetFocus; Exit; end; //текст есть. Очистим предыдущий запрос в наборе данных: ADOQuery1.SQL.Clear; //добавим новый запрос из Memo: ADOQuery1.SQL.Add(Memo1.Text); //открываем набор данных, т.е. выполняем запрос: ADOQuery1.Open; Комментарии здесь достаточно подробны, чтобы разобраться в происходящем. Заметим только, что набор данных ADOQuery1 обычно закрыт. После того, как мы изменяем его свойство SQL, прописывая туда новый SQL -запрос, этот набор данных открывается. В результате в БД передается SQL -запрос, получаются запрашиваемые данные, которые формируют набор данных ADOQuery1. Когда этот компонент активен, данные доступны. Можно также заполнить свойство SQL, дважды щелкнув по нему и открыв редактор запросов, и сделать активным во время проектирования программы. Тогда данные становятся доступны сразу. Такой подход удобен, когда программист не собирается в дальнейшем менять SQL -запрос этого набора данных. Однако чаще бывает наоборот - в зависимости от ситуации, используется то один, то другой запрос в одном и том же наборе данных. Так мы поступаем и в нашем примере - передача SQL -запроса и открытие набора данных мы будем делать программно. Еще мы можем заметить, что свойство SQL набора данных TADOQuery имеет тип TStrings, так же, как свойство Lines компонента Memo или свойство Items компонента ListBox. То есть, в свойстве SQL мы можем использовать все преимущества, которые нам дает тип TStrings, например, загрузка SQL -запроса из внешнего файла: ADOQuery1.SQL.LoadFromFile('c:\myfile.sql'); Подобный прием нередко используется программистами, когда нужно сделать программу более гибкой. Формируя файл с SQL -запросами можно получать различные наборы данных, в зависимости от обстоятельств. Но в нашей программе мы будем получать SQL -запрос из поля Memo. Поскольку тип TStrings используется и в Memo, и в ADOQuery, то следующие строки кода аналогичны, они одинаково сформируют SQL запрос на основе текста в поле Memo: ADOQuery1.SQL.Add(Memo1.Text); ADOQuery1.SQL := Memo1.Lines; Сгенерируйте событие нажатия на вторую кнопку, здесь мы должны просто очистить поле текста Memo1, и код совсем прост: Memo1.Clear; Вот и вся программа! Сохраните ее, скомпилируйте и запустите программу на выполнение. В поле Memo впишите следующие строки: SELECT * FROM LichData; После этого нажмите кнопку "Выполнить SQL -запрос". В сетке DBGrid отобразятся данные, которые представляют собой точную копию таблицы LichData из базы данных ok.mdb. Строки в примере написаны по правилам и рекомендациям языка SQL, то есть, операторы пишутся заглавными буквами, каждый оператор на отдельной строке, а в конце ставится точка с запятой. Однако рекомендации можно нарушать, а правила в Delphi более мягкие. Так, мы можем написать весь текст маленькими буквами, в одну строку, не ставить точку с запятой и не обращать внимания на регистр букв: select * from lichdata Запрос все равно будет выполнен. Однако лучше придерживаться рекомендаций и традиционного синтаксиса SQL, ведь этот язык имеет стандарты, и вы можете применять его не только при работе с Delphi. В других языках программирования или в клиентсерверных СУБД правила могут несколько отличаться, но в любом случае запрос, написанный в стандартном стиле, будет выполнен. Поэтому лучше сразу приучать себя к стандартному синтаксису. На данном курсе мы будем придерживаться рекомендаций SQL. Что же написано у нас в этом запросе? Оператор SELECT означает "выделить", звездочка означает "все поля", оператор FROM означает "из…". Таким образом, запрос означает: ВЫДЕЛИТЬ все поля ИЗ таблицы LichData Но такой запрос ничем не отличается от применения табличных компонентов, а ведь мы можем создавать и гораздо более сложные запросы! Предположим, нам нужно получить фамилию, имя и отчество сотрудника, а также город его проживания. Основные данные находятся в таблице LichData, а вот город находится в таблице Adres, связанной с таблицей LichData релятивной связью один-к-одному по полю "Ключ" таблицы LichData, и по полю "Сотрудник" таблицы Adres. В этом случае запрос будет выглядеть так: SELECT Фамилия, Имя, Отчество, Город FROM LichData, Adres WHERE Ключ = Сотрудник; Как видите, в операторе SELECT поля перечисляются через запятую. Также через запятую перечисляются используемые таблицы в операторе FROM. А вот оператор WHERE указывает, что нужны только те записи, в которых значения поля "Ключ" и "Сотрудник" равны. Если бы мы не использовали оператор WHERE, то получили бы кучу недостоверных записей, где к каждой записи одной таблицы добавлялись бы все записи другой. Оператор WHERE позволил нам получить связные данные, в которых к одной записи первой таблицы добавляется соответствующая запись из другой таблицы. С этими и другими операторами мы подробней познакомимся на следующей лекции. Теперь предположим, что в одном наборе данных нам нужно получить записи из двух таблиц, связанных релятивной связью один-ко-многим. Так, у одного сотрудника может быть несколько телефонов. В этом случае придется смириться, что некоторые данные будут продублированы. Например, запрос: SELECT Фамилия, Имя, Телефон FROM LichData, Telephones WHERE Ключ = Сотрудник; выдаст нам набор данных, в котором фамилия и имя сотрудника будут дублироваться для каждого номера его телефона. Компонент-запрос может формировать набор данных двух типов: изменяемый, в котором пользователь может менять (редактировать, удалять или добавлять) записи, и не изменяемый, предназначенный только для просмотра данных или для составления отчетности. Возможность получения "живого" набора данных зависит от разных факторов - от применяемого оператора, от механизма доступа к данным, от используемой клиентсерверной СУБД. В данном примере мы используем оператор SELECT, работаем с локальной БД посредством механизма ADO. Если вы воспользуетесь навигатором, то убедитесь, что записи можно добавлять и удалять, а в сетке DBGrid их можно редактировать. Однако при редактировании данных, полученных более чем из одной таблицы, могут возникнуть трудности. Зато набор данных из одной таблицы можно спокойно изменять. Подробней с "живыми" и неизменяемыми наборами данных мы познакомимся позднее. Компонент TQuery/ TADOQuery может выполнять запросы двумя разными способами. Вначале в свойство SQL компонента помещается необходимый запрос. Это можно сделать программно, как в нашем SQL -мониторе, так и на этапе проектирования приложения. Дальнейшие действия зависят от того, какой запрос нам нужно выполнить. Если это запрос на получение набора данных, то есть, оператор SELECT, то достаточно просто открыть TQuery/ TADOQuery методом Open, или присвоив True свойству Active. Если же запрос должен модифицировать данные, то есть, используются такие операторы, как INSERT, UPDATE, DELETE, то тогда запрос выполняется методом ExecSQL. С работой компонента-запроса TQuery ( TADOQuery ) мы поработали на практике. Как и табличные компоненты, компонент-запрос произошел от родительского класса TDBDataSet. Унаследовав его свойства, методы и события, он имеет и собственные, отличительные черты. Так, например, запрос может быть изменяемым (живым), при котором пользователь может модифицировать записи набора данных, и не изменяемым, при котором данные доступны только для просмотра и составления отчетности. Наиболее важные свойства, методы и события, отличные от TDBDataSet, рассматриваются ниже. Свойства компонента-запроса Constrained - Свойство логического типа. Если свойство имеет значение True, то в изменяемом наборе данных на модифицируемые записи накладываются ограничения блока WHERE оператора SELECT (с операторами SQL -запросов вплотную познакомимся на следующей лекции). DataSource - Указывает тот компонент TDataSource, который используется для формирования параметрического запроса. Local - Свойство логического типа. Если свойство имеет значение True, это означает, что компонент-запрос работает с локальной или файл-серверной базой данных. ParamCheck - Логическое свойство. При значении True список параметров автоматически обновляется при каждом программном изменении SQL -запроса. Params - Свойство имеет тип TParams и содержит массив объектов-параметров этого типа. На этом типе данных следует остановиться подробнее: Таблица 2. Свойства и методы типа TParams Свойство Описание Items Содержит массив параметров типа TParams и является свойством "по умолчанию". Индексация массива начинается с 0. ParamValues() Открывает доступ к значению параметра по его имени, указанному в скобках. Count Количество параметров в массиве. Метод Описание AddParam() Добавляет параметр в массив параметров. CreateParam() Создает параметр и добавляет его к массиву. FindParam() Ищет параметр по его имени, указанному в скобках. RemoveParam() Удаляет параметр из массива. Prepared - Свойство логического типа. Содержит значение True, если SQL -запрос был подготовлен методом Prepare. RequestLive - Логическое свойство. Если компонент-запрос содержит изменяемый (живой) набор данных, то RequestLive содержит True. RowsAffected - Свойство содержит количество записей, которые были удалены или отредактированы в наборе данных в результате выполнения SQL -запроса. SQL - Свойство типа TStrings, то есть, набор строк. Содержит SQL -запрос, который выполняется, как только компонент-запрос становится активным (открывается). При изменении этого свойства, компонент-запрос автоматически закрывается, так что программисту требуется перевести свойство Active набора данных в True (или вызвать метод Open ), чтобы запрос выполнился, и в НД появились запрошенные данные. Помещать строки запроса в свойство SQL можно как при проектировании, так и программно. В случае если программист создает запрос и открывает набор данных при проектировании приложения, он имеет возможность создать объекты-поля (см. предыдущую лекцию), и настраивать их свойства по своему усмотрению. При программном формировании НД такой возможности у него нет. UniDirectional - Свойство логического типа. Содержит True, если курсор набора данных может перемещаться только вперед (типы курсоров см. в лекции №4). Это свойство используется, в основном, при работе с клиент-серверными СУБД, не поддерживающими курсоры, которые могут двигаться как вперед, так и назад. Методы компонента-запроса ExecSQL() - Выполняет модифицирующие запросы, то есть запросы на изменение, добавление или удаление записей, а также создание или удаление таблиц. В случае обычных запросов, выполненных с помощью оператора SELECT, используется метод Open, или присвоение значения True свойству Active набора данных. ParamByName() - Метод дает доступ к значению параметра по его имени, указанному в скобках. Prepare() - Метод используется для передачи SQL -запроса механизму доступа к данным, чтобы последний оптимизировал запрос. Оптимизация запроса происходит следующим образом: при выполнении любого запроса, механизм доступа к данным проверяет его синтаксис, что отнимает некоторое время. В случае многократного применения запроса, его можно выполнить методом Prepare(). При этом запрос компилируется и запоминается в буфере. При повторном выполнении этого запроса его синтаксис уже не проверяется. UnPrepare() - Этот метод отменяет результаты действия метода Prepare(), и освобождает буфер от хранения компилированного запроса Приемы создания и модификации таблиц программно На прошлых лекциях мы изучили немало способов создания и обработки таблиц. В большинстве случаев, база данных и таблицы в ней проектируются и создаются заранее, например, такими утилитами, как Database Desktop, или СУБД MS Access. Однако иногда приходится создавать таблицы программно, то есть, не во время проектирования приложения, а во время его работы. Предположим, каждый пользователь программы должен иметь свою собственную таблицу в базе данных, или даже свою собственную БД. Сделать это заранее программист не может, ведь неизвестно, сколько пользователей на одном ПК будут работать с программой, и какие у них будут имена. К сожалению, в большинстве учебной литературы информация о программном создании таблиц или вовсе отсутствует, или очень скудна - описывается только какой то один способ создания только одного типа таблиц. А ведь программисту, в зависимости от обстоятельств, может понадобиться создание разного типа таблиц, разными способами. В данной лекции мы попробуем восполнить этот пробел и разберем три способа программного создания как простых, так и индексированных таблиц. BDE. Простая таблица. Наиболее простой способ создания таблицы без индексов предлагает механизм доступа к данным BDE. Плюсы данного способа в простоте выполнения, в возможности создания таблиц как текстового типа, так и dBase, Paradox или FoxPro. Суть данного способа заключается в предварительном создании объектов-полей в редакторе полей компонента TTable. Это также означает, что еще на этапе проектирования можно настроить формат объектов-полей по своему усмотрению. Рассмотрим этот способ на примере. Создайте новое приложение. Форму как всегда назовите fMain, сохраните модуль под именем Main, а проект в целом назовите как угодно. На форму установите простую панель, очистите ее свойство Caption, а свойству Align присвойте значение alTop. В левой части панели установите рядом две простые кнопки, а в правой - компонент TDBNavigator с вкладки Data Controls Палитры компонентов. Ниже панели установите сетку TDBGrid, в ее свойстве Align выберите значение alClient. У кнопок измените свойство Caption: на первой кнопке напишите "Создать таблицу", на второй - "Открыть таблицу". Также нам потребуется еще четыре не визуальных компонента. Прямо на сетку, или в любое другое место установите компонент TTable с вкладки BDE, компонент TDataSource с вкладки Data Access, и компоненты TSaveDialog и TOpenDialog с вкладки Dialogs. Подготовим диалоговые компоненты. Выделите их и присвойте свойству Filter обоих компонентов строку Таблицы dBase|*.dbf Таким образом, мы указали, что диалоги будут работать только с таблицами типа dBase. Кроме того, у обоих диалогов измените свойство DefaultExt, указав там: dbf Это свойство указывает расширение файла по умолчанию, если пользователь не назначит расширения сам. В свойстве DataSet компонента DataSource1 выберите таблицу Table1. В свойстве DataSource сетки DBGrid1 и навигатора DBNavigator1 выберите имеющийся DataSource1. Теперь при открытии таблицы она будет отображаться в сетке, а навигатор позволит управлять ей. Теперь сложнее - настраиваем компонент Table1. Табличный компонент TTable имеет одно важное свойство TableType, с которым раньше нам не приходилось сталкиваться; компонент TADOTable такого свойства не имеет. Это свойство указывает на тип используемой или создаваемой таблицы. Свойство может иметь следующие значения: Таблица 3 . Значения свойства TableType компонента TTable Значение Описание Таблица содержится в формате обычного текстового файла. Строки и поля ttASCI разделяются специальными символами - разделителями. Имя файла таблицы имеет расширение *.TXT ttDBase Таблица содержится в формате dBase, файл по умолчанию имеет расширение *.DBF ttDefault Компонент определяет тип таблицы по расширению имени файла таблицы. При создании таблицы, если не указано расширение имени файла, принимается тип Paradox. ttFoxPro Таблица содержится в формате FoxPro, файл по умолчанию также имеет расширение *.DBF ttParadox Таблица содержится в формате Paradox, файл по умолчанию имеет расширение *.DB Если выбран тип таблицы (не ttDefault ), то будет использован этот тип вне зависимости от расширения указанного имени файла таблицы. В свойстве TableType компонента Table1 выберите значение ttDBase, то есть, таблица будет работать только с типом dBase. Далее дважды щелкните по компоненту, открыв редактор полей. Нам нужно будет добавить запланированные ранее поля. Щелкните по редактору правой кнопкой, выберите команду New Field (Новое поле). В поле Name впишите имя поля, например, FCeloe. В поле Type выберите тип поля Integer. В поле Size нужно указывать размер поля, но это справедливо только для текстовых полей и полей типов Memo или BLOB. Убедитесь, что переключатель Field Type установлен на Data, это создаст пустое поле указанного типа. Нажав кнопку "ОК" добавьте объект-поле в редактор полей. Таким же образом создайте еще несколько разнотипных полей. Каждому полю присвойте уникальное имя (ведь в таблице не может быть двух полей с одинаковым именем!). Важно, чтобы вы добавляли только те типы полей, которые поддерживаются выбранным типом таблиц, в нашем случае это dBase. При добавлении типа Memo укажите размер от 1 до 255, например, 50. В этом случае в файле таблицы *.dbf будет сохранен текст поля в 50 символов. Текст, который не уместится в этот размер, будет сохранен в файле Memo с таким же именем, но с расширением *.dbt. Делать табличный компонент активным на этапе проектирования не нужно. Итак, не имея базы данных, не имея физической таблицы, мы заранее установили тип таблицы и нужные нам поля. Как вы, наверное, догадываетесь, мы также имеем возможность сразу настроить нужные нам форматы для каждого поля, изменяя такие его свойства, как DisplayFormat, EditMask, DisplayLabel и др. Далее нам осталось непосредственно создать и открыть таблицу. Дважды щелкните по кнопке "Создать таблицу", сгенерировав для нее событие. В процедуру этого события впишите код: //если пользователь не выбрал таблицу, выходим: if not SaveDialog1.Execute then Exit; //закроем таблицу, если вдруг уже есть открытая: Table1.Close; //вначале устанавливаем адрес базы данных: Table1.DatabaseName := ExtractFilePath(SaveDialog1.FileName); //теперь устанавливаем имя таблицы: Table1.TableName := SaveDialog1.FileName; //физически создаем таблицу: Table1.CreateTable; //и открываем ее: Table1.Open; //запишем имя открытой таблицы: fMain.Caption := 'Таблица - '+ Table1.TableName; Комментарии к каждой строке достаточно подробны, чтобы вы самостоятельно разобрались с кодом. Метод CreateTable() компонента-таблицы создает файл таблицы, и дополнительные файлы ( Memo, индексные), если они нужны. В свойстве DatabaseName табличного компонента вы можете установить любой необходимый вам адрес, мы использовали папку, выбранную диалогом SaveDialog. Для кнопки "Открыть таблицу" код будет почти таким же: //если пользователь не выбрал таблицу, выходим: if not OpenDialog1.Execute then Exit; //закроем таблицу, если вдруг уже есть открытая: Table1.Close; //вначале устанавливаем адрес базы данных: Table1.DatabaseName := ExtractFilePath(OpenDialog1.FileName); //теперь устанавливаем имя таблицы: Table1.TableName := OpenDialog1.FileName; //открываем таблицу: Table1.Open; //запишем имя открытой таблицы: fMain.Caption := 'Таблица - '+ Table1.TableName; Откомпилировав программу и поработав с ней, вы обнаружите, что можете создавать и открывать сколь угодно много таблиц программно. При этом на каждую таблицу создается по два файла (если вы используете поле Memo ). Попробуйте таким же образом создать таблицу типа Paradox. BDE. Таблица с ключом и индексами. В задачу данного раздела входит создание таблицы Paradox с различными типами полей, с первичным ключом и индексами по текстовому полю как в возрастающем, так и в убывающем порядке. Редактор полей компонента TTable при этом вызывать не нужно, добавлять поля мы тоже будем программно. В целях экономии места проектирование формы приложения не описывается - это несложная задача. Вы можете создать главную форму такой же, как в предыдущем примере, только кнопка там будет одна. При нажатии на эту кнопку мы должны открыть таблицу, если она существует, или создать и открыть новую таблицу. Располагаться таблица должна в той же папке, откуда запущено приложение. Файл с таблицей Paradox назовем Proba.db, файлы с Memo и индексные файлы сгенерируются автоматически, также с именем Proba, но с разными расширениями. На форму добавьте компонент TTable с вкладки BDE, свойству Name которого присвойте значение TMy (вместо Table1 ), а свойству TableType значение ttParadox. Если у вас в приложении есть сетка DBGrid и (или) навигатор DBNavigator, то добавьте также компонент DataSource, который необходимо подключить к таблице TMy, а сетку и навигатор - подключить к DataSource. Здесь следует иметь в виду одну деталь: описание методов создания полей и индексов хранится в модуле DBTables, который подключается к вашей форме сразу, как вы установите компонент TTable. Если же вы используете модуль данных, и устанавливаете табличный компонент там, то и создавать таблицу нужно тоже в этом модуле, а в главной форме лишь вызывать процедуру создания таблицы. Но в нашем простом примере модуля данных нет, модуль DBTables указан в разделе Uses главной формы, и никаких проблем возникнуть не должно. Код нажатия на кнопку выглядит так: {Если таблицы нет - создаем и открываем ее, если естьпросто открываем} procedure TfMain.Button1Click(Sender: TObject); begin //если таблица есть - открываем ее и выходим: if FileExists(ExtractFilePath(Application.ExeName) + 'Proba.db') then begin TMy.DatabaseName := ExtractFilePath(Application.ExeName); TMy.TableName := 'Proba.db'; TMy.Open; Exit; end; //if {Если дошли до этого кода, значит таблицы еще нет. Указываем данные таблицы:} TMy.DatabaseName := ExtractFilePath(Application.ExeName); TMy.TableType := ttParadox; TMy.TableName := 'Proba'; {Создаем поля:} with TMy.FieldDefs do begin //вначале очистим: Clear; //добавляем поле-счетчик типа автоинкремент: with AddFieldDef do begin Name := 'Key'; DataType := ftAutoInc; Required := True; end; //with //добавляем текстовое поле: with AddFieldDef do begin Name := 'Name'; DataType := ftString; Size := 30; end; //with //добавляем поле дата: with AddFieldDef do begin Name := 'Date'; DataType := ftDate; end; //with //добавляем логическое поле: with AddFieldDef do begin Name := 'MyLog'; DataType := ftBoolean; end; //with //добавляем целое поле: with AddFieldDef do begin Name := 'MyInt'; DataType := ftInteger; end; //with //добавляем вещественное поле: with AddFieldDef do begin Name := 'MyReal'; DataType := ftFloat; end; //with //добавляем денежное поле: with AddFieldDef do begin Name := 'MyCurr'; DataType := ftCurrency; end; //with //добавляем поле Memo: with AddFieldDef do begin Name := 'MyMemo'; DataType := ftMemo; Size := 20; end; //with end; //with {Создаем ключ и индексы:} with TMy.IndexDefs do begin Clear; //делаем первичный ключ: with AddIndexDef do begin Name := ''; Fields := 'Key'; Options := [ixPrimary]; end; //делаем индекс в возрастающем порядке: with AddIndexDef do begin Name := 'NameIndxASC'; Fields := 'Name'; Options := [ixCaseInsensitive]; end; //делаем индекс в убывающем порядке: with AddIndexDef do begin Name := 'NameIndxDESC'; Fields := 'Name'; Options := [ixCaseInsensitive, ixDescending]; end; end; //with //создаем таблицу: TMy.CreateTable; //и открываем ее: TMy.Open; end; Разберем приведенный код. Первый блок выполняет проверку на наличие таблицы. Таблица ищется в папке, откуда была запущена программа. Если таблица найдена, то компоненту TMy присваиваются свойства DatabaseName (папка, где располагается таблица) и TableName (имя таблицы). В нашем случае таблица называется Proba.db, но вы можете усложнить программу, используя диалог OpenDialog, как в прошлом примере. В этом случае пользователь сможет выбрать не только имя таблицы, но и ее расположение. Далее таблица открывается, а оператор Exit досрочно завершает выполнение процедуры. Если выполнение процедуры продолжается, значит, таблица не была найдена. В этом случае мы заполняем свойства компонента-таблицы DatabaseName, TableType и TableName необходимыми значениями. Далее начинаем добавлять поля. Чтобы уменьшить код, мы используем оператор with. Напомню, что этот оператор создает блок кода, который относится к указанному в with объекту. Так, вместо with TMy.FieldDefs do begin Clear; можно было бы написать TMy.FieldDefs.Clear; В случае одиночного оператора это допустимо, но в случае множественных команд ссылаться в каждой строчке на объект будет утомительно. Свойство FieldDefs таблицы содержит описание полей этого набора данных. Таким образом, мы начинаем с того, что очищаем это описание. Далее у нас идет метод AddFieldDef, предназначенный для добавления поля в описание. Опять же, чтобы не ссылаться каждый раз на этот метод, мы используем вложенный оператор with для каждого добавляемого поля. В простейшем случае в блоке добавления нового поля требуется указать только два свойства объекта-поля: Name (имя поля) и DataType (тип поля). С именем все понятно, а что касается типа поля, то он определяется свойством DataType класса TField. Чтобы получить подробную справку по возможным типам полей, установите курсор в редакторе кода на слове DataType и нажмите , чтобы вызвать контекстную справку. В списке тем выберите ту тему, которая относится к классу TField, а в открывшейся справке щелкните по ссылке TFieldType (относится к Delphi 7, хотя возможно, имеется и в предыдущих версиях). Откроется страница с подробным описанием типов полей. При использовании этого метода следует сверяться, имеется ли выбранный тип поля в таблицах используемого формата. Помимо этих двух свойств, при необходимости могут использоваться и другие: Required - Логическое свойство. Если равно True, то значения поля должны быть уникальными (не могут повторяться). В нашем примере такое свойство имеется у поля, которое мы будем использовать как первичный ключ. Size - Указывает размер поля. Используется в основном, со строковыми и Memo полями. После того, как в список полей были добавлены все необходимые поля, начинаем создание первичного ключа и индексов. Если за список полей отвечает свойство FieldDefs таблицы, то за список индексов отвечает свойство IndexDefs, а за добавление нового индекса - метод AddIndexDef. По аналогии с полями, используем оператор with для уменьшения кода. Для каждого индекса требуется указать по три свойства: Name (имя индекса), Fields (имя поля, по которому строится индекс) и Options (параметры индекса). Параметры индекса указаны в таблице 4: Таблица 4. Параметры типов индекса Тип Описание Первичный индекс (ключ). Не применяется с таблицами типа dBase. ixPrimary Уникальный индекс. Значения этого поля не могут повторяться. ixUnique Индекс в убывающем (обратном) порядке. ixDescending Ключевой индекс для таблиц dBase. ixExpression ixCaseInsensitive Индекс, нечувствительный к регистру букв. ixNonMaintained Этот тип используется редко. Он подразумевает, что при редактировании пользователем значения индексируемого поля, индексный файл автоматически не обновляется. Как видно из примера, свойству Options можно присвоить не один параметр, а список параметров: Options := [ixCaseInsensitive, ixDescending]; Далее все просто: указав необходимые поля и индексы, методом CreateTable формируются физические файлы таблицы. Сама таблица имеет расширение *.db, файл с полем Memo - *.mb, остальные файлы содержат созданные индексы. Для сортировки данных используем индексы. У нас их два -' NameIndxASC ' (в возрастающем порядке) и ' NameIndxDESC ' (в убывающем порядке). Чтобы сортировать данные, например, в убывающем порядке, нужно указать имя соответствующего индекса в свойстве IndexName компонента-таблицы: TMy.IndexName := 'NameIndxDESC'; Если же мы хотим снять сортировку, то достаточно просто присвоить этому свойству пустую строку: TMy.IndexName := ''; Описываемый выше пример взят из справочника Delphi и приведен с небольшими доработками. Пример описывает практически все аспекты создания таблицы; по аналогии вы сможете создавать таблицы любой сложности. ADO. Создание простой таблицы посредством запроса SQL Создание таблицы выполняется SQL -запросом CREATE TABLE. Тут есть одно но: дело в том, что существует два типа SQL -запросов. Запрос, который возвращает набор данных и начинается оператором SELECT, выполняется простым открытием компонентазапроса. При этом выполняется запрос, который содержится в свойстве SQL компонента. С модифицирующими командами дело обстоит иначе. Команда CREATE TABLE принадлежит к той части SQL, которая называется DDL (Data Definition Language) - Язык Определения Данных. Этот язык предназначен для изменения структуры базы данных. Команды INSERT, DELETE, UPDATE относятся к DML (Data Manipulation Language) Язык Обработки Данных, предназначенный для модификации данных. Эти команды объединяет то, что они не возвращают результирующий набор данных. Чтобы выполнить эти команды, нужно присвоить соответствующий SQL -запрос свойству SQL, а затем вызвать метод ExecSQL. Синтаксис создания таблицы несложный: CREATE TABLE ( [], …) Здесь, TableName - имя таблицы; ColumnName - имя столбца; DataType - тип данных и Size - размер, который указывается для некоторых типов данных, например, строки. Описания столбцов таблицы разделяются запятыми. В различных СУБД синтаксис и типы данных SQL могут отличаться. Поэтому запрос, прекрасно работающий в одной СУБД, может вызвать ошибку в другой. Чтобы избежать ошибок, рекомендуется везде использовать типы ANSI, являющиеся стандартом SQL. Увы, но этих типов очень немного. Рассмотрим их: Таблица 5 . Типы ANSI Тип Описание CHAR (CHARACTER) Строковые типы данных. Обычно имеют размер до 255 символов. Требуют указания размера. TEXT Целое число. Размер не указывается. INT (INTEGER) Короткое целое. Размер не указывается. SMALLINT Вещественные числа. Размер не указывается. FLOAT REAL Как видите, многих типов просто нет. Вместо логического типа, вероятно, придется использовать строковый тип с размером в один символ; при этом 'Y' или '1' будут означать истину, а 'N' или '0' - ложь. Программисту придется самостоятельно делать проверку на это значение. Нет типа Memo. Нет автоинкрементного типа. Однако стандартные типы непременно будут корректно работать в любой СУБД. Ниже приведен пример создания и открытия простой таблицы. В приложении должен иметься компонент ADOQuery, а если есть сетка и навигатор, то и DBSource. Для подключения к нужному провайдеру данных желательно использовать компонент TADOConnection. В его свойство ConnectionString нужно прописать строку подключения, например: Provider=MSDASQL.1;Persist Security Info=False;Data Source=Файлы dBASE Эту строку можно ввести программно, или создать подключение при проектировании (я так и сделал). Поставщик данных в примере оставлен по умолчанию Microsoft OLE DB Provider for ODBC Drivers, а в качестве источника данных (вкладка "Подключение" редактора связей TADOConnection ) используются файлы dBase. Не следует забывать и про свойство LoginPrompt, которое следует переводить в False, чтобы программа не запрашивала имя пользователя и пароль при каждом подключении. А также нужно сразу открыть TADOConnection, установив его свойство Connected в True. В свойстве Connection компонента TADOQuery следует выбрать ADOConnection1. Пример реализован, как событие нажатия на кнопку: procedure TfMain.Button1Click(Sender: TObject); var s: String; begin {Создаем текст запроса} s := 'CREATE TABLE MyTab(Key1 INT, Name CHAR(20), '+ ' MyFloat FLOAT, MyDate DATE)'; {Создаем таблицу} ADOQuery1.SQL.Clear; ADOQuery1.SQL.Add(s); ADOQuery1.ExecSQL; {Открываем таблицу} ADOQuery1.SQL.Clear; ADOQuery1.SQL.Add('SELECT * FROM MyTab'); ADOQuery1.Open; Как видите, создается четыре поля - целый тип, строковый размером 20 символов, вещественный и тип Дата. Последний тип не входит в стандартное описание ANSI -типов, тем не менее, работает в большинстве СУБД. Можете также поэкспериментировать и с типом BOOLEAN (Логический). Итак, в переменную s мы вносим строку записи SQL -запроса. Затем очищаем свойство SQL, на случай, если там уже имелся запрос. Далее этот запрос мы заносим в свойство SQL, и методом ExecSQL выполняем его. С открытием таблицы мы уже неоднократно сталкивались. В результате выполнения кода создается и открывается файл MyTab.dbf, который находится в той же папке, что и приложение. Отчеты. Quick Report Отчеты - это один из основных результатов работы проекта с базами данных. Для чего и создаются базы данных, как не для получения отчетов? Отчет подразумевает, что программа выбирает необходимые данные из НД ( TTable, TQuery и т.п.) и выводит их на экран в удобном виде. Сам отчет можно не только просматривать, но и выводить его на печать. Лист профессионального отчета представляет собой сгенерированное графическое изображение, картинку, другими словами, данные в отчете редактировать уже нельзя. Не получится также выделить и скопировать в буфер обмена текст отчета. Готовый отчет выводит информацию, разбитую на страницы, и подготовленную к печати. Отчеты создаются специальными наборами компонентов. Имеется очень много отчетов сторонних разработчиков, как платных, так и бесплатных, которые можно найти в Internet. В этой лекции мы разберем стандартный набор компонентов Quick Report, который поставляется вместе с Delphi. Установка Quick Report. Quick Report представляет собой стандартный набор компонентов для создания отчетов. Он поставляется вместе с Delphi, но не устанавливается в палитру компонентов автоматически. Нам придется установить его самостоятельно. Если пакет Quick Report у вас еще не установлен (на палитре компонентов отсутствует вкладка QReport ), то загрузите Delphi и закройте все открытые проекты (File > Close All). Выбрерите пункт меню "Component -> Install Packages". Нажмите кнопку "Add" и выберите пакет "dclqrt70.bpl", который по умолчанию устанавливается по адресу: c:\Program Files\Borland\Delphi7\bin\dclqrt70.bpl и нажмите кнопку "Открыть". Далее, нажмите кнопку "ОК" - пакет компонентов Quick Report установится, и его вкладка будет самой последней на Палитре компонентов. При желании можно перетащить ее мышью на другое место, поближе к началу. Простой отчет Создадим простой отчет на основе программы для отдела кадров из предыдущих лекций. Чтобы не менять старый проект, скопируйте его целиком в новую папку и откройте. Кнопок на панели инструментов у нас здесь достаточно, для работы с отчетами создадим главное меню. Добавьте компонент MainMenu и создайте разделы: Таблица 5. Разделы главного меню Раздел Подразделы Файл Выход Отчеты Кадры По телефонам По адресам Для самого отчета нам потребуется новая форма. Создайте ее, свойству Name присвойте значение fRepKadr, а модуль сохраните под именем RepKadr. Сразу же командой "File -> Use Unit" подключим к этой форме модуль данных DM, а к главной форме - только что созданный новый модуль. В палитре компонентов перейдем на вкладку QReport. Самым первым компонентом на вкладке является QuickRep - основа всех отчетов. Установите его на новую форму, и он примет вид разлинованного листа. Это своего рода холст, на котором мы будем собирать различные части нашего отчета: Рис. 16. Пустой "холст" QuickRep Выделите QuickRep и обратите внимание на его свойства. В самом верху находится свойство Bands (Ленты, полосы - англ.). Это раскрывающееся свойство, оно содержит шесть параметров. Щелкните по плюсу слева от свойства, чтобы раскрыть его. По умолчанию, все параметры имеют значение False, то есть, не установлены. Если какойлибо параметр перевести в значение True, на холсте появится соответствующая полоса. Попробуйте установить все параметры. Разберемся с их назначением. HasColumnHeader - Заголовки колонок. Здесь мы будем вписывать названия колонок таблицы. HasDetail - Детальная информация. На этой ленте формируются строки таблицы. HasPageFooter - Подвал (нижний колонтитул). Здесь можно установить информацию, которая будет появляться в нижней части каждой страницы. HasPageHeader - Шапка (верхний колонтитул). Здесь можно установить информацию, которая будет появляться в верхней части каждой страницы. HasSummary - Суммарная информация. Содержимое этой полосы печатается один раз в самом конце отчета. HasTitle - Заголовок отчета. Переведите в True полосы HasPageHeader, HasTitle, HasColumnHeader, HasDetail и HasPageFooter. Не установленной останется только полоса HasSummary. Если вы дважды щелкните мышью по свободному месту холста, появится настроечное окно: Рис. 17. Окно настроек компонента QuickRep В этом окне можно выполнить большинство настроек, причем в Инспекторе объектов соответствующие свойства будут изменены автоматически. Как видите, установленные нами полосы отмечены "галочкой" в разделе Bands окна. Выше располагается раздел Page frame, в котором можно задать обрамление для верхней ( Top ), нижней ( Bottom ), левой ( Left ) и правой ( Right ) границ холста, а также изменить цвет и ширину обрамления. Те же действия можно выполнить в Инспекторе объектов с помощью параметров раскрывающегося свойства Frame (пока открыто окно настроек, менять свойства в Инспекторе Объектов не получится). Еще выше располагается раздел Other, где можно установить общие данные для холста - шрифт, размер шрифта и единицы измерения (по умолчанию mm - миллиметры). В Инспекторе объектов за это отвечают свойства Font и Units. Далее находится раздел Margins (Границы, края), где можно задать расстояния от краев листа до рабочей части холста. На самом верху окна располагается раздел Paper size (Размер бумаги), где задаются тип листа и его размеры. Данные этих двух разделов можно изменить в Инспекторе объектов в раскрывающем свойстве Page. Еще следует обратить внимание на свойство Options, которое имеет три параметра: FirstPageHeader - Разрешает печать заголовков (шапку) первой страницы, если равно True. LastPageFooter - Разрешает печать подвала последней страницы, если равно True. Compression - Разрешает сжатие отчета при формировании из него метафайла (отчет представляет собой изображение), если равно True. Свойство PrintIfEmpty разрешает ( True ) или запрещает ( False ) печатать отчет, если в нем нет никаких данных. Свойство ShowProgress разрешает или запрещает показывать индикатор процесса печати отчета. По умолчанию индикатор разрешен. Свойство SnapToGrid разрешает или запрещает привязывание компонентов к сетке. По умолчанию привязка разрешена. Свойство Zoom имеет тип Integer и позволяет изменить масштаб отображения отчета при его разработке. Значение 100 указывает, что отчет показывается в 100% от листа бумаги. Изменение этого свойства не влияет на масштаб печати отчета или его предварительного просмотра. Теперь приступим к формированию отчета. На холсте у нас уже должны быть расположены пять полос. Теперь мы можем на эти полосы устанавливать другие компоненты. На самом верху холста находится полоса Page Header, которая, как мы уже знаем, является верхним колонтитулом. Установите в левой части этой полосы компонент QRSysData - компонент с различного рода системной информацией. Нас интересует свойство Data этого компонента. Data содержит несколько свойств, формирующих отображаемую информацию. Разберем эти свойства. qrsDate (Вывод текущей даты) qrsDateTime (Вывод текущих даты и времени) qrsDetailCount (Количество строк формируемой таблицы) qrsDetailNo (Номер текущей строки таблицы) qrsPageNumber (Номер текущей страницы отчета) qrsReportTitle (Заголовок отчета) qrsTime (Вывод текущего времени) Установим для этого свойства значение qrsDateTime, чтобы пользователь мог видеть, когда был сформирован отчет. Далее выделим всю полосу Page Header и в свойстве Frame переведем в True параметр DrawBottom. Это свойство позволяет задать обрамление выделенной полосе, а параметр DrawBottom рисует линию в нижней части полосы. То есть, мы визуально отделили колонтитул от данных листа. В нижней части холста располагается полоса Page Footer (подвал). Здесь желательно установить верхнюю линию в свойстве Frame, отделив от данных нижний колонтитул. А по центру полосы установить еще один компонент QRSysData, установив свойство Data в значение qrsPageNumber. Этот компонент будет выводить номер текущей страницы в нижней части листа. Примечание: для того, чтобы увидеть отчет в окне предварительного просмотра, не обязательно компилировать программу. Достаточно щелкнуть правой кнопкой мыши по свободному месту листа QuickRep , и в контекстном меню выбрать команду Preview. При этом следует иметь в виду, что такие данные, как значения вычисляемых полей, например, видны не будут. Эти данные станут доступны только в режиме выполнения программы. Колонтитулы мы установили, займемся данными. Прежде всего, напишем заголовок отчета. Для этого установите компонент QRLabel в центре полосы Title. QRLabel похож на обычный Label и служит тем же целям: выводит на лист какой то текст. Выделите его, и в свойстве Caption напишите "Отчет по кадрам". Чтобы заголовок был красивым, щелкните дважды по свойству Font, чтобы открылось окно шрифта. Здесь установите шрифт Times New Roman, начертание выберите жирное, а размер шрифта пусть будет 18 (вы можете использовать настройки по собственному выбору). Можно изменять и цвет шрифта, но при этом имейте в виду, что чаще всего отчеты печатают на черно-белых принтерах, так что злоупотреблять разными цветами не рекомендуется. Далее займемся полосой Column Header (Заголовки колонок). Здесь установите рядом пять компонентов QRLabel, в свойстве Caption которых напишите Фамилия Имя Отчество Дата рождения Образование Это будут названия колонок таблицы. Шрифт этих компонентов также желательно сделать крупнее, но не больше заголовка. Далее займемся полосой Detail, на которой, собственно, и будет формироваться таблица. Здесь нам нужно будет в самом крайнем положении слева установить компонент QRSysData, в свойстве Data которого выбрать qrsDetailNo - перед каждой строкой будет выходить ее номер. Далее установите пять компонентов QRDBText, в которых будут отражаться данные из соответствующих полей таблицы. Эти компоненты соответствуют обычному DBText, с которым мы неоднократно сталкивались. Расположите их точно под названиями столбцов, чтобы таблица была красивой. При этом может оказаться, что компонент QRSysData "наплывает" на QRDBText - ничего страшного, данные все равно не будут мешать друг другу. Выделите все QRDBText, и в их свойстве DataSet выберите нашу таблицу fDM.TLichData, затем поочередно в свойстве DataField этих компонентов выберите соответствующие поля таблицы: Фамилия Имя Отчество Дата_Рожд Образование Кроме того, сам компонент QuickRep1, который является "холстом" отчета, также должен знать, из какой таблицы ему нужно брать данные. Поэтому выделите его, и в свойстве DataSet также выберите нашу таблицу fDM.TLichData. Если этого не сделать, то в отчете будет выходить лишь текущая запись таблицы, а не все ее записи. Собственно, отчет уже готов: Рис. 18. Отчет Вернитесь в главное окно проекта и сгенерируйте обработку команды меню "Отчеты - Кадры". В созданной процедуре напишите такую строку (вы ведь добавили к главному окну командой File - Use Unit наш отчет?): {Отчет Кадры} procedure TfMain.N5Click(Sender: TObject); begin fRepKadr.QuickRep1.PreviewModal; end; После того, как вы сохраните проект, скомпилируете его и выполните команду меню, появится подобное окно с отчетом: Рис. 19. Окно отчета Как видите, окно уже содержит весь необходимый инструментарий в панели инструментов - позволяет настроить вид отчета, листать его страницы, сделать установку принтера и распечатать отчет, а также сохранить отчет в специальный файл с расширением *.qrp с возможностью последующей его загрузки. К сожалению, интерфейс этого окна доступен только на английском языке. Наш отчет был бы красивей, если бы полученная таблица была очерчена рамкой. Исправим этот недостаток. На вкладке QReport имеется компонент QRShape, который позволяет рисовать простейшие линии и фигуры. Он имеет свойство Shape, в котором можно задать нужную фигуру. Возможные значения этого свойства: qrsCircle (Круг) qrsHorLine (Горизонтальная линия) qrsRectangle (Прямоугольник) qrsRightAndLeft (Прямоугольник с очерченными левым и правым краями) qrsTopAndBottom (Прямоугольник с очерченными верхним и нижним краями) qrsVertLine (Вертикальная линия) Этот компонент можно использовать по-разному. Например, можно установить по одной горизонтальной линии сверху и снизу полосы Detail, а затем вертикальными линиями отделить каждый столбце страницы. Я сделал проще: каждый столбец заключил в прямоугольник (шесть компонентов QRShape ), а чтобы QRShape не перекрывал текст, щелкнул по ним правой кнопкой и выбрал команду Control -> Send to Back (поместить на задний план): Рис. 20. Компоненты QRShape играют роль границ таблицы В результате таблица отчета приняла вид: Рис. 21. Таблица отчета с границами Результат может не сразу получиться таким - придется поэкспериментировать с расположением и размерами прямоугольников QRShape. Отчет из связанных таблиц Часто бывает, когда недостаточно получить отчет по данным только из одной таблицы. И здесь можно поступить двумя способами: 1. Воспользоваться набором данных ADOQuery и с помощью SQL запроса получить нужные данные из двух таблиц. 2. Воспользоваться компонентом QRSubDetail, который специально предназначен для получения данных из связанной таблицы. Этим компонентом мы и воспользуемся. Итак, QRSubDetail - это полоса, которая импортирует в отчет данные из подчиненной таблицы. Создайте в проекте новую форму, назовите ее fRepTelephons, а модуль сохраните как RepTelephons. Сразу же к этому окну командой File -> Use Unit подключите модуль с наборами данных DM, а к главному модулю подключите только что созданный RepTelephons. Установите на новую форму основу отчета QuickRep. Проверьте, чтобы он не был смещен по отношению к листу (свойства Left и Top равны 0). В свойстве DataSet компонента-основы выберите fDM.TLichData, то есть, таблицу с личными данными. Теперь создайте на основе полосы Page Header, Title, Column Header и Detail. На верхнюю полосу Page Header установите по краям два компонента QRSysData, в свойстве Data у первого выберите qrsDateTime, а у второго qrsPageNumber. Кроме того, переведите в True параметр DrawBottom у свойства Frame полосы Page Header, чтобы отделить линией верхний колонтитул. Далее, на полосу Title установите один компонент QRLabel, на котором напишите заголовок "Отчет по телефонам сотрудников". Измените шрифт, начертание и размер, как в прошлом примере, и отцентрируйте заголовок по полосе. Ниже идет полоса Column Header с заголовками таблицы. Как и в прошлом примере, требуется из компонентов QRLabel сформировать заголовки столбцов, но в этот раз ограничимся только тремя заголовками: "Фамилия", "Имя" и "Отчество". К слову сказать, чтобы не делать работу дважды, вы можете открыть форму fRepKadr из прошлого примера, выделить нужные компоненты и командой всплывающего меню Edit -> Copy скопировать их. Затем перейти в новую форму, выделить нужную полосу, и командой Edit -> Paste вставить эти компоненты. Далее у нас идет полоса Detail, на которой нам нужно разместить три компонента QRDBText, которые привязать к соответствующим полям (не забудьте про свойства DataSet и DataField этих компонентов). Еще в свойстве Frame полосы Detail желательно перевести параметр DrawTop в True, чтобы каждая запись отчета отделялась линией. Пока что все, что мы делали, было практически таким же, как в прошлом отчете. Теперь добавим в отчет связанные данные из другой таблицы. Установите компонент QRSubDetail - эта полоса должна быть самой нижней. Сначала нужно выбрать главный по отношению к этому компонент: в свойстве Master выберите QuickRep1. Кроме того, полоса QRSubDetail должна знать, откуда листать данные, поэтому в свойстве DataSet полосы выберите fDM.TTelephones. Далее установим на полосу один компонент QRLabel, напишем на нем "Телефон:". Слева от него установите два компонента QRDBText, в свойстве Dataset которых выберите fDM.TTelephones, а в свойстве DataField выберите соответственно, поля "Телефон" и "Примечание". Теперь мышью немного перетащите нижний край полосы, чтобы сделать ее поуже. В результате у вас должна получиться форма, подобная этой: Рис. 22 Отчет по телефонам Наш отчет готов. Создайте процедуру вызова этого окна в команде главного меню Отчеты -> По телефонам, и пропишите там вызов fRepTelephons.QuickRep1.PreviewModal; Сохраните проект, скомпилируйте и запустите его. В результате выбора этой команды мы получим подробный отчет по телефонам сотрудников: Рис. 23. Окно отчета по телефонам Отчет по адресам сотрудников создается точно таким же образом, и теперь вы сможете сделать его самостоятельно. Экспорт отчета в другие форматы Отчет можно не только распечатать. Его также можно сохранить в специальном формате *.qrp, а затем загрузить в окно предварительного просмотра. Для этого соответственно служат кнопки " Save Report " и " Load Report " на панели инструментов окна предварительного просмотра: Рис. 24. Кнопки "Save Report" и "Load Report" Однако бывают случаи, когда отчет желательно сохранить в каком-нибудь общем формате, например, в текстовом, или html ( web -страница). Тогда отчет можно было бы просмотреть в стандартном Блокноте или web -броузере, переслать сотруднику, у которого ваша программа не установлена. На вкладке QReport имеются компоненты, которые позволяют это сделать. QRTextFilter - позволяет сохранить отчет в виде текстового файла. QRCSVFilter - позволяет сохранить отчет в специальном формате CSV (Comma Separated). QRHTMLFilter - позволяет сохранить отчет в формате web -страницы. Это не визуальные компоненты, на отчете они не отобразятся. Достаточно установить один из них (или все вместе) на основу отчета QuickRep, и при сохранении отчета пользователю станут доступны соответствующие форматы: Рис. 25. Выбор формата сохранения отчета Причем если у вас в проекте имеется несколько окон с отчетами, компоненты добавляются только в один из них, в остальных отчетах эти форматы также станут доступны Работа с сеткой 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: Рис. 26 . Форма проекта Допустим, в нашем проекте нам нужны не все поля таблицы, а только некоторые из них. Значит, придется поработать с редактором столбцов сетки DBGrid. Вызвать редактор можно тремя способами: дважды щелкнуть по сетке; щелкнуть правой кнопкой по сетке и в контекстном меню выбрать команду Columns Editor и, наконец, щелкнув дважды по свойству сетки Columns в Инспекторе Объектов: Рис. 27 . Редактор столбцов сетки DBGrid Работа с этим редактором очень похожа на редактор полей набора данных, но есть и отличия. В верхней части окна вы видите четыре кнопки, слева - направо: 1. Add New (Добавить новый столбец). 2. Delete Selected (Удалить выделенный столбец). 3. Add All Fields (Добавить все столбцы из набора данных). 4. 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; Сохраните проект, скомпилируйте его и проверьте работу кнопок. И шрифт, и цвет текущего столбца будут меняться: Рис. 28 . Изменение шрифта и цвета текущего столбца Следует заметить, что таким образом можно менять параметры не только содержимого текущего столбца, но и его заголовка, если, например, вместо 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 TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var Rect: 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, показывающие, соответственно, левую и верхнюю позиции текущей ячейки. В результате программа будет иметь вид: Рис. 29 . Заполнение пустого столбца Теперь о том, как использовать пустой столбец для вывода информации по требованию пользователя. Допустим, в сетке DBGrid нам нужна кнопка, нажатие на которую привело бы к выводу сообщения об образовании сотрудника. Создайте новый пустой столбец. Перетаскивать его не нужно, пусть будет последним. Свойство Width (ширина) установите в 20 пикселей. Название столбца ( Title.Caption ) вписывать не нужно, пусть будет пустым. В свойстве ButtonStyle выберите значение cbsEllipsis. Это приведет к тому, что при попытке редактировать этот столбец образуется кнопка с тремя точками: Рис. 30 . Кнопка в пустом столбце Нужный код пишется в событии OnEditButtonClick() сетки DBGrid, которое происходит всякий раз, когда пользователь нажимает на кнопку "…". Сгенерируйте это событие и впишите только одну строку: ShowMessage(ADOTable1['Образование']); Теперь, когда пользователь нажмет на эту кнопку, ему будет выведено сообщение с текстом об образовании текущего сотрудника. Список выбора в столбце Для организации списков выбора служит компонент ComboBox. Однако сетка DBGrid позволяет устроить такой же список в одном из своих столбцов без использования каких-либо других компонентов. В нашем примере есть поле "Пол". Это текстовое поле из трех символов. Во время конструирования базы данных ok.mdb с помощью программы MS Access мы указывали, что это поле может хранить значения либо "муж", либо "жен". То же самое можно было сделать с помощью сетки. Откройте редактор столбцов сетки и выделите столбец "Пол". Обратите внимание на свойство PickList в Инспекторе объектов. Это свойство имеет тип TStrings, то есть представляет собой набор строк, так же как и свойство Items у компонента ComboBox. Щелкните дважды по PickList, чтобы открыть редактор, и впишите туда муж жен именно так, каждое значение на своей строке. Сохраните проект, скомпилируйте его и попробуйте редактировать этот столбец. При попытке редактирования в ячейке покажется похожий на ComboBox список, в котором можно будет выбрать одно из указанных значений: Рис. 31 . Список выбора в сетке Следует иметь в виду, что наличие такого списка не препятствует пользователю ввести какое-то иное значение. Этот список нужен не для контроля, а только для облегчения пользователю ввода данных. Если же вы не желаете, чтобы пользователь имел возможность вводить другие данные, контроль следует организовать иным способом. Еще нужно заметить, что в практике программирования список чаще формируется во время работы программы, а строки списка берутся, как правило, из другой связанной таблицы. Добавить в список новую строку очень просто: DBGrid1.Columns.Items[4].PickList.Add('абв'); Если добавить в редактор столбцов сетки DBGrid новый столбец, но в свойстве FieldName не выбирать поле БД, а оставить его пустым, мы получим пустой столбец. Для чего нужны пустые столбцы в сетке? Во-первых, в них можно выводить обработанные данные из других столбцов. К примеру, пользователю неудобно просматривать три столбца "Фамилия", "Имя" и "Отчество". Ему было бы удобней просмотреть один сборный столбец в формате "Фамилия И.О.". Во-вторых, пустое поле может выводить информацию по требованию. Рассмотрим эти случаи. Создайте новый столбец, но не назначайте ему поле из НД. Выделите этот столбец, и в его свойстве Title.Caption впишите "Фамилия И.О.", а в свойстве Width укажите ширину в 150 пикселей. Эти сборные данные не будут видны в момент проектирования таблицы, они выйдут только во время работы программы. Столбцы "Фамилия", "Имя" и "Отчество" нам уже не нужны, скройте их, установив их свойство Visible в False. А новый столбец перетащите мышью наверх, его индекс будет равен 0. Нам придется написать код, который нужно вписать в событие OnDrawColumnCell сетки. Это событие наступает при прорисовке каждой ячейки столбца. Также имеется событие OnDrawDataCell, которое выполняет схожие функции, но оно оставлено для поддержки старых версий, и использовать его не желательно. Итак, выделяем сетку, генерируем событие OnDrawColumnCell и вписываем код: {Прорисовка таблицы} procedure TfMain.DBGrid1DrawColumnCell(Sender: TObject; const 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; Rect: Здесь мы вначале проверяем - наш ли это столбец (равен ли индекс нулю)? Если наш, то в переменную s начинаем собирать нужный текст. При этом имеем в виду, что пользователь мог и не заполнить некоторые поля. Чтобы у нас не произошло ошибки, вначале убеждаемся, что поле не равно Null (то есть, текст есть). Если текст есть, добавляем его в переменную s. Причем если это имя или отчество, с помощью функции Copy() получаем только первую букву и добавляем к ней точку. Когда s сформирована, добавляем этот текст в наш столбец с помощью метода TextOut() свойства Canvas сетки. В метод передаются три параметра: координаты левой позиции, верхней позиции и сам текст. Эти координаты мы берем из параметра события OnDrawColumnCell - Rect, который имеет такие свойства, как Left и Top, показывающие, соответственно, левую и верхнюю позиции текущей ячейки. В результате программа будет иметь вид: Рис. 32 . Заполнение пустого столбца Теперь о том, как использовать пустой столбец для вывода информации по требованию пользователя. Допустим, в сетке DBGrid нам нужна кнопка, нажатие на которую привело бы к выводу сообщения об образовании сотрудника. Создайте новый пустой столбец. Перетаскивать его не нужно, пусть будет последним. Свойство Width (ширина) установите в 20 пикселей. Название столбца ( Title.Caption ) вписывать не нужно, пусть будет пустым. В свойстве ButtonStyle выберите значение cbsEllipsis. Это приведет к тому, что при попытке редактировать этот столбец образуется кнопка с тремя точками: Рис. 33 . Кнопка в пустом столбце Нужный код пишется в событии OnEditButtonClick() сетки DBGrid, которое происходит всякий раз, когда пользователь нажимает на кнопку "…". Сгенерируйте это событие и впишите только одну строку: ShowMessage(ADOTable1['Образование']); Теперь, когда пользователь нажмет на эту кнопку, ему будет выведено сообщение с текстом об образовании текущего сотрудника. Список выбора в столбце Для организации списков выбора служит компонент ComboBox. Однако сетка DBGrid позволяет устроить такой же список в одном из своих столбцов без использования каких-либо других компонентов. В нашем примере есть поле "Пол". Это текстовое поле из трех символов. Во время конструирования базы данных ok.mdb с помощью программы MS Access мы указывали, что это поле может хранить значения либо "муж", либо "жен". То же самое можно было сделать с помощью сетки. Откройте редактор столбцов сетки и выделите столбец "Пол". Обратите внимание на свойство PickList в Инспекторе объектов. Это свойство имеет тип TStrings, то есть представляет собой набор строк, так же как и свойство Items у компонента ComboBox. Щелкните дважды по PickList, чтобы открыть редактор, и впишите туда муж жен именно так, каждое значение на своей строке. Сохраните проект, скомпилируйте его и попробуйте редактировать этот столбец. При попытке редактирования в ячейке покажется похожий на ComboBox список, в котором можно будет выбрать одно из указанных значений: Рис. 34 . Список выбора в сетке Следует иметь в виду, что наличие такого списка не препятствует пользователю ввести какое-то иное значение. Этот список нужен не для контроля, а только для облегчения пользователю ввода данных. Если же вы не желаете, чтобы пользователь имел возможность вводить другие данные, контроль следует организовать иным способом. Еще нужно заметить, что в практике программирования список чаще формируется во время работы программы, а строки списка берутся, как правило, из другой связанной таблицы. Добавить в список новую строку очень просто: 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 - ячейка - заголовок столбца. Допустим, в нашем примере требуется, чтобы строки с военнообязанными сотрудниками выделялись красным цветом: Рис. 35 . Выделение строк Заметим сразу, что наличие пустых столбцов создает дополнительные, но решаемые проблемы. Код события OnDrawColumnCell придется переделать, он будет таким: {Прорисовка таблицы} procedure TfMain.DBGrid1DrawColumnCell(Sender: TObject; const 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 Rect: 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); Обратите внимание, что мы добавили по два пикселя к крайней левой и крайней верхней координате ячейки. Если этого не сделать, то новая прорисовка не будет целиком закрашивать старую: Рис. 36 . Некорректная прорисовка Количество добавляемых пикселей зависит от формата данных и размера шрифта. Обычно это определяется путем проб. Например, ячейка может содержать цифры, которые обычно прижимаются к правому краю, и тут двумя пикселями не обойтись. Сделать проверку на тип данных можно, например, так: //если текст, сдвинем только на 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 любого компонента. Теперь вы сможете создать сетку со сложными элементами прорисовки. DBChart. Графики и диаграммы Для построения графиков и диаграмм используются компоненты Chart с вкладки Additional, и DBChart с вкладки Data Controls палитры компонентов. Это равноценные компоненты, отличие состоит в том, что DBChart принимает данные из указанного набора данных - таблицы или запроса, а при использовании Chart данные приходится вносить самостоятельно. Это довольно сложные компоненты, они имеют множество свойств, которые в свою очередь сами являются сложными объектами. Если описывать компоненты графиков и диаграмм подробно, то получится небольшая книга, поэтому мы рассмотрим лишь основные приемы работы с ними. Простое приложение с графиком Графики применяются там, где нужно показать динамику подъема или спада одного или нескольких объектов. Хорошим примером является график с кривыми, демонстрирующими динамику изменений курсов доллара и евро к рублю. Диаграммы же применяются для демонстрации сравнительных показателей разных объектов. Так, например, во время предвыборной кампании часто демонстрируют диаграмму, где столбики различного роста показывают, кто из кандидатов набрал больше голосов. Круговые диаграммы показывают сравнительное отношение каждого объекта к целому, например круговая диаграмма, показывающая процент депутатов каждой партии от общего количества депутатов. Познакомимся с работой компонента DBChart на практике. Для примера нам понадобится база данных с одной таблицей, в которую мы запишем курсы доллара и евро к рублю. Таблицу вы можете сделать какой угодно - Paradox или MS Access, в таблице создайте три поля: CDate, CDollar и CEvro. Саму таблицу назовите Curs. Заполните таблицу произвольными данными, делая по записи на каждый день, пусть в таблице будет не менее 10 записей. Данные не обязательно должны соответствовать действительности, но постарайтесь сделать их похожими на реальный курс доллара США и евро к рублю, например: CDate: 10.03.2010 CDollar: 29,21 CEvro: 40,16 Заполнив таблицу, подключите ее к новому проекту, используя технологию BDE или ADO, и сразу же переведите в True свойство Active компонента TTable ( TADOTable ) . Далее на форму установите пустую панель со свойством Align, равным alTop (она нам потребуется позже). На свободное место формы установите компонент DBChart с вкладки Data Controls Палитры компонентов. Свойство Align компонента установите в alClient, чтобы график занял все оставшееся место формы. Теперь дважды щелкните по графику, чтобы открыть редактор серий: Рис. 37. Редактор серий графика Все отображаемые на графике данные построены с помощью серий - объектов Series типа TChartSeries, которые являются отображением данных отдельного реального объекта. Например, если мы используем график динамики курса доллара и евро к рублю, то серия доллара будет содержать ряд точек на графике, которые соответствуют стоимости доллара на каждый день. Для евро будет создана собственная серия. Все настройки серий можно делать как с помощью этого редактора, так и программно изменяя свойства графика. В этом примере нам потребуется сделать две серии: для доллара и для евро. Ось X будет содержать дату, а ось Y - значение. Нажмите кнопку Add (добавить серию). У вас появится окно выбора графика: Рис. 38. Окно выбора графика. Помимо выбора графика мы так же можем оставить или снять "галочку" 3D, которая включает или выключает объемность. Объемный график смотрится наряднее и больше подходит для всякого рода докладов или презентаций. Если же вам придется строить график в строгой деловой программе, то объемность будет разумней не использовать. Выбор типа графика или диаграммы зависит от типа отображаемых данных. В нашем примере мы собрались чертить кривую, так что нам больше подойдут типы Line или Fast Line. Выберем первый из них. Как только мы это сделали, на компоненте DBChart отобразился график со случайными данными. Это происходит по умолчанию, чтобы легче было производить настройки серии. В редакторе серий появилась серия Series1. Выделите ее и щелкните по кнопке Title (заголовок). Измените заголовок на "Доллар": Рис. 39. Новая серия Теперь перейдите на вкладку Series на самом верху окна редактора, а уже на этой вкладке откройте внутреннюю вкладку Data Source. В выпадающем списке вы видите Random Values (случайные значения), которые и обеспечили показ серии на графике. Нам нужно выбрать Dataset, а в окне Dataset - наш набор данных: Рис. 40. Подключение серии к таблице В списке Labels можно выбрать поле с данными по доллару, а можно оставить его пустым, этот список используется для отображения меток, если они установлены. В списке X выберите поле с датой, автоматически должна установиться "галочка" DateTime. Эти даты будут отображаться по оси X. А по оси Y отобразим поле с курсом доллара. Как только вы закроете редактор кнопкой Close, на форме появится график курса доллара. Далее перейдите на вкладку Chart и добавьте еще одну серию. Сделайте все точно также, только отобразите курс евро. Мы получили не очень впечатляющий график: Рис. 41. График курсов доллара и евро Займемся настройкой графика. Вновь откройте редактор графика и перейдите на вкладку Chart, а в ней на вкладку Titles. В выпадающем списке мы видим " Title " (заголовок графика), а в текстовом окне отображается название графика "TDBChart". Впишите вместо него "Курсы доллара и евро". Кнопка Font позволяет изменить шрифт заголовка, кнопка Border откроет окно, в котором можно настроить обрамление. Кнопка Back Color открывает диалог выбора цвета для фона заголовка, кнопка Pattern также позволяет настроить фон, придав ему цвет "родителя" - самого графика. Если вы откроете выпадающий список, то увидите, что помимо " Title " (заголовка) доступен еще и " Foot " (подвал) - надпись, которая будет выведена внизу. Напишите там "Пример простого графика". Смотрим, какие вкладки здесь еще есть. Самой последней имеется вкладка 3D, на которой можно включить или выключить объемность графика, а также отрегулировать вращение, наклон или масштаб. На вкладке Walls (стены) можно отрегулировать "стены" осей, на рисунке 41 они выделяются желтым и белым цветами. Вкладка Paging позволяет настроить многостраничные графики, а вкладка Panel задать параметры фона. Интересны здесь параметры панели Gradient, позволяющие задать градиентную заливку. При этом фон будет плавно переходить из одного цвета в другой. Вкладка Legend позволяет настроить легенду графика, на рисунке вы видите ее в правой части графика с надписями "Доллар" и "Евро". Перейдем на вкладку Axis (оси). Здесь мы можем сделать множество настроек осей. Вначале в левой части окна в разделе Axis нужно выбрать ось. Мы выберем Left, то есть, ось Y. Правее находится дополнительное окно со своими собственными вкладками, причем открыта вкладка Scales (шкалы): Рис. 42. Настройка осей графика Здесь мы снимем "галочку" Automatic, которая автоматически устанавливает размер шкалы. В большинстве случаев этого не требуется, но в нашем примере мы получили относительно ровные линии, причем одна из них в нижней, а другая - в верхней части, что не делает график красивее. Итак, снимите эту галочку, а затем с помощью кнопок Change немного увеличьте максимальное значение, и немного уменьшите минимальное. В результате кривые графика сдвинутся к середине. Далее можете перейти на внутреннюю вкладку Title, где напишите "Курс к рублю". Эта надпись является заголовком оси Y. Больше, пожалуй, с этой осью делать ничего не нужно. Зато ось X у нас вместо дат показывает значения. Исправим это. В группе радиокнопок Axis выберем Bottom (нижняя ось), и перейдем на внутреннюю вкладку Labels. В разделе Style вместо Auto выберем Value, что изменит надписи к точкам оси X: вместо назначаемых автоматически, мы четко указали, что нужно взять значение поля, то есть, дату. В результате этих манипуляций мы получим уже достаточно привлекательный график: Рис. 43. График курсов валют Сохраните проект, скомпилируйте и загрузите полученную программу. Посмотрим, что умеет делать этот график в рабочем приложении. Прежде всего, если вы выделите какой то участок графика левой кнопкой мыши слева-направо и сверху-вниз, выделенный фрагмент увеличится во все окно графика. Ухватившись за график правой кнопкой мыши, его можно будет перемещать. Затем вы можете сделать обратное выделение левой кнопкой любого участка графика снизу-вверх и справа-налево, и масштаб графика восстановится. Изменить масштаб программно можно с помощью свойства Zoom объекта View3DOptions графика: DBChart1.View3DOptions.Zoom:= 100; Это целое число, содержащее процент масштаба. Значение 100 соответствует нормальному масштабу. Попробуйте изменять масштаб от 1 до 500. Печать графика Посмотреть, как реализована печать графика с предварительным просмотром можно уже на этапе конструирования, в редакторе серий. Для этого на вкладке Chart редактора серий перейдите на внутреннюю вкладку General и нажмите кнопку Print Preview. Вы получите такое окно: Рис. 44. Окно предварительного просмотра перед печатью В этом окне можно указать используемый принтер, если он установлен на вашем компьютере, направление печати (книжный или альбомный вариант), установить поля, произвести дополнительную настройку и непосредственно дать команду на печать. Но конечный пользователь не имеет доступа к редактору серий, поэтому нам нужно вывести это окно программным путем. Для этого установите кнопку на верхнюю панель, которую мы оставили специально для этого. На кнопке напишите "Печать". Щелкните по ней дважды, чтобы сгенерировать обработчик нажатия. Однако, прежде чем вписывать в обработчик код, нам необходимо подключить модуль Teeprevi в верхний раздел uses, потому что именно в этом модуле описана процедура ChartPreview(), вызывающая данное окно. Напомню, что для этого нужно после последнего подключенного модуля поставить запятую, после чего вписать Teeprevi а уже после этого модуля поставить точку с запятой. Код процедуры нажатия на кнопку будет содержать следующую строку: ChartPreview(Form1, DBChart1); Как видим, процедура ChartPreview() имеет два параметра: форму, содержащую график, и компонент DBChart (если вы изменили имена формы или графика, установленные по умолчанию, то и здесь нужно будет указать их). Сохраните проект, скомпилируйте его и попробуйте нажать на кнопку. У вас должно выйти окно печати. Основные методы и свойства DBChart На вкладке General, где мы вызывали окно печати, под кнопкой Print Preview имеется кнопка Export. Эта кнопка выводит следующее диалоговое окно: Рис. 45. Окно экспорта графика Как мы видим, график (диаграмму) можно экспортировать в буфер обмена или графический файл одного из четырех форматов. Формат BMP наиболее универсален, но файл получается большого размера. WMF - формат метафайлов Windows, который обеспечивает хорошее качество изображения при сравнительно небольшом размере файла. EMF - такой же формат, как и WMF, но более новый, используемый в 32разрядных Windows. Последний TEE формат - специализированный для TDBChart(TChart) формат, который сохраняет не только сам график, но и все его настройки, что в дальнейшем позволяет загрузить ранее сохраненный график в компонент TDBChart. Сохранение графика программным путем осуществляется следующими методами: Procedure SaveToBitmapFile(const Filename: String); Здесь график сохраняется в указанный в параметре файл формата BMP. Procedure SaveToMetafile(const Filename: String); График сохраняется в WMF формат. Procedure SaveToMetafileEnh(const Filename: String); График сохраняется в EMF формат. Procedure SaveChartToFile(AChart:TCustomChart; const AName: String); График сохраняется в специализированный формат. Первым параметром указывается сохраняемый график. Если вы будете использовать эту процедуру, не забудьте в раздел uses добавить модуль Teestore, где описан этот метод. Если вы сохранили график или диаграмму таким образом, то в дальнейшем вы можете и загрузить его методом Procedure LoadChartFromFile(AChart:TCustomChart; const AName: String); Следующая группа свойств позволяет вращать трехмерный график, изменять его масштаб и угол наклона, что позволяет нам в программе установить богатый инструментарий для пользователя, с помощью которого он сможет изменять вид графика. При разработке программы эти же инструменты доступны вам на вкладке Chart и на внутренней вкладке 3D. Многие возможности управления графиком зависят от того, включено ли свойство Orthogonal. При включенной ортогональности многие свойства становятся недоступными. Изменить состояние этого свойства можно просто: DBChart1.View3DOptions.Orthogonal:= False; Изменить масштаб можно, присвоив свойству Zoom целое число: DBChart1.View3DOptions.Zoom:= 300; Напомню, что нормальным масштабом является число 100. Пользователю можно дать возможность изменять масштаб от 1 до 500, еще больший масштаб будет уже неудобным. Свойство Rotation отвечает за горизонтальное вращение графика, и может быть целым числом от 0 до 360, это число указывает количество градусов: DBChart1.View3DOptions.Rotation:= 100; Свойство Tilt отвечает за вертикальное вращение и также содержит целое число от 0 до 360 (градусов): DBChart1.View3DOptions.Tilt:= 120; Свойство Elevation содержит целое число, указывающее наклон графика. Число может быть также от 0 до 360: DBChart1.View3DOptions.Elevation:= 50; Свойство Perspective указывает соответственно, на перспективу графика. Это число удобней делать от 0 до 100: DBChart1.View3DOptions.Perspective:= 30; Попробуйте сделать в проекте новое окно для настройки графика, и с помощью события onChange ползунков TrackBar изменять соответствующие свойства: Рис. 46. Окно настройки графика Напомню, что компонент TrackBar хранит значение в свойстве Position, а минимальное и максимальное значение можно установить при проектировании в свойствах Min и Max. Чтобы из главного окна можно было вызывать окно с настройками, а из окна настроек менять параметры графика, оба модуля придется подключить друг к другу командой File ->Use Unit. Еще раз напомню, что если ортогональность будет включена, пользователь сможет менять только масштаб графика, попытки изменить параметры вида графика ни к чему не приведут.
«ADO. Связь с таблицей MS Access» 👇
Готовые курсовые работы и рефераты
Купить от 250 ₽
Решение задач от ИИ за 2 минуты
Решить задачу
Помощь с рефератом от нейросети
Написать ИИ
Получи помощь с рефератом от ИИ-шки
ИИ ответит за 2 минуты

Тебе могут подойти лекции

Автор(ы) А.П. Димитриев
Автор(ы) Чубукова И. А.
Автор(ы) Варыгина М. П.
Смотреть все 588 лекций
Все самое важное и интересное в Telegram

Все сервисы Справочника в твоем телефоне! Просто напиши Боту, что ты ищешь и он быстро найдет нужную статью, лекцию или пособие для тебя!

Перейти в Telegram Bot