Урока Borland Delphi

         

В этой главе Вы узнаете


  32 Урока по Delphi
Урок 21: Объект TQuery
 
 
  Содержание Урока 21: Краткий Обзор Основные понятия TQuery Свойство SQL TQuery и Параметры Передача параметров через TDataSource Выполнение соединения нескольких таблиц

Open или ExecSQL? Специальные свойства TQuery
 ex21.zip
  Краткий Обзор В этой главе Вы узнаете некоторые основные понятия о запросах (queries) и транзакциях. Это достаточно широкие понятия, поэтому обсуждение разбито на следующие основные части: Объект TQuery. Использование SQL с локальным и удаленным серверами (Select, Update, Delete и Insert). Использование SQL для создания объединения (joins), связанных курсоров (linked cursors) и программ, которые ведут поиск заданных записей. Сокращение SQL означает Structured Query Language - Язык Структурированных Запросов, и обычно произноситься либо как "Sequel" либо " Ess Qu El”. Однако, как бы Вы его ни произносили, SQL - это мощный язык БД, который легко доступен из Delphi, но который отличается от родного языка Delphi. Delphi может использовать утверждения SQL для просмотра таблиц, выполнять объединение таблиц, создавать отношения один-ко-многим, или исполнить почти любое действие, которое могут сделать ваши основные инструменты БД. Delphi поставляется с Local SQL, так что Вы можете выполнять запросы SQL при работе с локальными таблицами, без доступа к SQL серверу. Delphi обеспечивает поддержку “pass through SQL”, это означает то, что Вы можете составлять предложения SQL и посылать их непосредственно серверам Oracle, Sybase, Inrterbase и другим. “Pass through SQL” - это мощный механизм по двум причинам: Большинство серверов могут обрабатывать SQL запросы очень быстро, а это означает, что используя SQL для удаленных данных, Вы получите ответ очень быстро. Есть возможность составлять SQL запросы, которые заставят сервер исполнить специализированные задачи, недоступные через родной язык Delphi. Перед чтением этой статьи Вы должны иметь, по крайней мере, элементарное понятие о серверах и различиях между локальными и удаленными (remote) данными.


Основные понятия о TQuery Предыдущий Урок был, в основном, посвящен объекту TTable, который служит для доступа к данным. При использовании TTable, возможен доступ ко всему набору записей из одной таблицы. В отличие от TTable, TQuery позволяет произвольным образом (в рамках SQL) выбрать набор данных для работы с ним. Во многом, методика работы с объектом TQuery похожа на методику работы с TTable, однако есть свои особенности. Вы может создать SQL запрос используя компонент TQuery следующим способом: Назначите Псевдоним (Alias) DatabaseName. Используйте свойство SQL чтобы ввести SQL запрос типа
“Select * from Country”. Установите свойство Active в True Если обращение идет к локальным данным, то вместо псевдонима можно указать полный путь к каталогу, где находятся таблицы.
Две основных вещи, которые Вы должны понять прежде, чем перейти дальше: Этот урок не является учебником для начинающих по SQL, а, скорее, описанием объекта TQuery и основных задач, которые Вы можете решить с его помощью. Если Вы не знаете ничто об SQL, Вы все же сможете воспользоваться этой статьей, и, в конце концов, приобретете некоторое понимание основ SQL. Однако, для полного изучения языка, Вы должны обратиться к любой из большого количества книг и документов, доступных по этому предмету. Delphi использует pass through SQL, поэтому для разных SQL серверов синтаксис может быть несколько разным. Версия SQL для локальных таблиц (Local SQL) очень сильно урезан, по сравнению со стандартом. Чтобы узнать о его возможностях, Вы должны прочитать не только эту статью, но также файл LOCALSQL.HLP. Вы увидите, что объект TQuery один из наиболее полезных и гибких компонентов, доступных в Delphi. С ним Вы сможете воспользоваться всей мощью, предоставляемой лидерами среди промышленных SQL серверов, вроде InrterBase, Oracle или Sybase. Свойство SQL

Свойство SQL - вероятно, самая важная часть TQuery. Доступ к этому свойству происходит либо через Инспектор Объектов во время конструирования проекта (design time), или программно во время выполнения программы (run time).


Интересней, конечно, получить доступ к свойству SQL во время выполнения, чтобы динамически изменять запрос. Например, если требуется выполнить три SQL запроса, то не надо размещать три компонента TQuery на форме. Вместо этого можно разместить один и просто изменять свойство SQL три раза. Наиболее эффективный, простой и мощный способ - сделать это через параметризованные запросы, которые будут объяснены в следующей части. Однако, сначала исследуем основные особенности свойства SQL, а потом рассмотрим более сложные темы, типа запросов с параметрами.
Свойство SQL имеет тип TStrings, который означает что это ряд строк, сохраняемых в списке. Список действует также, как и массив, но, фактически, это специальный класс с собственными уникальными возможностями. В следующих нескольких абзацах будут рассмотрены наиболее часто используемые свойства.
При программном использовании TQuery, рекомендуется сначала закрыть текущий запрос и очистить список строк в свойстве SQL:
Query1.Close;
Query1.SQL.Clear;
Обратите внимание, что всегда можно “безопасно” вызвать Close. Даже в том случае, если запрос уже закрыт, исключительная ситуация генерироваться не будет.
Следующий шаг - добавление новых строк в запрос:
Query1.SQL.Add(‘Select * from Country’);
Query1.SQL.Add(‘where Name = ’’Argentina’’’);
Метод Add используется для добавления одной или нескольких строк к запросу SQL. Общий объем ограничен только количеством памяти на вашей машине.
Чтобы Delphi отработал запрос и возвратил курсор, содержащий результат в виде таблицы, можно вызвать метод:
Query1.Open;
Демонстрационная программа THREESQL показывает этот процесс (см Рис.1)

Рис.1: Программа THREESQL показывает, как сделать несколько запросов с помощью единственного объекта TQuery.
Программа THREESQL использует особенность локального SQL, который позволяет использовать шаблоны поиска без учета регистра (case insensitive). Например, следующий SQL запрос:
Select * form Country where Name like ’C%’
возвращает DataSet, содержащий все записи, где поле Name начинается с буквы ‘C’. Следующий запрос позволит увидеть все страны, в названии которых встречается буква ‘C’:


Select * from Country where Name like ‘%C%’;
Вот запрос, которое находит все страны, название которых заканчивается на ‘ia’:
Select * from Country where Name like ‘%ia’;
Одна из полезных особенностей свойства SQL - это способность читать файлы, содержащие текст запроса непосредственно с диска. Эта особенность показана в программе THREESQL.
Вот как это работает. В директории с примерами к данному уроку есть файл с расширением SQL. Он содержат текст SQL запроса. Программа THREESQL имеет кнопку с названием Load, которая позволяет Вам выбрать один из этих файлов и выполнять SQL запрос, сохраненный в этом файле.
Кнопка Load имеет следующий метод для события OnClick:
procedure TForm1.LoadClick(Sender: TObject);
begin
if OpenDialog1.Execute then
with Query1 do begin
Close;
SQL.LoadFromFile(OpenDialog1.FileName);
Open;
end;
end;
Метод LoadClick сначала загружает компоненту OpenDialog и позволяет пользователю выбрать файл с расширением SQL. Если файл выбран, текущий запрос закрывается, выбраный файл загружается с диска в св-во SQL, запрос выполняется и результат показывается пользователю. TQuery и Параметры Delphi позволяет составить “гибкую” форму запроса, называемую параметризованным запросом. Такие запросы позволяют подставить значение переменной вместо отдельных слов в выражениях “where” или “insert”. Эта переменная может быть изменена практически в любое время. (Если используется локальный SQL, то можно сделать замену почти любого слова в утверждении SQL, но при этом та же самая возможность не поддерживается большинством серверов.)
Перед тем, как начать использовать параметризованные запросы, рассмотрим снова одно из простых вышеупомянутых предложений SQL:
Select * from Country where Name like ’C%’
Можно превратить это утверждение в параметризованный запрос заменив правую часть переменной NameStr:
select * from County where Name like :NameStr
В этом предложении SQL, NameStr не является предопределенной константой и может изменяться либо во время дизайна, либо во время выполнения. SQL parser (программа, которая разбирает текст запроса) понимает, что он имеет дело с параметром, а не константой потому, что параметру предшествует двоеточие ":NameStr". Это двоеточие сообщает Delphi о необходимости заменить переменную NameStr некоторой величиной, которая будет известна позже.


Обратите внимание, слово NameStr было выбрано абсолютно случайно. Использовать можно любое допустимое имя переменной, точно также, как выбирается идентификатор переменной в программе.
Есть два пути присвоить значение переменной в параметризованном запросе SQL. Один способ состоит в том, чтобы использовать свойство Params объекта TQuery. Второй - использовать свойство DataSource для получения информации из другого DataSet. Вот ключевые свойства для достижения этих целей:
property Params[Index: Word];
function ParamByName(const Value: string);
property DataSource;
Если подставлять значение параметра в параметризованный запрос через свойство Params, то обычно нужно сделать четыре шага: Закрыть TQuery Подготовить объект TQuery, вызвав метод Prepare Присвоить необходимые значения свойству Params Открыть TQuery Второй шаг выполняется в том случае, если данный текст запроса выполняется впервые, в дальнейшем его можно опустить.
Вот фрагмент кода, показывающий как это может быть выполнено практически:
Query1.Close;
Query1.Prepare;
Query1.Params[0].AsString := ‘Argentina’;
Query1.Open;
Этот код может показаться немного таинственным. Чтобы понять его, требуется внимательный построчный анализ. Проще всего начать с третьей строки, так как свойство Params является “сердцем” этого процесса.
Params - это индексированное свойство, которое имеет синтаксис как у свойства Fields для TDataSet. Например, можно получить доступ к первой переменной в SQL запросе, адресуя нулевой элемент в массиве Params:
Params[0].AsString := ‘”Argentina”’;
Если параметризованный SQL запрос выглядит так:
select * from Country where Name = :NameStr
то конечный результат (т.е. то, что выполнится на самом деле) - это следующее предложение SQL:
select * from Country where Name = “Argentina”
Все, что произошло, это переменной :NameStr было присвоено значение "Аргентина" через свойство Params. Таким образом, Вы закончили построение простого утверждения SQL.
Если в запросе содержится более одного параметра, то доступаться к ним можно изменяя индекс у свойства Params


Params[1].AsString := ‘SomeValue’;
либо используя доступ по имени параметра
ParamByName(‘NameStr’).AsString:=’”Argentina”’;
Итак, параметризованные SQL запросы используют переменные, которые всегда начинаются с двоеточия, определяя места, куда будут переданы значения параметров.
Прежде, чем использовать переменную Params, сначала можно вызвать Prepare. Этот вызов заставляет Delphi разобрать ваш SQL запрос и подготовить свойство Params так, чтобы оно "было готово принять” соответствующее количество переменных. Можно присвоить значение переменной Params без предварительного вызова Prepare, но это будет работать несколько медленнее.
После того, как Вы вызывали Prepare, и после того, как присвоили необходимые значения переменной Params, Вы должны вызвать Open, чтобы закончить привязку переменных и получить желаемый DataSet. В нашем случае, DataSet должен включать записи где в поле “Name” стоит “Argentina”.
Рассмотрим работу с параметрами на примере (программа PARAMS.DPR). Для создания программы, разместите на форме компоненты TQuery, TDataSource, TDBGrid и TTabSet. Соедините компоненты и установите в свойстве TQuery.DatabaseName псевдоним DBDEMOS. См. рис.2

Рис.2 : Программа PARAMS во время дизайна.
В обработчике события для формы OnCreate напишем код, заполняющий закладки для TTabSet, кроме того, здесь подготавливается запрос:
procedure TForm1.FormCreate(Sender: TObject);
var
i : Byte;
begin
Query1.Prepare;
for i:=0 to 25 do
TabSet1.Tabs.Add(Chr(Byte('A')+i));
end;
Текст SQL запроса в компоненте Query1:
select * from employee where LastName like :LastNameStr
Запрос выбирает записи из таблицы EMPLOYEE, в которых поле LastName похоже (like) на значение параметра :LastNameStr. Параметр будет передаваться в момент переключения закладок:
procedure TForm1.TabSet1Change(Sender: TObject; NewTab: Integer; var AllowChange: Boolean); begin
with Query1 do begin
Close;
Params[0].AsString:=
'"'+TabSet1.Tabs.Strings[NewTab]+'%"';
Open;
end;
end;

Рис.3: Программа PARAMS во время выполнения.



 
  Передача параметров через TDataSource
В предыдущем Уроке Вы видели способ создания отношения однин-ко-многим между двумя таблицами. Теперь речь пойдет о выполнении того же самого действия с использованием объекта TQuery. Этот способ более гибок в том отношении, что он не требует индексации по полям связи.
Объект TQuery имеет свойство DataSource, которое может использоваться для того, чтобы создать связь с другим DataSet. Не имеет значения, является ли другой DataSet объектом TTable, TQuery, или некоторый другим потомком TDataSet. Все что нужно для установления соединения - это удостовериться, что у того DataSet есть связанный с ним DataSource.
Предположим, что Вы хотите создать связь между таблицами ORDERS и CUSTOMERS так, что каждый раз, когда Вы просматриваете конкретную запись о заказчике, будут видны только заказы, связанные с ним.
Рассмотрите следующий параметризованный запрос:
select * from Orders where CustNo = :CustNo
В этом запросе :CustNo - связывающая переменная, которой должно быть присвоено значение из некоторого источника. Delphi позволяет использовать поле TQuery.DataSource чтобы указать другой DataSet, который предоставит эту информацию автоматически. Другими словами, вместо того, чтобы использовать свойство Params и “вручную” присваивать значения переменной, эти значения переменной могут быть просто взяты автоматически из другой таблицы. Кроме того, Delphi всегда сначала пытается выполнить параметризованный запрос используя свойство DataSource, и только потом (если не было найдено какое-то значение параметра) будет пытаться получить значение переменной из свойства Params. При получении данных из DataSource считается, что после двоеточия стоит имя поля из DataSource. При изменении текущей записи в главном DataSet запрос будет автоматически пересчитываться.
Давайте переделаем пример из прошлого урока (LINKTBL - связывание двух таблиц). Создайте новый проект, положите на форму один набор TTable, TDataSource и TDBGrid. Привяжите его к таблице CUSTOMER. Положите на форму второй набор - TQuery, TDataSource и TDBGrid и свяжите объекты между собой. (см рис.4).



В свойстве SQL наберите текст запроса:
select * from Orders where CustNo = :CustNo
В свойстве DatabaseName для Query1 укажите DBDEMOS.
В свойстве DataSource для Query1 укажите DataSource1.
Поставьте Active = True и запустите программу.

Рис.4: Программа LINKQRY - связанные курсоры с помощью SQL
 
  Выполнение соединения нескольких таблиц. Вы видели что таблицы CUSTOMERS и ORDERS связаны в отношении один-ко-многим, основанному на поле CustNo. Таблицы ORDERS и ITEMS также связаны отношении один-ко-многим, только через поле OrderNo.
Более конкретно, каждый заказ который существует в таблице ORDERS будет иметь несколько записей в таблице ITEMS, связанных с этим заказом. Записи из таблицы ITEMS определяют тип и количество изделий, связанных с этим заказом.
Пример.
Некто Иванов Ф.П. 1 мая 1995г. заказал следующее: Гайка 4х-угольная - 50 штук Вентиль - 1 штука А некто Сидорчук Ю.Г. 8 декабря 1994г. заказал: М/схема КР580 ИК80 - 10 штук Транзистор КТ315 - 15 штук Моток провода - 1 штука В ситуации подобной этой, иногда проще всего "соединить" данные из таблиц ORDERS и ITEMS так, чтобы результирующий DataSet содержал информацию из обеих таблиц:
Иванов Ф.П. 1 мая 1995г Гайка 4х-угольная 50 штук
Иванов Ф.П. 1 мая 1995г Вентиль 1 штука
Сидорчук Ю.Г. 8 декабря 1994г М/схема КР580 ИК80 10 штук
Сидорчук Ю.Г. 8 декабря 1994г Транзистор КТ315 15 штук
Сидорчук Ю.Г. 8 декабря 1994г Моток провода 1 штука
Слияние этих двух таблиц называется "соединение" и это одно из фундаментальных действий, которые Вы можете выполнить на наборе двух или больше таблиц.
Взяв таблицы ORDERS и ITEMS из подкаталога DEMOS\DATA, их можно соединить их таким путем, что поля CustNo, OrderNo и SaleDate из таблицы ORDERS будут “слиты” с полями PartNo и Qty из таблицы ITEMS и сформируют новый DataSet, содержащий все пять полей. Grid содержащий результирующий DataSet показан на рис.5
 
 

Рис.5: Соединение таблиц ORDERS и ITEMS может быть сделано так, что формируется новый DataSet содержащий поля из каждой таблицы.


Имеется существенное различие между связанными курсорами и соединенными таблицами. Однако они имеют две общие черты: И те, и другие используют две или более таблиц Каждый таблица связана с другой по одному или более одинаковых полей. Соединение таблиц ORDERS и ITEMS может быть выполнено единственным SQL запросом, который выглядит так:
select
O.CustNo, O.OrderNo, O.SaleDate, I.PartNo, I.Qty
from Orders O, Items I
where O.OrderNo = I.OrderNo
Этот запрос состоит из четырех различных частей: Выражение Select определяет, что Вы хотите получить - курсор, содержащий некоторую форму DataSet. Затем идет список полей которые Вы хотите включить в dataset. Этот список включает поля CustNo, OrderNo, SaleDate, PartNo и Qty. Первые три поля из таблицы ORDERS, а два других - из таблицы ITEMS. Выражение from объявляет, что Вы работаете с двумя таблицами, одна называется ORDERS, а другая ITEMS. Для краткости, в запросе используется особенность SQL, которая позволяет Вам ссылаться на таблицу ORDERS буквой O, а на таблицу ITEMS буквой I. Выражение where жизненно важно потому, что оно определяет поля связи для двух таблиц. Некоторые серверы могут вернуть DataSet, даже если Вы не включите выражение where в запрос, но почти всегда результирующий набор записей будет не тем, что Вы хотели видеть. Чтобы получить нужный результат, убедитесь что Вы включили выражение where. Open или ExecSQL?

После того, как составлен SQL запрос, есть два различных способа выполнить его. Если Вы хотите получить курсор, то нужно вызывать Open. Если выражение SQL не подразумевает возвращение курсора, то нужно вызывать ExecSQL. Например, если происходит вставка, удаление или обновление данных (т.е. SQL запросы INSERT, DELETE, UPDATE), то нужно вызывать ExecSQL. Тоже самое можно сказать по-другому: Open вызывается при запросе типа SELECT, а ExecSQL - во всех остальных случаях.
Вот типичный SQL запрос, который используется для удаления записи из таблицы:
delete from Country where Name = ‘Argentina’;
Этот запрос удалил бы любую запись из таблицы COUNTRY, которая имеет значение "Argentina" в поле Имя.


Не трудно заметить, что это тот случай, когда удобно использовать параметризованный запрос. Например, неплохо было бы менять имя страны, которую требуется удалить:
delete from Country where Name = :CountryName
В этом случае переменная :CountryName может быть изменена во время выполнения:
Query2.Prepare;
Query2.Params[0] := ‘Argentina’;
Query2.ExecSQL;
Код сначала вызывает Prepare, чтобы сообщить Delphi что он должен разобрать SQL запрос и подготовить свойство Params. Следующим шагом присваивается значение свойству Params и затем выполняется подготовленный SQL запрос. Обратите внимание, что он выполняется через ExecSQL, а не Open.
Программа INSQUERY из примеров Delphi демонстрирует эту технику (проект C:\DELPHI\DEMOS\DB\INSQUERY.DPR) Специальные свойства TQuery Есть несколько свойств, принадлежащих TQuery, которые еще не упоминались:
property UniDirectional: Boolean;
property Handle: HDBICur;
property StmtHandle: HDBIStmt;
property DBHandle: HDBIDB;
Свойство UniDirectional используется для того, чтобы оптимизировать доступ к таблице. Если Вы установите UniDirectional в True, то Вы можете перемещаться по таблице более быстро, но Вы сможете двигаться только вперед.
Свойство StmtHandle связано со свойством Handle TDataSet. То есть, оно включено исключительно для того, что Вы могли делать вызовы Borland Database Engine напрямую. При нормальных обстоятельствах, нет никакой необходимости использовать это свойство, так как компоненты Delphi могут удовлетворить потребностями большинства программистов. Однако, если Вы знакомы с Borland Database Engine, и если Вы знаете что существуют некоторые возможности не поддерживаемые в VCL, то Вы можете использовать TQuery.StmtHandle, или TQuery. Handle, чтобы сделать вызов напрямую в engine.
Следующий фрагмент кода показывает два запроса к BDE:
var
Name: array[0..100] of Char;
Records: Integer;
begin
dbiGetNetUserName(Name);
dbiGetRecordCount(Query1.Handle, Records);
end;

Редактор DataSet


32 Урока по Delphi Урок 22: Редактор DataSet, Вычисляемые поля
 
 
 
 
  Содержание Урока 22: Обзор
Редактор DataSet
Вычисляемые поля
Управление TDBGrid во время выполнения
 ex22.zip
  Обзор

В этой статье вы узнаете о Редакторе DataSet и о способах управления компонентом TDBGrid во время выполнения программы. Здесь же будут рассмотрены вычисляемые поля - весьма ценная особенность Редактора DataSet.
Примеры, которые вы увидите в этой статье, продемонстрируют основные способы, которыми пользуются большинство программистов для показа таблиц БД пользователям. Для понимания большей части материала требуется общее знание среды и языка Delphi. Редактор DataSet

Редактор DataSet может быть вызван с помощью объектов TTable или TQuery. Чтобы начать работать с ним, положите объект TQuery на форму, установите псевдоним DBDEMOS, введите SQL запрос "select * from customer" и активизируйте его (установив св-во Active в True).
Откройте комбобокс “Object Selector” вверху Инспектора Объектов - в настоящее время там имеется два компонента: TForm и TQuery.
Нажмите правую кнопку мыши на объекте TQuery и в контекстном меню выберите пункт “Fields Editor”. Нажмите кнопку Add - появиться диалог Add Fields, как показано на рис.1

Рис.1: Диалог Add Fields Редактора DataSet.
По-умолчанию, все поля в диалоге выбраны. Нажмите на кнопку OK, чтобы выбрать все поля, и закройте редактор. Снова загляните в “Object Selector”, теперь здесь появилось несколько новых объектов, (см. рис.2)

Рис.2: Object Selector показывает в списке все объекты созданные в Редакторе DataSet. Вы можете также найти этот список в определении класса TForm1.
Эти новые объекты будут использоваться для визуального представления таблицы CUSTOMER пользователю.
Вот полный список объектов, которые только что созданы:
Query1CustNo: TFloatField;
Query1Company: TStringField;
Query1Addr1: TStringField;
Query1Addr2: TStringField;
Query1City: TStringField;
Query1State: TStringField;


Query1Zip: TStringField;
Query1Country: TStringField;
Query1Phone: TStringField;
Query1FAX: TStringField;
Query1TaxRate: TFloatField;
Query1Contact: TStringField;
Query1LastInvoiceDate: TDateTimeField;
Я вырезал и вставил этот список из определения класса TForm1, которое можно найти в окне Редактора исходного текста. Происхождение имен показанных здесь, должно быть достаточно очевидно. Часть "Query1" берется по-умолчанию от имени объекта TQuery, а вторая половина от имени поля в таблице Customer. Если бы мы сейчас переименовали объект Query1 в Customer, то получили бы такие имена:
CustomerCustNo
CustomerCompany
Это соглашение может быть очень полезно, когда Вы работаете с несколькими таблицами, и сразу хотите знать, на поле какой таблицы ссылается данная переменная.
Любой объект, созданный в редакторе DataSet является наследником класса TField. Точный тип потомка зависит от типа данных в конкретном поле. Например, поле CustNo имеет тип TFloatField, а поле Query1City имеет тип TStringField. Это два типа полей, которые Вы будете встречать наиболее часто. Другие типы включают тип TDateTimeField, который представлен полем Query1LastInvoiceDate, и TIntegerField, который не встречается в этой таблице.
Чтобы понять, что можно делать с потомками TField, откройте Browser, выключите просмотр полей Private и Protected, и просмотрите свойства и методы Public и Published соответствующих классов.
Наиболее важное свойство называется Value. Вы можете получить доступ к нему так:
procedure TForm1.Button1Click(Sender: TObject);
var
d: Double;
S: string;
begin
d := Query1CustNo.Value;
S := Query1Company.Value;
d:=d+1;
S := 'Zoo';
Query1CustNo.Value := d;
Query1Company.Value := S;
end;
В коде, показанном здесь, сначала присваиваются значения переменным d и S. Следующие две строки изменяют эти значения, а последний две присваивают новые значения объектам. Не имеет большого смысла писать код, подобный этому, в программе, но этот код служит лишь для того, чтобы продемонстрировать синтаксис, используемый с потомками TField.


