«ПРОГРАММИРОВАНИЕ ОБЗОР НОВШЕСТВ DELPHI 2005 IDE ОСОБЕННОСТИ ПРОГРАММИРОВАНИЯ НА ПЛАТФОРМЕ WINDOWS 2000/XP/2003 СЕКРЕТЫ СОЗДАНИЯ ПРИЛОЖЕНИЙ ADO.NET МНОГОУРОВНЕВЫЕ ПРИЛОЖЕНИЯ, КОМПОНЕНТНОЕ ПРОГРАММИРОВАНИЕ ПРИМЕРЫ ...»
АНДРЕЙ БОРОВСКИЙ
ПРОГРАММИРОВАНИЕ
ОБЗОР НОВШЕСТВ
DELPHI 2005 IDE
ОСОБЕННОСТИ
ПРОГРАММИРОВАНИЯ
НА ПЛАТФОРМЕ
WINDOWS 2000/XP/2003
СЕКРЕТЫ СОЗДАНИЯ
ПРИЛОЖЕНИЙ ADO.NET
МНОГОУРОВНЕВЫЕ
ПРИЛОЖЕНИЯ,КОМПОНЕНТНОЕ
ПРОГРАММИРОВАНИЕ
ПРИМЕРЫ НАПИСАНИЯ
ГРАФИЧЕСКИХ
И МУЛЬТИМЕДИЙНЫХ
ПРИЛОЖЕНИЙ
ПРОФЕССИОНАЛЬНОЕ
ПРОГРАММИРОВАНИЕ
+CD Андрей БоровскийПРОГРАММИРОВАНИЕ
Санкт-Петербург «БХВ-Петербург»УДК 681.3.068+800.92Delphi ББК 32.973.26-018. Б Боровский А. Н.
Б83 Программирование в Delphi 2005. — СПб.: БХВ-Петербург, 2005. - 448 с : ил.
ISBN 5-94157-409- Книга посвящена разработке в Delphi 2005 различных типов приложений для Windows 2000/ХР/2003. Описаны приемы программирования Win с учетом специфики Windows 2000/XP/2003, архитектура.NET и особенности создания приложений Windows Forms и VCL.Forms. Рассмотрены разработка приложений bdExpress, WebSnap и WebBroker, а также интернетприложений с использованием компонентов Internet Direct 10. Уделено внимание многоуровневому компонентному программированию и бизнесориентированному моделированию с помощью компонентов ЕСО.
Описаны технологии ADO.NET, Borland Data Provider, ASP.NET и разработка приложений баз данных с помощью ADO.NET и ASP.NET. Рассмотрено создание мультимедиа-приложений с использованием расширенных возможностей графики GDI+, а также.NET и DirectX 9 SDK.
Для программистов УДК 681.3.068+800.92Delphi ББК 32.973.26-018. Группа подготовки издания:
Главный редактор Екатерина Кондукова Зам. главного редактора Игорь Шишигин Зав. редакцией Григорий Добин Редактор Анна Кузьмина Компьютерная верстка Ольги Сергиенко Корректор Зинаида Дмитриева Дизайн серии Инны Тачиной Оформление обложки Игоря Цырульникова Зав. производством Николай Тверских Лицензия ИД № 02429 от 24.07.00. Подписано в печать 25.03.05.
Формат 70x100Vie- Печать офсетная. Усл. печ. л. 36,12.
Тираж 4000 экз. Заказ № "БХВ-Петербург", 194354, Санкт-Петербург, ул. Есенина, 5Б.
Санитарно-эпидемиологическое заключение на продукцию No 77.99.02.953.Д.006421.11.04 от 11.11.2004 г. выдано федеральной службой по надзору в сфере защиты прав потребителей и благополучия человека.
Отпечатано с готовых диапозитивов в ГУП "Типография "Наука" 199034, Санкт-Петербург, 9 линия, ISBN 5-94157-409-6 « Боровский А. Н., О Оформление, издательство "БХВ-Петербург", Оглавление Глава 1. Новое в языке программирования Delphi Новые конструкции Delphi Language в Delphi 2005 Новые определители видимости элементов классов Директивы компилятора для.NET и ключевое слово unsafe Глава 2. Интегрированная среда разработки Delphi 2005 Глава 4. Разработка приложений баз с помощью компонентов VCL Оглавление Обработчики событий OnBeforeDispatch и OnAfterDispatch Управление памятью и программирование в Delphi для.NET Определение расположения специальных папок Windows Использование компонентов ActiveX в приложениях Windows Forms Глава 10. Разработка приложений баз данных с помощью ADO.NET Оглавление Разработка простейшего приложения ASP.NET в Delphi 2005 Анатомия приложения ASP.NET, созданного в Delphi 2005 Сохранение состояния в перерывах между транзакциями Пример сохранения состояния: программа-калькулятор Использование в шаблонах элементов управления ASP.NET Создание сервера и клиента Web-служб в Delphi 2005 Разработка собственного сервера и клиента Web-служб Глава 15. Разработка многоуровневых приложений и компонентов..... Преобразование форматов графических файлов Воспроизведение wav-файлов с помощью DirectX Предисловие Книга, которую вы держите в руках, предназначена для опытных программистов Delphi. Но строгого определения понятия "опытный программист" не существует. При написании этой книги автор считал опытным программистом Delphi любого, кто хотя бы несколько лет программировал в одной (или нескольких) версиях Delphi. Говорят, что писать книги для начинающих программистов легко. Автор написал такую книгу, и знает, что это не так. Но и писать книги для опытных программистов тоже непросто. Главная трудность заключается в подборе материала. Чего еще не знает опытный программист? Что он хотел бы узнать? Задача упрощается, когда пишешь о каком-то практически совершенно новом продукте, каким был, например, Delphi 8. Сталкиваясь с новым продуктом, каждый, в известном смысле, оказывается в положении новичка. В такой ситуации у меня, по крайней мере, не возникает вопрос, о чем нужно писать. Но Delphi 2005 отличается тем, что сочетает в себе новаторство и традицию. Новаторство заключается в объединении в одной среде разработки разных языков программирования, предназначенных, к тому же, для разных платформ (Win32 и.NET). Объем технологий, охваченных Delphi 2005, превышает объем технологий, включенных в любую другую среду разработки от Borland (о чем можно судить хотя бы по размеру дистрибутива). Среда разработчика тоже стала более удобной. Лично я нашел в ней много такого, что давно уже хотел видеть в комфортной среде IDE. Традиционность же Delphi 2005 заключается в том, что это по-прежнему старая добрая среда Delphi и почти все программы, написанные для предыдущих версий пакета, будут компилироваться и в Delphi 2005.
Таким образом, всякий, кто хочет описывать Delphi 2005, сталкивается с обилием старых и новых технологий, многие из которых, если рассматривать их досконально, заслуживают отдельной книги. К тому же технологии эти охватывают совершенно разные отрасли программирования и очень сильно различаются в концептуальном плане.
Учитывая все выше сказанное, автор решил не связывать основную идею книги с какой-то технологией программирования (если не считать само программирование в Delphi 2005). Основная идея книги заключается в другом. Авторов книг, посвященных различным программным продуктам, часто, и иногда не без основания, упрекают в "списывании" со справочных систем этих продуктов. И хотя, по моему мнению, даже такие книги могут быть полезны (учитывая, что справочные системы обычно написаны поанглийски, а английским владеют не все), основная цель этой книги как раз в том и заключается, чтобы рассказать о том, чего нет в справочной системе Delphi 2005. Естественно, невозможно написать книгу о Delphi 2005, которая бы вообще не пересекалась со справочной системой Delphi 2005. Но можно написать книгу, дополняющую и расширяющую сведения справочной системы, заполняющую пробелы и (насколько возможно) приводящую более наглядные примеры.
Информация, предоставляемая справочной системой, может быть дополнена в трех аспектах. Во-первых, справочная система — это все-таки справочник. Иногда в ней не хватает общего обзора той или иной технологии. Вовторых, иногда справочная система содержит неполные или непонятные объяснения, например, разделы справочной системы Delphi 2005, посвященные созданию служб Windows 2000+, по моему мнению, могут скорее запутать читателя, чем помочь разобраться. То же самое можно сказать об описании механизма наследования неименованных каналов в документации MSDN. Я вовсе не хочу сказать, что в моей книге не может быть таких же, или еще более серьезных, "ляпов". Каждый человек делает ошибки, и каждый имеет право исправлять ошибки других, если знает — как. Ну и наконец, справочная система не может охватить весь материал, необходимый программисту. Тем более опытному. В книге автор попытался (и надеется, что это ему удалось) рассказать о том, что должно быть наиболее интересно программистам Delphi, уже освоившим эту систему и знакомым с содержанием context help (контекстной справки).
В общем, если набор знаний программиста можно сравнить с неким программным обеспечением, то данную книгу следует рассматривать как "патч" для этого ПО или, лучше сказать, "набор патчей".
Всем серьезным программистам, работающим в Delphi, приходится читать дополнительную литературу по различным технологиям программирования, написанную с расчетом на другие языки программирования. Данная книга снабжена списком подобной литературы. В главах, посвященных разным технологиям, даются ссылки на соответствующие книги из этого списка.
Список литературы не очень длинный, но, по мнению автора, все перечисленные в нем источники являются классикой своего жанра. В список добавлено несколько интернет-сайтов, на которых программисты Delphi могут найти полезную для себя информацию.
Несколько слов нужно сказать о прилагаемом компакт-диске. Диск содержит полные исходные тексты примеров программ, описанных в книге.
В книге содержатся только авторские примеры, и читатель имеет полное право делать с этими текстами все, что ему (читателю) заблагорассудится (это право, естественно, ограничено правами издательства, прежде всего на компакт-диск в целом). Структура диска очень проста. Каждый каталог с именем С1ъОГ содержит примеры программ для главы XX. Ссылки на примеры в книге выглядят так: если в главе 9 вы встречаете ссылку типа "полные исходные тексты этой программы можно найти в каталоге EventHandlers", значит, на компакт-диске эти тексты находятся в каталоге ChO9\EventHandlers.
Компакт-диск содержит примеры не всех программ, обсуждаемых в книге.
Если какого-то примера на диске нет, это вызвано одной из двух причин:
либо текст примера слишком тривиален для размещения специальной программы на диске, либо примеры программ требуют наличия дополнительных файлов (например, объектов ActiveX сторонних приложений), на распространение которых у автора нет прав (у читателя, законно владеющего соответствующими приложениями, есть право писать такие программы, но для их распространения могут потребоваться дополнительные права).
В заключение автор хотел бы сказать, что он будет рад узнать мнение читателей о своей книге, ознакомиться с замечаниями и даже постарается ответить на вопросы, связанные с материалом книги. Для связи с автором вы можете использовать адрес электронной почты: [email protected].
ГЛАВА Новое в языке программирования Delphi Полное описание новшеств языка программирования Delphi 2005 неразумно размещать в одной главе. Прежде всего потому, что, в отличие от предыдущих версий Delphi, Delphi 2005 — это фактически не одна, а три среды программирования "в одном флаконе", так что сравнивать Delphi 2005 с предыдущими версиями следует сразу по трем направлениям. Для начала, необходимо сравнить Delphi 2005 для Win32 с Delphi 7 (Delphi 8 не обладала самостоятельной средой программирования для Win32). Delphi 2005 для Win32 отличается от Delphi 7 гораздо существеннее, чем Delphi 7 от Delphi 6.
Далее, сопоставляя Delphi 7 и Delphi 2005 для.NET мы, фактически, сравниваем две разных, хотя и совместимых, системы разработки. Но и сравнивая среду программирования.NET в Delphi 2005 и Delphi 8 мы найдем немало различий. Наконец, сравнивая Delphi 7 и Delphi 8 с одной стороны и среду программирования Delphi 2005 для С# с другой стороны, мы должны рассматривать средства разработки, ориентированные на разные языки программирования (так, как если бы мы сравнивали Delphi и C++ Builder).
Таким образом, эта глава отражает то новое, что появилось в Delphi 8 по сравнению с Delphi 7, а также добавления, сделанные в Delphi 2005 с точки зрения языка программирования Delphi Language. Следующая глава посвящена новшествам интегрированной среды разработки. Описания программирования в Delphi 2005 на языке С# приводится в главе 3. В результате первые две главы дают обзор новшеств Delphi 2005, которого может быть достаточно для программистов, не собирающихся использовать С#. Глава 6, посвященная С#, является кратким обзором этого языка программирования, предназначенного, в основном, для "перевода" исходных текстов с языка С# на Delphi Language. Впрочем, главу 6 рекомендуется прочитать всем программистам, которые собираются осваивать.NET.
Новшества в Delphi Language Описывая новые элементы языка Delphi Language и среды разработки Delphi 2005, за "точку отсчета" мы возьмем Delphi 7 (а не непосредственного предшественника Delphi 2005 — Delphi 8). Выбор для сравнения именно Delphi 7 обоснован двумя причинами. Во-первых, Delphi 2005 можно рассматривать как непосредственное продолжение Delphi 7 (Delphi 8 таким продолжением не была), а значит, многие программисты, особенно те, кто программирует для Win32, перейдут на Delphi 2005 с Delphi 7. Во-вторых, Delphi 2005 появилась менее чем год спустя после выхода Delphi 8. Это означает, что даже среди тех программистов, которые начали работать с Delphi 8 и программировать для.NET, не все еще овладели новшествами языка Delphi Language образца Delphi 8.
Новая модель идентификаторов Для того чтобы понять новые особенности Delphi 2005 (и Delphi 8), следует запомнить, что среда разработки должна теперь подчиняться неким правилам, общим для всех средств, ориентированных на платформу.NET. Многие элементы языка программирования определяются теперь не стандартами Object Pascal или Delphi Language и даже не стандартами ОС Windows.
В частности, это касается идентификаторов.
Имена многих новых идентификаторов типов в Delphi 2005 совпадают с ключевыми словами языка программирования или с идентификаторами, которые в предыдущих версиях использовались в совершенно ином контексте. Для того чтобы избежать конфликта идентификаторов, в Delphi введена новая схема обозначения идентификаторов. Рассмотрим, например, класс Туре, являющийся частью.NET Framework. В Delphi 8 этот класс определен в модуле System. Само слово туре является зарезервированным в языке Delphi Language, поэтому для объявления переменной соответствующего типа следует воспользоваться одним из двух вариантов:
MyVar : System.Type;
ИЛИ MyVar : SType;
Первый вариант является традиционным для Delphi методом разрешения конфликтов идентификаторов — указывается полное имя идентификатора, включающее имя модуля, в котором этот идентификатор объявлен. Второй вариант введен в Delphi 8 и представляет собой сокращение первого. Префикс & указывает компилятору, что следующий за ним описатель является идентификатором, а не зарезервированным словом.
Пространства имен В соответствии со структурой общей среды выполнения.NET в Delphi введено понятие "пространство имен". В рамках языка Delphi Language пространство имен можно рассматривать как дополнение концепции модуля.
Каждый модуль декларирует собственное пространство имен. Важнейшим отличием системы пространств имен от традиционной системы модулей является возможность создавать иерархии пространств имен. Система иерархических пространств имен в Delphi Language служит той же цели, что и аналогичные системы в других языках программирования — она позволяет избежать конфликтов, возникающих при совпадении имен идентификаторов. Кроме того, соблюдение иерархии пространств имен требуется средой.NET, т. к. в ней все идентификаторы включают пространства имен.
Рассмотрим все вышесказанное на простом примере. Что мы делаем при программировании на традиционном Delphi Language, когда два модуля, используемых нашим модулем, содержат объекты с одинаковыми именами?
Для того чтобы различать такие объекты, мы добавляем имя модуля к имени объекта, например, system, close. Концепция пространств имен развивает этот подход. Теперь пространство имен system содержит кроме традиционных объектов модуля System ряд дочерних пространств имен.
Мы можем объявить переменную:
Navigator : System.Xml.XPath.XPathNavigator;
Это означает, что переменная Navigator имеет тип xpathNavigator, принадлежащий пространству имен xpath, являющемуся дочерним пространством имен по отношению к пространству имен xml, которое, в свою очередь, принадлежит пространству имен system. Для того чтобы такое объявление переменной было возможным, раздел uses модуля должен содержать пространство имен System.Xml.XPath.
На практике пространства имен реализуются следующим образом. В каталоге, содержащем модули и пакеты библиотеки времени выполнения, можно найти файл System.Xml.dcpil. Этот файл, являющийся пакетом, содержит модули, реализующие дочерние пространства имен пространства имен System.xml. Модуль, в котором определяются элементы, принадлежащие пространству имен System.Xml.XPath, Должен иметь И Я System.Xml.XPath (речь идет именно об имени модуля, т. к. имя файла, в котором хранится данный модуль, должно включать еще и соответствующее расширение). Таким образом, имя модуля содержит полный путь к реализованному модулем пространству имен. Имена иерархически подчиненных пространств имен при этом разделяются точками.
Выше уже говорилось о том, что среда.NET использует полные идентификаторы пространств имен для обращения к объектам. По этой причине иерархия пространств имен Delphi 8 соответствует иерархии пространств имен.NET. В Delphi 2005 концепция пространства имен подверглась дополнительной переработке. Первое отличие: понятие пространства имен распространено теперь и на среду программирования для Win32. Поскольку пространства имен хорошо согласуются с концепцией модулей, работающие в среде Win32 программисты могут и не заметить различий, за исключением введения новой терминологии в справочной системе и подсказках в интегрированной среде разработки.
Второе отличие более существенно. В Delphi 2005 появилась возможность агрегации нескольких модулей в одном пространстве имен. Это означает, что модуль, использующий несколько других модулей, может рассматривать идентификаторы, определенные в этих модулях, как принадлежащие одному пространству имен.
Пусть, например, у нас есть два модуля: Uniti и unit2, которые мы хотим использовать в модуле Unit3. В таком случае мы можем написать в разделе uses модуля unit3:
uses My.New.Namespace in 'unitl.pas;unit2.pas';
Теперь все идентификаторы, определенные в модулях unitl и Unit2, с точки зрения модуля unit3 будут принадлежать пространству имен My.New.Namespace.
Поскольку в каждом данном пространстве имен не может быть двух одинаковых идентификаторов, идентификаторы в модулях unitl и Unit2 не должны повторяться, иначе компилятор выдаст сообщение об ошибке.
Новые типы данных Прежде чем рассказать о новых типах данных, следует отметить, что и старые типы данных изменились в новых версиях Delphi. В среде программирования, предназначенной для.NET, типы данных были приведены в соответствие требованиям.NET Framework. Тем, кто привык программировать на Delphi, новая система типов данных может показаться не такой уж и новой. Отличительной чертой Delphi является стремление использовать для переменных-объектов динамически выделяемую память. Классы библиотеки компонентов VCL и их наследники ориентированы на использование именно динамической памяти и этим они отличаются от простых типов, таких как integer или char. Среда.NET заимствовала многие архитектурные особенности Delphi, в том числе и различие типов данных. Среда.NET делит типы переменных на размерные и ссылочные. К размерным относятся простые типы, содержащие атомарные значения. Переменные размерных типов во многом похожи на обычные переменные языка Delphi Language: их инициализация выполняется с помощью оператора присваивания, а не с поНовое в языке программирования Delphi мощью вызова конструктора, и даже до инициализации эти переменные имеют значения, которые хотя и являются бессмысленными с точки зрения программы, позволяют корректно работать с этими переменными. Высвобождение памяти для переменных размерных типов также происходит несколько иначе, чем для переменных ссылочных типов. Кроме описанных выше простых типов к размерным типам относятся также типы-записи (объявленные с помощью ключевого слова record).
Ссылочные типы представляют собой указатели на объекты классов, хранящихся в куче (heap). Для инициализации переменных ссылочных типов необходимо вызывать конструкторы. Поскольку до вызова конструктора переменная ссылочного типа содержит значение nil, работать с этой переменной до ее инициализации нельзя. Переменные ссылочных типов не уничтожаются сразу, как только выходят из области видимости. Высвобождение занимаемой ими памяти выполняется сборщиком мусора по мере необходимости, и эта память вообще может не высвобождаться до конца работы программы. Для гарантированного высвобождения критических ресурсов в ссылочных типах реализован механизм финализации, который отсутствует в размерных типах. Подробнее об управлении памятью в.NET Framework говорится в главе 7.
Отношение между размерными и ссылочными типами может показаться более простым, чем оно есть на самом деле. Хотя переменные размерных типов и похожи на "обычные" переменные, между ними все же существуют различия. Переменные размерных типов являются объектами и у них есть методы.
Рассмотрим простейший пример переменной размерного типа:
В : Byte;
Тип Byte позволяет хранить в переменной целочисленные значения от О до 255, и для его инициализации не нужен конструктор, однако, в соответствии с моделью.NET, у типа Byte есть методы. Например, выражение B.ToString возвращает строковое представление значения переменной в.
Еще один метод типа Byte — метод Parse, выполняющий обратное преобразование из строки в число (листинг 1.1).
procedure TForml.ButtonlClick(Sender: TObject);
var S : String;
begin end;
Обратите внимание, что новое значение не присваивается экземпляру Byte, а возвращается как значение функции. Простой вызов в.Parse (S); не приведет к изменению значения переменной в. Вот почему переменная в встречается слева от оператора присваивания.
Следует также помнить, что все размерные типы приводимы к типу system.TObject, который является предком всех ссылочных типов. Более подробно различия между размерными и ссылочными типами среды.NET описаны в книге [7].
Многие размерные типы данных Delphi Language и.NET, будучи аналогичными по сути, имеют разные имена (размерные типы.NET определены в пространстве имен system). Для того чтобы, с одной стороны, сохранить обратную совместимость на уровне кода, а с другой стороны, облегчить использование.NET Framework, в Delphi для.NET введены типы-"двойники" традиционных типов. Каждому размерному типу.NET соответствует тип Delphi для.NET (табл. 1.1).
Кроме этого в Delphi для.NET определен тип uint64 — беззнаковый 64-битный тип, соответствующий одноименному типу.NET.
Сложнее обстоит дело с логическими типами. Все логические типы (Boolean, ByteBooi, wordBooi и LongBool), определенные в прежних версиях Delphi, сохранились и в новой версии. Сложности возникают при переводе типов. В.NET существует тип Boolean, синонимом которого является ключевое слово С# booi. В Delphi 8 тип Boolean занимает 1 байт, а тип Bool эквивалентен типу LongBool и занимает 4 байта. При этом Delphi-тип Boolean автоматически конвертируется в.NET-тип Boolean.
Тип char в.NET — 16-битный, что соответствует типу widechar в Delphi, однако традиционный тип Delphi char (занимающий 1 байт) автоматически преобразуется в тип char.NET.
Типы Real и Extended в Delphi для.NET эквивалентны типу Double. Типы Real48 и Сотр не поддерживаются при программировании для.NET. Delphiтипы single и Double соответствуют одноименным типам.NET. Тип currency теперь основан на.NET-типе Decimal, который также определен в Delphi для.NET.
Переменные всех перечисленных выше типов имеют методы, подобные методам типа Byte. С методами можно работать и при манипуляции значениями соответствующих типов. Например, строка LabelI.Caption := Sin(Pi/4).ToString;
эквивалентна строке Labell.Caption := FloatToStr(Sin(Pi/4));
Тип данных shortstring сохранен в Delphi 8 исключительно ради обратной совместимости. В.NET существует класс string, который оперирует двухбайтовыми символами и потому в большей степени соответствует типу Delphi 8 widestring, нежели типу Ansistring. Тип string в Delphi 8 соответствует классу string в.NET. Тип pchar в Delphi для.NET совпадает с типом PWideChar.
Работа со строками Для работы со строками в Delphi для.NET реализован тип string, очень похожий на одноименный тип традиционной среды для Win32. Особенность типа string заключается в том, что, будучи ссылочным типом, он допускает инициализацию с помощью операции присваивания.
Класс string содержит множество полезных методов для обработки строк.
Одни из этих методов имеют аналоги в наборе функций для работы со строками, реализованные в предыдущих версиях Delphi. Другие методы предоставляют новые возможности. Рассмотрим несколько примеров использования класса string. Методы тоиррег и ToLower позволяют изменять регистр символов строки (тоиррег переводит все символы строки в верхний регистр, ToLower — в нижний). Очевидно, что для правильного выполнения подобной операции у метода должна быть информация об используемой кодировке. Методы тоиррег и ToLower перегружены, т. е. каждый из них суГлава ществует в двух вариантах — без параметра и с параметром Cultureinfo, передающим информацию о том, как, должны преобразовываться символы.
При вызове методов без параметра необходима информация о языках и кодировках символов, предоставляемая операционной системой. Вызов методов с параметром Cultureinfo позволяет обрабатывать строки, содержащие символы кодировок, не установленных.в данной системе. Рассмотрим пример использования параметра cultureinfo (листинг 1.2).
Листинг 1.2. Пример использования класса C u l t u r e i n f o S : String;
begin S := 'здравствуй, м и р ' ;
end;
Сначала мы присваиваем переменной s строку 'здравствуй, мир', содержащую символы русского алфавита в нижнем регистре. Свойству Labeii. Caption будет присвоена строка, в которой все символы переведены в верхний регистр. Для этого вызывается метод тоиррег, которому передается параметр Cultureinfo, содержащий данные, необходимые для правильной обработки символов русского алфавита. При вызове метода тоиррег с таким параметром коды символов будут преобразованы правильно даже в нерусифицированной системе.
Параметр cultureinfo представляет собой класс, определенный в пространстве имен system.Globalization. Обратите внимание на то, что экземпляр класса создается "на месте вызова". Это новая характерная особенность, связанная со спецификой программирования для.NET. Динамически созданные классы не уничтожаются явным образом, поэтому если экземпляр класса нужен исключительно как параметр какого-либо метода, его можно создать прямо в строке вызова метода. Система.NET сама позаботится об уничтожении экземпляра класса, когда он не будет нужен (подробнее об этом будет сказано в следующих главах).
У класса cultureinfo несколько конструкторов. Параметры этих конструкторов позволяют указать используемый набор правил — "культуру" в терминологии.NET, а также, следует ли программе пользоваться настройками системы. В приведенном выше примере используемая "культура" указывалась с помощью численного идентификатора. Другой вариант конструктора cultureinfo позволяет применять для этой же цели строковый идентификатор. Например, численному значению $0419 соответствует строка 'ru-RU'.
Полный перечень идентификаторов культур можно найти в справочной системе Delphi 8.
Несмотря на все изменения, которые претерпел тип string, к нему попрежнему можно применять старые функции и приемы работы (листинг 1.3).
var S : String;
begin end;
Тип string по-прежнему можно использовать как динамический массив.
Новые конструкции языка В следующих разделах мы рассмотрим новые конструкции языка Delphi Language. Сначала будут описаны новые конструкции Delphi 2005, затем — конструкции, появившиеся еще в Delphi 8.
Новые конструкции Delphi Language в Delphi Рассматриваемые здесь конструкции применимы к средам.NET и Win32.
Цикл for in do В Delphi 2005 появился новый вариант цикла for, упрощающий перебор значений из заданного диапазона. Например, следующий фрагмент программы (листинг 1.4) приведет к распечатыванию на консоли всех заглавных букв латинского алфавита.
! Листинг 1.4. Пример цикла f o r i n do var i : Char;
begin end.
Оператор for i in ['A'..'Z'] do эквивалентен for i := 'A' to 'Z 1 do Если А — переменная-массив элементов некоторого типа, a i — переменная того же типа, то оператор for i in A do выполнит перебор всех элементов массива А С ПОМОЩЬЮ i. Удобство этого оператора заключается в том, что нам не нужно заботиться об указании нижней и верхней границ массива. Границы будут учтены автоматически.
С другой стороны, чрезмерное увлечение этой формой оператора может сделать ваш код трудночитаемым.
Очень удобно применять цикл for in do при переборе элементов класса c o l l e c t i o n, например:
Item : ListViewItem;
for Item in Listviewl.Items do...
Оператор f o r i n do подобен оператору С# foreach, но не соответствует ему полностью. В частности, цикл for i n do нельзя применять в тех случаях, когда для перебора значений используются итераторы.NET.
Встраиваемые процедуры и функции Процесс вызова процедуры или функции занимает определенное количество машинного времени. В ситуации, когда быстрота работы программы важнее чем компактность ее кода, многие языки программирования позволяют использовать встраиваемые функции. Встраиваемые функции и процедуры не вызываются программой. Вместо этого их код просто добавляется в участок вызова. Таким образом, мы выигрываем в скорости (не тратится время на вызов функции), но проигрываем в размерах программы (код функции помещается в программу много раз). Теперь встраиваемые функции (и процедуры) появились и в Delphi Language. Для того чтобы определить встраиваемую функцию или процедуру, к ее заголовку необходимо добавить ключевое слово inline (листинг 1.5).
Листинг 1.5. Определение встраиваемой функции function Sin2x(x : Double) : Double; inline;
begin Result := 2*Sin(x)*Cos(x);
end;
Встраиваемыми могут быть не только отдельные функции и процедуры, но и методы классов. Следует помнить, что компилятор не всегда делает функНовое в языке программирования Delphi цию или процедуру, помеченную как inline, встраиваемой. Во-первых, встраивание функций не всегда приводит к реальной оптимизации работы программы. Во-вторых, для встраиваемых процедур и функций существует ряд ограничений. Функция (процедура) не может быть встраиваемой, если она:
О является конструктором или деструктором класса;
D является методом класса и помечена как виртуальная, динамическая или как обработчик сообщения;
• является методом класса и обращается к другому методу с более ограниченной областью видимости;
D содержит ассемблерную вставку;
П объявлена в разделе модуля interface и обращается к элементам, объявленным В разделе implementation;
• вызывается как часть условного выражения операторов while...do и repeat...until (но та же функция может быть встраиваемой при вызове в других местах программы).
Кроме описанных выше, существуют и другие, менее распространенные случаи, когда функция или процедура не может быть встроенной.
Новые символы в идентификаторах Delphi 2005 позволяет использовать в идентификаторах символы Unicode, в том числе символы кириллицы, так что теперь в программах на Delphi можно писать, например так, как указано в листинге 1.6.
Листинг 1.6. Русские буквы в именах переменных var Ускорение, Время, Скорость, Путь : Double;
begin Ускорение := 9.81;
Время := 10;
Скорость := Ускорение*Время;
Путь := YcKopeHMe*Sqr(Время)/2;
Write('Скорость = ', Скорость, 'Путь = ', Путь);
end.
Стоит отметить, что среда.NET умеет перекодировать регистр нелатинских букв, так что в соответствии с правилами Delphi Language идентификатор время тождественен идентификатору время. В Delphi 2005 кириллицу можно использовать не только в проектах.NET, но и в проектах Win32.
Хотя мне, как автору книги про Delphi, было бы удобнее использовать кириллицу для обозначения функций и переменных (раскладку клавиатуры пришлось бы переключать гораздо реже), я все же считаю, что программы с такими переменными теряют читабельность и привычный вид, и не советую злоупотреблять этой возможностью.
Многомерные динамические массивы Еще одно новшество Delphi 2005 — возможность объявления многомерных массивов с определением длины во время выполнения программы. Рассмотрим пример использования динамических двумерных массивов (листинг 1.7).
I Листинг 1.7. Пример умножения матриц ($APPTYPE CONSOLE} SysUtils;
type TMatrFloat = array of array of Double;
( Процедура умножения двух матриц R = М1*М процедура сама определяет размерность массива R *) procedure MultMatrix(const Ml, M2 : TMatrFloat; var R : TMatrFloat);
var i, j, k : Integer;
begin if Length(Ml[0]) Length(M2) then raise Exception.Create('Число столбцов Ml не равно числу строк М2');
SetLength(R, Length(Ml));
for i : 0 to Length(Ml) - 1 do SetLength(R[i], Length(M2[0]));
for i := 0 to Length(Ml) - 1 do for j := 0 to Length(M2[0]) - 1 do begin R[i, j] := R[i, j] + Ml[i,k] *M2 [k, j];
end;
end;
II Вывод матрицы на печать procedure PrintMatrix(const M : TMatrFloat);
var i, j : Integer;
begin for i := 0 to Length(M)-l do begin for j := 0 to Length(M[i])-l do Write (M[i, j], ' ' ) ;
WriteLn;
end;
WriteLn;
end;
var i : Integer;
А, В, С : TMatrFloat;
begin SetLength(A, 3);
SetLength(A[O], 2);
SetLength(A[l], 2);
SetLength(A[2], 2);
A[0, 0] := 1; A[0, 1] := 2;
A[l, 0] \- 6; A[l, 1] := 10;
A[2, 0] := 4; A[2, 1] := 11;
SetLength(B, 2);
SetLength(B[0], 2);
SetLength(B[l], 2 ) ;
B[0, 0] := 3; B[0, 1] := 2;
B[l, 0] := 4; B[l, 1] := 1;
MultMatrix(А, В, С);
PrintMatrix (A) ;
PrintMatrix(B);
PrintMatrix(C);.
end.
В этом примере мы определяем динамический двумерный массив элементов типа Double с помощью конструкции array of array of Double Для удобства мы присваиваем новому типу имя TMatrFloat. Первое, что должна сделать процедура умножения матриц — определить размерность каждой матрицы. Выяснить размерность двумерного массива м по первому индексу можно с помощью вызова функции Length (M). Для второго индекса можно воспользоваться значением длины первой строки массива м:
Length(м[0]). Однако тут надо учесть один нюанс. Строки динамического многомерного массива не обязательно должны иметь одну и ту же длину (обратите внимание, что в тексте программы длина каждой строки матриц А и в задается отдельно). Это означает, что матрица типа TMatrFloat может быть и не прямоугольной. В принципе в процедурах MultMatrix и PrintMatrix мы должны были бы проверять длину всех строк матрицаргументов. Мы не делаем этого только для упрощения листинга.
Примечание Реализация динамических многомерных массивов в Delphi 2005 делает их похожими на массивы-указатели С#, что упрощает "перевод" программ из С# в Delphi Language.
Наконец, обращаем ваше внимание на то, что для выделения многострочного комментария были использовали символы (* и *). Этот способ выделения комментариев появился в языке Pascal очень давно и возможен до сих пор. Многие программисты считают, что лучше использовать для этой цели символы { и }. Однако, по мнению автора, в среде, интегрированной с С#, имеет смысл отказаться от такого способа выделения комментариев, чтобы не создавать путаницу (в С# символы { и } имеют совсем другой смысл).
Раз уж речь зашла о комментариях, стоит отметить новый тип комментариев.NET (появившийся еще в Delphi 8). Это комментарии, начинающиеся символами ///. От обычных эти комментарии отличаются тем, что их можно использовать при автоматической генерации документации к проекту, (текст комментариев добавляется в этом случае в файл документации).
Новые элементы, введенные в Delphi Поскольку среда Delphi 8 была ориентирована исключительно на.NET, новшества, введенные в язык Delphi Language в Delphi 8, продиктованы, в основном, требованиями.NET Framework. Все эти новшества сохранились и в Delphi 2005, хотя за пределами среды программирования для.NET они и не приносят особой пользы.
Новые определители видимости элементов классов В соответствии с общей языковой спецификацией.NET (.NET Common Language Specification) в язык Delphi Language, в дополнение к уже имеющимся, введены новые ключевые слова, определяющие видимость полей и методов класса. Эти ключевые слова — s t r i c t private и s t r i c t protected.
Элементы класса, объявленные в разделе s t r i c t private, видимы только для методов данного класса. В отличие от элементов, объявленных в разделе private, эти элементы невидимы для других процедур и функций из того же модуля.
Элементы класса, объявленные в разделе s t r i c t protected, видимы только для методов данного класса и для методов его потомков (независимо от того, в каком модуле объявлены классы-потомки). Так же, как и элементы класса, объявленные в разделе s t r i c t private, элементы класса, объявленные в разделе s t r i c t protected, невидимы для других процедур и функций из того же модуля.
Ключевое слово s t r i c t является таковым только внутри объявления класса.
За пределами объявления класса это слово может использоваться как идентификатор.
В Delphi 8 введено ключевое слово static, позволяющее объявлять статические методы классов. Смысл статических элементов классов такой же, как и в языке C++ (и С#), т. е. статические методы используются независимо от конкретных экземпляров класса — им не передается неявный параметр Self. В листинге 1.8 приводится пример объявления статического метода в классе и его вызова.
TMyClass = class public function MyFunc : Integer; static;
end;
i := TMyClass.MyFunc;
В силу своей специфики статические методы класса не могут напрямую обращаться к элементам класса, зависящим от экземпляра класса (т. к. для такого обращения требуется неявный параметр Self). Статический метод класса может работать с нестатическими методами своего класса, только если у него есть явная ссылка на экземпляр такого класса.
Статические методы классов можно использовать для определения свойств классов, и тогда эти свойства допустимо вызывать так же, как и статические методы. Для объявления таких свойств следует использовать сочетание c l a s s p r o p e r t y (ЛИСТИНГ 1.9).
\ Листинг 1.9. Объявление и вызов статического свойства класса | TMyClass = class strict private class var FX: Integer; // переменная для хранения значения свойства X strict protected function GetX: Integer; static; // выполняет: Result := FX procedure SetX(val: Integer); static; // выполняет: FX := val;
public class property X: Integer read GetX write SetX;
end;
TMyClass.X := 123;
Обратите внимание, что переменная, хранящая значение "статического" свойства, должна быть объявлена как class var (к таким переменным могут напрямую обращаться статические методы классов).
Декларация новых типов внутри классов Платформа.NET позволяет объявлять новые типы внутри объявлений классов, и Delphi поддерживает такую возможность. В листинге 1.10 показано, как объявить один класс внутри другого, как создавать экземпляры внешнего и внутреннего классов и как получать доступ к их методам.
| Листинг 1.10. Объявление класса внутри другого класса type TGarage = class public strict private constructor Create(Color : Integer); override;
function GetColor : Integer;
procedure AddCar(Car : TCar);
private end;
constructor TGarage.TCar.Create;
begin inherited Create ();
FColor : Color;
end;
function TGarage.TCar.GetColor;
begin Result : FColor;
end;
procedure TGarage.AddCar;
begin FCar := Car;
end;
procedure TForml.FormCreate(Sender: TObject);
var Garage : TGarage;
Car : TGarage.TCar;
Color : Integer;
begin Garage : TGarage.Create;
Car : TGarage.TCar.Create($ff0000) Garage.AddCar(Car);
Color : Car.GetColor;
end;
Объявление нового типа внутри класса производится с помощью ключевого слова type, так же как объявление нового типа в модуле. После этого внешний класс может использовать переменные нового типа.
Описывая методы внутреннего класса, в заголовке метода следует сначала указать имя внешнего класса, а затем имя внутреннего класса, отделенное от имени внешнего класса точкой. Таким же образом следует указывать внутренний класс и при объявлении переменной соответствующего типа.
Поскольку для полной идентификации внутреннего типа требуется указывать и имя внешнего типа, ничто не мешает нам определить внешний тип (класс) с таким же именем, как у какого-либо внутреннего типа, но с другим содержанием. Нет также причин, запрещающих нам объявить внутренний тип с таким же именем в каком-либо другом классе.
Декларация констант внутри классов Delphi 8 позволяет объявлять внутри класса не только типы, но и константы (листинг 1.11).
type TMyClass = class HelloWorld = Hello + ' World!';
WriteLn(TMyClass.HelloWorld);
Новые типы классов В Delphi 8 были введены новые спецификаторы типов классов:
Abstract
и sealed. Эти спецификаторы противоположны по своему смыслу. Если класс объявлен как abstract (абстрактный), это означает, что данный класс не может использоваться в программах непосредственно. Для того чтобы использовать функциональность абстрактного класса, необходимо определить класс-потомок этого класса (даже в том случае, если в самом абстрактном классе нет методов, помеченных как abstract).
Если класс объявлен как sealed (закрытый), на его основе нельзя создавать классы потомков. Данное требование касается всех языков программирования, поддерживаемых.NET. Это значит, что если вы опишете закрытый класс, другие программисты не смогут создавать классы-потомки указанного класса, даже если они пишут на другом языке программирования (например, С#). Точно так же и вы не можете создавать потомков закрытого класса, независимо от того, был ли этот класс написан на Delphi Language или на каком-либо другом языке. В листинге 1.12 приводится пример объявления закрытого класса.
! Листинг 1.12. Объявление закрытого класса TMyClass = class sealed private FX : Integer;
public procedure SetX(X : Integer);
function GetX : Integer;
end;
Попытка определить потомка TMyciass приведет к ошибке во время компиляции. В листинге 1.13 тот же класс определяется как abstract.
TMyClass = class abstract private FX : Integer;
public procedure SetX(X : Integer);
function GetX : Integer;
end;
TMyClassDesc = class(TMyClass) end;
Объявление класса TMyciass как абстрактного не избавляет нас от необходимости определять его методы (если, конечно, они сами не абстрактные).
А вот создать экземпляр такого класса мы не сможем. Для того чтобы получить доступ к методам класса TMyciass, нам придется создать класспотомок, например такой, как TMyClassDesc.
Перегрузка операторов в классах Глядя на изменения, происходящие в Delphi Language, приходишь к мысли, что этот язык программирования все больше становится похож на C++.
В предыдущих версиях Delphi существовала возможность перегрузки методов и функций. Начиная с Delphi 8, Delphi Language допускает перегрузку операторов, связанных с классами. К сожалению, в Delphi 2005 эта возможность поддерживается только в среде профаммирования для.NET.
Для того чтобы синтаксис определения перефуженных операторов не выходил за рамки традиционного синтаксиса Delphi Language, каждому оператору присвоено имя (табл. 1.2). Например, оператор сложения имеет имя Add, оператор вычитания — имя subtract, оператор проверки равенства — имя Equal.
LogicalNot LogiealNot(a: type) : Логическое отрицание Round Round(a: type) : resultType; Округление GreaterThanOrEqual GreaterThanOrEqual(a: type; Больше либо равно (>=) Другое удобство палитры компонентов — поле вода, позволяющее найти установленные компоненты по их именам. При вводе фрагмента имени компонента в это поле в палитре компонентов отображаются компоненты из разных групп, имена которых полностью или частично совпадают с введенным именем.
Одним из новшеств палитры инструментов Delphi 8 и 2005 (по сравнению с палитрой компонентов предыдущих версий Delphi) следует признать широкие возможности настройки внешнего вида палитры. С помощью контекстного меню палитры инструментов можно изменять размер значков компонентов, режим отображения подписей к ним, формат отображения заголовков списков палитры, а также их цвета. Кроме того, в контекстном меню имеется команда, позволяющая вывести список всех установленных компонентов, с помощью которого можно определить, какие компоненты будут отображаться в палитре, а какие — нет. Впрочем, эта возможность относится скорее к функциональности палитры, а не к ее внешнему виду.
Инспектор объектов Инспектор объектов (Object Inspector) также выглядит по-другому. Свойства и события объектов разделены на категории, и эти категории могут сворачиваться и разворачиваться так же, как и страницы панели инструментов (рис. 2.3). К неудобствам нового такого представления элементов объектов следует отнести то, что теперь имена свойств и событий не расположены в алфавитном порядке (алфавитный порядок сохраняется только внутри категорий). Впрочем, с помощью контекстного меню инспектора объектов (команда Arrange | By Name) можно вернуться к прежнему способу сортировки.
Рис. 14.5. Результат вызова метода G e t c o u n t e r в окне браузера ГЛАВА Разработка многоуровневых приложений и компонентов Многоуровневые (multi-tier) архитектуры часто используются в системах, связанных с базами данных. Чаще других применяется так называемая трехуровневая модель приложения. Delphi 2005 позволяет создавать многоуровневые приложения как на платформе Win32, так и на платформе.NET. Мы рассмотрим разработку многоуровневого приложения на платформе.NET.
Трехуровневая модель приложения В рамках трехуровневой модели (рис. 15.1) приложения разделяются на три основных слоя (уровня) — уровень представления, уровень бизнес-логики и уровень данных.
Уровень представления реализует пользовательский интерфейс (элементы управления, осуществляющие ввод, вывод и проверку вводимых данных).
На уровне бизнес-логики реализована логика работы (правила обработки данных) конкретного приложения. На уровне данных действуют механизмы обращения к хранилищам данных (файлам или базам данных) приложения.
Классическое многоуровневое программирование предполагает следование двум важным правилам: обмен данными только между смежными уровнями (например, уровень представления не должен обращаться напрямую к уровню данных) и изоляцию (инкапсуляцию) уровней. Это значит, что взаимодействие между уровнями осуществляется с помощью четко определенных интерфейсов, предоставляющих смежному уровню только те методы, которые ему необходимы. Однако на практике эти правила соблюдаются не всегда. Например, часто бывает удобно объединить уровень бизнес-логики и уровень данных. Важной особенностью многоуровневого программирования является возможность повторного использования классов, реализующих различные уровни. В литературе, посвященной многоуровневому программированию в среде.NET, встречается понятие бизнес-объекта. Бизнес-объГлава екты представляют собой объекты, реализованные на уровне бизнес-логики, в которых определены правила поведения (бизнес-правила) приложения.
Компонентное программирование В Delphi понятие компонента традиционно имеет вполне определенный смысл. В среде.NET также реализована концепция компонентов, однако понятие компонента в.NET несколько отличается от понятия компонента в среде Delphi. Хотя, как вы уже знаете, компоненты.NET используются не только при программировании многоуровневых приложений, мы рассматриваем их разработку в главе, посвященной многоуровневым приложениям, т. к. если вам придется разрабатывать.NET-компоненты самостоятельно, скорее всего, это будет связано с многоуровневыми приложениями.
В архитектуре.NET компонент — это класс, реализующий интерфейс IComponent (определенный В пространстве имен System. ComponentModel), ИЛИ класс-наследник такого класса.
Интерфейс icomponent включает в себя интерфейс IDisposabie. Этот интерфейс очень важен для работы компонентов, т. к. позволяет организовать гарантированное высвобождение занятых компонентом ресурсов в определенный момент работы программы. Напомним, что в общем случае высвоРазработка многоуровневых приложений и компонентов бождение ресурсов осуществляет сборщик мусора, который может уничтожить объект в любой момент после того, как объект перестает быть доступным приложению, а может и вообще не вызываться до завершения работы программы. Кроме того, сборщик мусора высвобождает лишь память, занимаемую объектами, причем только в том случае, если эта память была выделена стандартными средствами.NET. Сборщик мусора не может освободить другие ресурсы, занятые объектом, такие как дескрипторы файлов или графические объекты. Сборщик мусора также не может высвободить память, занятую при помощи средств, не входящих в CLR. В интерфейсе ioisposabie объявляется метод Dispose, который должен быть реализован в компоненте таким образом, чтобы ресурсы, зависимые от экземпляра компонента, высвобождались при вызове этого метода.
Рассмотрим процесс создания собственного компонента в Delphi 2005.
Прежде всего, модифицируем демонстрационную базу данных. Мы добавим в нее две таблицы: users и orders (рис. 15.2). На компакт-диске вы найдете файл Orders.sql, содержащий скрипт, генерирующий новые таблицы и связи между ними.
Таблица users содержит информацию о пользователях нашего каталога книг (имя пользователя, пароль и идентификатор пользователя). Таблица Orders хранит сведения о книгах, заказанных пользователем в каталоге. Эта таблица связывает через отношения внешнего ключа таблицы Users и Books_DotNet. Конечно, наши таблицы выглядят гораздо проще, чем базы данных из реальной жизни. Таблица users должна была бы содержать дополнительные сведения о пользователе (его "человеческое" имя, адрес и т. д.), а система заказов должна была бы позволять заказывать сразу несколько книг.
Рис. 15.2. Связи между таблицами Books_DotNet, Users и O r d e r s В качестве примера мы напишем компонент, выполняющий роль "связующего звена" между классами, взаимодействующими с базой данных и интерфейсом приложения. Delphi 2005 предоставляет несколько возможностей создания компонентов. Поскольку мы хотим сформировать класс, пригодный для повторного использования, удобнее всего разместить класс в сборке DLL, которую, как мы знаем, лучше всего создавать на основе пакета.
Откройте окно New Items, в группе Delphi for.NET Projects выберите пункт Package. Затем перейдите в подгруппу New Files группы Delphi for.NET Projects и выберите пункт Component for Windows Forms. В проект будет добавлен новый модуль с заготовкой класса TComponent. С помощью инспектора объектов переименуйте класс TComponent в orderinfo (в Delphi перед именем класса, как и любого другого типа, принято ставить префикс "Т", от слова type — тип, но в библиотеке компонентов.NET имена классов не снабжаются специальными префиксами).
Мы выбрали пункт Component for Windows Forms, т. к. создаваемый нами компонент— невизуальный. Если мы хотели создать визуальный компонент Windows Forms, нам нужно было бы выбрать пункт User Control for Windows Давайте теперь рассмотрим заготовку класса Orderinfo (листинг 15.1).
! Листинг 15.1. Заготовка класса Orderinfo unit Orders;
interface uses System.Drawing, System.Collections, System.ComponentModel;
type O r d e r i n f o = class(System.ComponentModel.Component) • {$REGION ' D e s i g n e r Managed Code'} {$ENDREGION} procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } constructor Create; overload;
Разработка многоуровневых приложений и компонентов implementation uses System.Globalization;
{$AUTOBOX ON} {$REGION 'Windows Form Designer generated code1} {$ENDREGION} constructor Orderlnfo.Create;
begin inherited Create;
// Required for Windows Form Designer support InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call end;
constructor OrderInfo.Create(Container: System.ComponentModel.IContainer);
begin inherited Create;
// Required for Windows Form Designer support Container.Add(Self) ;
InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call end;
procedure Orderlnfo.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
end.
Для сокращения размеров листинга из него исключен код, не предназначенный для ручной модификации. Класс orderinfo наследует от класса System. ComponentModel. Component, В котором реализованы интерфейсы, необходимые для компонента. Мы видим, что в заготовку класса включены три метода — два конструктора и метод Dispose. Один из конструкторов необходим для создания компонента явным образом в программе, другой — для работы с компонентом в редакторе форм.
Создаваемый нами компонент может использовать другие компоненты Delphi. Самый простой способ добавить компоненты в наш компонент — перейти на страницу визуального редактирования и перетащить нужные компоненты с палитры инструментов.
Основная задача компонента orederinfo — предоставлять информацию о заказах из системы таблиц, показанной на рис. 15.2. При реализации нашего компонента мы ограничимся одной функцией — просмотром списка заказов, сделанных каким-либо пользователем каталога (пользователь идентифицируется именем и паролем). При этом в выводимой компонентом Orederinfo информации о заказах должны присутствовать полные сведения о книгах (полученные из таблицы Books_DotNet). Таким образом, наш компонент должен обрабатывать данные сразу нескольких таблиц и сводить их воедино.
Добавим в наш компонент три компонента Bdpcommand. Один из них мы назовем SelectCoramand, другой*— GetUIDCommand, а третий — SelectBooksCommand.
Теперь можно наполнить компонент orederinfo методами, полями и свойствами, реализующими логику его работы (листинг 15.2).
! Листинг 15.2. Компонент O r d e r i n f о с добавленной в него функциональностью unit Orders;
interface uses System.Drawing, System.Collections, System.ComponentModel, System.Data, Borland.Data.Provider;
type Orderinfo = class(System.ComponentModel.Component) {$REGION 'Designer Managed Code'} Разработка многоуровневых приложений и компонентов {$ENDREGION} strict protected /// Clean up any resources being used.
procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } function GetOrdersTable : DataTable;
function GetBooksTable : DataTable;
protected UID : Integer;
FConnection : BdpConnection;
procedure Init;
public constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer);
overload;
function Authorise(const Login, Password : String) : Boolean;
property OrdersTable : DataTable read GetOrdersTable;
property BooksTable : DataTable read GetBooksTable;
published property Connection : BdpConnection read FConnection write FConnection;
end;
implementation uses System.Globalization;
f$AUTOBOX ON) f$REGION 'Windows Form Designer generated code1} f$ENDREGION} constructor Orderlnfo.Create;
begin inherited Create;
// Required for Windows Form Designer support InitializeComponent;
II TODO: Add any constructor code after InitializeComponent call Init;
end;
constructor Orderlnfo.Create(Container: System. ComponentModel. IContainer);
begin inherited Create;
// Required for Windows Form Designer support Container.Add(Self) ;
InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call Init;
end;
procedure Orderlnfo.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing) ;
end;
procedure Orderlnfo.Init;
var CmdString : String;
begin CmdString : 'SELECT Orders.OrderlD, Books_DotNet.Title,' + 'Books_DotNet.Author, Books_DotNet.Publisher, ' + 'Books_DotNet.Pub_Date FROM Books_DotNet ' + 'INNER JOIN Orders ON Books_DotNet.ID = Orders.BookID ' + SelectCommand.CommandText : CmdString;
SelectCommand.Parameters.Add('UserlD', DbType.Int32, 4);
SelectBcoksCommand.CommandText : 'SELECT * FROM Books DotNet';
Разработка многоуровневых приложений и компонентов GetUIDCommand.CorranandText : 'SELECT ID from Users WHERE ' + GetUIDCommand.Parameters.Add('Login, DbType.String);
GetUIDCommand.Parameters.Add('Password', DbType.String);
end;
function Orderlnfo.Authorise(const Login, Password : String) : Boolean;
var DR : BdpDataReader;
begin GetUIDCommand.Connection : FConnection;
GetUIDCommand.Parameters.Item['Login'].Value : Login;
GetUIDCommand.Parameters.Item['Password'].Value : Password;
GetUIDCommand.Connection.Open;
DR : GetUIDCommand.ExecuteReader;
DR.Read;
if DR.IsDBNull(O) then begin Result : False;
end else begin UID : Integer (DR. Itemf ID'] ) ;
Result : True;
end;
DR.Close;
GetUIDCommand.Connection.Close;
end;
function Orderlnfo.GetOrdersTable : DataTable;
var DR : BdpDataReader;
Row : DataRow;
begin if UID = 0 then raise Exception.Create('Ошибка авторизации');
Result := DataTable.Create('Информация о заказах');
Result.Columns.Add('Заказ');
Result.Columns.Add('Название');
Result.Columns.Add('Автор');
Result.Columns.Add('Издательство');
Result.Columns.Add('Год выхода');
SelectCommand.Connection := FConnection;
SelectCommand.Parameters.Item['UserlD'].Value := TObject(UID);
SelectCommand.Connection.Open;
DR : SelectCommand.ExecuteReader;
while DR.Read do begin Row : Result.NewRow;
Row['Заказ'] := DR.GetValue(0);
Row['Название'] := DR.GetValue(1);
Row['Автор'] := DR.GetValue(2);
Row['Издательство'] := DR.GetValue(3);
Row['Год выхода'] := DR.GetValue(4);
Result.Rows.Add(Row);
end;
DR.Close;
SelectCommand.Connection.Close;
end;
function OrderInfo.GetBooksTable : DataTable;
var DR : BdpDataReader;
Row : DataRow;
begin if UID = 0 then raise Exception.Create('Ошибка авторизации');
Result := DataTable.Create('Информация о заказах');
Result.Columns.Add('ID');
Result.Columns.Add('Название');
Result.Columns.Add('Автор');
Result.Columns.Add('Издательство');
Result.Columns.Add('Год выхода');
SelectBooksCommand.Connection := FConnection;
SelectBooksCommand.Connection.Open;
DR := SelectBooksCommand.ExecuteReader;
while DR.Read do begin Row := Result.NewRow;
Row['ID'] := DR.GetValue(0);
Row['Название'] := DR.GetValue(1);
Row['Автор'] := DR.GetValue(2);
Row['Издательство'] := DR.GetValue(3);
Row['Год выхода'] := DR.GetValue(4);
Result.Rows.Add(Row);
end;
DR.Close ;
SelectBooksCommand.Connection.Close;
end;
end.
Интерфейс компонента составляют метод Authorise, позволяющий авторизоваться пользователям каталога, свойство OrdersTable, возвращающее таблицу заказов текущего пользователя, свойство BooksTabie, возвращающее таблицу всех книг каталога, и свойство connection, связывающее компонент С К о м п о н е н т о м BdpConnection. ПОСКОЛЬКУ СВОЙСТВО Connection р а с п о л о ж е н о в разделе published, к нему можно обращаться из инспектора объектов.
Метод i n i t служит для инициализации исходных значений компонента.
В нем задаются тексты SQL-команд и параметры соответствующих объектов BdpCommand. Объекту seiectcommand мы присваиваем довольно сложное выражение на языке SQL. Мы не будем разбирать синтаксис этого выражения.
Интересующимся рекомендуется ознакомиться с документацией, прилагаемой к SQL Server 2000.
Метод Authorise делает запрос к таблице users, передавая данные об имени пользователя и пароле. Если соответствующая комбинация в таблице users существует, запрос возвращает идентификатор пользователя, который записывается в поле UID, а метод Authorise возвращает значение True. Если заданная комбинация имени пользователя и пароля в таблице отсутствует, полю UID присваивается значение 0 и метод Authorise возвращает значение False (в таблице users не должно быть пользователя с идентификатором 0).
В методах GetOrdersTabie и GetBooksTabie мы проверяем сначала, прошел ли пользователь авторизацию. Если поле UID равно 0, значит, пользователь не авторизовался, и вызывается исключение. Если пользователь зарегистрировался, мы создаем экземпляры соответствующих таблиц, задаем имена столбцов и с помощью команд выборки данных из таблиц orders и Books_DotNet заполняем таблицы.
После того как сборка, содержащая компонент, скомпилирована (полные исходные тексты компонента можно найти в каталоге Orderlnfo), компонент можно установить так же, как и любой другой компонент.NET. Для этого с помощью команды контекстного меню палитры инструментов Installed.NET Components... нужно открыть одноименное окно (рис. 15.3) и с помощью кнопки Select an Assembly... выбрать сборку Orderlnfo.dll.
В результате компонент orderlnfo будет установлен автоматически. По умолчанию новый компонент будет добавлен в категорию General. В палитре инструментов должна появиться соответствующая страница с новым компонентом.
Теперь вы можете использовать компонент Orderlnfo в приложениях Windows Forms и ASP.NET. Создайте новый проект приложения Windows Forms и перенесите в него компонент orderinfo. Добавьте в проект приложения компонент Dbpconnection, настройте его на соединение с базой данных DelphiDemo, а ссылку на объект DbpConnectioni присвойте свойству connection объекта orderinfoi. Разместите два компонента TextBox для ввода имени пользователя и пароля. Далее нам понадобятся компоненты для отображения данных. Разместите в форме приложения компоненты DataGrid и Button. Исходный текст приложения приводится в листинге 15.3.
|й§ Installed.NET Components.NET Components ActiveX Components j.NET VCL Components Assembly Search Paths | j Листинг 15.3. Программа, использующая компонент O r d e r i n f o unit WinForml;
interface System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data, Orders, Borland.Data.Provider;
type TWinForml = class(System.Windows.Forms.Form) {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected /// Clean up any resources being used.
Разработка многоуровневых приложений и компонентов procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } public constructor Create;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForml))] implementation ($AUTOB0X ON} {$REGION 'Windows Form Designer generated code'} ($ENDREGION} procedure TWinForml.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForml.Create;
begin inherited Create;
// Required for Windows Form Designer support InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call end;
procedure TWinForml.EnterButton_Click(sender: System.Object;
var ОТ : DataTable;
begin if not Orderlnfol.Authorise(LoginTextBox.Text, PasswordTextBox.Text) then raise Exception.Create('Ошибка авторизации');
ОТ := Orderlnfol.OrdersTable;
DataGridl.DataSource := ОТ;
DataGridl.CaptionText := OT.TableName;
end;
end.
Наш компонент orderinfo не позволяет отображать данные в процессе редактирования приложения. Мы могли бы написать этот компонент таким образом, чтобы он передавал данные на этапе редактирования, однако в этом нет большого смысла, т. к. для передачи данных компоненту требуется имя пользователя и пароль, которые все равно должны вводиться во время выполнения программы (если, конечно, вы не собираетесь создавать программу с фиксированным именем пользователя и паролем).
Имя пользователя и пароль передаются объекту orderinfoi в методе 3uttoni_ciick (при помощи метода Authorise). В этом же методе мы получаем таблицу OrdersTabie объекта Orderinfoi. Поскольку при каждом обращении к свойству OrdersTabie создается новый экземпляр объекта-таблицы имеет смысл использовать промежуточную переменную от, чтобы сократить затраты на вычисления. Объект DataGridl отображает данные таблицы (рис. 15.4). Исходные тексты программы можно найти в каталоге OrderViewApp.
Для того чтобы программа работала корректно, таблицы, естественно, должны быть заполнены соответствующими значениями.
Наш компонент Orderinfo обладает минимальной функциональностью. Мы можем расширить ее, создав компонент-наследник компонента orderinfo, с возможностью добавлять и удалять заказы.
Создайте новый пакет, с помощью менеджера проектов в раздел Requires добавьте сборку Orderlnfo.dll (ее, естественно, нужно предварительно скомРазработка многоуровневых приложений и компонентов пилировать). Новый компонент мы назовем ManageOrders, а модуль, в котором он реализован, сохраним под именем Orders.Manage.pas. Добавим в модуль четыре компонента BdpCommand (назовем И RemoveOrder, GetBooksIDs, GetMaxOrderiD и AddOrder). Осталась самая малость — написать код класса ManageOrders (ЛИСТИНГ 15.4).
! ЛИСТИНГ 15.4. Модуль Orders.Manage unit Orders.Manage;
interface System.Drawing, System.Collections, System.ComponentModel, Orders, System.Data, Borland.Data.Provider, Borland.Data.Common;
type ManageOrders = class(Orderlnfo) {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected /// Clean up any resources being used.
procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } procedure Init;
public constructor Create; overload;
constructor Create(Container: System.ComponentModel.IContainer);
overload;
procedure NewOrder(BookID : Integer);
procedure DeleteOrder(OrderlD : Integer);
end;
imp 1 emen t at i on uses System.Globalization;
{$AUTOBOX ON} {$REGION 'Windows Form Designer generated code'} {$ENDREGION} constructor ManageOrders.Create;
begin inherited Create;
// Required for Windows Form Designer support InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call Intend;
constructor ManageOrders.Create(Container: System. ComponentModel.IContainer);
begin inherited Create;
// Required for Windows Form Designer support Container.Add(Self);
InitializeComponent;
// TODO: Add any constructor code after InitializeComponent call Init;
end;
procedure ManageOrders.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
procedure ManageOrders.Init;
begin GetBooksIDs.CommandText : 'SELECT ID FROM Books_DotNet WHERE ID = ?';
GetBooksIDs.Parameters.Add('ID', BdpType.Int32);
GetMaxOrderlD.CommandText : 'SELECT Max(OrderlD) FROM Orders';
RemoveOrder.CommandText := 'DELETE FROM Orders WHERE (OrderlD = ?) AND (UserlD = ?)';
Разработка многоуровневых приложений и компонентов RemoveOrder.Parameters.Add('OrderlD', BdpType.Int32);
RemoveOrder.Parameters.Add('UserlD', BdpType.Int32);
AddOrder.CommandText : 'INSERT INTO Orders VALUES(?, ?, ?)';
AddOrder.Parameters.Add('OrderlDl', BdpType.Int32);
AddOrder.Parameters.Add('UserlDl', BdpType.Int32);
AddOrder.Parameters.Add('BooklDl', BdpType.Int32);
end;
procedure ManageOrders.NewOrder(BookID : Integer);
var DR : BdpDataReader;
MaxOrder : Integer;
begin raise Exception.Create('Ошибка авторизации');
GetBooksIDs.Connection := FConnection;
GetBooksIDs.Parameters.Item['ID'].Value := BookID;
GetBooksIDs.Connection.Open;
DR := GetBooksIDs.ExecuteReader;
if not DR.Read then raise Exception.Create('запись отсутствует');
GetMaxOrderID.Connection := FConnection;
DR := MaxOrderlD.ExecuteReader;
if not DR.Read then MaxOrder := begin MaxOrder := Integer(DR.GetValue(0));
Inc(MaxOrder);
AddOrder.Connection := FConnection;
AddOrder.Parameters.Item['OrderlDl'].Value := MaxOrder;
AddOrder.Parameters.Item['UserlDl'].Value := UID;
AddOrder.Parameters.Item['BooklDl'].Value := BookID;
AddOrder.ExecuteNonQuery;
FConnection. Closerend;
procedure ManageOrders.DeleteOrder(OrderlD : Integer);
begin if UID = 0 then raise Exception.Create('Ошибка авторизации');
RemoveOrder.Connection := FConnection;
RemoveOrder.Parameters.Item['OrderlD'].Value := OrderlD;
RemoveOrder.Parameters.Item['UserID'].Value := UID;
RemoveOrder.Connection.Open;
RemoveOrder.ExecuteNonQuery;
RemoveOrder.Connection.Close;
end;
end.
Первое, что мы видим, — класс Manageorders является наследником класса orderinfo. Именно для этого некоторые элементы класса orderinfo были помещены в раздел protected. В классе-потомке мы добавили два новых метода — NewOrder и DeieteOrder, служащих, соответственно, для добавления и удаления заказов. Методу NewOrder в качестве параметра передается идентификатор книги из каталога. Команда GetBooksiDs используется для того, чтобы определить, что в каталоге действительно есть книга с данным идентификатором (иначе по ошибке можно было бы создать заказ на несуществующую книгу). Далее нам нужно сгенерировать идентификатор для нового заказа. С помощью команды MaxOrderiD мы находим наибольший идентификатор заказа в таблице заказов и добавляем к нему единицу. Далее с помощью команды AddOrder мы добавляем в таблицу Orders новую запись.
Текст процедуры DeieteOrder выглядит гораздо проще. С помощью команды RemoveOrder мы удаляем из таблицы запись, заданную идентификатором заказа. Обратите внимание на текст SQL-команды для удаления записи. Эта команда построена так, что невозможно удалить заказ, если он не принадлежит текущему пользователю (и нам не нужно проверять принадлежность заказа в компоненте). Полный исходный текст компонента Manageorders находится в одноименном каталоге. Вы можете установить компонент Manageorders по той же процедуре, что и для компонента orderinfo, и тогда он тоже появится на странице General палитры инструментов.
Многоуровневое приложение ASP.NET В этом разделе мы напишем многоуровневое приложение ASP.NET, использующее компонент Manageorders и имитирующее работу сайта интернет-магазина.
Создайте новый проект ASP.NET. Первой страницей нашего приложения будет страница авторизации пользователя. Кроме нее в нашем приложении будет страница, отображающая каталог книг и позволяющая пользователю сделать заказ, а также страница, отображающая перечень заказов данного пользователя, на которой можно будет удалить сделанный заказ.
Сохраните ASPX-страницу под именем AuthForm.aspx и переименуйте класс TWebFormi в TAuthForm. Перенесите в форму страницы компоненты BdpConnection И Manageorders. Н а с т р о й т е объект BdpConnectionl н а СВЯЗЬ С базой д а н н ы х DelphiDemo, а СВОЙСТВУ C o n n e c t i o n объекта ManageOrdersl ПриСВОЙте ССЫЛКУ на объект BdpConnectionl.
В форме страницы разместите два компонента TextBox. Один компонент, предназначенный для ввода имени пользователя, назовите userNameTextBox, другой, предназначенный для ввода пароля — PasswordTextBox (его свойству TextMode присвойте значение Password). Добавьте в форму страницы кнопку Button. Она нужна для отправки данных на сервер. Рассмотрим обработчик Buttoni_ciick — единственный обработчик, который понадобится нам для страницы AuthForm.aspx (листинг 15.5).
Листинг 15.5. Обработчик B u t t o n l _ c l i c k begin i f Page.Session.Item['ManageOrders'] = n i l then i f ManageOrdersl.Authorise(UserNameTextBox.Text, Server.Transfer('ShowBooks.aspx');
Server.Transfer('ShowBooks.aspx');
end;
В нашем приложении ASP.NET нам необходимо сохранять состояние в перерывах между транзакциями отдельно для каждого пользователя приложения. Проще всего сделать это, сохранив объект ManageOrdersl в коллекции page.session. Если объект класса ManageOrders содержится в коллекции Page.Session, значит, пользователь уже авторизовался. В этом случае мы сразу перенаправляем пользователя на страницу ShowBooks.aspx, на которой отображается список книг. Если пользователь еще не авторизовался, мы вызываем метод Authorise, и если авторизация прошла успешно, помещаем объект ManageOrdersl В Коллекцию Page. S e s s i o n И ОПЯТЬ ж е п е р е н а п р а в л я е м пользователя на страницу ShowBooks.aspx.
Теперь нам следует создать страницу ShowBooks.aspx. Добавьте новую страницу ASP.NET, сохраните ее под именем ShowBooks.aspx, переименуйте класс TWebFormi в TShowBooksForm. Перенесите в форму новой страницы компонент BdpConnection, и настройте его на соединение с базой данных DeiphiDemo. Добавьте в форму компонент DataGrid. Теперь нам потребуется наПИСаТЬ Обработчик СОбыТИЯ Page_Load ДЛЯ МОДУЛЯ TShowBooksForm (ЛИСТИНГ 15.6).
\ ЛИСТИНГ 15.6. Обработчик события Page_Load ДЛЯ М О дул Я TShowBooksForm procedure TShowBooksForm.Page_Load(sender: System.Object;
begin if Page.Session.Item['ManageOrders'] = nil then Server.Transfer('AuthForm.aspx');
MO := ManageOrders(Page.Session.Item['ManageOrders']);
MO.Connection := BdpConnectionl;
DataGridl.DataSource := MO.BooksTable;
if not Page.IsPostBack then DataBind;
end;
В обработчике Page_Load мы, прежде всего, проверяем наличие в коллекции session объекта класса ManageOrders. Если такового объекта в коллекции нет, пользователь перенаправляется на страницу авторизации. Далее мы извлекаем объект ManageOrders из коллекции и присваиваем его свойству Connenction ссылку на объект класса BdpConnectionl. Объект нужен нам на этой странице для добавления заказов. Мы не размещаем компонент ManageOrders в форме страницы, а используем объект, созданный на странице авторизации. Это логично, поскольку данный объект уже содержит идентификатор текущего пользователя. Поле мо должно быть добавлено в класс TShowBooksForm явным образом.
Теперь нам нужно поместить на страницу кнопку выбора. Откройте редактор свойств объекта DataGridl (см. главу 13). В данном случае у нас нет таблицы, содержащей данные во время редактирования, т. к. их можно получить только во время выполнения программы. Мы воспользуемся редактором свойств компонента DataGrid только для добавления столбца кнопок.
Остальные столбцы будут добавлены автоматически во время выполнения программы.
Для этого в списке Available Columns выберем группу Button Column, раскроем эту группу и выберем пункт Select. После этого в группе Selected Columns появится элемент Select. В строке ввода Text заменим подпись кнопки на Выбрать. В списке Button type отметим пункт PushButton. Свойству DataKeyField объекта DataGridl мы присвоим значение ID (это поле содержит идентификаторы книг в каталоге).
Теперь нам нужен обработчик события itemCommand для компонента DataGridl (листинг 15.7).
p r o c e d u r e TShowBooksForm.DataGridl_ItemCommand(source: System.Object;
e: System.Web.01.WebControls.DataGridCommandEventArgs);
begin MO.NewOrder( StrToInt((DataGrid.DataKeys.Item[e.Item.Itemlndex]).ToString));
end;
Обработчик DataGridi_itemCommand вызывается при нажатии на любую из кнопок Выбрать в таблице. В этом обработчике мы используем метод NewOrder класса orderinfo. Выражение Integer(BooksDataGrid.DataKeys.Item[e.Item.Itemlndex]) позволяет получить значение поля ID таблицы Books_DotNet для выбранной книги (для этого мы присваивали значение ID свойству DataKeyField).
Добавим на страницу кнопку перехода на страницу заказов. Обработчик события click этой кнопки состоит из одной строки:
Server.Transfer('ShowOrders.aspx');
Таким образом, мы создали таблицу, которая содержит информацию о книгах каталога, а кроме того — столбец кнопок выбора книги (рис. 15.5).
• h« :/ c h sВид8Избранноеp am coot nen t x oe Адрес;. Ш | http://locafho5t:8080/WebAppiication 1/Auth.aspx часа Создание приложений ASP.NET, XML и [•
OCHOEUASPI-^THVBNET
Программирование Web-сервисов для.NET.Библиотека программиста Программирование на платформе.NET Visual Basic NET для "чайников" Обработчик Buttoni_ciick обрабатывает событие, возникающее при щелчке по кнопке Просмотр заказов. Этот обработчик просто перенаправляет пользователя на страницу ShowOrders.aspx, предназначенную для отображения списка заказов пользователя, возвращаемого свойством ordersTabie объекта класса ManageOrders.
Приступим теперь к созданию этой страницы. Добавьте в проект новую страницу ASP.NET, сохраните ее под именем ShowOrders.aspx, переименуйте класс TWebForml В TShowOrdersForm.
Перенесите в форму новой страницы компонент BdpConnection, и настройте объект BdpConnectioni на соединение с базой данных DeiphiDemo. Перенесите в форму страницы компонент DataGrid и добавьте кнопку Удалить тем же способом, которым мы добавляли кнопку Выбрать на странице ShowBooks.aspx. Компонент DataGrid заполняется данными в обработчике Page_Load так же, как в листинге 15.7 с той разницей, что мы используем таблицу OrdersTable.
В результате компонент DataGrid формирует таблицу заказов (рис. 15.6).
H t t :/o ah s: 0 oW b p lc to 2W b om. s x.M,,;ros,,n internet E p r r DataGridl ItemCoramand (ЛИСТИНГ 15.8).
ЛИСТИНГ 15.8. Метод DataGridl ItemCommand procedure TShowOrdersForm.DataGridl_ItemCommand(source: System.Object;
e: System.Web.UI.WebControls.J)ataGridCommandEventArgs);
begin MO.DeleteOrder(StrToInt( DataGridl.DataKeys.Item[e.Item.Itemlndex].ToString));
Server.Transfer('ShowOrders.aspx');
end;
Разработка многоуровневых приложений и компонентов Для удаления заказа мы используем метод DeieteOrder. Идентификатор заказа получается таким же способом, как и идентификатор записи о книге на странице ShowBooks.aspx. С помощью метода server.Transfer мы вызываем принудительную перезагрузку страницы ShowOrders.aspx для того, чтобы отобразить на странице результаты операции удаления.
На странице есть еще кнопки Выход и Каталог. Обработчики событий этих кнопок представлены в листинге 15.9.
I Листинг 15.9. Обработчики событий кнопок Выход и Каталог j procedure TShowOrdersForm.Buttonl_Click(sender: System.Object;
begin Page.Session.Remove('ManageOrders');
Server.Transfer('AuthForm.aspx');
end;
procedure TShowOrdersForm.Button2_Click(sender: System.Object;
begin Server.Transfer('ShowBooks.aspx');
end;
Обработчик Buttoniciick вызывается при щелчке по кнопке Выход. В нем мы удаляем объект класса Orderinfo из коллекции Session и перенаправляем пользователя на страницу Auth.aspx, так что для продолжения работы с приложением пользователю придется снова авторизоваться. Обработчик Button2_ciick вызывается при щелчке по кнопке Каталог. В этом обработчике мы перенаправляем пользователя на страницу ShowBooks.aspx, на которой он снова может сделать заказ. Исходный текст Web-приложения можно найти в каталоге OrderClient.
Таким образом, мы создали многоуровневое приложение с использованием компонента Orderinfo. Из схемы приложения (рис. 15.7) можно видеть, что все взаимодействия между уровнем представления (страницы ASPX) и уровнем данных (база данных DeiphiDemo) выполняются через промежуточный уровень (бизнес-уровень), реализованный в компоненте ManageOrders. Промежуточный компонент берет на себя всю механику взаимодействия с базой данных, а клиентские приложения получают простой интерфейс, абстрагированный от конкретной реализации.
Рис. 15.7. Схема многоуровневого приложения с использованием компонента O r d e r i n f о ГЛАВА Графика и мультимедиа в Delphi Мы уже встречались с изображениями при работе в.NET в главе 9, посвященной Windows Forms. Но возможности.NET (и Delphi 2005) этим не исчерпываются. В.NET можно выполнять обработку изображений, воспроизводить анимацию, видео и звук. В.NET можно программировать трехмерную графику, используя интерфейсы DirectX и OpenGL. Я пока еще не знаю, чтобы кто-нибудь писал игры на.NET, но уверен, что это вполне возможно! Замечательным руководством по обработке двумерных изображений в.NET (с примерами на С#) является книга [11].
Работа с изображениями В библиотеке классов.NET существует класс, специально предназначенный для работы с изображениями. Это класс image, определенный в пространстве имен System. Drawing. Ранее мы использовали этот класс для загрузки изображений из файлов. В следующих разделах мы рассмотрим дополнительные возможности, предоставляемые классом image.
Просмотр изображений Класс image позволяет создавать уменьшенные копии изображений (миниатюры) для просмотра (thumbnails). Эту операцию следует отличать от операции простого масштабирования файлов. Дело в том, что многие форматы графических файлов содержат уменьшенные варианты изображений, специально предназначенные для просмотра. Статический метод GetThumbnaiiimage класса image позволяет извлекать из файлов поддерживаемых форматов эти изображения.
Примечание Если графический файл не содержит миниатюры, метод GetThumbnaiiimage выполняет масштабирования файла, что может привести к потере качества.
Напишем приложение Windows Forms, позволяющее просматривать изображения, хранящиеся в каталоге C:\Windows\Web\Wallpaper (рис. 16.1).
Создайте новое приложение Windows Forms и разместите в его форме компонент Panel. С помощью свойства Anchor настройте объект Paneil таким образом, чтобы он занимал все пространство формы и изменял свои размеры вместе с формой. Вся работа по выводу изображений будет выполняться в обработчике события Paint (листинг 16.1) объекта Paneil. Наша задача заключается в том, чтобы получить имена всех файлов из каталога C:\Windows\Web\Wallpaper и для файлов изображений вывести их миниатюры в поле объекта Paneil. Полный текст программы можно найти в каталоге Viewlmages.
! Листинг 16.1. Обработчик события P a i n t о б ъ е к т а P a n e i l •.;.•:• procedure TWinForm31.Panell_Paint(sender: System.Object;
const var i : Integer;
Ext : String;
Files := System.10.Directory.GetFiles('C:\Windows\Web\Wallpaper');
for i := 0 to Length(Files) - 1 do begin Ext := Files[i].Substring(Length(Files[i])-2) ;
if GrExts.IndexOf(Ext) > 0 then Newlmg := Image.FromFile(Files[i]).GetThumbnaillmage(100, 100, e.Graphics.Drawlmage(Newlmg, Point.Create((i mod 5)*150, end;
end;
Вращение изображений Метод RotateFlip класса image позволяет вращать изображения на 90, 180, 270° по часовой стрелке, а также делать зеркальные отражения изображения в горизонтальной и вертикальной плоскостях. Рассмотрим пример программы Windows Forms, вращающей изображение (листинг 16.2). Эта программа использует компонент PictureBox для показа изображения и компонент Timer для выполнения поворотов через регулярные промежутки времени.
u n i t WinForml;
interface uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms;
type TWinForml = class(System.Windows.Forms.Form) {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected procedure Dispose(Disposing: Boolean); override;
private { Private Declarations ) public constructor Create;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForml))] implementation {$REGION 'Windows Form Designer generated code'} {$ENDREGION} procedure TWinForml.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForml.Create;
begin inherited Create;
InitializeComponent;
end;
procedure TWinForml.Timerl_Tick(sender: System.Object;
begin PictureBoxl.Image.RotateFlip(RotateFlipType.Rotate90FlipNone);
PictureBoxl.Refresh;
end;
procedure TWinForml.TWinForml_Load(sender: System.Object;
begin PictureBoxl.Image := Image.FromFile('С:\Windows\Кофейня.bmp');
Timer1.Interval := 500;
Timerl.Enabled := True;
end;
end.
Полный текст можно найти в каталоге Rotlmages.
Отсечение изображений Термином отсечение в компьютерной графике обозначается ограничение области вывода изображений каким-либо графическим примитивом (прямой, замкнутой областью и т. п.). Отсечение может быть не только внешним, т. е. задающим.внешние границы для вывода изображения, но и внутренним, когда в области изображения выделяются участки, в которых изображение не выводится. GDI+ позволяет применять оба типа отсечений и предоставляет широкий набор инструментов для формирования границ области отсечения. Для того чтобы выполнить отсечение изображения, нам необходимо сперва создать объект-контур (graphics path), установить созданный контур в качестве границы отсечения и вывести изображение. Эти задачи решает программа из листинга 16.3.
В этом и других примерах будет использоваться файл изображения Фото-jpg, хранящийся на компакт-диске. На всякий случай я предупреждаю, что эту фотографию я сделал сам и дарю ее всем читателям моей книги.
; Листинг 16.3. Программа отсечения изображений' unit WinForml;
interface uses System.Drawing, System.Drawing.Drawing2D, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data;
type TWinForml = class(System.Windows.Forms.Form) {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } GP : GraphicsPath;
procedure CreatePath(Offs : Point);
constructor Create;
14 3ак. [assembly: RuntimeRequiredAttribute(TypeOf(TWinForml))] implementation {$REGION 'Windows Form Designer generated code'} ($ENDREGION} procedure TWinForml.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForml.Create;
begin inherited Create;
InitializeComponent;
end;
procedure TWinForml.TWinForml_Paint(sender: System.Object;
e: System.Windows.Forms.PaintEventArgs);
begin e.Graphics.SetClip(GP) ;
e.Graphics.Drawlmage(Img, 10, 10);
end;
procedure TWinForml.TWinForml_Load(sender: System.Object;
begin Img := Image.FromFile('..\Фото.jpg');
CreatePath(Point.Create(10, 10)) ;
end;
procedure TWinForml.CreatePath(Offs : Point);
begin GP := GraphicsPath.Create(FillMode.Alternate);
GP.AddEllipse(Offs.X, Offs.Y, Img.Width, Img.Height);
end;
end.
В методе TwinFormi_Load мы загружаем изображение из файла и вызываем метод CreateFath. Метод createPath создает эллиптический контур отсечения при помощи объекта GP класса GraphicsPath. Параметр offs задает смещение контура относительно начала координат. После того как контур создан В Объекте GP, В Методе TWinForml_Paint (обработчике событий Paint) МЫ устанавливаем границу отсечения для изображения выводимого в окне формы с помощью метода Setclip объекта Graphics. В результате изображение оказывается ограниченным восьмиугольной рамкой (рис. 16.2).
Для выполнения внутреннего отсечения нам придется создать область (region). В листинге 16.4 приводится текст программы, выполняющей внутреннее отсечение.
{ Листинг 16.4. Программа, выполняющая внутреннее отсечение unit WinForml;
interface uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data, System.Drawing.Drawing2D;
type TWinForml = class(System.Windows.Forms.Form) {$REGION 'Designer Managed Code1} {$ENDREGION} strict protected /// Clean up any resources being used.
procedure Dispose (Disposing: Boolean); overriderprivate ( Private Declarations } Img : Image;
BgBrush : System. Drawing.Drawing2D.HatchBrush;
class function GetRegion(Offs : Point) : Region; staticpublic constructor Create;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForml))] implementation {$AUTOBOX ON} {$REGION 'Windows Form Designer generated code'} {$ENDREGION} procedure TWinForml.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForml.Create;
begin inherited Create;
InitializeComponent;
BgBrush : HatchBrush.Create(HatchStyle.DiagonalCross, Color.Yellow, Img := Image.FromFile('..\Фото.jpg');
end;
procedure TWinForml.TWinForm2_Paint(sender: System.Object;
e: System.Windows.Forms.PaintEventArgs);
begin e.Graphics.FillRectangle(BgBrush, Self.ClientRectangle);
e.Graphics.ExcludeClip(GetRegion(Point.Create(55, 55)));
e.Graphics.Drawlmage(Img, 15, 15);
end;
class function TWinForml.GetRegion(Offs : Point) : Region;
var Points : array[0..2] of Point;
GP : GraphicsPath;
begin Points[0] := Point.Create(Offs.X, Offs.Y);
Points[l] := Point.Create(200 + Offs.X, Offs.Y);
Points[2] := Point.Create(100 + Offs.X, 120 + Offs.Y);
GP := GraphicsPath.Create(FillMode.Alternate);
GP.AddPolygon(Points);
Result := System.Drawing.Region.Create(GP);
end;
end.
Область отсечения формируется в статической функции GetRegion, на основе треугольного контура, созданного так же, как и в предыдущем случае.
В методе TWinFormiPaint мы сначала закрашиваем все окно формы, используя кисть BgBrush. Далее мы задаем область отсечения с помощью меГлава тода Exciudeciip объекта Graphics, а затем выводим загруженное из файла изображение. В результате изображение не выводится там, где задана область отсечения (рис. 16.3).
Исходные тексты вы найдете в каталоге IntClipping. Описанными примерами возможности отсечения не ограничиваются. Вы можете комбинировать внешнее и внутреннее отсечения и задавать границы областей отсечения, используя не только отрезки прямых линий, но и дуги эллипса и кривые Безье.
Другие трансформации изображений Наклон изображений Метод Drawimage позволяет вписать прямоугольное изображение в параллелограмм, заданный координатами трех вершин. При этом изображение автоматически масштабируется до размеров параллелограмма. Рассмотрим простую программу, выполняющую наклон изображений (листинг 16.5).
u n i t WinForml;
interface uses System.Drawing, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data;
type TWinForml = class(System.Windows.Forms.Form) * {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected /// Clean up any resources being used.
procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } Img : Image;
Points : array[0..2] of Point;
public constructor Create;
end;
[assembly: RuntimeRequiredAttribute(TypeOf(TWinForml))] implementation ($AUTOBOX ON} ($REGION 'Windows Form Designer generated code'} ($ENDREGION} procedure TWinForml.Dispose(Disposing: Boolean);
begin if Disposing then begin if Components nil then Components.Dispose();
end;
inherited Dispose(Disposing);
end;
constructor TWinForml.Create;
begin inherited Create;
InitializeComponent;
Img := Image.FromFile('..\Фото.jpg');
Points[0] := Point.Create(80, 0);
Points[1] := Point.Create(260, 0);
Points[2] := Point.Create(0, 120);
end;
procedure TWinForml.TWinForml_Paint(sender: System.Object;
e: System. Windows.Forms.PaintEventArgs);
begin e.Graphics.Drawlmage(Img, Points);
end;
end.
Координаты трех вершин параллелограмма задаются в порядке: левая верхняя, правая верхняя, левая нижняя. В результате получается наклонное изображение в измененном масштабе (рис. 16.4).
Создание полупрозрачных изображений Иногда при выводе изображения бывает необходимо наложить на него какие-либо графические элементы, не скрывающие основного изображения.
Самый удобный способ сделать это — использовать полупрозрачные элементы, цвет которых задается с помощью дополнительного канала прозрачности. Рассмотрим пример (листинг 16.6), исходные тексты — в каталоге BlendDemo.
unit WinForml;
interface uses System.Drawing, System.Drawing.Drawing2D, System.Collections, System.ComponentModel, System.Windows.Forms, System.Data;
type TWinForm35 = class(System.Windows.Forms.Form) {$REGION 'Designer Managed Code'} {$ENDREGION} strict protected /// Clean up any resources being used.
procedure Dispose(Disposing: Boolean); override;
private { Private Declarations } TransBrush, TransBrush2 : SolidBrush;
public constructor Create;
end;