Свойство Value всегда соответствует типу поля, к которому оно относится. Например у TStringFields - string, TCurrencyFields - double. Однако, если вы отображаете поле типа TCurrencyField с помощью компонент, “чувствительных к данным” (data-aware: TDBEdit, TDBGrid etc.), то оно будет представлена строкой типа: "$5.00".
Это могло бы заставить вас думать, что у Delphi внезапно отключился строгий контроль типов. Ведь TCurrencyField.Value объявлена как Double, и если Вы пробуете присвоить ему строку, Вы получите ошибку “type mismatch” (несоответствие типа). Вышеупомянутый пример демонстрирует на самом деле свойства объектов визуализации данных, а не ослабление проверки типов. (Однако, есть возможность получить значение поля уже преобразованное к другому типу. Для этого у TField и его потомков имеется набор методов типа AsString или AsFloat. Конечно, преобразование происходит только тогда, когда имеет смысл.)
Если нужно получить имена полей в текущем DataSet, то для этого используется свойство FieldName одним из двух способов, показанных ниже:
S := Query1.Fields[0].FieldName;
S := Query1CustNo.FieldName;
Если вы хотите получить имя объекта, связанного с полем, то вы должны использовать свойство Name:
S := Query1.Fields[0].Name;
S := Query1CustNo.Name;
Для таблицы CUSTOMER, первый пример вернет строку "CustNo", а любая из строк второго примера строку "Query1CustNo". Вычисляемые Поля Создание вычисляемых полей - одно из наиболее ценных свойств Редактора DataSet. Вы можете использовать эти поля для различных целей, но два случая выделяются особо: выполнение вычислений по двум или более полям в DataSet, и отображение результата вычислений в третьем поле. имитация соединения двух таблиц с возможностью редактировать результат соединения. Программа CALC_SUM.DPR из примеров к данному уроку иллюстрирует первый случай использования вычисляемых полей.
 
 
Эта программа связывает три таблицы в отношении один ко многим. В частности, ORDERS и ITEMS связаны по полю OrderNo, а ITEMS и PARTS


связаны по полю PartNo. ( В таблице ORDERS хранятся все заказы; в таблице ITEMS - предметы, указанные в заказах; PARTS - справочник предметов). В программе можно перемещаться по таблице ORDERS и видеть связанный с текущим заказом список включенных в него предметов. Программа CALC_SUM достаточно сложная, но хорошо иллюстрирует мощность вычисляемых полей. Последовательность создания проекта CALC_SUM: Создайте новый проект (File|New Project) и удалите из него форму (в Менеджере Проекта View|Project Manager) Выберите эксперта форм БД из меню Help. На первом экране, выберите "Create a master/detail form" и "Create a form using TQuery Objects". Нажмите кнопку Next и выберите таблицу ORDERS.DB из псевдонима БД DBDEMOS. Нажмите Next и выберите поля OrderNo, CustNo, SaleDate, ShipDate и ItemsTotal из таблицы ORDERS.DB. Нажмите Next и выберите "Horizontal" из расстановки компонентов dbEdit на форме. Нажмите Next и выберите таблицу ITEMS.DB. В двух следующих экранах выберите все поля из таблицы и поместите их в grid. Нажмите Next и выберите поле OrderNo из Master и Detail ListBoxes, и Нажмите кнопку Add. Нажмите Next и сгенерируйте форму. Требуется много слов для того, чтобы описать процесс показанный выше, но, фактически, выполнение команд в Эксперте форм БД легко и интуитивно.
Выделите первый из двух объектов TQuery и установят свойство Active в True. Для Query2 в свойстве SQL напишите текст запроса:
select * from Items I, Parts P
where (I.OrderNo =:OrderNo) and
(I.PartNo=P.PartNo)
Активизируйте объект Query2 (Active установите в True) и вызовите редактор DataSet (Fields Editor) для него. Вызовите диалог Add Fields и добавьте поля OrderNo, PartNo, Qty и ListPrice.
Нажмите Define и ведите слово Total в поле FieldName. Установите Field Type в CurrencyField. Проверьте что Calculated CheckBox отмечен. Нажмите Ok и закройте редактор DataSet.
Простой процесс описанный в предыдущем абзаце, показывает как создать вычисляемое поле. Если посмотреть в DBGrid, то можно видеть, что там теперь есть еще одно пустое поле. Для того, чтобы поместить значение в это поле, откройте в Инспекторе Объектов страницу событий для объекта Query2 и сделайте двойной щелчок на OnCalcFields. Заполните созданный метод так:


procedure TForm2.Query2CalcFields(DataSet: TDataSet);
begin
Query2NewTotalInvoice.Value := 23.0;
end;
После запуска программы поле Total будет содержит строку $23.00.
Это показывает, насколько просто создать вычисляемое поле, которое показывает правильно сформатированные данные. На самом деле это поле должно показывать нечто другое - произведение полей Qty (количество) и ListPrice (цена). Для этого вышеприведенный код для события OnCalcFields нужно изменить следующим образом:
procedure TForm1.Query2CalcFields(DataSet: TDataset);
begin
Query2Total.Value:=Query2Qty.Value*Query2ListPrice.Value;
end;
 
 
Если теперь запустить программу, то поле Total будет содержать требуемое значение.
В обработчике события OnCalcFields можно выполнять и более сложные вычисления (это будет показано позже), однако следует помнить, что это вызывает соответствующее замедление скорости работы программы.
Теперь давайте добавим вычисляемое поле для первой таблицы (Query1, ORDERS), которое будет отображать сумму значений из поля Total второй таблицы (Query2) для данного заказа. Вызовите редактор DataSet для объекта Query1 и добавьте вычисляемое поле NewItemsTotal типа CurrencyField. В обработчике события OnCalcFields для Query1 нужно подсчитать сумму и присвоить ее полю NewItemsTotal:
procedure TForm1.Query1CalcFields(DataSet: TDataset);
var
R : Double;
begin
R:=0;
with Query2 do begin
DisableControls;
Close;
Open;
repeat
R:=R+Query2Total.Value;
Next;
until EOF;
First;
EnableControls;
end;
Query1NewItemsTotal.Value:=R;
end;
В данном примере сумма подсчитывается с помощью простого перебора записей, это не самый оптимальный вариант - можно, например, для подсчета суммы использовать дополнительный объект типа TQuery. Метод DisableControls вызывается для того, чтобы отменить перерисовку DBGrid при сканировании таблицы. Запрос Query2 переоткрывается для уверенности в том, что его текущий набор записей соответствует текущему заказу.
Поместите на форму еще один элемент DBEdit и привяжите его к Query1, полю NewItemsTotal.


Запустите программу, ее примерный вид показан на рис.3

Рис.3: Программа CALC_SUM
Как видно из программы, наличие поля ItemsTotal в таблице ORDERS для данного примера необязательно и его можно было бы удалить (однако, оно необходимо в других случаях).
 
  Управление TDBGrid во время выполнения Объект DBGrid может быть полностью реконфигурирован во время выполнения программы. Вы можете прятать и показывать колонки, изменять порядок показа колонок и их ширину.
Вы можете использовать свойство Options объекта DBGrid, чтобы изменить ее представление. Свойство Options может принимать следующие возможные значения:
 
DgEditing Установлен по-умолчанию в true, позволяет пользователю редактировать grid. Вы можете также установить свойство ReadOnly grid в True или False.
DgTitles Будут ли видны названия колонок.
DgIndicator Будут ли видны небольшие иконки слева.
DgColumnResize Может ли пользователь менять размер колонки.
dgColLines Показывать ли линии между колонками.
dgRowLines Показывать ли линии между строками.
dgTabs Может ли пользователь использовать tab и shift-tab для переключения между колонками.

Как объявлено в этой структуре:
TDBGridOption = (dgEditing, gdAlwaysShowEditor, dgTitles,
dgIndicator, dgColumnResize, dgColLines,
dgRowLines, dgTabs);
Например Вы можете установить опции в Runtime написав такой код:
DBGrid1.Options := [dgTitles, dgIndicator];
Если Вы хотите включать и выключать опции, это можно сделать с помощью логических операций. Например, следующий код будет добавлять dgTitles к текущему набору параметров:
DBGrid1.Options := DBGrid1.Options + [dgTitles];
Пусть есть переменная ShowTitles типа Boolean, тогда следующий код позволяют включать и выключать параметр одной кнопкой:
procedure TForm1.Button3Click(Sender: TObject);
begin
if ShowTitles then
DBGrid1.Options := DBGrid1.Options + [dgTitles]
else
DBGrid1.Options := DBGrid1.Options - [dgTitles];
ShowTitles := not ShowTitles;
end;
 
 
Если Вы хотите скрыть поле в run-time, то можете установить свойство visible в false:


Query1.FieldByName(‘CustNo’).Visible := False;
Query1CustNo.Visible := False;
Обе строки кода выполняют идентичную задачу. Чтобы показать поле снова, установите видимый в true:
Query1.FieldByName(‘CustNo’).Visible := True;
Query1CustNo.Visible := True;
Если Вы хотите изменить положение колонки в Runtime, можете просто изменить индекс, (первое поле в записи имеет индекс нуль):
Query1.FieldByName(‘CustNo’).Index := 1;
Query1CustNo.Index := 2;
По-умолчанию, поле CustNo в таблице Customer является первым. Код в первой строке перемещает это поле во вторую позицию, а следующая строка перемещает его в третью позицию. Помните, что нумерация полей начинается с нуля, так присвоение свойству Index 1 делает поле вторым в записи. Первое поле имеет Index 0.
Когда Вы изменяете индекс поля, индексы других полей в записи изменяются автоматически.
Если Вы хотите изменить ширину колонки в Runtime, только измените свойство DisplayWidth соответствующего TField.
Query1.FieldByName(‘CustNo’).DisplayWidth := 12;
Query1CustNo.DisplayWidth := 12;
Величина 12 относится к числу символов, которые могут быть показаны в видимом элементе.
Программа DBGR_RT показывает как работать с DBGrid в Runtime. Программа достаточно проста, кроме двух небольших частей, которые описаны ниже. Первая часть показывает, как создать check box в Runtime, а вторая показывает, как изменить порядок пунктов в listbox в Runtime.
При создании формы (событие OnCreate) ListBox заполняется именами полей, далее создается массив объектов CheckBox, соответствующий полям в таблице. Сперва все CheckBox’ы выбраны и все поля в таблице видимы. Программа узнает через TTable1 имена полей и присваивает их свойству Caption соответствующего CheckBox. Кроме того, обработчику события OnClick всех CheckBox’ов присваивается процедура ChBClick, которая и включает/выключает поля в DBGrid.
procedure TForm1.FormCreate(Sender: TObject);
var
i : Word;
R : Array[0..49] of TCheckBox;
begin
{Fill ListBox}
ListBox1.Clear;
for i:=0 to Table1.FieldCount-1 do


ListBox1.Items.Add(Table1.Fields[i].FieldName);
{Make CheckBoxes}
for i:=0 to Table1.FieldCount-1 do begin
R[I] := TCheckBox.Create(Self);
R[I].Parent := ScrollBox1;
R[I].Caption := Table1.Fields[i].FieldName;
R[I].Left := 10;
R[I].Top := I * CheckBox1.Height + 5;
R[I].Width := 200;
R[I].Checked := True;
R[I].OnClick := ChBClick;
end;
end;
Большая часть кода в этом примере выполняет относительно простые задачи, типа назначения имен и положений check boxes. Вот две ключевых строки:
R[I] := TCheckBox.Create(Self);
R[I].Parent := ScrollBox1;
Первая строки создает CheckBox с заданным Owner (Владельцем). Вторая строки назначает Parent (Родителя) для CheckBox. Чтобы понять различия между Родителем и Владельцем, посмотрите соответствующие свойства в online-help.
Программа содержит ListBox, который показывает текущий порядок полей в DataSet. Для изменения порядка полей в DataSet (а, следовательно, в DBGrid) используются две кнопки. При нажатии на одну из кнопок, выбранное в ListBox’е поле перемещается на одну позицию вверх или вниз. Синхронно с этим меняется и порядок полей в DBGrid. Код, показанный ниже, изменяет Index поля для Table1, изменяя, таким образом, позицию поля в DBGrid. Эти изменения касаются только визуального представления DataSet. Физически данные на диске не изменяются.
procedure TForm1.downButtonClick(Sender: TObject);
var
i : Integer;
begin
with ListBox1 do
if (ItemIndex<Items.Count-1)and(ItemIndex<>-1) then begin
i := ItemIndex;
{move ListBox item}
Items.Move(i, i+1);
ItemIndex := i+1;
{move Field}
Table1.Fields[i].Index:=i+1;
end;
end;
Последняя строка в примере как раз та, которая фактически изменяет индекс колонки, которую пользователь хочет переместить. Две строки кода непосредственно перед ней перемещают текущую строку в ListBox на новую позицию.
Внешний вид программы DBGR_RT показан на рис.4

Рис.4: Программа DBGR_RT
 
 
 
 

Определение собственного диалога при соединении


32 урока по Delphi Урок 23: Управление соединением с базой данных (класс TDataBase,объект Session)

 
Содержание урока 23:
Обзор
Класс TDataBase
Создание постоянного соединения с базой данных
Определение собственного диалога при соединении с базой данных
Создание локального псевдонима базы данных
Изменение параметров соединения
Управление транзакциями
Объект Session
Указание сетевого протокола при соединении с БД
 ex23.zip
 
  Обзор
В данной статье рассказывается об управлении соединением с базой данных при помощи компоненты TDataBase и объекта TSession, который создается в программе автоматически. Описываются процедуры создания локального псевдонима базы данных и доступа к таблицам Paradox по паролю.
 
  Класс TDataBase
Объект типа TDataBase не является обязательным при работе с базами данных, однако он предоставляет ряд дополнительных возможностей по управлению соединением с базой данных. TDataBase служит для: Создания постоянного соединения с базой данных Определения собственного диалога при соединении с базой данных (опрос пароля) Создания локального псевдонима базы данных Изменения параметров при соединении Управления транзакциями TDataBase является невидимым во время выполнения объектом. Он находится на странице “Data Access” Палитры Компонент. Для включения в проект TDataBase нужно “положить” его на главное окно вашей программы. Создание постоянного соединения с базой данных
Если вы работаете с базой данных, то перед началом работы выполняется процедура соединения с этой базой. В процедуру соединения, кроме прочего, входит опрос имени и пароля пользователя (кроме случая работы с локальными таблицами Paradox и dBase через IDAPI). Если в программе не используется TDataBase, то процедура соединения выполняется при открытии первой таблицы из базы данных. Соединение с базой данных обрывается, когда в программе закрывается последняя таблицы из этой базы (это происходит в том случае, если свойство KeepConnections


объекта Session установлено в False, но об этом чуть позже). Теперь, если снова открыть таблицу, то процедура установки соединения повторится и это может быть достаточно неудобно для пользователя. Чтобы соединение не обрывалось даже в том случае, когда нет открытых таблиц данной базы, можно использовать компонент типа TDataBase. В свойстве AliasName укажите псевдоним базы данных, с которой работает программа; в свойстве DatabaseName - любое имя (псевдоним БД), на которое будут ссылаться таблицы вместо старого псевдонима базы. Свойство Connected установите в True - процедура соединения с базой будет выполняться при запуске программы. И, наконец, свойство KeepConnection нужно установить в True (см. рис.1).

Рис.A: Свойства TDataBase в Инспекторе объектов
В нашем примере, после задания свойств DataBase1 нужно у всех таблиц, работающих с IBLOCAL в свойстве DatabaseName поставить Loc_IBLOCAL.
Определение собственного диалога при соединении с базой данных
По умолчанию при соединении с базой данных используется диалог опроса имени и пароля пользователя, показанный на рис.2

Рис.B: Диалог авторизации пользователя
При желании можно изменить внешний вид диалога или вообще его отменить. Для этого используются свойства и события класса TDataBase - LoginPrompt, Params и OnLogin.
Чтобы отключить опрос имени и пароля установите свойство LoginPrompt в False. При этом в свойстве Params требуется в явном виде (во время дизайна либо во время выполнения) указать имя и пароль пользователя. Например, в программе можно написать (до момента соединения с базой, например в событии для Form1 OnCreate) :
DataBase1.LoginPrompt:=False;
DataBase1.Params.Clear;
DataBase1.Params.Add(‘USER NAME=SYSDBA’);
DataBase1.Params.Add(‘PASSWORD=masterkey’);
DataBase1.Connected:=True;
Чтобы использовать свой собственный диалог, в котором можно опрашивать не только имя и пароль пользователя, но и, например, сетевой протокол - создайте обработчик события OnLogin для DataBase1:
procedure TForm1.Database1Login(Database: TDatabase;


LoginParams: TStrings);
begin
Form2.ShowModal;
if Form2.ModalResult = mrOK then
with LoginParams do begin
Values['USER NAME'] := User_Name;
Values['PASSWORD'] := User_Pass;
end;
end;
Здесь Form2 - новое окно-диалог для ввода имени и пароля, User_Name и User_Pass - строки, куда сохраняются введенные имя и пароль.
Создание локального псевдонима базы данных
Обычно, псевдоним базы данных(Alias) определяется в утилите конфигурации BDE и информация о нем сохраняется в файле конфигурации IDAPI.CFG. Однако, в программе можно использовать не только ранее определенный в утилите конфигурации BDE псевдоним базы данных, но и так называемый локальный (т.е. видимый только внутри данной программы) псевдоним. Это иногда бывает нужно, например, для того, чтобы обезопасить программу в случае удаления используемого псевдонима из файла конфигурации BDE.
Для того, чтобы создать локальный псевдоним БД, положите на главное окно проекта компонент DataBase1. Дальнейшие действия можно выполнить с помощью Инспектора Объектов, но удобнее это сделать через редактор компонент. Щелкните дважды мышкой на DataBase1 - появится диалог, показанный на рис.3

Рис.C: Редактор компоненты класса TDataBase
В этом диалоге требуется указать имя базы данных - это будет ее локальный псевдоним, на который ссылаются таблицы (свойство DatabaseName); тип драйвера (в нашем примере это INTRBASE); а также параметры, используемые при соединении с базой данных. Получить список параметров в поле “Parameter Overrides” можно по нажатию кнопки “Defaults”. Набор параметров зависит от типа БД, с которой вы работаете. Этим параметрам нужно присвоить требуемые значения - указать путь к серверу, имя пользователя и т.д. После выхода из редактора компонент имя, указанное в поле “Name” появится в списке имен баз данных для компонент типа TDataSet (TTable, TQuery etc.).
Изменение параметров при соединении
Иногда требуется изменить определенные в утилите конфигурации BDE параметры, используемые при установлении соединения с БД. Это можно сделать во время дизайна с помощью диалога, показанного на рис.3, в поле “Parameter Overrides”. Либо во время выполнения программы (до попытки соединения) прямым присвоением свойству Params объекта DataBase1:


DataBase1.Params.Add(‘LANGDRIVER=ancyrr’);
 
 
Управление транзакциями
TDataBase позволяет начать в БД транзакцию (метод StartTransaction), закончить (Commit) или откатить ее (RollBack). Кроме того, можно изменять уровень изоляции транзакций (свойство TransIsoltion).
TransIsolation Oracle Sybase and Informix InterBase
Microsoft SQL
Dirty read Read committed Read committed Dirty Read Read committed
Read committed(Default) Read committed Read committed Read committed Read committed
Repeatable read Repeatable read Read committed Repeatable Read Repeatable Read
“Dirty Read” - внутри вашей текущей транзакции видны все изменения, сделанные другими транзакциями, даже если они еще не завершились по Commit. “Read Committed” - видны только “закоммитченные” изменения, внесенные в базу. “Repeatable Read” - внутри транзакции видны те данные, что были в базе на момент начала транзакции, даже если там на самом деле уже имеются изменения.
 
  Объект Session

Объект Session, имеющий тип TSession создается автоматически в программе, работающей с базами данных (в этом случае Delphi подключает в программу модуль DB). Вам не нужно заботиться о создании и уничтожении данного объекта, но его методы и свойства могут быть полезны в некоторых случаях. В этом компоненте содержится информация обо всех базах данных, с которыми работает программа. Ее можно найти в свойстве DataBases. Со свойством KeepConnections данного объекта мы уже знакомы. Это свойство определяет, нужно ли сохранять соединение с базой, если в программе нет ни одной открытой таблицы из этой базы. NetDir - директория, в которой лежит общий сетевой файл PDOXUSRS.NET, необходимый BDE. PrivateDir - директория для хранения временных файлов.
С помощью методов объекта Session можно получить информацию о настройках BDE, например, список всех псевдонимов, драйверов баз данных или список всех таблиц в базе.
Еще одно важное назначение объекта Session - доступ с его помощью к таблицам Paradox, защищенным паролем. Прежде, чем открыть такую таблицу, требуется выполнить метод AddPassword :
Session.AddPassword(‘my_pass’);
Удалить пароль можно с помощью метода RemovePassword или RemoveAllPasswords.
 
  Указание сетевого протокола при соединении с БД В случае с InterBase можно в явном виде указать, какой сетевой протокол используется при соединении с базой данных. Эта установка выполняется либо в утилите конфигурации BDE, либо в программе - нужно изменить параметр “SERVER NAME”, который содержит полный путь к файлу с базой данных.
Итак:
Протокол Параметр SERVER NAME
TCP/IP IB_SERVER:PATH\DATABASE.GDB ( nt:c:\ib\base.gdb ) ( unix:/ib/base.gdb )
IPX/SPX IB_SERVER:PATH\DATABASE.GDB ( nw@sys:ib\base.gdb )
NetBEUI \\IB_SERVER\PATH\DATABASE.GDB ( \\nt\c:\ib\base.gdb )

с данными на SQL сервере,


32 урока по Delphi Урок 24: Управление транзакциями
 
 
 
 
 
 
Содержание урока 24: Обзор
SQL-выражения для управления транзакциями
Запуск транзакции
Завершение транзакции
Управление транзакциями в Delphi Обзор
Все операции, выполняемые с данными на SQL сервере, происходят в контексте транзакций. Транзакция - это групповая операция, т.е. набор действий с базой данных; самым существенным для этих действий является правило либо все, либо ни чего. Если во время выполнения данного набора действий, на каком-то этапе невозможно произвести очередное действие, то нужно выполнить возврат базы данных к начальному состоянию (произвести откат транзакции). Таким образом (при правильном планировании транзакций), обеспечивается целостность базы данных. В данном уроке объясняется, как начинать, управлять и завершать транзакции с помощью SQL выражений. А так же рассматривается вопрос об использовании транзакций в приложениях, созданных в Delphi. Вся приведенная информация касается InterBase. SQL-выражения для управления транзакциями

Для управления транзакциями имеется три выражения:
SET TRANSACTION - Начинает транзакцию и определяет ее поведение.
COMMIT - Сохраняет изменения, внесенные транзакцией, в базе данных и завершает транзакцию.
ROLLBACK - Отменяет изменения, внесенные транзакцией, и завершает транзакцию.
 
  Запуск транзакции
Выполнять транзакции можно, например, из Windows Interactive SQL, из программы, из сохраненной процедуры или триггера. В общем виде, синтаксис команды SQL для запуска транзакции: SET TRANSACTION [Access mode] [Lock Resolution]
[Isolation Level] [Table Reservation]
Значения, принимаемые по-умолчанию:
выражение
SET TRANSACTION
равносильно выражению
SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT
 
 
 
 
Access Mode - определяет тип доступа к данным. Может принимать два значения: READ ONLY - указывает, что транзакция может только читать данные и не может модифицировать их.


READ WRITE - указывает, что транзакция может читать и модифицировать данные. Это значение принимается по умолчанию. Пример: SET TRANSACTION READ WRITE
Isolation Level - определяет порядок взаимодействия данной транзакции с другими в данной базе. Может принимать значения: SNAPSHOT - значение по умолчанию. Внутри транзакции будут доступны данные в том состоянии, в котором они находились на момент начала транзакции. Если по ходу дела в базе данных появились изменения, внесенные другими завершенными транзакциями, то данная транзакция их не увидит. При попытке модифицировать такие записи возникнет сообщение о конфликте. SNAPSHOT TABLE STABILITY - предоставляет транзакции исключительный доступ к таблицам, которые она использует. Другие транзакции смогут только читать данные из них. READ COMMITTED - позволяет транзакции видеть текущее состояние базы. Конфликты, связанные с блокировкой записей происходят в двух случаях: Транзакция пытается модифицировать запись, которая была изменена или удалена уже после ее старта. Транзакция типа READ COMMITTED может вносить изменения в записи, модифицированные другими транзакциями после их завершения. Транзакция пытается модифицировать таблицу, которая заблокирована другой транзакцией типа SNAPSHOT TABLE STABILITY. Lock Resolution - определяет ход событий при обнаружении конфликта блокировки. Может принимать два значения: WAIT - значение по умолчанию. Ожидает разблокировки требуемой записи. После этого пытается продолжить работу. NO WAIT - немедленно возвращает ошибку блокировки записи. Table Reservation - позволяет транзакции получить гарантированный доступ необходимого уровня к указанным таблицам. Существует четыре уровня доступа: PROTECTED READ - запрещает обновление таблицы другими транзакциями, но позволяет им выбирать данные из таблицы. PROTECTED WRITE - запрещает обновление таблицы другими транзакциями, читать данные из таблицы могут только транзакции типа SNAPSHOT или READ COMMITTED. SHARED READ - самый либеральный уровень. Читать могут все, модифицировать - транзакции READ WRITE.


SHARED WRITE - транзакции SNAPSHOT или READ COMMITTED READ WRITE могут модифицировать таблицу, остальные - только выбирать данные. Завершение транзакции Когда все действия, составляющие транзакцию успешно выполнены или возникла ошибка, транзакция должна быть завершена, для того, чтобы база данных находилась в непротиворечивом состоянии. Для этого есть два SQL-выражения: COMMIT - сохраняет внесенные транзакцией изменения в базу данных. Это означает, что транзакция завершена успешно. ROLLBACK - откат транзакции. Транзакция завершается и никаких изменений в базу данных не вносится. Данная операция выполняется при возникновении ошибки при выполнении операции (например, при невозможности обновить запись). Управление транзакциями в Delphi Прежде всего, транзакции в Delphi бывают явные и неявные. Явная транзакция - это транзакция, начатая и завершенная с помощью методов объекта DataBase: StartTransaction, Commit, RollBack. После начала явной транзакции, все изменения, вносимые в данные относятся к этой транзакции.
Другого способа начать явную транзакцию, нежели с использованием DataBase, нет. (Точнее говоря, такая возможность есть, но это потребует обращения к функциям API InterBase. Однако, это уже достаточно низкоуровневое программирование.) Следовательно, в рамках одного соединения нельзя начать две транзакции.
Неявная транзакция стартует при модификации данных, если в данный момент нет явной транзакции. Неявная транзакция возникает, например, при выполнении метода Post для объектов Table и Query. То есть, если Вы отредактировали запись, в DBGrid и переходите на другую запись, то это влечет за собой выполнение Post, что, в свою очередь, приводит к началу неявной транзакции, обновлению данных внутри транзакции и ее завершению. Важно отметить, что неявная транзакция, начатая с помощью методов Post, Delete, Insert, Append и т.д. заканчивается автоматически.
Для модификации данных может использоваться и PassThrough SQL - SQL-выражение, выполняемое с помощью метода ExecSQL класса TQuery. Выполнение модификации через PassThrough SQL также приводит к старту неявной транзакции. Дальнейшее поведение транзакции, начатой таким путем, определяется значением параметра SQLPASSTHRU MODE для псевдонима базы данных (или тот-же параметр в св-ве Params объекта DataBase). Этот параметр может принимать три значения:


SHARED AUTOCOMMIT - слово SHARED указывает на то, что оба вида транзакций(через Passthrough SQL и через методы TTable и TQuery) разделяют одно и то же соединение к базе данных. Слово AUTOCOMMIT указывает на то, что неявная транзакция, начатая через Passthrough SQL, завершается после выполнения действия по модификации данных (автоматически выполняется COMMIT). SHARED NOAUTOCOMMIT - отличается от предыдущего тем, что неявная транзакция, начатая через Passthrough SQL, не завершается после выполнения, ее нужно явно завершить, выполнив SQL-выражение “COMMIT”. NOT SHARED - транзакции разных типов работают через разные соединения с базой. Данное значение параметра подразумевает также NOAUTOCOMMIT. То есть все неявные PassthroughSQL-транзакции нужно завершать явно - выполняя SQL-выражение “COMMIT” для Passtrough SQL. Рассмотрим возможные сценарии поведения транзакций при разных значениях параметра. В первом случае, если нет в данный момент начатой транзакции, то попытка модификация данных методами TTable или TQuery, как и выполнение через Passtrough SQL какой-либо операции приведет к старту неявной транзакции. После выполнения, такая транзакция будет автоматически завершена (если не возникло ошибки по ходу транзакции). Если уже имеется начатая явно (метод StartTransaction объекта DataBase) транзакция, то изменения будут проходить в ее контексте. Все транзакции используют одно и то-же соединение.
Во втором случае все происходит, как в первом. Отличие в том, что неявная PassthroughSQL-транзакция не завершается, пока не будет выполнена команда “COMMIT”.
В третьем случае, при выполнении команды Passthrough SQL, будет установлено еще одно соединение, начата неявная транзакция и выполнены действия по модификации данных. Транзакция не будет завершена, пока не будет выполнена команда “COMMIT”. Наличие транзакции, начатой явно с помощью DataBase никак не отразится на ходе выполнения PassthroughSQL-транзакции. Пока PassthroughSQL-транзакция не завершится, изменения, внесенные ей, не будут видны в объектах Table и Query, работающих через другое соединение. PassthroughSQL-транзакции можно рассматривать в некотором смысле, как транзакции из другого приложения.
Взаимодействие транзакций данной программы с транзакциями из других приложений определяется свойством TransIsolation объекта DataBase. Для InterBase имеет смысл два значения: tiReadCommitted и tiRepeatableRead. Выполнение метода StartTransaction в этих двух случаях равносильно выполнению SQL-выражений, соответственно:
SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL READ COMMITTED
и
SET TRANSACTION READ WRITE WAIT ISOLATION LEVEL SNAPSHOT

Утилиты для Local InterBase


32 урока по Delphi Урок 25: Утилиты для Local InterBase
 
 
 
 
 
 
 
Содержание урока 25: Обзор
Некоторые технические характеристики InterBase
InterBase Interactive SQL
Установка соединения
Создание новой базы данных
Получение информации о структуре базы данных
Выполнение SQL запросов
InterBase Server Manager
Резервное копирование
  Обзор
InterBase - это система управления реляционными базами данных, поставляемая корпорацией BORLAND для построения приложений с архитектурой клиент-сервер произвольного масштаба: от сетевой среды небольшой рабочей группы с сервером под управлением Novell NetWare или Windows NT на базе IBM PC до информационных систем крупного предприятия на базе серверов IBM, Hewlett-Packard, SUN и т.п.
В пакет Delphi версии 1.0 входит однопользовательская версия InterBase для Windows - Local InterBase. Используя Local InterBase можно создавать и отлаживать приложения, работающие с данными по схеме клиент-сервер, без подключения к настоящему серверу. В дальнейшем потребуется только перенастроить используемый псевдоним базы данных и программа будет работать с реальной базой без перекомпиляции. Кроме того, Local InterBase можно использовать в приложениях для работы с данными вместо таблиц Paradox.
В данном уроке рассматриваются утилиты, поставляемые в пакете Delphi 2.0, которые служат для администрирования баз данных (как локальных, так и на сервере) и для доступа к этим данным посредством SQL запросов.
 
  Некоторые технические характеристики InterBase
Отличия Local InterBase от InterBase для других платформ, в частности, от InterBase для Windows NT: Local InterBase не поддерживает: функции, определяемые пользователем (UDF). BLOB фильтры сигнализатор событий (event alerters) запись через журнал (Write Ahead Log (WAL)) "отключение" и "включение" базы данных (database shutdown or restart) ведение теневой базы данных (database shadowing) Все остальные функции полностью поддерживаются, совпадает даже структура хранения базы на диске.



 
 
 
  Максимальный размер базы данных
Реально ограничение на размер накладывается временем обработки запросов, временем резервного копирования, восстановления базы и т.д. Рекомендуется не более 10 GB.
Максимальное количество физических файлов, из которых может состоять база
В системных таблицах InterBase поле, описывающее из каких файлов состоит база данных, включая все shadow, имеет тип SHORT. Соответственно не более 65,536.
Максимальное количество таблиц в базе данных
65,536. Таблицы нумеруются с использованием типа данных SHORT.
Максимальное количество записей в таблице и полей в записи
В записи может быть не более 1000 полей. Количество записей в таблице не ограничено.
Максимальный размер записи и поля
Запись не может быть больше 64К байт (не считая размера BLOB). Поле не может быть больше 32К байт, размер поля типа BLOB не ограничен.
Максимальное количество индексов в таблице и базе
В базе может быть 64K индексов. В одной таблице - 64 индекса.
Максимальное количество уровней вложенности SQL запроса
16 уровней вложенности.
Максимальное количество полей в составном индексе
Составной индекс может включать в себя не более 16 полей.
Максимальный размер stored procedure или trigger
Stored procedure или trigger может иметь размер кода не более 48K байт.
Количество UDF, определенных в одной базе
Длина имени UDF не более 31 символа. Соответственно максимальное количество UDF в базе ограниченно количеством уникальных имен в пределах этой длины.
 
  InterBase Interactive SQL

В поставке Delphi есть две утилиты для доступа к базам данных и администрации сервера InterBase. Утилита Windows ISQL позволяет интерактивно выполнять SQL запросы к базе данных и получать результат. Это требуется в двух случаях: для отладки SQL выражения и для управления данными и их структурой.
Кроме того, создать базу данных, хранимые процедуры, триггеры, и т.п. также удобнее с помощью ISQL. ISQL позволяет обращаться как к данным на удаленном сервере, так и к локальным (к Local InterBase).


Рассмотрим порядок работы с этой программой. Прежде, чем начать работу, нужно либо установить соединение с имеющейся базой данных, либо создать новую базу.
Установка соединения
После запуска ISQL выберите пункт меню “File|Connect to Database…”, появится диалог (см. рис.1), в котором нужно выбрать сервер (удаленный или локальный, в данном случае мы обращаемся к Local InterBase), файл базы данных, указать имя пользователя (SYSDBA - имя системного администратора) и пароль (masterkey - пароль по умолчанию). Если все указано правильно, то по нажатию клавиши “OK” установится соединение с базой данных и можно приступать к дальнейшей работе.

Рис. A: Диалог соединения с базой данных.
Создание новой базы данных
Эту операцию можно выполнить в пункте меню “File|Create Database” (см. рис.2). В диалоге нужно указать имя файла (c:\bases\new_base.gdb), имя и пароль системного администратора (SYSDBA и masterkey), и, при необходимости, дополнительные параметры. В данном случае создается база данных, поддерживающая русскую кодовую страницу WIN1251. Если Вы собираетесь работать из ISQL с базой данных в русской кодировке, то перед установкой соединения нужно в пункте меню “Session|Advanced Settings” установить “Character set on connect” в WIN1251.

Рис. B: Диалог создания новой базы данных
Получение информации о структуре базы данных
В ISQL можно получить полную информацию о структуре базы данных: список таблиц и их структуры, списки и текст триггеров, хранимых процедур и т.п. Эту операцию можно выполнить в пункте меню View или Extract. Например, для базы данных из поставки Delphi (лежит в \IBLOCAL\EXAMPLES\EMPLOYEE.GDB), попробуем выбрать “Extract|SQL Metadata for Table” для таблицы COUNTRY. В окошке ISQL Output появится текст SQL запроса, который создавал данную таблицу:
/* Extract Table COUNTRY */
/* Domain definitions */
CREATE DOMAIN COUNTRYNAME AS VARCHAR(15);
/* Table: COUNTRY, Owner: SYSDBA */
CREATE TABLE COUNTRY (COUNTRY COUNTRYNAME NOT NULL,
CURRENCY VARCHAR(10) NOT NULL,


PRIMARY KEY (COUNTRY));
 
 
 
 
 
 
Выполнение SQL запросов
Текст SQL запроса вводится в окошке “SQL Statement”. Для запуска его на выполнение, нажмите кнопку “Run”. На рис.3 приведен результат работы примерного запроса.

Рис. C: Окно ISQL с текстом и результатом выполнения SQL запроса.
 
  InterBase Server Manager Утилита предназначена для администрирования InterBase. С ее помощью можно выполнить следующие операции: определить пользователей и их пароли произвести резервное копирование удалить “мусор” из базы завершить/откатить зависшие транзакции произвести проверку базы на наличие ошибок
 
 

Рис. D: Утилита для администрирования InterBase
Резервное копирование
Соответствующий диалог показан на рис. 5

Рис. E: Диалог резервного копирования базы данных.
Обычно, операционные системы сами предоставляют возможности по сохранению баз данных в архивах. Однако, при резервном копировании, проведенном с помощью Server Manager, выполняются дополнительные операции. При этом: Увеличивается быстродействие базы. В процессе копирования/восстановления происходит “сбор мусора” - в базе данных освобождается место, занятое удаленными записями. Тем самым уменьшается физический размер базы. При восстановлении можно изменить размер страницы и разбить базу на несколько файлов. Резервное копирование может выполняться на работающей базе, для этого не надо отключать пользователей от нее. При этом, изменения, вносимые в базу данных после начала процесса копирования, не записываются в резервный файл. Данные можно перенести на другую операционную систему. Различные компьютеры имеют собственные форматы файлов баз данных и эти файлы нельзя просто перенести на другую операционную систему. Для выполнения этой операции нужно создать резервную копию базы в транспортном формате.

Новые концепции ООП в Object Pascal



 
 
32 урока по Delphi
 
Урок 26: Новые концепции ООП в Object Pascal
Данный материал принадлежит Сергею Орлику. ?1996 Все права защищены.
 
 
В языке Object Pascal, используемом в Delphi, произошел ряд давно ожидаемых программистами изменений, по сравнению с последней версией Borland Pascal. Перечислим, основные из них, позволившие назвать объектную модель Object Pascal новой объектной моделью: изменения в синтаксисе объявления и использования объектов введение функций классов введение методов классов изменения в организации определений и вызовов методов введение раздела объявления интерфейса разработчика объектного типа - protected введение раздела объявления design-time интерфеса объектного типа - published введение механизмов RTTI - информации о типах на этапе выполнения программ введение поддержки процедурных полей введение понятия "свойства" - property В отличие от объявления старых объектных типов, использовавшего ключевое слово object, новые объектные типы определяются с помощью слова class. Здесь уместно привести определение отношения между понятиями объекта и класса, данное Гради Бучем:
 
 
 
 
В новой объектной модели программист работает только с динамическими экземплярами классов (то есть с теми, для которых выделяется память в heap-области), в отличие от старой модели, где можно было работать как с динамическими, так и со статическими экземплярами. По этой причине изменен синтаксис обращения к полям и методам объектов. Если раньше для работы с динамическими экземпляров объектов (инициализированными с использованием обращения к конструктору в сочетании с функцией New) программист должен был использовать обращение "по адресу" ( ^ ), то теперь такой доступ подразумевается автоматически. В качестве примера сравните два следующих фрагмента исходного текста:
{ Старая объектная модель }
type
PMyObject = ^TMyObject;
TMyObject = object (TObject)
MyField : PMyType;
constructor Init;
end;
...
var
MyObject : PMyObject;


begin
MyObject:=New(PMyObject,Init);
MyObject^.MyField:= ...
end;
{ Новая объектная модель }
type
TMyObject = class (TObject)
MyField : TMyType;
constructor Create;
end;
...
var
MyObject : TMyObject;
begin
MyObject:=TMyObject.Create;
MyObject.MyField:= ...
end;
Как Вы могли заметить, в Object Pascal расширен синтаксис использования “точечной нотации” для доступа к методам объектов. Кроме того, изменено соглашение и об именовании конструкторов и деструкторов. В старой объектной модели вызов New отвечал за распределение памяти, а обращение к конструктору инициализировало выделенную область памяти. В новой модели эти функции выполняет конструктор Create.
Приведем объявление базового для всех объектных типов класса TObject:
TObject = class
constructor Create;
destructor Destroy; virtual;
procedure Free;
class function NewInstance: TObject; virtual;
procedure FreeInstance; virtual;
class procedure InitInstance(Instance: Pointer): TObject;
function ClassType: TClass;
class function ClassName: string;
class function ClassParent: TClass;
class function ClassInfo: Pointer;
class function InstanceSize: Word;
class function InheritsFrom(AClass: TClass): Boolean;
procedure DefaultHandler(var Message); virtual;
procedure Dispatch(var Message);
class function MethodAddress(const Name: string):
Pointer;
class function MethodName(Address: Pointer): string;
function FieldAddress(const Name: string): Pointer;
end;
 
 
Компилятор Object Pascal является основой Delphi. Визуальные же средства Delphi построены на концепции Two-Way Tools, позволяющей синхронизировать процесс визуального проектирования форм приложения с генерацией исходного кода.
 
 
Такая архитектура возможна только при наличии механизма поддержки информации о типах - RTTI (RunTime Type Information). Основой такого механизма является внутренняя структура классов и, в частности, возможность доступа к ней за счет использования методов классов, описываемых конструкцией class function... Дадим определение понятия метода класса:



 
 
 
  С одной стороны, Delphi, будучи визуальной средой разработки приложений, ориентирован на тех программистов, которые из готовых компонент "собирают" конкретные приложения для конечных пользователей. С другой стороны, являясь расширяемым объектно-ориентированным инструментом, этот продукт представляет интерес и для специалистов, занимающихся наращиванием функциональных возможностей уже существующих программных библиотек. Поэтому, выглядит абсолютно логичным появление в Object Pascal новых разделов в описании классов, соответственно, published и protected. Вместе с ранее введенными разделами (public и private) они предоставляют полный контроль над возможностями использования и "безболезненной" (в смысле предотвращения фатальных с точки зрения идеологии ошибок) модификации компонент Visual Component Library (VCL - библиотека классов Delphi). Чтобы была более ясна логика использования новых разделов, дадим, также, краткую характеристику и уже существующих: private - внутренние деталей реализации protected - интерфес разработчика public - run-time интерфейс published - design-time интерфейс Все эти разделы работают на уровне модулей (в смысле языка Pascal): если какая-либо часть объекта доступна (или не доступна) в одной области модуля, то такая же доступность будет определена и в другой области модуля (для классов, объявленных в секции Interface). Если вы нуждаетесь в специальной защите объекта или его части, то для этого необходимо его поместить в отдельный модуль.
Раздел protected комбинирует функциональную нагрузку разделов private и public таким образом, что, если вы хотите скрыть внутренние механизмы вашего объекта от конечного пользователя, этот пользователь не сможет в run-time использовать ни одно из объявлений объекта из его protected области, но это не помешает разработчику новых компонент использовать эти механизмы в других модулях. То есть, protected-объявления доступны у любого из наследников вашего класса.
Раздел published


оказался необходимым при введении в Delphi возможности установки свойств и поведения компонент еще на этапе конструирования форм и самого приложения. Именно published-объявления доступны через Object Inspector, будь это ссылки на свойства или обработчики событий. Следует отметить тот факт, что, при порождении нового класса, возможен перенос объявлений из одного раздела в другой, с единственным ограничением - если вы производите скрытие объявления за счет его переноса в раздел private - в дальнейшем его "вытаскивание" у наследника в более доступный раздел в другом модуле будет уже невозможен. Такое ограничение, к счастью, не распространяется на динамические методы-обработчики сообщений Windows.
Учитывая, что наследование представляет собой один из краеугольных камней объектной идеологии, очевидной проблемой реализации объектной ориентированности языка является проблема диспетчеризации вызовов методов объектов.
 
 
 
 
Методы объектов Object Pascal могут иметь любой из трех типов: статический, виртуальный или динамический.
Так как статические и виртуальные методы не претерпели принципиальных изменений, по сравнению с Borland Pascal 7.0, остановимся на новом по реализации типе - динамическом (который, вообще говоря, присутствовал в неявном форме в библиотеке OWL).
Динамические (dynamic) методы, по возможностям наследования и перекрытия, аналогичны виртуальным, но в отличие от последних не имеют входов в таблицу VMT. Такой подход позволяет снизить расход памяти при большом количестве этих методов и самих классов.
 
 
 
 
 
 
В отличие от виртуальных методов и самой идеологии VMT, таблица динамических методов (DMT) содержит входы только для методов, объявленных или перекрытых для данного класса. На каждый динамический метод приходится только одна ссылка, представленная так называемым "индексом", по которому и происходит поиск метода для вызова (базовая информация по обработке динамических методов содержится в модуле x:\delphi\source\rtl\sys\dmth.asm). C точки зрения синтаксиса, перекрытие динамических и виртуальных методов производится одинаково - с использованием ключевого слова override. Исключение составляют обработчики Windows-сообщений wm_Xxx.


В Delphi существуют понятия, принципиально новые для уже существующих объектно-ориентированных реализаций Pascal. К числу этих понятий относятся свойства, функция класса и объектная ссылка.
В Object Pascal добавлена возможность определения полей процедурного типа. Очевидно, что в теле функций привязываемых к этим полям, разработчику необходим доступ к другим полям объекта, методам и т.п. Возможность такого доступа базируется на передаче в эти функции неявного, но доступного в их коде, параметра, автоматически принимающего значение поля объекта Self. Такие функции называются функциями классов. Для объявления функций классов необходимо использовать специальную конструкцию function ... of object.
Delphi позволяет вам создать специальный описатель объектного типа (именно типа, а не на экземпляра !), известный как object reference - объектная ссылка.
Объектные ссылки используются в следующих случаях: тип создаваемого объекта не известен на этапе компиляции необходим вызов метода класса, чей тип не известен на этапе компиляции в качестве правого операнда в операциях проверки и приведения типов с использованием is и as (о них мы будем говорить при обсуждении механизмов RTTI в главе 1.4) Объектная ссылка определяется с использованием конструкции class of... . Приведем пример объявления и использования class reference:
type
TMyObject = class (TObject)
MyField:TMyObject;
constructor Create;
end;
TObjectRef = class of TObject;
...
var
ObjectRef:TObjectRef;
s:string;
begin
ObjectRef:=TMyObject; {присваиваем тип, а не экземпляр !}
s:=ObjectRef.ClassName; { строка s содержит ‘TMyObject’ }
end;
Таким образом в Delphi определена специальная ссылка TClass, совместимая по присваиванию с любым наследником TObject. Аналогично объявлены TPersistentClass и TComponentClass.
Методы в новой объектной модели используют те же соглашения о вызовах, что и обычные процедуры или функции, за некоторыми исключениями. "Ключом" внутренней организации вызовов методов объектов является тот факт, что для каждого метода, в дополнение к объявленным параметрам, передается неявный параметр Self, который описан для каждого класса или экземпляра объекта. Параметр Self всегда передается последним и представляет собой указатель. Для обычных методов, Self - указатель на экземпляр объекта, для методов классов - это указатель на Таблицу Виртуальных Методов (VMT). Например, для данного объявления:


type
TMyObject = class (TObject)
procedure One;
procedure Two; virtual;
class procedure Three; virtual;
end;
TMyClass = class of TObject;
...
var
MyObject:TMyObject;
MyClass:TMyClass;
вызов MyObject.One сгенерирует следующий код:
les DI,MyObject
push ES
push DI
call MyObject.One
При возврате управления, метод должен удалить из стека вначале параметр Self, а затем и остальные - явные параметры.
Методы всегда используют дальнюю (far) модель вызова, несмотря на то, каким образом установлена директива компиляции $F. Для вызова виртуального метода, компилятор генерирует код, загружающий из объекта указатель на VMT, и затем вызывает через точку входа в VMT ( ES:[DI] ), ассоциированный с этой точкой входа метод. Например, вызов MyObject.Two приведет к генерации следующего:
les DI,MyObject
push ES
push DI
les DI, ES:[DI]
call DWORD PTR ES:[DI]
Вызов MyObject.Three :
les DI,MyObject
les DI, ES:[DI]
push ES
push DI
call DWORD PTR ES:[DI+4]
; +4, т.к. это смещение в VMT для второго
; по счету виртуального метода
А для MyClass.Three будет сгенерированно:
les DI,MyClass
push ES
push DI
call DWORD PTR ES:[DI+4]
Очевидно, что приведенные примеры генерируемого кода соответствуют 16-разрядной версии Delphi, но по своей идеологии они остаются верной и для Delphi32.
 
 
 
 

Поскольку Delphi является открытой средой


32 Урока по Delphi
Урок 27 : Создание собственных компонент

Содержание урока 27:
Обзор
Добавление новых объектов в VCL
Заготовка для нового компонента
Соглашения по наименованиям
Выбор предка
Пример создания компонента
 ex27.zip
 
 
 
  Обзор

Поскольку Delphi является открытой средой и позволяет не только использовать объекты из Библиотеки Визуальных Компонент (VCL) в своей программе, но и создавать новые объекты. Причем, ничего другого, кроме Delphi, для этого не требуется. Создание нового объекта в Delphi не является очень сложной задачей, хотя для этого и требуется знание Windows API, объектно-ориентированного программирования и иерархии классов в VCL.
Может возникнуть вопрос; если в Delphi уже есть своя библиотека, то зачем еще создавать какие-то объекты? Ответ прост: нельзя создать библиотеку на все случаи жизни и на все вкусы. Новые компоненты, во-первых, позволяют расширить область применения Delphi: например, с помощью библиотек объектов третьих фирм разрабатывать приложения для работы в Internet. Во-вторых, позволяют дополнить или настроить для себя имеющиеся в VCL объекты (например, переопределить значения свойств, устанавливаемые по умолчанию). Добавление новых объектов в VCL

Предположим, что у вас появился уже готовый компонент. Как его добавить в VCL? Для этого выберите пункт меню Options|Install Components… Появится диалог, как на рис.1

Рис.A: Диалог установки нового компонента
Нажмите “Add” и укажите модуль, содержащий процедуру регистрации, нажмите “OK” и после успешной перекомпиляции новый объект появится в палитре.
 
  Заготовка для нового компонента

В среде Delphi есть специальный эксперт, создающий заготовку для нового компонента. Вызвать его можно в пункте меню File|New Component… (см рис.2)

Рис.B: Эксперт для создания нового компонента
В диалоге нужно указать имя нового класса (например, TMyButton), предка класса (TButton) и страницу палитры, куда поместить новый компонент (Samples). Если нажать “OK”, то эксперт создаст модуль - заготовку для нового компонента:


unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls;
type
TMyButton = class(TButton)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
published
{ Published declarations }
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TMyButton]);
end;
end.
Модуль содержит декларацию нового класса и процедуру его регистрации в Палитре Компонент. В процедуре RegisterComponents первый параметр - имя страницы (можно указать свое имя - появится новая страница); второй параметр - множество объектов для регистрации.
Теперь модуль нужно сохранить под новым именем (например, NEW_BTN.PAS) и приступить к дописыванию новых свойств и методов. После того, как эта работа закончена и новый компонент отлажен, можно добавить его в Палитру (см. предыдущую главу). Но перед этим желательно создать файл ресурсов, в котором будет лежать пиктограмма для представления данного объекта в Палитре Компонент. Файл ресурсов можно создать с помощью программы Resource Workshop, называться он должен точно так же, как модуль регистрации компонента и иметь расширение .DCR (т.е., если объект регистрируется в модуле NEW_BTN.PAS, то тогда имя файла ресурсов будет NEW_BTN.DCR). В файле ресурсов должен находиться ресурс типа BITMAP - картинка размером 28x28 точки (можно меньше), название картинки должно совпадать с именем класса (в нашем случае TMYBUTTON). Соглашения по наименованиям Если вы рассматривали исходные тексты VCL, то могли видеть, что они следуют нескольким простым соглашениям при определении новых классов. Delphi этого не требует, имена методов, свойств и т.п. могут быть любыми, компилятору это безразлично. Но если следовать этим соглашениям, то разработка новых компонентов и чтение исходных текстов станет существенно проще.
Итак: Все декларации типов начинаются на букву T. Еще раз, Delphi не требует этого, но это делает очевидным, что "TEdit", например, есть определение типа, а не переменная или поле класса.


Имена свойствам нужно давать легко читаемые и информативные. Нужно помнить, что пользователь будет их видеть в Инспекторе Объектов. И имя вроде "TextOrientation" много удобнее, нежели "TxtOr". То же самое относится к методам. Методы, доступные пользователю, должны иметь удобные названия. При создании свойств типа Event, имя такого свойства должно начинаться с “On” (например, OnClick, OnCreate и т.д.). Имя метода для чтения свойства должен начинаться со слова “Get”. Например, метод GetStyle должен выполнять чтение для свойства Style. Имя метода для записи свойства должен начинаться со слова “Set”. Например, метод SetStyle должен выполнять запись в свойство Style. Внутреннее поле для хранения данных свойства должно носить имя, начинающееся с буквы “F”. Например, свойство Handle могло бы храниться в поле FHandle. Конечно же, есть исключения из правил. Иногда бывает удобнее их нарушить, например, класс TTable имеет свойства типа Event, которые называются BeforePost, AfterPost и т.п. Выбор предка Прежде, чем приступить к написанию кода, нужно определиться, хотя бы приблизительно, что за компонент вы собираетесь делать. Далее, исходя из его предполагаемых свойств, определите класс-предок. В VCL имеется несколько базовых классов, рекомендуемых для наследования: TObject - Можно использовать в качестве предка, если с этим компонентом не нужно работать во время дизайна. Это может быть, например, класс, содержащий значения переменных среды (environment) или класс для работы с INI файлами. TComponent - Отправная точка для многих невидимых компонент. Данный класс обладает встроенной возможностью сохранять/считывать себя в потоке во время дизайна. TGraphicControl - Используйте этот класс для создания видимых компонент, которым не нужен handle. Такие компоненты рисуют прямо на своей поверхности и требуют мало ресурсов Windows. TWinControl - Базовый класс для компонент, которые имеют окно. Данное окно имеет свой handle, его используют при доступе к возможностям Windows через API.


TCustomControl - Потомок TWinControl, вводит понятие канвы (Canvas) и метод Paint() для лучшего контроля за прорисовкой компонента. Именно этот класс используется в качестве базового для построения большинства видимых компонент, имеющих оконный handle. TXxxxx - Класс вроде TEdit или TButton. Используются с целью доопределения их свойств и методов или переопределения значения свойств, принимаемых по умолчанию. Пример создания компонента Для примера создадим новый класс, мутант TButton, в котором изменим значение по умолчанию свойства ShowHint на True и добавим новое свойство - счетчик нажатий на кнопку. Заготовка модуля для создания нового компонента уже есть (см. пункт Заготовка для нового компонента). Теперь исходный текст выглядит так: unit New_btn;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
Controls, Forms, Dialogs, StdCtrls;
type
TMyButton = class(TButton)
private
{ Private declarations }
FClickCount : Longint;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner : TComponent); override;
procedure Click; override;
property ClickCount : Longint read FClickCount write FClickCount; published
{ Published declarations }
end;
procedure Register;
implementation
constructor TMyButton.Create(AOwner : TComponent);
begin
inherited Create(AOwner);
ShowHint:=True;
FClickCount:=0;
end;
procedure TMyButton.Click;
begin
Inc(FClickCount);
inherited Click;
end;
procedure Register;
begin
RegisterComponents('Samples', [TMyButton]);
end;
end.
Для того, чтобы переопределить начальное значение свойства при создании объекта, нужно переписать конструктор Create, в котором и присвоить этому свойству нужное значение (не забыв перед этим вызвать конструктор предка).
Новое свойство для подсчета нажатий на клавишу называется ClickCount. Его внутреннее поле для сохранения значения - FClickCount имеет тип Longint, емкости поля хватит надолго.
 
 
 
 

Открытость Delphi проявляется наиболее ярко


32 урока по Delphi
Урок 28: Редакторы свойств

Содержание урока 28:
Обзор
Редакторы свойств
Стандартные редакторы свойств
Класс TPropertyEditor
Создание редактора свойств
Регистрация редактора свойств
Установка редактора свойств
Редакторы компонент
Пример редактора компонент
 ex28.zip
 
Обзор
Открытость Delphi проявляется наиболее ярко в том, что наряду с расширяемостью Библиотеки Визуальных Компонент можно изменять саму среду программирования. Delphi предоставляет соответствующее API с тем, чтобы программисты могли расширять функциональность среды разработки. С помощью этого API можно создать свои собственные Эксперты (Experts), свою Систему Контроля Версий (Version Control system), Редакторы Компонент (Component Editors) и Редакторы Свойств (Property Editors).
При написании новых объектов часто требуется создавать для них свои Редакторы Свойств и Редакторы Компонент. В данном уроке и рассказывается, как это сделать и приводятся примеры.
Краткое описание инструментов среды Delphi и модулей, в которых реализованы соответствующие API:
API Экспертов - Позволяет создать свои собственные эксперты; модули EXPINTF.PAS и VIRTINTF.PAS
API Контроля Версий - Дает возможность создать свою систему Контроля Версий или подключить систему третьей фирмы; модули VCSINTF.PAS и VIRTINTF.PAS
API Редакторов Компонент - Создание диалогов, связанных с объектом во время дизайна. Пример - Menu Designer для TMenu или Fields Editor для TTable; модуль DSGNINTF.PAS
API Редакторов Свойств - Создание редакторов для использования их при редактировании свойств в Инспекторе Объектов; модуль
DSGNINTF.PAS
Модули можно найти в библиотеке визуальных компонент (в директории X:\DELPHI\SOURCE\VCL).
Необходимо отметить, что большинству людей никогда не придется использовать вышеперечисленные API. Однако, некоторым программистам они очень могут пригодиться, особенно разработчикам новых объектов. Редакторы свойств

Как Вы знаете, во время дизайна для настройки внешнего вида и поведения объекта нужно пользоваться Инспектором Объектов. Например, можно изменить цвет фона у объекта TLabel

на форме. Перейдем в окно Инспектора Объектов и выберем свойство Color - отметьте, что справа есть маленькая стрелка, она означает, что мы можем выбрать цвет из списка. Нажмите мышкой на эту стрелку (рис.1)

Столкнувшись с этим в первый раз Вы могли подумать, что этот список цветов является некоей функцией, жестко заданной разработчиками среды программирования Delphi. В действительности, для свойства Color используется соответствующий Редактор Свойств. И Вам вовсе не нужно работать в компании Borland, чтобы создать подобные Редакторы Свойств. Точно так же, как Вы добавляете новые компоненты в Delphi, Вы можете добавить свой собственный Редактор Свойств в среду разработки. Стандартные Редакторы Свойств

Прежде, чем приступить к созданию своего Редактора Свойств, давайте исследуем уже имеющиеся в среде Delphi редакторы свойств. Вы уже видели редактор для свойства Color. Даже простейшие свойства, вроде Left или Caption, имеют свои редакторы. Причем, компоненты сами по себе даже не знают, что за редакторы используются для их свойств. Это означает, что Вы можете свой Редактор Свойств связать с уже существующими свойствами. Например, можно было бы написать Редактор Свойств, который ограничивает свойство, имеющее целый тип (Integer), некоторым максимальным значением и затем связать этот редактор со свойством Width для всех существующих компонент.
Взглянем на иерархию классов Редакторов Свойств. Базовым является класс TPropertyEditor:
TPropertyEditor
TOrdinalProperty
TIntegerProperty
TColorProperty
TModalResultProperty
TTabOrderProperty
TCharProperty
TEnumProperty
TSetProperty
TShortCutProperty
TFloatProperty
TStringProperty
TComponentNameProperty
TFontNameProperty
TCaptionProperty
TSetElementProperty
TClassProperty
TFontProperty
TMethodProperty
TComponentProperty
 
 
Названия классов в большинстве своем очевидны. Класс TFloatProperty связан со свойствами, которые имеют тип Float, класс TSetProperty связан со свойствами, которые имеют тип Set. Некоторые редакторы имеют специальное назначение. Так, например, TTabOrderProperty нужен для того, чтобы предотвратить изменение свойства TabOrder (тип Integer) при выборе на форме нескольких компонент одновременно.


Класс TPropertyEditor Прежде, чем писать свой собственный Редактор Свойств, нужно разобраться в базовом классе TPropertyEditor: TPropertyEditor = class
private
FDesigner: TFormDesigner;
FPropList: PInstPropList;
FPropCount: Integer;
constructor Create(ADesigner: TFormDesigner; APropCount: Integer);
function GetPrivateDirectory: string;
procedure SetPropEntry(Index: Integer; AInstance: TComponent;
APropInfo: PPropInfo);
protected
function GetPropInfo: PPropInfo;
function GetFloatValue: Extended;
function GetFloatValueAt(Index: Integer): Extended;
function GetMethodValue: TMethod;
function GetMethodValueAt(Index: Integer): TMethod;
function GetOrdValue: Longint;
function GetOrdValueAt(Index: Integer): Longint;
function GetStrValue: string;
function GetStrValueAt(Index: Integer): string;
procedure Modified;
procedure SetFloatValue(Value: Extended);
procedure SetMethodValue(const Value: TMethod);
procedure SetOrdValue(Value: Longint);
procedure SetStrValue(const Value: string);
public
destructor Destroy; override;
procedure Activate; virtual;
function AllEqual: Boolean; virtual;
procedure Edit; virtual;
function GetAttributes: TPropertyAttributes; virtual;
function GetComponent(Index: Integer): TComponent;
function GetEditLimit: Integer; virtual;
function GetName: string; virtual;
procedure GetProperties(Proc: TGetPropEditProc);virtual;
function GetPropType: PTypeInfo;
function GetValue: string; virtual;
procedure GetValues(Proc: TGetStrProc); virtual;
procedure Initialize; virtual;
procedure SetValue(const Value: string); virtual;
property Designer: TFormDesigner read FDesigner;
property PrivateDirectory: string read GetPrivateDirectory;
property PropCount: Integer read FPropCount;
property Value: string read GetValue write SetValue;
end;
Методы, приведенные ниже, можно переопределять (override) для изменения поведения Редактора свойств. ( "SetXxxValue" используется для представления одного из методов SetFloatValue, SetMethodValue, SetOrdValue или SetStrValue. "GetXxxValue" обозначает GetFloatValue, GetMethodValue, GetOrdValue или GetStrValue)


Activate Вызывается, когда свойство выбирают в инспекторе объектов. Может быть полезно позволить некоторым атрибутам свойства определяться в каждый момент выбора этого свойства. AllEqual Вызывается всякий раз, когда на форме выбирается более чем один объект. Если этот метод возвращает True, то вызывается GetValue, иначе в Инспекторе Объектов показывается пустая строка. AllEqual вызывается при условии, что GetAttributes возвращает paMultiSelect. Edit Вызывается при нажатии кнопки '...' или по двойному щелчку мыши на свойстве. Этот метод может, к примеру, показать какое-нибудь диалоговое окно для редактирования свойства (пример - свойство Font). GetAttributes Возвращает необходимую Инспектору Объектов информацию для того, чтобы тот смог отобразить свойство в подходящей манере. GetAttributes возвращает множество (set) значений типа TPropertyAttributes: paValueList: Редактор свойств может возвращать список значений для этого свойства. Если этот атрибут установлен, то нужно определить GetValues. В Инспекторе объектов справа от свойства появится кнопка для выпадающего списка. paSortList: Инспектор объектов будет сортировать список, полученный от GetValues.
paSubProperties: Свойство имеет подсвойства, которые будут показываться ниже в виде иерархии (outline). Если GetProperties будет генерировать объекты-свойства, то этот атрибут должен быть установлен.
paDialog: Показывает, что метод Edit будет вызывать диалог. Если данный атрибут установлен, то появится кнопка '...' справа от свойства в Инспекторе Объектов.
paMultiSelect: Позволяет свойству оставаться в Инспекторе Объектов, когда на форме выбрано сразу несколько объектов. Некоторые свойства не годятся для множественного выбора, например, Name.
paAutoUpdate: Если этот атрибут установлен, то метод SetValue будет вызываться при каждом изменении, произведенном в редакторе, а не после завершения редактирования (пример - свойство Caption).
paReadOnly: Значение менять нельзя. GetComponent Возвращает компонент под номером Index в случае множественного выбора объектов (multiselect). GetAttributes должен возвращать paMultiSelect.


GetEditLimit Возвращает число символов, которые пользователь может ввести при редактировании свойства. По умолчанию 255 символов. GetName Возвращает имя свойства. По умолчанию это имя получается из информации о типе, все подчеркивания замещаются пробелами. Данный метод Вам нужно переопределять только в том случае, если имя свойства отличается от того, которое нужно отображать в Инспекторе Объектов. GetProperties Должен быть переопределен для вызова PropertyProc для каждого подсвойства (или вложенного свойства) редактируемого свойства и передачи нового TPropertyEdtior для каждого подсвойства. По умолчанию, PropertyProc не вызывается и подсвойства не ожидаются. TClassProperty будет передавать новый редактор свойств для каждого свойства, объявленного published в классе. TSetProperty передает новый редактор для каждого элемента множества. GetPropType Возвращает указатель на информацию о типе редактируемого свойства. GetValue Возвращает значение свойства в виде строки. По умолчанию возвращает '(unknown)'. Этот метод нужно переопределять с тем, чтобы возвращать правильное значение. GetValues Вызывается, если GetAttributes возвращает paValueList. Должно вызвать Proc для каждого значения, которое приемлемо для данного свойства. Initialize Вызывается при создании Редактора свойств. SetValue(Value) Вызывается для того, чтобы установить значение свойства. Редактор свойств должен уметь разобрать строку (Value) и вызвать метод SetXxxValue. Если строка имеет некорректный формат или неверное значение, то редактор Свойств должен сгенерировать исключительную ситуацию (exception), описывающую данную проблему. SetValue может вообще проигнорировать все изменения и оставить всю обработку изменений методу Edit (как в свойстве Picture).
 
  Свойства и методы полезные при создании нового класса Редактора свойств: PrivateDirectory (свойство) Это директория, в которой находится .EXE, либо рабочая директория, указанная в DELPHI.INI. Если редактор должен сохранить какую-то информацию (установки), то лучше в этой директории.


Value (свойство) Текущее значение свойства, то же самое возвращает GetValue. Modified (метод) Вызывается для того, чтобы показать, что значение свойства изменилось. Методы SetXxxValue вызывают Modified автоматически. GetXxxValue (метод) Возвращает значение первого из редактируемых свойств. SetXxxValue (метод) Устанавливает значения свойства для всех выбранных объектов. Создание Редактора Свойств
При создании нового Редактора Свойств, конечно, не нужно всегда переписывать его заново от базового класса TPropertyEditor. Может оказаться достаточным выбрать в качестве предка уже существующий для данного свойства редактор и переопределить некоторые его методы. Давайте рассмотрим простейший пример нового Редактора Свойств. Как Вы знаете, у всех видимых объектов есть свойство Hint - подсказка, появляющаяся во время выполнения программы, если задержать на некоторое время мышь на объекте. Это свойство имеет тип String и во время дизайна для его редактирования используется Редактор типа TStringProperty. Обычно, подсказка бывает однострочной, но в некоторых случаях ее нужно сделать многострочной. В принципе, здесь проблемы нет, достаточно во время выполнения программы присвоить свойству Hint нужное значение, например:
Button1.Hint:=’Line1’#13#10’Line2’;
Теперь подсказка будет состоять из двух строк. Но это достаточно неудобно, более удобно было бы формировать многострочную подсказку во время дизайна, однако редактор TStringProperty такой возможности не дает. Давайте создадим новый редактор, который мог бы это сделать.
В нашем случае будет достаточно выбрать в качестве предка редактор TStringProperty и переписать некоторые методы. Во-первых, нужно переопределить метод Edit, в котором будет вызываться диалог для ввода строк подсказки. Во-вторых, нужно переопределить функцию GetAttributes, которая возвращает набор параметров, описывающих данное свойство. В частности, должен быть установлен атрибут paDialog, при этом в Инспекторе Объектов у свойства появится кнопка ‘…’ для вызова диалога. И вообще-то нужно изменить метод GetValue, который используется для отображения значения свойства в Инспекторе Объектов.



Назовем новый Редактор Свойств THintProperty, декларация нового класса:
THintProperty = class(TStringProperty)
public
function GetAttributes: TPropertyAttributes; override;
function GetValue : String; override;
procedure Edit; override;
end;
Рассмотрим по порядку методы нового класса.
Функция GetAttributes добавляет к унаследованному множеству атрибуты paDialog (появляется кнопка ‘…’) и paReadOnly (свойство нельзя редактировать непосредственно в Инспекторе Объектов, а только в диалоге, вызываемом через кнопку ‘…’):
function THintProperty.GetAttributes: TPropertyAttributes;
begin
Result := inherited GetAttributes + [paDialog,
paReadOnly];
end;
Функция GetValue заменяет “неправильные” символы #10 и #13 (перевод каретки и переход на новую строку) на символ “>”:
function THintProperty.GetValue : string;
var
i : Byte;
begin
result:=inherited GetValue;
for i:=1 to Byte(result[0]) do
if result[i]<#32 then result[i]:='>';
end;
Процедура Edit вызывает диалог для ввода строк подсказки. Диалог можно было бы нарисовать свой собственный, однако можно воспользоваться уже готовым. Несколько разных диалогов лежит в директории X:\DELPHI\SOURCE\LIB. Мы воспользуемся модулем STREDIT.PAS, в котором есть необходимый диалог редактирования строк. Итак, процедура Edit:
procedure THintProperty.Edit;
var
HintEditDlg : TStrEditDlg;
s : string;
begin
HintEditDlg:=TStrEditDlg.Create(Application);
with HintEditDlg do
try
Memo.MaxLength := 254;
s:=GetStrValue+#0;
Memo.Lines.SetText(@s[1]);
UpdateStatus(nil);
ActiveControl := Memo;
if ShowModal = mrOk then begin
s:=StrPas(Memo.Lines.GetText);
if s[0]>#2 then Dec(Byte(s[0]),2);
SetStrValue(s);
end;
finally
Free;
end;
end;
Строка if s[0]>#2 then Dec(Byte(s[0]),2) нужна, так как Memo.Lines.GetText возвращает все строки с символами #13#10. Регистрация Редактора Свойств

Новый Редактор Свойств готов, осталось только его зарегистрировать в среде Delphi. Для этого в интерфейсной части модуля с нашим редактором требуется поместить декларацию процедуры Register, а в части implementation


написать следующее: procedure Register;
begin
RegisterPropertyEditor(TypeInfo(String), TControl, 'Hint',
THintProperty);
end;
Как уже сообщалось выше, один и тот же редактор свойств можно “привязать” к свойствам, в зависимости от их названия или типа объекта. Это определяется параметрами (второй и третий), которые передаются во время регистрации в процедуре RegisterPropertyEditor. Возможны четыре варианта:
 
Класс компоненты Имя свойства Для каких свойств
Nil ‘’ совпадает тип свойства
Nil ‘Name’ Тип свойства + Имя свойства
TClass ‘’ Тип свойства + класс компоненты
TClass ‘Name’ Тип свойства + Имя свойства+ класс компоненты

  Пояснение к таблице. Если вы зарегистрировали Редактор и указали как класс компоненты, так и имя свойства, то данный редактор “привязывается” ко всем свойствам, которые: имеют тип, указанный в первом параметре процедуры; принадлежат компоненте, которая относится к классу (или его потомкам), указанному во втором параметре; имеют имя, совпадающее с указанным в третьем параметре; Если вместо типа класса в процедуре регистрации стоит Nil, а вместо имени свойства - пустая строка ‘’, то данный редактор “привязывается” ко всем свойствам, которые имеют тип, указанный в первом параметре, независимо от их имени или принадлежности к объекту какого-либо класса.
Если указан только класс, то редактор относится ко всем свойствам указанного типа для объектов указанного класса.
Если указано только имя, то редактор относится к свойствам указанного типа, которые имеют указанное имя.
В нашем случае Редактор Свойств зарегистрирован для всех свойств, которые имеют тип String, относятся к компоненте класса TControl или наследника от него и имеют имя ‘Hint’. Установка Редактора свойств После того, как модуль с новым редактором свойств подготовлен, его нужно подключить к среде Delphi. Установка Редактора Свойств абсолютно аналогична установке новых объектов в палитру компонент и происходит следующим образом: выберите пункт меню "Options|Install Components...."


нажмите кнопку “Add” укажите имя подключаемого модуля (или воспользуйтесь кнопкой “Browse”) нажмите “OK” и еще раз “OK” После успешной перекомпиляции библиотеки проверьте, как действует новый редактор свойств. Для этого создайте новый проект, положите на форму какой-либо видимый объект, например TButton, установите ShowHint для него в True, вызовите редактор подсказки (кнопка ‘…’ в свойстве Hint), редактор выглядит примерно так:

 
 
В диалоге нажмите “OK” и запустите программу.
Полный текст модуля с Редактором Свойств см. в примерах к данному уроку. Редактор Компонент

Редактор Компонент во многом похож на Редактор свойств, отличия в том, что его используют для изменений скорее всего объекта, нежели отдельного свойства.
Давайте взглянем на класс TComponentEditor в модуле DSGNINTF.PAS:
TComponentEditor = class
private
FComponent: TComponent;
FDesigner: TFormDesigner;
public
constructor Create(AComponent: TComponent;
ADesigner: TFormDesigner); virtual;
procedure Edit; virtual;
procedure ExecuteVerb(Index: Integer); virtual;
function GetVerb(Index: Integer): string; virtual;
function GetVerbCount: Integer; virtual;
procedure Copy; virtual;
property Component: TComponent read FComponent;
property Designer: TFormDesigner read FDesigner;
end;
Редактор Компонент создается для каждого выбранного объекта на форме основываясь на классе объекта. При двойном щелчке на объекте вызывается метод Edit Редактора Компонент. При вызове контекстного меню (popup menu) по правой кнопке мыши, то для построения этого меню вызываются методы GetVerbCount и GetVerb. Если в этом меню выбирается пункт, то вызывается метод ExecuteVerb. Copy вызывается при копировании компонента в Clipboard.
Редактор Компонент по умолчанию (TDefaultEditor) при двойном щелчке на объекте создает (или переходит на) в Редакторе Исходного Текста заготовку для событий OnCreate, OnChanged или OnClick (какое первым попадется).
При создании Редактора Компонент вы должны переопределить либо метод Edit, либо три следующих метода: GetVerb, GetVerbCount и


ExecuteVerb. Можно переопределять все четыре метода. Если Редактор Компонент был вызван и изменил компонент, то всегда обязательно нужно вызвать метод Designer.Modified, чтобы Дизайнер об этом узнал.
Методы и свойства TComponentEditor:
Create(AComponent, ADesigner): Конструктор Редактора Компонент. AComponent - редактируемый компонент. ADesigner - интерфейс к Дизайнеру среды Delphi.
Edit: Вызывается при двойном щелчке мышью на компоненте. Редактор Компонент может вызвать какой-нибудь диалог или эксперт.
ExecuteVerb(Index): Выполняется, когда был выбран пункт номер Index из контекстного меню. Считается, что Редактор Компонент знает, как проинтерпретировать это значение.
GetVerb(Index): Редактор Компонент должен вернуть в этом методе строку, которая будет показана в виде пункта контекстного меню. Можно использовать обычные для пунктов меню символы, например &.
GetVerbCount: Возвращает число, которое определяет на какие значения будут отвечать методы GetVerb и ExecuteVerb. Например, если это число равно 3, то в меню будет добавлено три пункта со значениями Index от 0 до 2.
Copy: Вызывается, когда компонент нужно скопировать в Clipboard. На самом деле, образы полей компонента уже находятся в Clipboard. Просто предоставляется возможность скопировать различные типы форматов, которые игнорируются Дизайнером, но которые могут быть распознаны другими приложениями. Пример Редактора Компонент В качестве примера давайте создадим Редактор Компонент для класса TButton. Этот Редактор будет показывать сообщение и изменять свойство Caption у объекта TButton. В данном примере это будет срабатывать и при двойном щелчке мыши, и через контекстное меню.
Декларация нового класса Редактора Компонент:
TButtonEditor = class(TComponentEditor)
private
procedure HiThere;
public
procedure Edit; override;
procedure ExecuteVerb(Index: Integer); override;
function GetVerb(Index: Integer): string; override;
function GetVerbCount: Integer; override;
end;
Процедура HiThere и будет показывать сообщение и изменять свойство Caption:


procedure TButtonEditor.HiThere;
begin
MessageDlg('Hi! It replaces Default Component Editor.',
mtInformation, [mbOK], 0);
(Component as TButton).Caption:='Hi!';
Designer.Modified;
end;
Процедуры Edit и ExecuteVerb только вызывают HiThere:
procedure TButtonEditor.Edit;
begin
HiThere;
end;
procedure TButtonEditor.ExecuteVerb(Index: Integer);
begin
if Index = 0 then HiThere;
end;
Процедуры GetVerb и GetVerbCount определяют вид контекстного меню:
function TButtonEditor.GetVerb(Index: Integer): string;
begin
result:='&Get message ...'
end;
function TButtonEditor.GetVerbCount: Integer;
begin
result:=1;
end;
Здесь в контекстное меню добавляется один пункт “Get message …”.
Редактор Компонент готов.
Необходимо зарегистрировать новый Редактор Компонент, это делается аналогично регистрации Редактора Свойств, только проще:
procedure Register;
begin
RegisterComponentEditor(TButton, TButtonEditor);
end;
После того, как Вы подключите новый Редактор Компонент в среду Delphi, а это делается в пункте меню “Options|Install Components”, создайте новый проект, положите на форму объект TButton и щелкните дважды на нем - появится диалог:

После того, как Вы нажмете “OK”, текст на кнопке изменится.
Созданный нами Редактор Компонент заместит Редактор по умолчанию для всех объектов класса TButton и его наследников, например, TBitBtn.
Полный текст Редактора Компонент приведен в файле SBEDIT.PAS в примерах к данному уроку.

Важной составной частью приложения является




32 урока по Delphi
Урок 29: Генератор отчетов ReportSmith
 
  Содержание Урока 29: Краткий Обзор ReportSmith Компонент TReport Добавление TReport в приложение Свойства TReport Методы TReport Передача переменной в отчет Пример использования Delphi + ReportSmith Создание отчета в ReportSmith
 ex29.zip
  Краткий Обзор

Важной составной частью приложения является вывод данных на печать - получение отчета. В пакет Delphi входит средство для генерации и печати отчетов - ReportSmith. Вы можете объединить отчет с приложениями Delphi. Также, библиотека визуальных компонент Delphi включает специальный компонент TReport. В данном уроке показано, как использовать компоненту TRepor и рассмотрены основные принципы проектирования отчетов в ReportSmith. ReportSmith Borland ReportSmith является инструментом для получения отчетов и интегрирован в среду Delphi. Он может быть вызван непосредственно из меню Tools. Отчет может быть добавлен к приложениям Delphi, для этого есть компонента TReport на странице Data Access Палитры Компонентов. Войти в ReportSmith можно, нажав правую кнопка мыши на компоненте TReport и выбрав пункт контекстного меню (popup menu) или двойным щелчком левой кнопки мыши на компоненте TReport на форме.
Отчеты могут быть созданы для SQL БД или локальных БД и не требуют знания сложных команд БД. Интерфейс ReportSmith использует стандартные инструменты Windows 3.1 типа tool bar, formatting ribbon, и “drag and drop”. Если пользователь уже знаком с интерфейсом стандартных Windows-программ, типа Word for Windows или Quattro Pro for Windows, ему будет “знаком” и интерфейс ReportSmith. ReportSmith предлагает 4 типа отчетов: Табличный, Кросс-таблица(CrossTab), Форма(Form) и Наклейка(Label).
ReportSmith использует концепцию “живых данных”, т.е. работа происходит с настоящими данными все время, а не только тогда, когда запускается просмотр (preview). Кроме этого, ReportSmith легко работает с чрезвычайно большими БД при помощи адаптивной технологии управления памятью. В ReportSmith можно управлять тем, где сохраняется результат выборки данных из БД: в локальный памяти клиентской PC, на жестком диске клиентской PC, или на сервере.


ReportSmith включает поддержку: Встроенных шаблонов и стилей Отчетов типа перекрестных таблиц (Cross tab) Отчетов в виде почтовых адресов Вычисляемых полей и полей суммирования Многоуровневой сортировки и группировки Многоуровневых отчетов (master-details) Отчеты, созданные с помощью ReportSmith могут распространяться бесплатно вместе с ReportSmith runtime-модулем. Конечные пользователи могут купить полную версию ReportSmith, для того чтобы создать свои собственные отчеты. Информация о ReportSmith доступна в руководстве ReportSmith for Windows - Creating Reports из коробки Delphi. Компонент TReport

Библиотека визуальных компонент Delphi включает объект TReport. TReport обеспечивает вызов из программы Delphi программы ReportSmith runtime и печати отчета. TReport расположен на странице Data Access Палитры Компонент. Добавление TReport в приложение

Добавить отчет в приложение Delphi очень легко. Положите компонент TReport на форму. Инспектор Объектов Delphi показывает, что компонент TReport имеет несколько свойств и ни одного события.

Рис.1: Инспектор объектов для свойств TReport Свойства TReport

У TReport есть следующие свойства:
AutoUnload определяет, выгружается ли ReportSmith Runtime из памяти после завершения печати отчета. Если AutoUnload True, то ReportSmith Runtime выгружается, как только закончена пересылка отчета на печать. Если AutoUnload False, то ReportSmith Runtime остается в памяти. Например, можно создать приложение, которое включает пункт меню, запускающий отчет. После того, как отчет выполнился, можно пожелать, чтобы ReportSmith Runtime остался в памяти, и повторно отчет напечатается быстрее. Чтобы выгрузить ReportSmith Runtime из памяти при AutoUnload=False, нужно вызывать метод CloseApplication.
EndPage указывает последнюю страницу отчета, которая будет напечатана. По-умолчанию это 9999 (чтобы напечатать весь отчет).
InitialValues - строка переменных отчета, которые используются отчетом при запуске (если таковые имеются). Например, в отчет можно передавать из программы начальную и конечную даты для выборки данных. Указывая значения этих переменных, не требуется использовать диалоги для ввода во время выполнения отчета.


MaxRecords - количество записей БД, которые вы хотите использовать для создания отчета. Например, если вы хотите только просмотреть примерный отчет, а ваша БД содержит 50,000 записей, вы можете определить в MaxRecords величину, которая ограничивает число записей в отчете значительно меньшей величиной, например 100. Это тот же самое, что и использование ReportSmith в draft режиме.
PrintCopies определяет, сколько копий отчета будут напечатаны.
ReportDir - каталог, где хранятся файлы отчетов. Определяя каталог отчета, не нужно включать туда имя файла отчета.
ReportName содержит имя отчета, который нужно выполнить. Здесь можно указать полное имя отчета (каталог + имя файла), если вы не определили каталог в свойстве ReportDir или хотите выполнить отчет, которое сохранен в другом месте. Если св-во ReportDir определено, то имя каталога опускается и просто указывается имя отчета.
StartPage - номер страницы, с которой вы хотите начать печатать отчет. По-умолчанию равен 1. Методы TReport

Методы TReport включают:
CloseReport прекращает печать отчета.
CloseApplication прекращает выполнение ReportSmith Runtime, если он запущен.
Connect служит для установления соединения с SQL БД.
Print - функция, проверяет, идет ли печать в данный момент.
RecalcReport пересчитывает и перепечатывает отчет с новым значением для переменной отчета, предварительно измененной методом SetVariable.
Run запускает ReportSmith Runtime, выполняет отчет указанный в свойстве ReportName, и посылает отчет на принтер.
RunMacro - вызывает выполнение в ReportSmith макроса (программы, написанной на ReportSmith Baisc).
SetVariable изменяет значение переменной отчета. Параметр Name определяет, какая переменная изменяется, и параметр Value определяет новое значение. После вызова метода SetVariable, ваше приложение может вызывать метод RecalcReport, который пересчитывает и перепечатывает отчет с новым значением переменой. Передача переменной в отчет

Следующий код показывает, как передать переменную в отчет. В примере строковой переменной отчета 'City' присваивается значение 'Bombey'. Подразумевается, что есть готовый отчет с данной переменной.


Поместите компонент TReport на форму и установите требуемые свойства для вызова печати отчета. Напишите обработчик OnClick для кнопки Button1 на форме (кнопка - для простоты) :
procedure TForm1.Button1Click(Sender: TObject);
begin
Report1.InitialValues.Clear;
Report1.InitialValues.Add('@City=<Bombey>');
Report1.Run;
end;
 
  Пример использования Delphi + ReportSmith

Завершенное приложение Delphi + ReportSmith есть в примерах к данному уроку. Приложение позволяет выбрать имя отчета в диалоге открытия файлов и выполнить этот отчет. Код для кнопки PrintReport (Печатать отчета) показан ниже.
procedure TForm1.PrintReportClick(Sender: TObject);
begin
if OpenDialog1.Execute then begin
Report1.ReportName := OpenDialog1.Filename;
Report1.Run
end
end; Создание отчета В данной главе показан пример построения достаточно простого отчета на основе данных из таблиц, которые находятся в каталоге \DELPHI\DEMOS\DATA. В отчете для каждого заказчика будет выводиться список его заказов с сортировкой по имени заказчика. Для этого потребуется использовать таблицы ORDERS.DB (заказы) и CUSTOMER.DB (заказчики).
Запустите ReportSmith. Он попросит вас открыть отчет (если отчет уже существует, то можно выбрать имя отчета). Чтобы построить новый отчет, нажмите кнопку Cancel и затем в меню ReportSmith выберите пункт File|New. ReportSmith попросит выбрать тип отчета, который вы хотите построить (см. рис.2). В нашем примере мы будем строить табличный отчет (Columnar report).
 
 

Рис.2: Диалог выбора типа отчета
Если данных в таблицах много, то лучше выбрать режим Draft прежде, чем нажать OK. В этом случае ReportSmith спросит, сколько записей вы хотите использовать при построении отчета. Когда отчет запускается на выполнение, то будут использоваться все записи или то число, которое вы определяете в свойстве MaxRecords.
После выбора типа отчета укажите ReportSmith таблицу(ы), по которым вы хотите сделать отчет (см. рис.3).
 
 

Рис. 3: Диалог добавления таблиц в отчет.


Для добавления таблицы в отчет нажмите кнопку "Add table...", выберите тип таблицы Paradox (IDAPI) (см. рис.4), и выберите таблицу CUSTOMER.DB из каталога \DEMOS. Точно также добавьте таблицу ORDERS.DB. Следующим шагом нужно установить условия, по которым будет выполняться соединение таблиц при выполнении отчета. В данном случае они связаны по полю CustNo - код заказчика. Установки связи между таблицами происходит в соответствующем диалоге при нажатии кнопки “Add new link…” (см. рис.5). Предварительный этап закончен и можно нажать кнопку “Done”.
 
 

Рис. 4: Диалог добавления таблицы в отчет

Рис. 5: Определение связи между таблицами
ReportSmith считает данные из таблиц и создаст начальный табличный отчет. В нашем отчете слишком большое количество столбцов и он не умещается по ширине на стандартной странице. Можно убрать ненужные колонки, выбирая столбец и нажимая клавишу Del. Удалите все столбцы кроме OrderNo, SaleDate, ShipDate, paymentMethod, AmountPaid. ReportSmith позволяет изменить ширину колонки с помощью мыши. Теперь отчет выглядит примерно так, как на рис.6.
 
 

Рис. 6: Отчет с выбранными полями
Однако, пока это не то, что нам нужно - в отчете нет информации о заказчике, для которого данный заказ предназначен. Прежде, чем добавить эту информацию нужно сгруппировать записи в отчете по принадлежности заказчику. Это делается в пункте меню Tools|Report Grouping… (см. рис.7). Просто выберите поле группировки (в нашем случае нужно сгруппировать записи по коду заказчика - поле CustNo) и нажмите кнопку “OK”. Следующим шагом добавим Header и Footer (т.е. поле перед группой и после нее) для каждой группы. Это выполняется в пункте меню Insert|Headers/Footers (см. рис.8).

Рис. 7: Диалог группировки записей в отчете

Рис. 8: Добавление Header/Footer для группы
В дальнейшем, в Header для группы мы поместим информацию о заказчике, а в Footer - итоговую сумму всех заказов (т.е. сумму по полю AmountPaid для данной группы). А теперь наш отчет имеет вид, представленный на рис.9. (Для того, чтобы показывались названия полей в каждой группе нужно вызвать пункт меню Insert|Field Labels…)



Рис. 9: Отчет с группами записей и полями перед и после них
Для того чтобы добавить данные в Header нужно выбрать пункт меню Insert|Field. Появится соответствующий диалог (см. рис.10). В этом диалоге требуется указать поле, которое вы хотите вставить в отчет, нажать кнопку “Insert” и щелкнуть мышью в то место на отчете, куда его нужно поместить. В нашем отчете это будет поле Company и размещаться оно будет в заголовке группы (Header). Кроме того, если нужно, чтобы названия компаний в отчете шли в алфавитном порядке, то это можно указать в пункте меню Tools|Sorting… В диалоге укажите поля для сортировки - Company и OrderNo (номера заказов внутри каждой группы должны быть также упорядочены). После этого нажмите “Done”.

Рис. 10: Диалог добавления поля в отчет
Теперь добавим суммирующее поле в Footer для группы. Для этого выберите пункт меню Tools|Summary Fields… В диалоге нужно выбрать группу CustNo_Group, поле AmountPaid, операцию Sum и нажать “Add To Group”(см. рис.11). Далее, по аналогии с полем Company, добавьте суммирующее поле в Footer группы (в диалоге вставки поля пункта меню, рис.10, в верхнем ComboBox’е нужно выбрать Summary Fields).
Отчет готов, его вид показан на рис.12.

Рис. 11: Диалог определения полей суммирования

Рис. 12: Готовый отчет.
 
 

Реляционные операции. Команды языка манипулирования


32 урока по Delphi

 
 
Урок 30: Основы языка SQL
 
 

Содержание урока 30: Обзор
Состав языка SQL
Реляционные операции. Команды языка манипулирования данными
Команда SELECT
Простейшие конструкции команды SELECT
Список полей
Все поля
Все поля в произвольном порядке
Блобы
Вычисления
Литералы
Конкатенация
Использование квалификатора AS
Работа с датами
Агрегатные функции
Предложение FROM команды SELECT
Ограничения на число выводимых строк
Операции сравнения
BETWEEN
IN
LIKE
CONTAINING
IS NULL
Логические операторы
Преобразование типов (CAST)
Изменение порядка выводимых строк (ORDER BY)
Упорядочивание с использованием имен столбцов
Упорядочивание с использованием номеров столбцов
Устранение дублирования (модификатор DISTINCT)
Соединение (JOIN)
Внутренние соединения
Самосоединения
Внешние соединения
Обзор

SQL (обычно произносимый как "СИКВЭЛ" или “ЭСКЮЭЛЬ”) символизирует собой Структурированный Язык Запросов. Это - язык, который дает Вам возможность создавать и работать в реляционных базах данных, являющихся наборами связанной информации, сохраняемой в таблицах.
Информационное пространство становится более унифицированным. Это привело к необходимости создания стандартного языка, который мог бы использоваться в большом количестве различных видов компьютерных сред. Стандартный язык позволит пользователям, знающим один набор команд, использовать их для создания, нахождения, изменения и передачи информации - независимо от того, работают ли они на персональном компьютере, сетевой рабочей станции, или на универсальной ЭВМ.
В нашем все более и более взаимосвязанном компьютерном мире, пользователь снабженый таким языком, имеет огромное преимущество в использовании и обобщении информации из ряда источников с помощью большого количества способов.
Элегантность и независимость от специфики компьютерных технологий, а также его поддержка лидерами промышленности в области технологии реляционных баз данных, сделало SQL (и, вероятно, в течение обозримого будущего оставит его) основным стандартным языком. По этой причине, любой, кто хочет работать с базами данных 90-х годов, должен знать SQL.


Стандарт SQL определяется ANSI (Американским Национальным Институтом Стандартов) и в данное время также принимается ISO (Международной Организацией по Стандартизации). Однако, большинство коммерческих программ баз данных расширяют SQL без уведомления ANSI, добавляя различные особенности в этот язык, которые, как они считают, будут весьма полезны. Иногда они несколько нарушают стандарт языка, хотя хорошие идеи имеют тенденцию развиваться и вскоре становиться стандартами "рынка" сами по себе в силу полезности своих качеств.
На данном уроке мы будем, в основном, следовать стандарту ANSI, но одновременно иногда будет показывать и некоторые наиболее общие отклонения от его стандарта.
Точное описание особенностей языка приводится в документации на СУБД, которую Вы используете. SQL системы InterBase 4.0 соответствует стандарту ANSI-92 и частично стандарту ANSI-III. Состав языка SQL Язык SQL предназначен для манипулирования данными в реляционных базах данных, определения структуры баз данных и для управления правами доступа к данным в многопользовательской среде.
Поэтому, в язык SQL в качестве составных частей входят: язык манипулирования данными (Data Manipulation Language, DML) язык определения данных (Data Definition Language, DDL) язык управления данными (Data Control Language, DCL). Подчеркнем, что это не отдельные языки, а различные команды одного языка. Такое деление проведено только лишь с точки зрения различного функционального назначения этих команд.
Язык манипулирования данными используется, как это следует из его названия, для манипулирования данными в таблицах баз данных. Он состоит из 4 основных команд: SELECT (выбрать)
INSERT (вставить)
UPDATE (обновить)
DELETE (удалить).
 
  Язык определения данных используется для создания и изменения структуры базы данных и ее составных частей - таблиц, индексов, представлений (виртуальных таблиц), а также триггеров и сохраненных процедур. Основными его командами являются: CREATE DATABASE (создать базу данных)


CREATE TABLE (создать таблицу)
CREATE VIEW (создать виртуальную таблицу)
CREATE INDEX (создать индекс)
CREATE TRIGGER (создать триггер)
CREATE PROCEDURE (создать сохраненную процедуру)
ALTER DATABASE (модифицировать базу данных)
ALTER TABLE (модифицировать таблицу)
ALTER VIEW (модифицировать виртуальную таблицу)
ALTER INDEX (модифицировать индекс)
ALTER TRIGGER (модифицировать триггер)
ALTER PROCEDURE (модифицировать сохраненную процедуру)
DROP DATABASE (удалить базу данных)
DROP TABLE (удалить таблицу)
DROP VIEW (удалить виртуальную таблицу)
DROP INDEX (удалить индекс)
DROP TRIGGER (удалить триггер)
DROP PROCEDURE (удалить сохраненную процедуру).
 
  Язык управления данными используется для управления правами доступа к данным и выполнением процедур в многопользовательской среде. Более точно его можно назвать “язык управления доступом”. Он состоит из двух основных команд: GRANT (дать права)
REVOKE (забрать права).
 
  С точки зрения прикладного интерфейса существуют две разновидности команд SQL: интерактивный SQL встроенный SQL. Интерактивный SQL используется в специальных утилитах (типа WISQL или DBD), позволяющих в интерактивном режиме вводить запросы с использованием команд SQL, посылать их для выполнения на сервер и получать результаты в предназначенном для этого окне. Встроенный SQL используется в прикладных программах, позволяя им посылать запросы к серверу и обрабатывать полученные результаты, в том числе комбинируя set-ориентированный и record-ориентированный подходы.
Мы не будем приводить точный синтаксис команд SQL, вместо этого мы рассмотрим их на многочисленных примерах, что намного более важно для понимания SQL, чем точный синтаксис, который можно посмотреть в документации на Вашу СУБД.
Итак, начнем с рассмотрения команд языка манипулирования данными. Реляционные операции. Команды языка манипулирования данными Наиболее важной командой языка манипулирования данными является команда SELECT. За кажущейся простотой ее синтаксиса скрывается огромное богатство возможностей. Нам важно научиться использовать это богатство!


На данном уроке предполагается, если не оговорено противное, что все команды языка SQL вводятся интерактивным способом. В качестве информационной основы для примеров мы будем использовать базу данных “Служащие предприятия” (employee.gdb), входящую в поставку Delphi и находящуюся (по умолчанию) в поддиректории \IBLOCAL\EXAMPLES.

???. 1: ????????? ???? ?????? EMPLOYEE
На рис.1 приведена схема базы данных EMPLOYEE для Local InterBase, нарисованная с помощью CASE-средства S-Designor (см. доп. урок). На схеме показаны таблицы базы данных и взаимосвязи, а также обозначены первичные ключи и их связи с внешними ключами. Многие из примеров, особенно в конце урока, являются весьма сложными. Однако, не следует на этом основании делать вывод, что так сложен сам язык SQL. Дело, скорее, в том, что обычные (стандартные) операции настолько просты в SQL, что примеры таких операций оказываются довольно неинтересными и не иллюстрируют полной мощности этого языка. Но в целях системности мы пройдем по всем возможностям SQL: от самых простых - до чрезвычайно сложных.
Начнем с базовых операций реляционных баз данных. Таковыми являются: выборка (Restriction) проекция (Projection) соединение (Join) объединение (Union). Операция выборки позволяет получить все строки (записи) либо часть строк одной таблицы. SELECT * FROM country Получить все строки
таблицы Country COUNTRY CURRENCY
=============== ==========
USA Dollar
England Pound
Canada CdnDlr
Switzerland SFranc
Japan Yen
Italy Lira
France FFranc
Germany D-Mark
Australia ADollar
Hong Kong HKDollar
Netherlands Guilder
Belgium BFranc
Austria Schilling
Fiji FDollar
 
  В этом примере и далее - для большей наглядности - все зарезервированные слова языка SQL будем писать большими буквами. Красным цветом будем записывать предложения SQL, а светло-синим - результаты выполнения запросов. SELECT * FROM country
WHERE currency = “Dollar” Получить подмножество строк таблицы Country, удовлетворяющее условию Currency = “Dollar”
 
 


Результат последней операции выглядит следующим образом: COUNTRY CURRENCY =============== ==========
USA Dollar
 
  Операция проекции позволяет выделить подмножество столбцов таблицы. Например: SELECT currency FROM country Получить список
денежных единиц
 
  CURRENCY
==========
Dollar
Pound
CdnDlr
SFranc
Yen
Lira
FFranc
D-Mark
ADollar
HKDollar
Guilder
BFranc
Schilling
FDollar
 
  На практике очень часто требуется получить некое подмножество столбцов и строк таблицы, т.е. выполнить комбинацию Restriction и Projection. Для этого достаточно перечислить столбцы таблицы и наложить ограничения на строки. SELECT currency FROM country
WHERE country = “Japan” Найти денежную
единицу Японии
 
  CURRENCY
==========
Yen SELECT first_name, last_name
FROM employee
WHERE first_name = "Roger" Получить фамилии
работников,
которых зовут “Roger” FIRST_NAME LAST_NAME
=============== ====================
Roger De Souza
Roger Reeves
 
  Эти примеры иллюстрируют общую форму команды SELECT в языке SQL (для одной таблицы): SELECT (выбрать) специфицированные поля
FROM (из) специфицированной таблицы
WHERE (где) некоторое специфицированное условие является истинным
 
  Операция соединения позволяет соединять строки из более чем одной таблицы (по некоторому условию) для образования новых строк данных. SELECT first_name, last_name, proj_name
FROM employee, project
WHERE emp_no = team_leader Получить список
руководителей проектов
 
  FIRST_NAME LAST_NAME PROJ_NAME
============== ================= ====================
Ashok Ramanathan Video Database
Pete Fisher DigiPizza
Chris Papadopoulos AutoMap
Bruce Young MapBrowser port
Mary S. MacDonald Marketing project 3
 
  Операция объединения позволяет объединять результаты отдельных запросов по нескольким таблицам в единую результирующую таблицу. Таким образом, предложение UNION объединяет вывод двух или более SQL-запросов в единый набор строк и столбцов. SELECT first_name, last_name, job_country


FROM employee
WHERE job_country = "France"
UNION
SELECT contact_first, contact_last, country
FROM customer
WHERE country = "France" Получить список
работников и заказчиков,
проживающих во Франции
 
  FIRST_NAME LAST_NAME JOB_COUNTRY
=============== ================= ===============
Jacques Glon France
Michelle Roche France
Для справки, приведем общую форму команды SELECT, учитывающую возможность соединения нескольких таблиц и объединения результатов: SELECT [DISTINCT] список_выбираемых_элементов (полей)
FROM список_таблиц (или представлений)
[WHERE предикат]
[GROUP BY поле (или поля) [HAVING предикат]]
[UNION другое_выражение_Select]
[ORDER BY поле (или поля) или номер (номера)]; ???. 2: ????? ?????? ??????? SELECT
Отметим, что под предикатом понимается некоторое специфицированное условие (отбора), значение которого имеет булевский тип. Квадратные скобки означают необязательность использования дополнительных конструкций команды. Точка с запятой является стандартным терминатором команды. Отметим, что в WISQL и в компоненте TQuery ставить конечный терминатор не обязательно. При этом там, где допустим один пробел между элементами, разрешено ставить любое количество пробелов и пустых строк - выполняя желаемое форматирование для большей наглядности.
Гибкость и мощь языка SQL состоит в том, что он позволяет объединить все операции реляционной алгебры в одной конструкции, “вытаскивая” таким образом любую требуемую информацию, что очень часто и происходит на практике. Команда SELECT Простейшие конструкции команды SELECT Итак, начнем с рассмотрения простейших конструкций языка SQL. После такого рассмотрения мы научимся: назначать поля, которые должны быть выбраны назначать к выборке “все поля” управлять “вертикальным” и “горизонтальным” порядком выбираемых полей подставлять собственные заголовки полей в результирующей таблице производить вычисления в списке выбираемых элементов использовать литералы в списке выбираемых элементов ограничивать число возвращаемых строк


формировать сложные условия поиска, используя реляционные и логические операторы устранять одинаковые строки из результата. Список выбираемых элементов может содержать следующее: имена полей * вычисления литералы функции агрегирующие конструкции Список полей
SELECT first_name, last_name, phone_no
FROM phone_list получить список
имен, фамилий и служебных телефонов
всех работников предприятия
FIRST_NAME LAST_NAME PHONE_NO
============= ==================== ====================
Terri Lee (408) 555-1234
Oliver H. Bender (408) 555-1234
Mary S. MacDonald (415) 555-1234
Michael Yanowski (415) 555-1234
Robert Nelson (408) 555-1234
Kelly Brown (408) 555-1234
Stewart Hall (408) 555-1234
...
Отметим, что PHONE_LIST - это виртуальная таблица (представление), созданная в InterBase и основанная на информации из двух таблиц - EMPLOYEE и DEPARTMENT. Она не показана на рис.1, однако, как мы уже указывали в общей структуре команды SELECT, к ней можно обращаться так же, как и к “настоящей” таблице. Все поля

SELECT *
FROM phone_list получить список служебных телефонов
всех работников предприятия
со всей необходимой информацией
EMP_NO FIRST_NAME LAST_NAME PHONE_EXT LOCATION PHONE_NO
====== ========== ========= ========= ============= ==============
12 Terri Lee 256 Monterey (408) 555-1234
105 Oliver H. Bender 255 Monterey (408) 555-1234
85 Mary S. MacDonald 477 San Francisco (415) 555-1234
127 Michael Yanowski 492 San Francisco (415) 555-1234
2 Robert Nelson 250 Monterey (408) 555-1234
109 Kelly Brown 202 Monterey (408) 555-1234
14 Stewart Hall 227 Monterey (408) 555-1234
... Все поля в произвольном порядке

SELECT first_name, last_name, phone_no,
location, phone_ext, emp_no
FROM phone_list получить список служебных телефонов
всех работников предприятия
со всей необходимой информацией,
расположив их в требуемом порядке
FIRST_NAME LAST_NAME PHONE_NO LOCATION PHONE_EXT EMP_NO
========== ========= ============== ============= ========= ======
Terri Lee (408) 555-1234 Monterey 256 12



Oliver H. Bender (408) 555-1234 Monterey 255 105
Mary S. MacDonald (415) 555-1234 San Francisco 477 85
Michael Yanowski (415) 555-1234 San Francisco 492 127
Robert Nelson (408) 555-1234 Monterey 250 2
Kelly Brown (408) 555-1234 Monterey 202 109
Stewart Hall (408) 555-1234 Monterey 227 14
... Блобы

Получение информации о BLOb выглядит совершенно аналогично обычным полям. Полученные значения можно отображать с использованием data-aware компонент Delphi, например, TDBMemo или TDBGrid. Однако, в последнем случае придется самому прорисовывать содержимое блоба (например, через OnDrawDataCell). Подробнее об этом см. на уроке, посвященном работе с полями.
SELECT job_requirement
FROM job получить список
должностных требований
к кандидатам на работу
JOB_REQUIREMENT:
No specific requirements.
JOB_REQUIREMENT:
15+ years in finance or 5+ years as a CFO
with a proven track record.
MBA or J.D. degree.
... Вычисления

SELECT emp_no, salary, salary * 1.15
FROM employee получить список номеров
служащих и их зарплату,
в том числе увеличенную на 15%
EMP_NO SALARY
====== ====================== ======================
2 105900.00 121785
4 97500.00 112125
5 102750.00 118162.5
8 64635.00 74330.25
9 75060.00 86319
11 86292.94 99236.87812499999
12 53793.00 61861.95
14 69482.62 79905.01874999999
...
Порядок вычисления выражений подчиняется общепринятым правилам: сначала выполняется умножение и деление, а затем - сложение и вычитание. Операции одного уровня выполняются слева направо. Разрешено применять скобки для изменения порядка вычислений.
Например, в выражении col1 + col2 * col3 сначала находится произведение значений столбцов col2 и col3, а затем результат этого умножения складывается со значением столбца col1. А в выражении (col1 + col2) * col3 сначала выполняется сложение значений столбцов col1 и col2, и только после этого результат умножается на значение столбца col3. Литералы

Для придания большей наглядности получаемому результату можно использовать литералы. Литералы - это строковые константы, которые применяются наряду с наименованиями столбцов и, таким образом, выступают в роли “псевдостолбцов”. Строка символов, представляющая собой литерал, должна быть заключена в одинарные или двойные скобки.


SELECT first_name, "получает", salary, "долларов в год"
FROM employee получить список сотрудников
и их зарплату
FIRST_NAME SALARY
=========== ======== ========== ==============
Robert получает 105900.00 долларов в год
Bruce получает 97500.00 долларов в год
Kim получает 102750.00 долларов в год
Leslie получает 64635.00 долларов в год
Phil получает 75060.00 долларов в год
K. J. получает 86292.94 долларов в год
Terri получает 53793.00 долларов в год
... Конкатенация

Имеется возможность соединять два или более столбца, имеющие строковый тип, друг с другом, а также соединять их с литералами. Для этого используется операция конкатенации (||).
SELECT "сотрудник " || first_name || " " || last_name
FROM employee получить список всех сотрудников
==============================================
сотрудник Robert Nelson
сотрудник Bruce Young
сотрудник Kim Lambert
сотрудник Leslie Johnson
сотрудник Phil Forest
сотрудник K. J. Weston
сотрудник Terri Lee
сотрудник Stewart Hall
... Использование квалификатора AS

Для придания наглядности получаемым результатам наряду с литералами в списке выбираемых элементов можно использовать квалификатор AS. Данный квалификатор заменяет в результирующей таблице существующее название столбца на заданное. Это наиболее эффективный и простой способ создания заголовков (к сожалению, InterBase, как уже отмечалось, не поддерживает использование русских букв в наименовании столбцов).
SELECT count(*) AS number
FROM employee подсчитать количество служащих
NUMBER
===========
42
SELECT "сотрудник " || first_name || " " || last_name AS employee_list
FROM employee получить список всех сотрудников
EMPLOYEE_LIST
==============================================
сотрудник Robert Nelson
сотрудник Bruce Young
сотрудник Kim Lambert
сотрудник Leslie Johnson
сотрудник Phil Forest
сотрудник K. J. Weston
сотрудник Terri Lee
сотрудник Stewart Hall
... Работа с датами Мы уже рассказывали о типах данных, имеющихся в различных СУБД, в том числе и в InterBase. В разных системах имеется различное число встроенных функций, упрощающих работу с датами, строками и другими типами данных. InterBase, к сожалению, обладает достаточно ограниченным набором таких функций. Однако, поскольку язык SQL, реализованный в InterBase, соответствует стандарту, то в нем имеются возможности конвертации дат в строки и гибкой работы с датами. Внутренне дата в InterBase содержит значения даты и времени. Внешне дата может быть представлена строками различных форматов, например:


“October 27, 1995” “27-OCT-1994” “10-27-95” “10/27/95” “27.10.95” Кроме абсолютных дат, в SQL- выражениях можно также пользоваться относительным заданием дат: “yesterday” вчера “today” сегодня “now” сейчас (включая время) “tomorrow” завтра Дата может неявно конвертироваться в строку (из строки), если: строка, представляющая дату, имеет один из вышеперечисленных форматов; выражение не содержит неоднозначностей в толковании типов столбцов. SELECT first_name, last_name, hire_date FROM employee
WHERE hire_date > '1-1-94' получить список сотрудников,
принятых на работу после
1 января 1994 года
 
  FIRST_NAME LAST_NAME HIRE_DATE
=============== ==================== ===========
Pierre Osborne 3-JAN-1994
John Montgomery 30-MAR-1994
Mark Guckenheimer 2-MAY-1994
 
  Значения дат можно сравнивать друг с другом, сравнивать с относительными датами, вычитать одну дату из другой. SELECT first_name, last_name, hire_date
FROM employee
WHERE 'today' - hire_date > 365 * 7 + 1
получить список служащих,
проработавших на предприятии
к настоящему времени
более 7 лет
 
  FIRST_NAME LAST_NAME HIRE_DATE
=============== ==================== ===========
Robert Nelson 28-DEC-1988
Bruce Young 28-DEC-1988
 
  Агрегатные функции

К агрегирующим функциям относятся функции вычисления суммы (SUM), максимального (SUM) и минимального (MIN) значений столбцов, арифметического среднего (AVG), а также количества строк, удовлетворяющих заданному условию (COUNT).
SELECT count(*), sum (budget), avg (budget),
min (budget), max (budget)
FROM department
WHERE head_dept = 100 вычислить: количество отделов,
являющихся подразделениями
отдела 100 (Маркетинг и продажи),
их суммарный, средний, мини- мальный и максимальный бюджеты
COUNT SUM AVG MIN MAX
====== =========== ========== ========== ===========
5 3800000.00 760000.00 500000.00 1500000.00 Предложение FROM команды SELECT В предложении FROM перечисляются все объекты (один или несколько), из которых производится выборка данных (рис.2). Каждая таблица или представление, о которых упоминается в запросе, должны быть перечислены в предложении FROM.


Ограничения на число выводимых строк Число возвращаемых в результате запроса строк может быть ограничено путем использования предложения WHERE, содержащего условия отбора (предикат, рис.2). Условие отбора для отдельных строк может принимать значения true, false или unnown. При этом запрос возвращает в качестве результата только те строки (записи), для которых предикат имеет значение true. Типы предикатов, используемых в предложении WHERE: сравнение с использованием реляционных операторов = равно
<> не равно
!= не равно
> больше
< меньше
>= больше или равно
<= меньше или равно BETWEEN IN LIKE CONTAINING IS NULL EXIST ANY ALL Операции сравнения Рассмотрим операции сравнения. Реляционные операторы могут использоваться с различными элементами. При этом важно соблюдать следующее правило: элементы должны иметь сравнимые типы. Если в базе данных определены домены, то сравниваемые элементы должны относиться к одному домену.
Что же может быть элементом сравнения? Элементом сравнения может выступать: значение поля литерал арифметическое выражение агрегирующая функция другая встроенная функция значение (значения), возвращаемые подзапросом. При сравнении литералов конечные пробелы игнорируются. Так, предложение WHERE first_name = ‘???? ‘ будет иметь тот же результат, что и предложение WHERE first_name = ‘????’. SELECT first_name, last_name, dept_no
FROM employee
WHERE job_code = "Admin" получить список сотрудников
(и номера их отделов),
занимающих должность
администраторов
 
  FIRST_NAME LAST_NAME DEPT_NO
=============== ==================== =======
Terri Lee 000
Ann Bennet 120
Sue Anne O'Brien 670
Kelly Brown 600 SELECT first_name, last_name, dept_no,
job_country
FROM employee
WHERE job_country <> "USA" получить список сотрудников
(а также номера их отделов
и страну),
работающих вне США
 
  FIRST_NAME LAST_NAME DEPT_NO JOB_COUNTRY
=============== ================ ======= ==============
Ann Bennet 120 England


Roger Reeves 120 England
Willie Stansbury 120 England
Claudia Sutherland 140 Canada
Yuki Ichida 115 Japan
Takashi Yamamoto 115 Japan
Roberto Ferrari 125 Italy
Jacques Glon 123 France
Pierre Osborne 121 Switzerland BETWEEN

Предикат BETWEEN задает диапазон значений, для которого выражение принимает значение true. Разрешено также использовать конструкцию NOT BETWEEN.
SELECT first_name, last_name, salary
FROM employee
WHERE salary BETWEEN 20000 AND 30000
получить список сотрудников,
годовая зарплата которых
больше 20000 и меньше 30000
FIRST_NAME LAST_NAME SALARY
=============== ========== ===============
Ann Bennet 22935.00
Kelly Brown 27000.00
Тот же запрос с использованием операторов сравнения будет выглядеть следующим образом:
SELECT first_name, last_name, salary
FROM employee
WHERE salary >= 20000
AND salary <= 30000 получить список сотрудников,
годовая зарплата которых
больше 20000 и меньше 30000
FIRST_NAME LAST_NAME SALARY
=============== ========== ===============
Ann Bennet 22935.00
Kelly Brown 27000.00
Запрос с предикатом BETWEEN может иметь следующий вид:
SELECT first_name, last_name, salary
FROM employee
WHERE last_name BETWEEN "Nelson" AND "Osborne"
получить список сотрудников,
фамилии которых начинаются
с “Nelson”
и заканчиваются “Osborne”
FIRST_NAME LAST_NAME SALARY
=============== =============== ================
Robert Nelson 105900.00
Carol Nordstrom 42742.50
Sue Anne O'Brien 31275.00
Pierre Osborne 110000.00
Значения, определяющие нижний и верхний диапазоны, могут не являться реальными величинами из базы данных. И это очень удобно - ведь мы не всегда можем указать точные значения диапазонов!
SELECT first_name, last_name, salary
FROM employee
WHERE last_name BETWEEN "Nel" AND "Osb"
получить список сотрудников,
фамилии которых находятся
между “Nel” и “Osb”
FIRST_NAME LAST_NAME SALARY
=============== =============== ================
Robert Nelson 105900.00
Carol Nordstrom 42742.50
Sue Anne O'Brien 31275.00
В данном примере значений “Nel” и “Osb” в базе данных нет. Однако, все сотрудники, входящие в диапазон, в нижней части которого начало фамилий совпадает с “Nel” (т.е. выполняется условие “больше или равно”), а в верхней части фамилия не более “Osb” (т.е. выполняется условие “меньше или равно” - а именно “O”, “Os”, “Osb”), попадут в выборку. Отметим, что при выборке с использованием предиката BETWEEN поле, на которое накладывается диапазон, считается упорядоченным по возрастанию.


Предикат BETWEEN с отрицанием NOT ( NOT BETWEEN) позволяет получить выборку записей, указанные поля которых имеют значения меньше нижней границы и больше верхней границы.
SELECT first_name, last_name, hire_date
FROM employee
WHERE hire_date NOT BETWEEN "1-JAN-1989" AND "31-DEC-1993" получить список самых “старых”
и самых “молодых” (по времени
поступления на работу)
сотрудников
FIRST_NAME LAST_NAME HIRE_DATE
=============== ================ ===========
Robert Nelson 28-DEC-1988
Bruce Young 28-DEC-1988
Pierre Osborne 3-JAN-1994
John Montgomery 30-MAR-1994
Mark Guckenheimer 2-MAY-1994 IN

Предикат IN проверяет, входит ли заданное значение, предшествующее ключевому слову “IN” (например, значение столбца или функция от него) в указанный в скобках список. Если заданное проверяемое значение равно какому-либо элементу в списке, то предикат принимает значение true. Разрешено также использовать конструкцию NOT IN.
SELECT first_name, last_name, job_code
FROM employee
WHERE job_code IN ("VP", "Admin", "Finan")
получить список сотрудников,
занимающих должности
“вице-президент”, “администратор”,
“финансовый директор”
FIRST_NAME LAST_NAME JOB_CODE
=============== ================ ========
Robert Nelson VP
Terri Lee Admin
Stewart Hall Finan
Ann Bennet Admin
Sue Anne O'Brien Admin
Mary S. MacDonald VP
Kelly Brown Admin
А вот пример запроса, использующего предикат NOT IN:
SELECT first_name, last_name, job_country
FROM employee
WHERE job_country NOT IN
("USA", "Japan", "England")
получить список сотрудников,
работающих не в США, не в Японии
и не в Великобритании
FIRST_NAME LAST_NAME JOB_COUNTRY
=============== ================ ===============
Claudia Sutherland Canada
Roberto Ferrari Italy
Jacques Glon France
Pierre Osborne Switzerland LIKE

Предикат LIKE используется только с символьными данными. Он проверяет, соответствует ли данное символьное значение строке с указанной маской. В качестве маски используются все разрешенные символы (с учетом верхнего и нижнего регистров), а также специальные символы:


% - замещает любое количество символов (в том числе и 0),
_ - замещает только один символ.
Разрешено также использовать конструкцию NOT LIKE.
SELECT first_name, last_name
FROM employee
WHERE last_name LIKE "F%" получить список сотрудников,
фамилии которых начинаются с буквы “F”
FIRST_NAME LAST_NAME
=============== ====================
Phil Forest
Pete Fisher
Roberto Ferrari
SELECT first_name, last_name
FROM employee
WHERE first_name LIKE "%er" получить список сотрудников,
имена которых заканчиваются буквами “er”
FIRST_NAME LAST_NAME
=============== ====================
Roger De Souza
Roger Reeves
Walter Steadman
А такой запрос позволяет решить проблему произношения (и написания) имени:
SELECT first_name, last_name
FROM employee
WHERE first_name LIKE "Jacq_es"
найти сотрудника(ов),
в имени которого
неизвестно произношение
буквы перед окончанием “es”
FIRST_NAME LAST_NAME
=============== ====================
Jacques Glon
Что делать, если требуется найти строку, которая содержит указанные выше специальные символы (“%”, “_”) в качестве информационных символов? Есть выход! Для этого с помощью ключевого слова ESCAPE нужно определить так называемый escape-символ, который, будучи поставленным перед символом “%” или “_”, укажет, что этот символ является информационным. Escape-символ не может быть символом “\” (обратная косая черта) и, вообще говоря, должен представлять собой символ, никогда не появляющийся в упоминаемом столбце как информационный символ. Часто для этих целей используются символы “@” и “~”.
SELECT first_name, last_name
FROM employee
WHERE first_name LIKE "%@_%" ESCAPE "@"
получить список сотрудников,
в имени которых содержится “_”
(знак подчеркивания) CONTAINING

Предикат CONTAINING аналогичен предикату LIKE, за исключением того, что он не чувствителен к регистру букв. Разрешено также использовать конструкцию NOT CONTAINING.
SELECT first_name, last_name
FROM employee
WHERE last_name CONTAINING "ne"
получить список сотрудников,
фамилии которых содержат буквы



“ne”, “Ne”, “NE”, “nE” FIRST_NAME LAST_NAME
=============== ====================
Robert Nelson
Ann Bennet
Pierre Osborne
 
  IS NULL

В SQL-запросах NULL означает, что значение столбца неизвестно. Поисковые условия, в которых значение столбца сравнивается с NULL, всегда принимают значение unknown (и, соответственно, приводят к ошибке), в противоположность true или false, т.е.
WHERE dept_no = NULL
или даже
WHERE NULL = NULL.
Предикат IS NULL принимает значение true только тогда, когда выражение слева от ключевых слов “IS NULL” имеет значение null (пусто, не определено). Разрешено также использовать конструкцию IS NOT NULL, которая означает “не пусто”, “имеет какое-либо значение”.
SELECT department, mngr_no
FROM department
WHERE mngr_no IS NULL получить список отделов,
в которых еще не назначены
начальники
DEPARTMENT MNGR_NO
========================= =======
Marketing <null>
Software Products Div. <null>
Software Development <null>
Field Office: Singapore <null>
Предикаты EXIST, ANY, ALL, SOME, SINGULAR мы рассмотрим в разделе, рассказывающем о подзапросах.
 
  Логические операторы К логическим операторам относятся известные операторы AND, OR, NOT, позволяющие выполнять различные логические действия: логическое умножение (AND, “пересечение условий”), логическое сложение (OR, “объединение условий”), логическое отрицание (NOT, “отрицание условий”). В наших примерах мы уже применяли оператор AND. Использование этих операторов позволяет гибко “настроить” условия отбора записей.
Оператор AND означает, что общий предикат будет истинным только тогда, когда условия, связанные по “AND”, будут истинны.
Оператор OR означает, что общий предикат будет истинным, когда хотя бы одно из условий, связанных по “OR”, будет истинным.
Оператор NOT означает, что общий предикат будет истинным, когда условие, перед которым стоит этот оператор, будет ложным.
В одном предикате логические операторы выполняются в следующем порядке: сначала выполняется оператор NOT, затем - AND


и только после этого - оператор OR. Для изменения порядка выполнения операторов разрешается использовать скобки. SELECT first_name, last_name, dept_no, job_code, salary
FROM employee
WHERE dept_no = 622
OR job_code = "Eng"
AND salary <= 40000
ORDER BY last_name получить список служащих,
занятых в отделе 622
или
на должности “инженер” с зарплатой
не выше 40000 FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
============ ============= ======= ======== ===========
Jennifer M. Burbank 622 Eng 53167.50
Phil Forest 622 Mngr 75060.00
T.J. Green 621 Eng 36000.00
Mark Guckenheimer 622 Eng 32000.00
John Montgomery 672 Eng 35000.00
Bill Parker 623 Eng 35000.00
Willie Stansbury 120 Eng 39224.06
 
  SELECT first_name, last_name, dept_no,
job_code, salary
FROM employee
WHERE (dept_no = 622
OR job_code = "Eng")
AND salary <= 40000
ORDER BY last_name получить список служащих,
занятых в отделе 622
или на должности “инженер”,
зарплата которых не выше 40000 FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
============ ============= ======= ======== ===========
T.J. Green 621 Eng 36000.00
Mark Guckenheimer 622 Eng 32000.00
John Montgomery 672 Eng 35000.00
Bill Parker 623 Eng 35000.00
Willie Stansbury 120 Eng 39224.06
 
  Преобразование типов (CAST)

В SQL имеется возможность преобразовать значение столбца или функции к другому типу для более гибкого использования операций сравнения. Для этого используется функция CAST.
Типы данных могут быть конвертированы в соответствии со следующей таблицей:
Из типа данных В тип данных
---------------------------------------
NUMERIC CHAR, VARCHAR, DATE
CHAR, VARCHAR NUMERIC, DATE
DATE CHAR, VARCHAR, DATE
 
 
SELECT first_name, last_name, dept_no
FROM employee
WHERE CAST(dept_no AS char(20))
CONTAINING "00" получить список сотрудников,
занятых в отделах,
номера которых содержат “00”
FIRST_NAME LAST_NAME DEPT_NO
=============== ==================== =======
Robert Nelson 600
Terri Lee 000
Stewart Hall 900
Walter Steadman 900


Mary S. MacDonald 100
Oliver H. Bender 000
Kelly Brown 600
Michael Yanowski 100
 
  Изменение порядка выводимых строк (ORDER BY) Порядок выводимых строк может быть изменен с помощью опционального (дополнительного) предложения ORDER BY в конце SQL-запроса. Это предложение имеет вид: ORDER BY <порядок строк> [ASC | DESC]
 
  Порядок строк может задаваться одним из двух способов: именами столбцов номерами столбцов. Способ упорядочивания определяется дополнительными зарезервированными словами ASC и DESC. Способом по умолчанию - если ничего не указано - является упорядочивание “по возрастанию” (ASC). Если же указано слово “DESC”, то упорядочивание будет производиться “по убыванию”.
Подчеркнем еще раз, что предложение ORDER BY должно указываться в самом конце запроса. Упорядочивание с использованием имен столбцов

SELECT first_name, last_name, dept_no,
job_code, salary
FROM employee
ORDER BY last_name получить список сотрудников,
упорядоченный по фамилиям
в алфавитном порядке
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
============ ============= ======= ======== ===========
Janet Baldwin 110 Sales 61637.81
Oliver H. Bender 000 CEO 212850.00
Ann Bennet 120 Admin 22935.00
Dana Bishop 621 Eng 62550.00
Kelly Brown 600 Admin 27000.00
Jennifer M. Burbank 622 Eng 53167.50
Kevin Cook 670 Dir 111262.50
Roger De Souza 623 Eng 69482.62
Roberto Ferrari 125 SRep 99000000.00
...
 
 
SELECT first_name, last_name, dept_no,
job_code, salary
FROM employee
ORDER BY last_name DESC получить список сотрудников,
упорядоченный по фамилиям
в порядке, обратном алфавитному
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE SALARY
============ ============= ======= ======== ===========
Katherine Young 623 Mngr 67241.25
Bruce Young 621 Eng 97500.00
Michael Yanowski 100 SRep 44000.00
Takashi Yamamoto 115 SRep 7480000.00
Randy Williams 672 Mngr 56295.00
K. J. Weston 130 SRep 86292.94
Claudia Sutherland 140 SRep 100914.00
Walter Steadman 900 CFO 116100.00
Willie Stansbury 120 Eng 39224.06


Roger Reeves 120 Sales 33620.62
...
Столбец, определяющий порядок вывода строк, не обязательно дожен присутствовать в списке выбираемых элементов (столбцов):
SELECT first_name, last_name, dept_no,
job_code
FROM employee
ORDER BY salary получить список сотрудников,
упорядоченный по их зарплате
FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
=============== =============== ======= ========
Ann Bennet 120 Admin
Kelly Brown 600 Admin
Sue Anne O'Brien 670 Admin
Mark Guckenheimer 622 Eng
Roger Reeves 120 Sales
Bill Parker 623 Eng Упорядочивание с использованием номеров столбцов SELECT first_name, last_name, dept_no,
job_code, salary * 1.1
FROM employee
ORDER BY 5 получить список сотрудников,
упорядоченный по их зарплате
с 10% надбавкой
 
  FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
============ ============= ======= ======== ===========
Ann Bennet 120 Admin 25228.5
Kelly Brown 600 Admin 29700
Sue Anne O'Brien 670 Admin 34402.5
Mark Guckenheimer 622 Eng 35200
Roger Reeves 120 Sales 36982.6875
Bill Parker 623 Eng 38500
 
  Допускается использование нескольких уровней вложенности при упорядочивании выводимой информации по столбцам; при этом разрешается смешивать оба способа. SELECT first_name, last_name, dept_no,
job_code, salary * 1.1
FROM employee
ORDER BY dept_no, 5 DESC, last_name
получить список сотрудников,
упорядоченный сначала по
номерам отделов,
в отделах - по убыванию их
зарплаты (с 10%),
а в пределах одной зарплаты - по фамилиям
 
  FIRST_NAME LAST_NAME DEPT_NO JOB_CODE
=========== ========== ======= ======== ===============
Oliver H. Bender 000 CEO 234135
Terri Lee 000 Admin 59172.3
Mary S. MacDonald 100 VP 122388.75
Michael Yanowski 100 SRep 48400.000000001
Luke Leung 110 SRep 75685.5
Janet Baldwin 110 Sales 67801.59375
Takashi Yamamoto 115 SRep 8228000.0000001
Yuki Ichida 115 Eng 6600000.0000001 Устранение дублирования (модификатор DISTINCT)

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


Иногда ( в зависимости от задачи) бывает необходимо устранить все повторы строк из результирующего набора. Этой цели служит модификатор DISTINCT. Данный модификатор может быть указан только один раз в списке выбираемых элементов и действует на весь список.
SELECT job_code
FROM employee получить список должностей сотрудников
JOB_CODE
========
VP
Eng
Eng
Mktg
Mngr
SRep
Admin
Finan
Mngr
Mngr
Eng
...
Данный пример некорректно решает задачу “получения” списка должностей сотрудников предприятия, так как в нем имеются многочисленные повторы, затрудняющие восприятие информации. Тот же запрос, включающий модификатор DISTINCT, устраняющий дублирование, дает верный результат.
SELECT DISTINCT job_code
FROM employee получить список должностей сотрудников
JOB_CODE
========
Admin
CEO
CFO
Dir
Doc
Eng
Finan
Mktg
Mngr
PRel
SRep
Sales
VP
Два следующих примера показывают, что модификатор DISTINCT действует на всю строку сразу.
SELECT first_name, last_name
FROM employee
WHERE first_name = "Roger" получить список служащих,
имена которых - Roger
FIRST_NAME LAST_NAME
=============== ====================
Roger De Souza
Roger Reeves
 
 
SELECT DISTINCT first_name, last_name
FROM employee
WHERE first_name = "Roger" получить список служащих,
имена которых - Roger
FIRST_NAME LAST_NAME
=============== ====================
Roger De Souza
Roger Reeves
 
  Соединение (JOIN) Операция соединения используется в языке SQL для вывода связанной информации, хранящейся в нескольких таблицах, в одном запросе. В этом проявляется одна из наиболее важных особенностей запросов SQL - способность определять связи между многочисленными таблицами и выводить информацию из них в рамках этих связей. Именно эта операция придает гибкость и легкость языку SQL.
После изучения этого раздела мы будем способны: соединять данные из нескольких таблиц в единую результирующую таблицу; задавать имена столбцов двумя способами; записывать внешние соединения; создавать соединения таблицы с собой. Операции соединения подразделяются на два вида - внутренние и внешние. Оба вида соединений задаются в предложении WHERE запроса SELECT с помощью специального условия соединения. Внешние соединения (о которых мы поговорим позднее) поддерживаются стандартом ANSI-92 и содержат зарезервированное слово “JOIN”, в то время как внутренние соединения (или просто соединения) могут задаваться как без использования такого слова (в стандарте ANSI-89), так и с использованием слова “JOIN” (в стандарте ANSI-92).


Связывание производится, как правило, по первичному ключу одной таблицы и внешнему ключу другой таблицы - для каждой пары таблиц. При этом очень важно учитывать все поля внешнего ключа, иначе результат будет искажен. Соединяемые поля могут (но не обязаны!) присутствовать в списке выбираемых элементов. Предложение WHERE может содержать множественные условия соединений. Условие соединения может также комбинироваться с другими предикатами в предложении WHERE.
 
  Внутренние соединения Внутреннее соединение возвращает только те строки, для которых условие соединения принимает значение true. SELECT first_name, last_name, department
FROM employee, department
WHERE job_code = "VP" получить список сотрудников,
состоящих в должности “вице-
президент”, а также названия
их отделов
 
  FIRST_NAME LAST_NAME DEPARTMENT
=============== ================ ======================
Robert Nelson Corporate Headquarters
Mary S. MacDonald Corporate Headquarters
Robert Nelson Sales and Marketing
Mary S. MacDonald Sales and Marketing
Robert Nelson Engineering
Mary S. MacDonald Engineering
Robert Nelson Finance
Mary S. MacDonald Finance
...
 
  Этот запрос (“без соединения”) возвращает неверный результат, так как имеющиеся между таблицами связи не задействованы. Отсюда и появляется дублирование информации в результирующей таблице. Правильный результат дает запрос с использованием операции соединения: SELECT first_name, last_name, department
FROM employee, department
WHERE job_code = "VP"
AND employee.dept_no = department.dept_no имена таблиц
получить список сотрудников,
состоящих в должности “вице-
президент”, а также названия
их отделов FIRST_NAME LAST_NAME DEPARTMENT
=============== ================ ======================
Robert Nelson Engineering
Mary S. MacDonald Sales and Marketing
 
  В вышеприведенном запросе использовался способ непосредственного указания таблиц с помощью их имен. Возможен (а иногда и просто необходим) также способ указания таблиц с помощью алиасов (псевдонимов). При этом алиасы определяются в предложении FROM запроса SELECT и представляют собой любой допустимый идентификатор, написание которого подчиняется таким же правилам, что и написание имен таблиц. Потребность в алиасах таблиц возникает тогда, когда названия столбцов, используемых в условиях соединения двух (или более) таблиц, совпадают, а названия таблиц слишком длинны...


Замечание 1: в одном запросе нельзя смешивать использование написания имен таблиц и их алиасов.
Замечание 2: алиасы таблиц могут совпадать с их именами.
 
  SELECT first_name, last_name, department
FROM employee e, department d
WHERE job_code = "VP"
AND e.dept_no = d.dept_no алиасы таблиц
получить список сотрудников,
состоящих в должности “вице-
президент”, а также названия
их отделов FIRST_NAME LAST_NAME DEPARTMENT
=============== ================ ======================
Robert Nelson Engineering
Mary S. MacDonald Sales and Marketing
 
  А вот пример запроса, соединяющего сразу три таблицы: SELECT first_name, last_name, job_title,
department
FROM employee e, department d, job j
WHERE d.mngr_no = e.emp_no
AND e.job_code = j.job_code
AND e.job_grade = j.job_grade
AND e.job_country = j.job_country
получить список сотрудников
с названиями их должностей
и названиями отделов
 
  FIRST_NAME LAST_NAME JOB_TITLE DEPARTMENT
========== ============ ======================= ======================
Robert Nelson Vice President Engineering
Phil Forest Manager Quality Assurance
K. J. Weston Sales Representative Field Office: East Coast
Katherine Young Manager Customer Support
Chris Papadopoulos Manager Research and Development
Janet Baldwin Sales Co-ordinator Pacific Rim Headquarters
Roger Reeves Sales Co-ordinator European Headquarters
Walter Steadman Chief Financial Officer Finance
В данном примере последние три условия необходимы в силу того, что первичный ключ в таблице JOB состоит из трех полей - см. рис.1.
Мы рассмотрели внутренние соединения с использованием стандарта ANSI-89. Теперь опишем новый (ANSI-92) стандарт: условия соединения записываются в предложении FROM, в котором слева и справа от зарезервированного слова “JOIN” указываются соединяемые таблицы; условия поиска, основанные на правой таблице, помещаются в предложение ON; условия поиска, основанные на левой таблице, помещаются в предложение WHERE. SELECT first_name, last_name, department


FROM employee e JOIN department d
ON e.dept_no = d.dept_no
AND department = "Customer Support"
WHERE last_name starting with "P"
получить список служащих
(а заодно и название отдела),
являющихся сотрудниками отдела
“Customer Support”, фамилии кото-
рых начинаются с буквы “P”
 
  FIRST_NAME LAST_NAME DEPARTMENT
============= =============== ===================
Leslie Phong Customer Support
Bill Parker Customer Support
 
  Самосоединения

В некоторых задачах необходимо получить информацию, выбранную особым образом только из одной таблицы. Для этого используются так называемые самосоединения, или рефлексивные соединения. Это не отдельный вид соединения, а просто соединение таблицы с собой с помощью алиасов. Самосоединения полезны в случаях, когда нужно получить пары аналогичных элементов из одной и той же таблицы.
SELECT one.last_name, two.last_name,
one.hire_date
FROM employee one, employee two
WHERE one.hire_date = two.hire_date
AND one.emp_no < two.emp_no
получить пары фамилий сотрудников,
которые приняты на работу в один
и тот же день
LAST_NAME LAST_NAME HIRE_DATE
==================== ==================== ===========
Nelson Young 28-DEC-1988
Reeves Stansbury 25-APR-1991
Bishop MacDonald 1-JUN-1992
Brown Ichida 4-FEB-1993
 
 
SELECT d1.department, d2.department, d1.budget
FROM department d1, department d2
WHERE d1.budget = d2.budget
AND d1.dept_no < d2.dept_no
получить список пар отделов с
одинаковыми годовыми бюджетами
DEPARTMENT DEPARTMENT BUDGET
======================== ========================= =========
Software Development Finance 400000.00
Field Office: East Coast Field Office: Canada 500000.00
Field Office: Japan Field Office: East Coast 500000.00
Field Office: Japan Field Office: Canada 500000.00
Field Office: Japan Field Office: Switzerland 500000.00
Field Office: Singapore Quality Assurance 300000.00
Field Office: Switzerland Field Office: East Coast 500000.00
 
  Внешние соединения Напомним, что внутреннее соединение возвращает только те строки, для которых условие соединения принимает значение true. Иногда требуется включить в результирующий набор большее количество строк.


Вспомним, запрос вида SELECT first_name, last_name, department
FROM employee e, department d
WHERE e.dept_no = d.dept_no
 
  возвращает только те строки, для которых условие соединения (e.dept_no = d.dept_no) принимает значение true.
Внешнее соединение возвращает все строки из одной таблицы и только те строки из другой таблицы, для которых условие соединения принимает значение true. Строки второй таблицы, не удовлетворяющие условию соединения (т.е. имеющие значение false), получают значение null в результирующем наборе.
Существует два вида внешнего соединения: LEFT JOIN и RIGHT JOIN.
В левом соединении (LEFT JOIN) запрос возвращает все строки из левой таблицы (т.е. таблицы, стоящей слева от зарезервированного словосочетания “LEFT JOIN”) и только те из правой таблицы, которые удовлетворяют условию соединения. Если же в правой таблице не найдется строк, удовлетворяющих заданному условию, то в результате они замещаются значениями null.
Для правого соединения - все наоборот. SELECT first_name, last_name, department
FROM employee e LEFT JOIN department d
ON e.dept_no = d.dept_no
получить список сотрудников
и название их отделов,
включая сотрудников, еще
не назначенных ни в какой отдел
 
  FIRST_NAME LAST_NAME DEPARTMENT
=============== ============== =====================
Robert Nelson Engineering
Bruce Young Software Development
Kim Lambert Field Office: East Coast
Leslie Johnson Marketing
Phil Forest Quality Assurance
...
 
  В данном запросе все сотрудники оказались распределены по отделам, иначе названия отделов заместились бы значением null.
А вот пример правого соединения: SELECT first_name, last_name, department
FROM employee e RIGHT JOIN department d
ON e.dept_no = d.dept_no
получить список сотрудников
и название их отделов,
включая отделы, в которые еще
не назначены сотрудники
 
  FIRST_NAME LAST_NAME DEPARTMENT
=============== ============= =========================
Terri Lee Corporate Headquarters
Oliver H. Bender Corporate Headquarters
Mary S. MacDonald Sales and Marketing
Michael Yanowski Sales and Marketing
Robert Nelson Engineering
Kelly Brown Engineering
Stewart Hall Finance
Walter Steadman Finance
Leslie Johnson Marketing
Carol Nordstrom Marketing
<null> <null> Software Products Div.
Bruce Young Software Development
...
 
  В результирующий набор входит и отдел “Software Products Div.” (а также отдел “Field Office: Singapore”, не представленный здесь), в котором еще нет ни одного сотрудника.

Примеры вызовов API Windows в Delphi


32 урока по Delphi
Урок 31: Примеры вызовов API Windows в Delphi

Содержание урока 31:
1.0 Обзор
1.1 Стандартная страница Палитры Компонент
TMainMenu, TPopupMenu
TMemo
TListBox, TComboBox
1.2 Страница “Additional” Палитры Компонент
TSpeedButton
TTabSet, TNoteBook
TTabbedNoteBook
1.3 Страница “System” Палитры Компонент
TOLEContainer
1.4 Страница “Data Access” Палитры Компонент
TDataSource
TTable, TQuery
1.5 Страница “Data Controls” Палитры Компонент
TDBGrid
1.6 Окна в Delphi
1.7 Обработка событий от клавиатуры
2.0 Вызов методов дальних предков
Обзор
Разработчики библиотеки визуальных компонент (VCL) Delphi очень серьезно поработали над ее проектированием и воплощением в реальность. Как показывает практика, стандартного набора объектов обычно достаточно для создания реальных приложений. И, что более существенно, им (разработчикам) удалось решить очень сложную задачу, стоящую перед создателями любой среды визуального программирования - скрыть от программиста сложность и трудоемкость программирования в Windows и, в то же время, не лишать его возможности доступа к тем богатым возможностям системы, которые предоставляет Windows API.
В данной главе на примерах показано, как с помощью вызовов Windows API можно управлять объектами из VCL. Кроме того, здесь же можно найти описание некоторых программных трюков, которые помогут придать вашей программе оригинальный вид. 1.1 Стандартная страница Палитры Компонент

Компоненты, расположенные на странице “Standard”, представляют из себя объектную оболочку для стандартных управляющих элементов Windows. Поэтому для них существуют ограничения, накладываемые самой системой. Например, 32Кб - максимальный размер текста в TMemo.
TMainMenu, TPopupMenu
Добавление картинки (BitMap) в меню.
Для добавления в меню картинки можно использовать функцию API Windows SetMenuItemBitmaps(), например, следующим образом:

implementation

var
BMP1, BMP2 : TBitMap;

procedure TForm1.FormCreate(Sender: TObject);
begin
BMP1:=TBitMap.Create;


BMP1.LoadFromFile('c:\images\uncheck.bmp');
BMP2:=TBitMap.Create;
BMP2.LoadFromFile('c:\images\check.bmp');
SetMenuItemBitmaps(File1.Handle, 1, MF_BYPOSITION, BMP1.Handle, BMP2.Handle);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
BMP1.Free;
BMP2.Free;
end;
File1 это объект класса TMenuItem - пункт меню “File”. Значения параметров при вызове функции можно посмотреть в справочнике по Windows API.
При уничтожении меню освобождения связанных с ним картинок не происходит и их надо уничтожать вручную.
Вторая картинка BMP2 отображается рядом с пунктом меню, когда он выбран (Checked=True).
 
 
TMemo
Компонент класса TMemo может содержать до 32К текста (для Windows 3.x) вследствие ограничения Windows. В Delphi 2.0 предел увеличен до 64К (в декабрьской бета-версии).
I. Определение позиции каретки в TMemo.
Можено использовать сообщения Windows API EM_LINEFROMCHAR и EM_LINEINDEX для определения текущей позиции каретки.
procedure TForm1.ShowPosition;
var
LineNum: longint;
CharNum: longint;
begin
LineNum:= Memo1.Perform(EM_LINEFROMCHAR, Memo1.SelStart,0);
CharNum:= Memo1.Perform(EM_LINEINDEX, LineNum, 0);
Label1.Caption := 'Line : '+ IntToStr(LineNum+1);
Label2.Caption := 'Position :' + IntToStr((Memo1.SelStart -
CharNum)+1);
end;
Метод Perform, определенный в классе TControl, посылает сообщение своему же объекту, то есть его использование имеет тот же эффект, что и вызов функции API SendMessage():
SendMessage(Memo1.Handle,EM_LINEFROMCHAR, Memo1.SelStart,0);
II. Операция UnDo в TMemo.
Отмена последнего редактирования (операция UnDo) в объекте класса TMemo выполняется так же с помощью сообщений, посылаемых в данный объект:
procedure TForm1.UnDoClick(Sender: TObject);
begin
if Memo1.Perform(EM_CANUNDO, 0, 0)<>0 then
Memo1.Perform(EM_UNDO, 0, 0);
end;
В справочнике по Windows API описаны сообщения, которые можно послать в объект TMemo для управления его поведением. Кроме вышеназванных, имеется еще несколько полезных:
EM_EMPTYUNDOBUFFER Сбрасывает флажок UnDo


EM_GETHANDLE Получает указатель на буфер с текстом
EM_LINESCROLL Прокрутка текста в окне TMemo
EM_SETHANDLE Установка указателя на буфер с текстом
EM_SETTABSTOPS Устанавливает табуляцию в окне с текстом
 
 
TListBox, TComboBox
Windows накладывает ограничение на количество элементов в списке этих управляющих элементов. В случае Windows 3.x это количество равно 5440, в Windows’95 - 32767.
I. Как получить горизонтальную прокрутку (scrollbar) в ListBox?
Так же как в случае с TMemo, здесь можно использовать сообщения. Например, сообщение может быть отослано в момент создания формы:
procedure TForm1.FormCreate(Sender: TObject);
begin
ListBox1.Perform(LB_SETHORIZONTALEXTENT, 1000, Longint(0));
end;
Второй параметр в вызове - ширина прокрутки в точках.
 
 
II. Вставка графики в ListBox.
У класса TListBox (и TComboBox тоже) есть свойство Style, определяющее порядок рисования объекта. По-умолчанию оно установлено в lbStandard и за внешний вид объекта отвечает Windows. Если установить это значение в lbOwnerDrawFixed или lbOwnerDrawVariable, то можно несколько разнообразить внешний вид объекта. Давайте построим для примера ListBox, отображающий названия файлов формата .BMP из какой-либо директории вместе с их картинками.
Прежде всего, оказывается, что вовсе не нужно заполнять ListBox вручную именами файлов, для этого достаточно послать ему сообщение:
procedure TForm1.Button1Click(Sender: TObject);
var
s : String;
begin
s:='c:\windows\*.bmp'#0;
ListBox1.Perform(LB_DIR, DDL_READWRITE, Longint(@s[1]));
end;
Здесь мы указали ListBox’у, какие файлы требуется отображать.
Далее, как уже было сказано, свойство Style нужно установить в lbOwnerDrawFixed и создать обработчик события OnDrawItem:
 
 
procedure TForm1.ListBox1DrawItem(Control: TWinControl;
Index: Integer; Rect: TRect; State: TOwnerDrawState);
var
Bitmap: TBitmap;
Offset: Integer;
BMPRect : TRect;
begin
with (Control as TListBox).Canvas do
begin
{очищаем прямоугольник}
FillRect(Rect);
{считываем картинку}


Bitmap:=TBitMap.Create;
Bitmap.LoadFromFile('c:\windows\'+ListBox1.Items[Index]);
if Bitmap <> nil then begin
{вычисляем квадрат для показа картинки}
BMPRect:=Bounds(Rect.Left + 2, Rect.Top + 2,
Rect.Bottom-Rect.Top-2, Rect.Bottom-Rect.Top-2);
{рисуем картинку}
StretchDraw(BMPRect, BitMap);
Offset := Rect.Bottom-Rect.Top + 6;
end;
{выводим текст}
TextOut(Rect.Left+Offset,Rect.Top,Listbox1.Items[Index]);
{не забыть освободить!}
Bitmap.Free;
end;
end;
Чтобы картинки получились побольше, значение свойства ItemHeight можно увеличить.
Есть около двух десятков сообщений, которые можно послать в объекты класса TListBox и TComboBox. Подробнее о них можно узнать в справочнике по Windows API (on-line Help).
 
  1.2 Страница “Additional” Палитры Компонент

Компоненты, размещенные на этой странице представляют из себя объектную оболочку для управляющих элементов, появившихся в более поздних версиях Windows.
TSpeedButton
I. Эмуляция потери фокуса.
Особенность этой кнопки в том, что она никогда не получает фокус и, следовательно, при нажатии на нее, текущий активный элемент фокус не теряет. В некоторых случаях бывает необходимо эмулировать потерю фокуса активным объектом. Пример, при нажатии кнопки, данные из объектов типа TEdit записываются в файл, на событие OnExit для них (объектов) установлена процедура верификации. В этом случае надо вызывать обработчик в явном виде:
 
 
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
if ActiveControl is TEdit then
(ActiveControl as TEdit).OnExit(ActiveControl);
end;
Обработчик события должен быть определен, иначе возникнет GPF.
II. Обработка двойного щелчка мышью.
Если значение свойства GroupIndex равно 0, то двойное нажатие будет воспринято объектом как два одиночных. Если требуется, чтобы в результате двойного щелчка кнопка не фиксировалась в нажатом состоянии, то ее свойство AllowAllUp устанавливается в True, и в обработчике события кнопка возвращается в прежнее состояние:
procedure TForm1.SpeedButton1DblClick(Sender: TObject);


begin
SpeedButton1.Down:= Not SpeedButton1.Down;
Do_Something;
end;
 
 
TTabSet, TNoteBook
Ограничения по количеству страниц для этих объектов - 16364 (еще одно “магическое число” из класса TList). Но, на самом деле, не имеет смысла создавать более сотни страниц.
I. Переход на страницу по ее имени.
Если объект типа TTabSet содержит большое количество страниц, то пролистывать их в поисках нужной - дело утомительное. Проще найти ее по имени. Предположим, что имя страницы вводится в Edit1 :
procedure TMultPageDlg.Edit1Change(Sender: TObject);
var
i : Integer;
s1, s2: String;
begin
s1:=Edit1.Text;
if s1 = '' then Exit;
for i:=TabSet.Tabs.Count-1 downto 0 do begin
s2:=Copy(TabSet.Tabs[i], 1, Ord(s1[0]));
if CompareText(s1, s2)<=0 then
TabSet.TabIndex:=i;
end;
end;
 
 
TTabbedNoteBook
I. Добавление новых объектов во время выполнения программы.
После создания нового объекта, нужно в его свойстве Parent указать требуемую страницу TabbedNotebook:
 
 
...
var
Btn : TButton;
begin
Btn := TButton.Create(Self);
Btn.Parent:=TWinControl(TabbedNotebook1.Pages.Objects[1]);
...
end;
 
 
 
  1.3 Страница “System” Палитры Компонент

TOLEContainer
TOLEContainer - компонент, который нужно использовать во время дизайна с осторожностью, поскольку вся информация о нем сохраняется в .DFM файле. И если попытаться использовать встроенный (embedded) OLE-объект большого размера, то при сохранении формы может возникнуть ошибка “Out of resource”.
I. Хранение OLE-объектов в базе данных
Использование технологии OLE для хранения информации в базе данных оправдано, если эта информация неоднородна. Иногда возникает необходимость одновременно сохранять в таблице и использовать в дальнейшем графические изображения, документы в формате Microsoft Word, звук и тому подобное. К сожалению, в стандартном наборе компонент отсутствует компонент вроде TDbOleContainer. Однако, обойтись без него можно достаточно просто.
Для хранения OLE-объекта подойдет поле типа BLOB, а для работы с этим объектом - существующий компонент TOLEContainer. Если предполагается использовать таблицу на разных компьютерах, то OLE-объект нужно делать встроенным (embedded) и следить за тем, чтобы присутствовали соответствующие OLE-серверы.


Итак, сперва нужно заполнить таблицу:
{создание и инициализация OLE-объекта во время выполнения}
procedure TForm1.OLEInitClick(Sender: TObject);
var
OLE : TOLEContainer;
Info : Pointer;
begin
{создание OLE-контейнера}
OLE:=TOLEContainer.Create(Self);
OLE.Name:='Temp_OLE';
OLE.Parent:=Self;
{вызов диалога инициализации OLE-объекта}
if InsertOLEObjectDlg(Self, 0, Info) then begin
OLE.PInitInfo := Info;
ReleaseOLEInitInfo(Info);
end
else
OLE.Free;
end;
{уничтожение OLE-контейнера}
procedure TForm1.OLEFreeClick(Sender: TObject);
begin
FindComponent('Temp_OLE').Free;
end;
{сохранение OLE-объекта в BLOB поле}
procedure TForm1.InsertClick(Sender: TObject);
var
OLE : TOLEContainer;
TBS : TBlobStream;
begin
OLE:=FindComponent('Temp_OLE') as TOLEContainer;
if Assigned(OLE) then
with OLE do begin
{добавляем в таблицу новую запись}
Table1.Insert;
{создаем поток, связанный с BLOB полем}
TBS:=TBlobStream.Create(Table1.FieldByName('OLE') as
TBLOBField, bmReadWrite);
{сохраняем OLE-объект в поток}
SaveToStream(TBS as TStream);
{уничтожаем поток}
TBS.Free;
Exit;
end;
end;
{завершаем редактирование таблицы}
procedure TForm1.PostClick(Sender: TObject);
begin
if Table1.State <> dsBrowse then Table1.Post;
end;
 
 
 
 
Вторая часть проблемы - чтение OLE-объекта из таблицы и обновление информации при его модификации. Здесь можно предложить динамически создавать OLE-контейнер и считывать OLE-объект из потока, связанного с BLOB полем. И делать это каждый раз при переходе на новую запись (перехватывать для DataSource событие OnDataChange). Либо вытаскивать информацию по запросу пользователя, а уничтожать OLE-контейнер при переходе на новую запись.
{считывание OLE-объекта из таблицы}
procedure TForm1.ViewClick(Sender: TObject);
var
OLE : TOLEContainer;
TBS : TBLOBStream;
begin
{создаем OLE-контейнер}
OLE:=TOLEContainer.Create(Self);
OLE.Parent:=Self;
OLE.Name:='Temp_OLE';
{создаем поток для BLOB поля}
TBS:=TBLOBStream.Create(Table1.FieldByName('OLE') as


TBLOBField, bmRead);
{инициализируем OLE-объект}
OLE.LoadFromStream(TBS as TStream);
{уничтожаем поток}
TBS.Free;
end;
 
 
{сохранение модифицированного OLE-объекта}
procedure TForm1.SaveClick(Sender: TObject);
var
TBS : TBLOBStream;
OLE : TOLEContainer;
begin
{находим объект}
OLE:=FindComponent('Temp_OLE') as TOLEContainer;
if Assigned(OLE) then
with OLE do
{сохраняем, если был модифицирован}
if Modified then begin
Table1.Edit;
{очищаем BLOB поле}
TBLOBField(Table1.FieldByName('OLE')).Clear;
{создаем поток}
TBS:= TBlobStream.Create(Table1.FieldByName('OLE') as
TBLOBField, bmReadWrite);
{сохраняем объект}
SaveToStream(TBS as TStream);
{уничтожаем поток}
TBS.Free;
Table1.Post;
end;
end;
{уничтожение OLE- контенера при переходе на другую запись}
procedure TForm1.DataSource1DataChange(Sender: TObject;
Field: TField);
begin
if Table1.State = dsBrowse then
FindComponent('Temp_OLE').Free;
end;
В примерах выше предполагается наличие таблицы с BLOB полем, которое называется “OLE”.
 
 
 
  1.4 Страница “Data Access” Палитры Компонент

Компоненты, расположенные на данной странице представляют из себя объектную оболочку для BDE (Borland Database Engine) - библиотеки доступа к данным. Компоненты TTable, TQuery, TStoredProc, TDataBase и TBatchMove (а так же TSession, который включен в Палитру в версии Delphi 2.0) имеют свои низкоуровневые аналоги. Хотя эти объекты достаточно полно реализуют возможности BDE, тем не менее, существуют некоторые задачи, решить которые можно только с помощью прямых вызовов функций BDE. Например, сменить пароль в таблице Paradox или упаковать таблицу dBase. Но об этом позже, а сейчас - о компонентах на странице “Data Access”.
 
 
 
 
TDataSource
I. Определение момента перехода на другую запись.
Если в момент вызова обработчика события OnDataChange для TDataSource
состояние его DataSet есть dsBrowse, то произошел переход на другую запись.
procedure TForm1.DataSource1DataChange(Sender: TObject;


Field: TField);
begin
if DataSource1.DataSet.State = dsBrowse then
Do_Somehing;
end;
 
 
TTable, TQuery
I. Проблемы при создании таблиц.
Как известно, класс TTable имеет метод для создания таблиц CreateTable, который использует при этом структуры FieldDefs и IndexDefs. Однако, некоторые возможности не реализованы (видимо из соображений общности - TTable должен работать одинаково с таблицами разных форматов). Два примера на эту тему: используя TTable нельзя создать автоинкрементное поле для таблицы формата Paradox и нельзя указать разрядность и число знаков после запятой для числового поля в таблице dBase. В подобных случаях лучше использовать Local SQL (и компонент TQuery). Выражение SQL, решающее первую проблему: CREATE TABLE “PDX_TBL.DB”
(
KOD AUTOINC,
FIELD1 CHAR(10),
PRIMARY KEY (KOD)
)
 
  Выражение SQL, решающее вторую проблему: CREATE TABLE “DB_TBL.DBF”
(
KOD INTEGER,
FIELD1 DECIMAL (10,3)
)
 
  II. Добавление записи в DataSet, не имеющий первичного ключа.
Не вдаваясь в теорию реляционных СУБД, скажу, что BDE не очень хорошо работает с наборами данных, у которых нет первичного ключа и нельзя однозначно идентифицировать запись. Для программиста, использующего компонент TTable для доступа к таблице без первичного ключа, проблемы возникают при добавлении в такую таблицу новой записи. Для того, чтобы состояние TTable соответствовало состоянию физической таблицы, ее требуется переоткрыть (Close, а потом Open). Метод Refresh не поможет - он проходит только при наличии первичного ключа. После пероткрытия возникает проблема, как позиционироваться на запись, которая была текущей до переоткрытия. Найти ее можно только перебором записей, поскольку закладки (BookMark) для такой таблицы нестабильны.
В случае с объектом класса TQuery, работающим с живым набором данных (live dataset), ситуация аналогичная. Не имеет значения, что запрос выполняется для таблицы, имеющей первичный ключ - результатом такого запроса все равно является набор данных, у которого нет ни первичного ключа, ни индексов.


III. Определение номера текущей записи.
Понятие номера (физического) текущей записи имеет некоторый смысл при работе с таблицами в формате dBase. Этот номер меняется достаточно редко, только при упаковке удаленных записей в таблице. В случае таблиц Paradox можно говорить только о логическом номере записи: место записи меняется при добавлении/удалении новой записи или при наложении индекса. Для данных на SQL сервере понятия “номер записи” не имеет смысла вообще (этого понятия нет в теории реляционных баз данных).
Так как компоненты TQuery и TTable должны работать одинаково с данными различного формата, то у них нет свойства, отражающего номер записи.
Так как же быть программисту, если он работает с данными в формате dBase и желает, все-таки, что бы номер записи присутствовал на форме? Ему придется использовать прямой вызов функции BDE (подключив сперва к программе модули DBITYPES и DBIPROCS):
 
 
function TForm1.GetRecNo : Longint;
var
Pr : RECProps;
begin
dbiGetRecord(Table1.Handle, dbiNoLock, NIL, @Pr);
Result:=Pr.iPhyRecNum;
end;
Функция GetRecNo возвращает номер записи, ее можно использовать, например, для определения вычисляемого поля (обработчик OnCalcFields) и отражать это поле в DBGrid первым.
Более подробно о вызовах BDE будет рассказано позже. 1.5 Страница “Data Controls” Палитры Компонент

TDBGrid
Это наиболее применяемый в программах компонент для отображения данных. Понятно, что задачи бывают очень разными и требования к TDBGrid также очень разнообразны. Но в первой версии Delphi этот объект достаточно прост, он не умеет отображать графические и мемо-поля, в него нельзя поместить CheckBox или ComboBox. Есть достаточно много претензий к данному компоненту, но есть уверенность, что в Delphi 2.0 многие проблемы, связанные с этим компонентом, решены. Однако, многое можно сделать и с той версией DBGrid, что поставляется с Delphi 1.0.
 
 
I. Унаследованные свойства.
Многие проблемы можно решить простым переносом некоторых свойств и методов из раздела protected


в декларации класса TDBGrid в разделы public и published. Лучше всего создать новый класс - наследника от TDBGrid: TNewDBGrid = class(TDBGrid)
public
{прямоугольник для указанной ячейки}
function CellRect(ARow, AСol : Integer) : TRect;
{текущий столбец}
property Col;
{текущая строка}
property Row;
{счетчик столбцов}
property ColCount;
{ширина столбцов}
property ColWidths;
{счетчик строк}
property RowCount;
{высота строк}
property RowHeights;
published
{количество “зафиксированных” столбцов}
property FixedCols;
{обработчики событий от мыши}
property OnClick;
property OnMouseMove;
property OnMouseUp;
property OnMouseDown;
end;
. . .
function TNewDBGrid.CellRect(ARow, Acol : Integer) : TRect;
begin
Result:= inherited CellRect(ARow, ACol);
end;
 
 
В примере приведены не все возможные методы и свойства для публикации. Некоторые могут быть полезны. Информацию об этих свойствах и методах можно подчерпнуть из исходных текстов библиотеки и в пункте меню среды Delphi “View|Browser”. Но некоторые свойства, вроде FixedRows, работают не так как ожидается, поскольку их значения переопределяется внутри объекта.
II. Вертикальный ScrollBar.
Обычно возникает вопрос, почему позиция движка в вертикальном ScrollBar’е не соответствует позиции текущей записи в таблице. Ответ частично был дан в предыдущем пункте: не всегда можно говорить о номере текущей записи. Но при желании можно заставить ScrollBar отображать позицию текущей записи в таблице, разумеется, только в случае локальных данных. Например так:
 
 
procedure TForm1.DataSource1DataChange(Sender: TObject;
Field: TField);
begin
if Table1.State = dsBrowse then begin
SetScrollRange(DBGrid1.Handle, SB_VERT, 0,
Table1.RecordCount-1, False);
SetScrollPos(DBGrid1.Handle, SB_VERT, GetRecNo-1, False);
end;
end;
Процедура GetRecNo (приведена в предыдущем пункте) возвращает номер записи. Недостаток данного способа состоит в том, что при переходе на другую запись движок будет перерисовываться два раза, один раз в процедуре Paint, а второй раз в OnDataChange.


III. Обработчик события OnDrawDataCell.
Большое количество задач можно решить, если использовать событие перерисовки ячейки в DBGrid. Например: выделить цветом отрицательные значения или целиком колонку, вывести картинку в ячейке DBGrid, поместить DBCheckBox или DBComboBox в ячейку и многое другое. Ниже приведен вариант, в котором отрицательные значения выделяются красным цветом:
procedure TForm1.DBGrid1DrawDataCell(Sender: TObject; const
Rect: TRect; Field: TField; State: TGridDrawState);
begin
with DBGrid1.Canvas do begin
if (Not (gdFocused in State)) and
(Field.FieldName='BALANCE') and
(Field.AsFloat<0) then begin
Brush.Color:=clRed;
Font.Color:=clWhite;
end;
FillRect(Rect);
TextOut(Rect.Left, Rect.Top, Field.Text);
end;
end;
 
 
 
  1.6 Окна в Delphi Большинство видимых компонентов в Delphi (все компоненты, имеющие предком класс TWinControl) являются для Windows полноценными окнами. При доступе к этим компонентам из Windows API используется свойство Handle каждого компонента. Ниже приводится несколько “фокусов”, которые можно произвести с окнами компонентов, включая TForm. “Перетаскивание” окна без помощи Caption. Достаточно известен способ “перетаскивания” окна приложения без Caption (caption - поле заголовка вверху формы), этот способ описан и в разделе технической информации на CD-ROM с Delphi 1.02.
Для решения этой задачи нужно переопределить обработчик события WM_NCHITTEST для окна следующим образом:
 
 
 
 
type
TForm1 = class(TForm)
. . .
private
procedure WMNCHitTest(var M: TWMNCHitTest);
message wm_NCHitTest;
. . .
end;
. . .
procedure TForm1.WMNCHitTest(var M: TWMNCHitTest);
begin
{вызов унаследованного обработчика события}
inherited;
{если событие произошло в клиентской области,}
if M.Result = htClient then
{то пусть Windows думает, что это произошло на Caption}
M.Result := htCaption;
end;
Теперь окно можно будет переместить на экране, даже если оно совсем не имеет Caption. Однако, неприятно то, что для такого окна не будут вызываться обработчики событий, связанных с мышкой (OnClick, OnMouseMove и т.д.).


Все, что было сейчас проделано с формой ( объектом класса TForm) можно применить и к остальным оконным компонентам. Нам никто не запрещает и для них переопределить обработку события WM_NCHITTEST.
II. Объекты, перетаскиваемые во время работы программы.
Проще всего было бы решить эту задачу переопределением соответствующего обработчика и добавлением нового свойства для класса TWinControl. Однако, после этого перекомпилировать библиотеку будет трудно, так как не все модули предоставлены в исходных текстах. Поэтому, приведу решение задачи на примере класса TMemo.
type
TMoveMemo = class(TMemo)
private
FMoveable : Boolean;
procedure WMNCHitTest(var M : TWMNCHitTest);
message WM_NCHitTest;
published
property Moveable:Boolean read FMoveable write FMoveable;
end;
. . .
procedure TMoveMemo.WMNCHitTest(var M : TWMNCHitTest);
begin
inherited;
if (not (csDesigning in ComponentState)) and FMoveable then
if M.Result = htClient then
M.Result:=htCaption;
end;
Свойство Moveable определяет, можно ли объект перетаскивать. Если бы все оконные объекты имели такой обработчик события и свойство Moveable, то было бы очень просто построить приложение с изменяемым во время работы программы пользовательским интерфейсом.
III. Внешний вид окна.
Как известно, внешний вид окна в Windows определяется при его создании параметром Style (тип Longint). Полный список возможных значений этого параметра можно найти в справочнике по Windows API, все они начинаются с префикса WS_ (например, WS_CAPTION, WS_MAXIMIZED).
Изменить внешний вид окна во время выполнения программы можно, если изменить значение его параметра Style. Для этого используется вызов функции API SetWindowLong(). Конечно, в случае формы (TForm) можно обойтись свойствами этого класса (но не всегда). Но в случае с обычными оконными компонентами получается забавный эффект. Попробуйте изменить стиль обычной кнопки (TButton):
procedure TForm1.Button2Click(Sender: TObject);
var
Style : Longint;
begin
{старый стиль окна}
Style:=GetWindowLong(Button1.Handle, GWL_STYLE);


{меняем стиль окна}
Style:=Style or WS_OVERLAPPEDWINDOW;
SetWindowLong(Button1.Handle, GWL_STYLE, Style);
{обновление окна (Invalidate не сработает)}
SetWindowPos(Button1.Handle, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE or SWP_NOSIZE or SWP_NOZORDER or SWP_DRAWFRAME
or SWP_NOACTIVATE);
end;
Указать свой стиль окна во время его создания можно, если переопределить для любого оконного объекта (включая TForm) процедуру CreateParams:
type
TForm1 = class(TForm)
. . .
procedure CreateParams(var Params:TCreateParams);
override;
. . .
end;
. . .
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style:=Params.Style and not WS_CAPTION;
end;
 
 
IV. Распахивание (maximize) и захлопывание (minimize) окон.
Иногда нужно, чтобы окно при нажатии кнопки “Maximize” распахивалось не на весь экран, а только на часть его. Вспомните редактор исходных текстов в среде Delphi. Эта задача, как и многие другие, решается написанием обработчика соответствующего события - WM_GETMINMAXINFO. Это сообщение посылается системой в окно при максимизации окна или при изменении его размеров с помощью рамки. Окно возвращает требуемые размеры и положение.
TForm1 = class(TForm)
. . .
private
procedure WMGetMinMaxInfo(var M: TWMGetMinMaxInfo);
message WM_GETMINMAXINFO;
. . .
end;
. . .
procedure TForm1.WMGetMinMaxInfo(var M: TWMGetMinMaxInfo);
begin
{на всякий случай}
inherited;
{указываем верхнюю границу окна ниже другого окна}
M.MinMaxInfo^.PTMaxPosition.Y := Form2.Top+Form2.Height;
end;
С помощью обработчика данного события устанавливается не только место и размер распахнутого полностью окна, но и минимальные/максимальные размеры окна при изменении их с помощью рамки. Пример, установка в обработчике события WM_GETMINMAXINFO
m.minmaxinfo^.ptMinTrackSize.y := 100;
m.minmaxinfo^.ptMaxTrackSize.y := 300;
не даст сделать вертикальный размер окна менее 100 и более 300 точек.

1.7 Обработка событий от клавиатуры

I. Эмуляция нажатия клавиши.
Внутри приложения это выполняется достаточно просто с помощью вызова функции Windows API SendMessage() (можно воспользоваться и методом Perform того объекта (или формы), кому посылается сообщение о нажатой клавише).


Код
Memo1.Perform(WM_CHAR, Ord(‘A’), 0);
или
SendMessage(Memo1.Handle, WM_CHAR, Ord(‘A’), 0);
приведет к печати символа “A” в объекте Memo1.
II. Перехват нажатий клавиши внутри приложения.
Задача решается очень просто. Можно у формы установить свойство KeyPreview в True и обрабатывать событие OnKeyPress. Второй способ - перехватывать событие OnMessage для объекта Application.
 
 
 
 
III. Перехват нажатия клавиши в Windows.
Существуют приложения, которым необходимо перехватывать все нажатия клавиш в Windows, даже если в данный момент активно другое приложение. Это может быть, например, программа, переключающая раскладку клавиатуры, резидентный словарь или программа, выполняющая иные действия по нажатию “горячей” комбинации клавиш.
Перехват всех событий в Windows (в том числе и событий от клавиатуры) выполняется с помощью вызова функции SetWindowsHook(). Данная функция регистрирует в системе Windows ловушку (hook) для определенного типа событий/сообщений. Ловушка - это пользовательская процедура, которая будет обрабатывать указанное событие.
Основное здесь то, что эта процедура должна всегда присутствовать в памяти Windows. Поэтому ловушку помещают в DLL и загружают эту DLL из программы. Пока хоть одна программа использует DLL, та не может быть выгружена из памяти. Приведем пример такой DLL и программы, ее использующей. В примере ловушка перехватывает нажатие клавиш на клавиатуре, проверяет их и, если это клавиши “+” или “-”, посылает соответствующее сообщение в конкретное приложение (окно). Окно ищется по имени его класса (“TForm1”) и заголовку (caption, “XXX”).
 
 
{текст библиотеки}
library SendKey;
uses
WinTypes, WinProcs, Messages;
const
{пользовательские сообщения}
wm_NextShow_Event = wm_User + 133;
wm_PrevShow_Event = wm_User + 134;
{handle для ловушки}
HookHandle: hHook = 0;
var
SaveExitProc : Pointer;
 
 
{собственно ловушка}
function Key_Hook(Code: integer; wParam: word; lParam: Longint): Longint; export;
var


H: HWND;
begin
{если Code>=0, то ловушка может обработать событие}
if Code >= 0 then
begin
{это те клавиши?}
if ((wParam = VK_ADD)or(wParam = VK_SUBTRACT)) and
(lParam and $40000000 = 0) then
begin
{ ищем окно по имени класса и по заголовку}
H := FindWindow('TForm1', 'XXX');
{посылаем сообщение}
if wParam = VK_ADD then
SendMessage(H, wm_NextShow_Event, 0, 0)
else
SendMessage(H, wm_PrevShow_Event, 0, 0);
end;
{если 0, то система должна дальше обработать это событие}
{если 1 - нет}
Result:=0;
end
else
{если Code<0, то нужно вызвать следующую ловушку}
Result := CallNextHookEx(HookHandle,Code, wParam, lParam);
end;
 
 
{при выгрузке DLL надо снять ловушку}
procedure LocalExitProc; far;
begin
if HookHandle<>0 then
begin
UnhookWindowsHookEx(HookHandle);
ExitProc := SaveExitProc;
end;
end;
{инициализация DLL при загрузке ее в память}
begin
{устанавливаем ловушку}
HookHandle := SetWindowsHookEx(wh_Keyboard, Key_Hook,
hInstance, 0);
if HookHandle = 0 then
MessageBox(0, 'Unable to set hook!', 'Error', mb_Ok)
else begin
SaveExitProc := ExitProc;
ExitProc := @LocalExitProc;
end;
end.
Размер такой DLL в скомпилированном виде будет около 3Кб, поскольку в ней не используются объекты из VCL.
Далее приведен код модуля в Delphi, который загружает DLL и обрабатывает сообщения от ловушки, просто отображая их в Label1.
unit Unit1;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
{пользовательские сообщения}
const
wm_NextShow_Event = wm_User + 133;
wm_PrevShow_Event = wm_User + 134;
 
 
type
TForm1 = class(TForm)
Label1: TLabel;
procedure FormCreate(Sender: TObject);
private
{обработчики сообщений}
procedure WM_NextMSG (Var M : TMessage);
message wm_NextShow_Event;
procedure WM_PrevMSG (Var M : TMessage);
message wm_PrevShow_Event;
end;
var
Form1: TForm1;
P : Pointer;
implementation
{$R *.DFM}
{загрузка DLL}
function Key_Hook : Longint; far; external 'SendKey';
procedure TForm1.WM_NextMSG (Var M : TMessage);


begin
Label1.Caption:='Next message';
end;
procedure TForm1.WM_PrevMSG (Var M : TMessage);
begin
Label1.Caption:='Previous message';
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
{ если не использовать вызов процедуры из DLL в программе,
то компилятор удалит загрузку DLL из программы}
P:=@Key_Hook;
end;
end.
Конечно, свойство Caption в этой форме должно быть установлено в “XXX”.
 
  2.0 Вызов методов дальних предков Проблема в следующем. Допустим, есть иерархия классов, у которых перекрывается (override) один и тот же виртуальный (или динамический - не важно) метод, и в одной из реализаций этого метода вы хотите вызвать виртуальный метод предка своего предка (“дедушки”, “прадедушки” и т.д.). Новая объектная модель Delphi допускает только вызов методов непосредственного предка (с помощью ключевого слова inherited) либо вызов методов класса с префиксом - типом класса (например, TLevel1.ClassName).
Эта задача стандартными средствами не решается. Но сделать требуемый вызов можно. Причем, способом, показанным ниже, можно вызвать любой метод для любого класса, однако, в этом случае вся ответственность за правильность работы с методами и полями ложится на программиста. В примере, приведенном ниже, в методе VirtualFunction класса TLevel3 напрямую вызывается метод класса TLevel1 (метод “деда”). В функции Level1Always всегда вызывается метод класса TLevel1 для любого его наследника.
{декларация классов}
TLevel1 = class(TComponent)
public
function VirtualFunction: string; virtual;
end;
TLevel2 = class(TLevel1)
public
function VirtualFunction: string; override;
end;
TLevel3 = class(TLevel2)
public
function VirtualFunction: string; override;
end;
function Level1Always(MyLevel: TLevel1): string;
implementation
type
PClass = ^TClass;
{виртуальная функция “дедушки”}
function TLevel1.VirtualFunction: string;
begin
Result := 'Level1';
end;
{виртуальная функция “отца”, вызывает унаследованный метод “дедушки”}
function TLevel2.VirtualFunction: string;
begin
Result := inherited VirtualFunction+' Level2';


end;
{ виртуальная функция самого младшего класса, вызывает
напрямую метод “дедушки”, минуя “отца”}
function TLevel3.VirtualFunction: string;
var
ClassOld: TClass;
begin
ClassOld := PClass(Self)^;
PClass(Self)^ := Self.ClassParent.ClassParent;
Result := VirtualFunction + ' Level3';
PClass(Self)^ := ClassOld;
end;
function Level1Always(MyObject: TObject): string;
var
ClassOld: TClass;
begin
ClassOld := PClass(MyObject)^;
PClass(MyObject)^ := TLevel1;
Result := (MyObject as TLevel1).VirtualFunction;
PClass(MyObject)^ := ClassOld;
end;
Как же это работает? Стандартные, так называемые объектные типы (object types - class of ...), на самом деле представляют из себя указатель на VMT (Virtual Method Table) - таблицу виртуальных методов, который (указатель) лежит по смещению 0 в экземпляре класса. Воспользовавшись этим, мы сначала сохраняем 'старый тип класса' - указатель на VMT, присваиваем ему указатель на VMT нужного класса, делаем вызов и восстанавливаем все как было. Причем нигде не требуется, чтобы один из этих классов был бы порожден от другого, т.е. функция Level1Always вызовет требуемый метод для любого экземпляра любого класса.
Если в функции Level1Always попробовать сделать вызов
Result := MyObject.VirtualFunction;
то будет ошибка на стадии компиляции, так как у класса TObject нет метода VirtualFunction.
Другой вызов
Result := (MyObject as TLevel3).VirtualFunction;
будет пропущен компилятором, но вызовет Run-time ошибку, даже если передается экземпляр класса TLevel3 или один из его потомков, так как информация о типе объекта была изменена.
Динамически распределяемые (dynamic) методы можно вызывать точно таким же образом, т.к. информация о них тоже хранится в VMT.
Статические методы объектов вызываются гораздо более простым способом, например
 
 
var
MyLevel3: TLevel3;
...
(MyLevel3 as TLevel1).SomeMethode;
 
 
вызовет метод класса TLevel1 даже если у MyLevel3 есть свой такой же метод.