close

Вход

Забыли?

вход по аккаунту

?

2036.Технология объектного программирования

код для вставкиСкачать
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Федеральное агентство по образованию Российской Федерации
Государственное образовательное учреждение
высшего профессионального образования
Ивановский государственный химико-технологический университет
Технология объектного программирования
Часть 2
Лабораторный практикум
Составители: Е. К. Шигалов
В. А. Бобкова
Иваново 2008
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Составители: Е.К. Шигалов, В.А. Бобкова
УДК 613.19
Технология объектного программирования. Часть 2: Лабораторный
практикум. / Сост.: Е. К. Шигалов, В. А. Бобкова; ГОУВПО Иван. гос. хим.технол. ун-т. Иваново, 2008. 72 с.
В учебном пособии представлены материалы для выполнения лабораторного практикума по курсу «Технология объектного программирования». Пособие имеет целью поддержку второй части единого курса по указанной дисциплине. Рассмотрены следующие нетрадиционные приемы: использование технологии файлов, отображенных в память, для корректировки дисковых файлов;
консольные приложения и использование функций ShellAPI Windows; разработка собственных визуальных и невизуальных компонентов как на основе базовых классов, так и “с нуля”. Эти приемы активно используют технологию
объектно-ориентированного программирования.
Пособие предназначено для самостоятельной работы студентов специальностей «Информационные системы и технологии» и «Информационное обеспечение систем управления».
Ил. 9. Библиогр.: 5 назв.
Рецензент доктор технических наук, профессор А.Н.Лабутин (Ивановский государственный химико-технологический университет)
2
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
1. Файловые потоки (FileStream). Создание объекта программным путем.
Сохранение объекта в файле и считывание его из файла
Цель занятия: ознакомление с методами ReadComponent и WriteComponent класса TFileStream; изучение примера создания объекта на основе базового
класса TButton программным путем.
Технология работы с файловыми потоками применяется для создания
файлов, организации доступа (чтения, записи, поиска данных, навигации по
файлу) к данным при обработке типизированных файлов, то есть файлов из записей, структуру которых определяет пользователь. Запись - это прежде всего
пользовательский тип данных, некая структура. Заметим, что в некоторых языках программирования записи так и называются структурами, хотя существуют
также понятия массива структур и структур в массивах. Применим запись к нестандартной задаче - сохранению некоторого объекта вместе с его свойствами и
методами в дисковом файле.
Задание.
1.
Расположите на форме (рис.1) три обычные кнопки с именами btn_Create,
btn_Write и btn_Read, выполняющие следующие действия:
• btn_Create создает обычную кнопку на основе класса TButton, определяя
для нее один метод – реакцию на щелчок мышки, то есть обработчик события OnClick;
• btn_Write записывает созданную кнопку в файл;
• btn_Read восстанавливает записанную кнопку из файла, одновременно переопределяя метод обработки события OnClick. Внешне это выглядит как
вывод некоторого текста в ответ на щелчок после восстановления кнопки
с диска.
2.
Используя методы обработки ошибок, корректно удалите файл, остающийся на диске после сохранения кнопки. Учтите, что с этим файлом в данном
случае не связана никакая файловая переменная. Следовательно, файловую переменную нужно объявить, назначить файлу, файл закрыть (Close) и после этого удалить (Erase). Можно считать, что открытый файл – это ресурс, который
необходимо вернуть операционной системе.
3.
Просмотрите в Help иерархию класcа TFileStream, уточните, какие классы его порождают и какие еще методы, кроме WriteComponent и ReadComponent, какие события и свойства класc TFileStream наследует из этих конкретных классов.
3
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.1. Вид главной формы приложения
Указания и рекомендации
В конце раздела описания класса формы введите приведенный ниже
код, определяющий три процедуры в части Private. Первая из этих процедур
будет обеспечивать доступность всех кнопок формы в зависимости от текущей
ситуации, например, сделает недоступной кнопку "btn_Create" после создания
тестируемой кнопки. Запустите программу Stream.exe для ознакомления с тем,
как это может быть реализовано.
.............
private
{ Private declarations }
procedure EnableButtons;
procedure BtnOnClick(Sender: TObject) ;
procedure BtnOnClick2(Sender: TObject);
public
{ Public declarations }
end;
Const
File_Name = 'StreamButton.str' ;
var
MainForm: TMainForm;
Btn: TButton;
implementation
4
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для определенности назовите файл для хранения кнопки "StreamButton.str", а
создаваемую программой кнопку назовите "Btn". Это будет объект класса TButton:
Var
Btn: TButton;
Первая из процедур (в виде заготовки) может быть подобна следующей:
Procedure TMainForm.EnableButtons;
{ процедура определяет доступность кнопок }
begin
// Начальная инициализация доступности трех кнопок, как "False"
btn_Create.Enabled := False;
btn_Write.Enabled := False;
btn_Read.Enabled := False;
if Btn <> Nil Then
// кнопка Btn создана
btn_Write.Enabled := True
else
if FileExists (File_Name) Then
btn_Read.Enablwd:=True { разрешение на чтение, если файл существует }
else btn_Create.Enabled:=True;
end;
Эта процедура может быть применена сразу при создании формы следующим
образом:
а далее должна вызываться при каждом нажатии любой из кнопок и определять
их доступность.
Две другие процедуры будут обработчиками одного и того же события OnClick для создаваемой кнопки, но назначаемыми в разное время ее жизни. В частности, эти процедуры могут выводить два разных сообщения в диалоговое
окно. Они будут заимствоваться у формы. Это – методы формы, совместимые
по типу с событием OnClick, то есть явно имеющие только один параметр
Sender типа TObject. В реальности можно было бы для новой кнопки определить класс, для этого класса написать пользовательский метод, определяющий
любой сложности реакцию на щелчок и перекрывающий (override) стандартную
реакцию на Click (если бы она была запланирована в классе-предшественнике).
Приведем в качестве примера простой код этих процедур-обработчиков:
5
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
procedure TMainForm.BtnOnClick(Sender: TObject);
begin
ShowMessage ('Thanks. I needed that !');
end;
procedure TMainForm.BtnOnClick2(Sender: TObject);
begin
ShowMessage ('Hello. It ''s nice to be back !');
end;
Для создания кнопки "в коде" используйте следующую процедуру:
procedure TMainForm.btn_CreateClick(Sender: TObject);
begin
Btn :=TButton.Create (Self);
Btn.Parent:=Self;
Btn.Left:=200;
btn.Top:=80;
btn.Height:=45;
btn.Caption:='Click me!';
btn.OnClick := btnOnClick;
// назначили первый обработчик события
EnableButtons;
end;
Процедура для записи кнопки имеет вид:
procedure TMainForm.btn_WriteClick(Sender: TObject);
Var
Stream : TStream;
begin
Stream := TFileStream.Create (File_Name, fmCreate );
try
Stream.WriteComponent(Btn);
Btn.Free;
Btn:=Nil;
EnableButtons;
finally
Stream.Free;
end;
end;
Посмотрите в Help, какие значения параметра, помимо fmCreate, может иметь
конструктор Create класса TFileStream (рис.2).
6
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.2. Описание конструктора Create класса TFileStream.
Процедура для чтения кнопки из потока:
procedure TMainForm.btn_ReadClick(Sender: TObject);
Var
Stream : TStream;
begin
Stream := TFileStream.Create (File_Name, fmOpenRead );
try
// btn:= (Stream.ReadComponent(Nil));
// ошибка: справа - тип TComponent !
btn:= TButton(Stream.ReadComponent(Nil)); // явное приведение типа к TButton
btn.Parent:=Self;
{ всегда назначайте "родителя" }
btn.OnClick := btnOnClick2;
// заменили "для интереса" обработчик
EnableButtons;
finally
Stream.Free;
end;
end;
В заключение заметим, что в таком виде вам не удастся сохранять кнопку
в потоке и считывать ее, так как не хватает еще одного оператора а именно:
RegisterClass(TButton);
Если кнопка, или любой компонент, появилась на форме в процессе проектирования, то класс формы и все компонентные классы будут зарегистриро-
7
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
ваны автоматически. В нашем случае кнопка создается программно, поэтому ее
класс должен быть зарегистрирован вручную. Только тогда экземпляры этого
класса могут быть использованы (загружены или сохранены) в VCL-потоковой
системе Delphi. Это может быть сделано как в начале основной программы (см.
ниже), так и в методе FormCreate.
{ ===== Основная программа ====== }
begin
RegisterClass (TButton);
// ShowMessage (GetClass('TButton').ClassName);
end.
Информация о процедуре RegisterClass в Help выглядит таким образом:
Рис.3. Описание процедуры RegisterClass.
Для закрытия формы может быть применен следующий код, использующий в целях большей надежности обработку исключений:
8
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
2. Работа с файлами, отображенными в память
Цель занятия: изучение техники отображения файлов в оперативную
память и его корректировки на примере произвольного текстового файла.
Технология отображения файлов в память позволяет обойтись без
обычных операций файлового ввода-вывода, а именно: связать физическую память на диске с адресом некоего виртуального адресного пространства вашей
программы и сослаться на содержимое файла с помощью обычного указателя
так, как будто файл располагается в оперативной памяти. Достоинство метода
состоит в том, что он позволяет манипулировать огромными блоками данных,
то есть открывать файлы размером в несколько гигабайт. Более того, при использовании этого метода можно отображать файлы частично. Но в нашем
примере объем файла не должен быть очень большим, и файл будет текстовым,
так как мы отобразим его содержимое (для визуального контроля) в редактируемом элементе управления TMemo.
Внешний вид программы представлен на рис.4.
9
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.4. Внешний вид программы для отображения текстового файла в память.
Здесь в диалоге открытия вы отбираете для отображения в память произвольный текстовый файл, который выводится в компоненте TMemo. После этого вы можете преобразовать все символы файла в верхний или нижний регистр
и тут же увидеть результат. Все это будет производиться с помощью функций
Win API и функций работы с памятью, то есть максимально быстро. Заметим,
что в нашей практике доводилось отображать бинарные файлы размером более
трех мегабайт с целью проведения их предобработки-декодирования, и этот
процесс на ПК 130 Mhz не занимал ощутимого времени. Это ожидаемый результат, так как ведь именно с помощью данного механизма ОС Windows загружает и выполняет EXE-и DLL-файлы. Кроме того, отображенные файлы позволяют организовать разделение их данных между различными процессами,
выполняющимися одновременно на одном компьютере. Сейчас мы отметили
три области применения этой методики, но рассмотрим только собственно отображение файлов, так как именно оно может заинтересовать разработчика.
Доступ к содержимому отображенных в памяти файлов
В результате отображения дисковый файл связывается с некоторой областью виртуальной памяти процесса (напомним, что в Windows 9x/NT/2000/XP
каждому процессу выделяется 4 Гб виртуального адресного пространства). Для
организации этой связи необходимо выполнить последовательно несколько
этапов: создать или открыть файл, создать объект отображения файла (filemapping object), создать специальное окно просмотра (file view). Далее система
сама выполняет кэширование, буферизацию и загрузку данных файла в это окно просмотра, а также управляет выделением и освобождением памяти. Нам
остается только организовать редактирование данных.
10
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Создание или открытие файла, функции FileOpen() и CreateFile()
Первый шаг для создания или открытия отображенного в память файла
состоит в получении дескриптора файла. Для этого используются функции
FileCreate() или FileOpen() библиотеки Delphi или функция CreateFile() Win32
API. Последняя, несмотря на название, также открывает существующий файл
(см. описание ее параметров в Windows SDK). Все функции при успешном выполнении возвращают действительный дескриптор файла, две первые при неудаче вернут значение, определяемое константой INVALID_HANDLE_VALUE.
Функция FileOpen() имеет следующее описание (см. SysUtils.pas):
function FileOpen(const FileName: string; Mode: word) : integer;
Первый параметр представляет полный путь к файлу, второй – режим доступа,
а именно:
• fmOpenRead - только чтение из файла;
• fmOpenWrite - только запись;
• fmOpenReadWrite - чтение и запись в файл.
При передаче значения 0 невозможно ни чтение, ни запись, а остается
только возможность узнать атрибуты файла. Режим совместного доступа определяется дополнительными параметрами и их комбинированием с использованием поразрядной операции OR (ИЛИ).
Функция CreateFile() Win32 имеет большие возможности, широкий набор параметров, некоторые из которых можно просто положить равными Nil
или совсем не определять (см. ниже).
После получения дескриптора файла можно создать для него объект отображения в память.
11
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Создание объекта отображения файла в память
Для получения дескриптора объекта отображения файла используется
функция CreateFileMapping(), первым параметром которой является полученный ранее дескриптор открытого файла. Часть параметров можно не определять, но предварительно следует определить необходимый для вызова размер
файла в байтах, для чего используется функция Win32 GetFileSize():
После этого возможен вызов CreateFileMapping():
Другой способ обращения, явно использующий в качестве параметра объем файла, имеет следующий вид:
В приведенном выше фрагменте в блоке finally сразу, как только это становится возможным, освобождается дескриптор FFileHandle файла, ресурс операционной системы. Это – демонстрация кода, в котором нет ничего лишнего
(авторы – К.Пачеко, Ст.Тейксейра).
Получив объект отображения файла, можно отобразить данные файла в
адресное пространство процесса.
12
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Отображение данных файла в адресное пространство процесса,
функция MapViewOfFile()
Для отображения данных файла в адресное пространство процесса используется функция MapViewOfFile(), при успехе возвращающая указатель на
начало данных в памяти:
Data := MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (Data <> nil)
then Result := True
else raise Exception.Create('Не удалось отобразить окно просмотра');
Объект отображения представляется в окне просмотра. Функция MapViewOfFile() вернет указатель (переменная Data) на данные файла. При неудаче
генерируется исключение.
Первым параметром функции является дескриптор объекта отображения,
полученный ранее.
Второй параметр определяет режим доступа к данным. Он может принимать также значения
• FILE_MAP_READ (при этом в функции CreateFileMapping() должен
использоваться атрибут PAGE_READ_WRITE или PAGE_READ);
• FILE_MAP_WRITE (тогда в функции CreateFileMapping() должен использоваться атрибут PAGE_READ_WRITE);
• FILE_MAP_ALL_ACCESS (аналогично WRITE).
Последний параметр представляет собой количество байт, предназначенных для отображения; если он равен 0, то отображается весь файл.
Переменная Data должна иметь тип указателя, а именно: Pointer, PByte
или PChar. В данном случае это был PChar.
В фрагменте кода показано, как можно манипулировать байтами данных.
Для сведения: переменная S имеет тип PChar, переменная Size – это доступная
в данном месте кода переменная, хранящая длину файла в байтах.
Data^ := '*';
// явная разадресация, это первый символ дискового
файла
(Data+4)^ := 'f';
Data[5] := '@';
// меняется пятый символ
// доступ как к массиву Char
For i:=1 to Size do
ShowMessage('i = ' + IntToStr(i) + ' ' + Data[i-1]);
GetMem(S, Size);
Move(Data^, S^, Size);// Длина строки Length(S) была 17,
а будет сделана 15:
13
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
S[Size - 2] := #0;
// здесь заменяется на 0-символ, т.е. терминатор,
предпоследний невидимый символ 0D строки текстового файла (примените
FAR и HEX-форму просмотра по клавише F3 и вы его увидите).
// For i:=1 to Length(S) do
// ShowMessage('i = ' + IntToStr(i) + ' ' + Data[i-1]);
SetWindowText(Memo1.Handle, S);
FreeMem(S, Size);
Поскольку в приведенном фрагменте мы заполняем окно редактирования
компонента TMemo таким необычным образом, минуя библиотечную фунцию
LoadFromFile() и вызывая функцию SetWindowText Win32 API, то мы вынуждены поставить где-то в конце образа текстового файла нуль-терминатор, тогда
это будет совпадать с внутренним содержанием строк типа PChar.
Функция Move, известная своей скоростью перемещения блоков данных
и отсутствием какой-либо их защиты, перемещает блок данных в памяти с места на место, используя значения указателей Data^ и S^ .
В цикле мы по существу просматриваем все символы короткого текстового файла известной нам длины таким образом, как будто это элементы массива
(файл отображен в память, которую мы рассматриваем как массив символов,
поскольку это текстовый файл). В приведенном примере мы использовали символы ради простоты, чтобы просто понять технику работы.
Заметим, что поскольку мы выделили память для переменной S программно, то сами освободили ее командой FreeMem().
Освобождение окна просмотра файла
Функция UnmapViewOfFile() удаляет отображение файла и имеет следующее описание:
function UnmapViewOfFile (lpBaseAddress: Pointer) : BOOL;
Ее единственный параметр совпадает со значением указателя – базового
адреса
отображаемой
области,
возвращенным
ранее
функцией
MapViewOfFile(). Если функция не будет вызвана, то система не освободит
отображаемую область памяти до завершения процесса:
if not UnmapViewOfFile(Data) then
ShowMessage('No unmapping: освобождение ссылки Data на данные
файла неудачно’);
Закрытие объекта отображения
Обращения к функциям FileOpen() и CreateFileMapping() связаны с открытием объектов ядра операционной системы, поэтому вы несете ответственность за их закрытие. Это делается с помощью функции CloseHandle(). Ее описание имеет вид:
14
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
function CloseHandle (hObject: THandle): BOOL;
Пример использования:
if not CloseHandle(hMapping) then ShowMessage('No closing: Неудачное
освобождение дескриптора объекта отображения файла’);
Причину неуспешного завершения функции, как и любой другой, можно
проанализировать, вызвав функцию GetLastError().
Пример приложения с использованием файла, отображенного в память
Форма на этапе разработки имеет вид:
Описание класса формы в модуле формы может быть таким:
15
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Вся основная работа вынесена в отдельный метод OpenMappedFile():
Метод кнопки выбора файла и отображения содержимого в окне TMemo
имеет вид:
16
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для обеспечения повтора преобразования одного и того же файла вызов
функции CloseHandle() закрытия дескриптора окна отображения hMapping и
UnmapViewOfFile() с целью освобождения ссылки Data был перенесен в событие закрытия формы. Обратите внимание, что действительность указателя Data
мы проверяем сравнением с Nil , а дескриптора hMapping – с числом 0.
Для кнопки преобразования в выбранный регистр отображенного в память файла может быть применен следующий метод:
17
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Представляет интерес, что в одном из первоисточников сходный по функциональности код имеет вид, не содержащий распределения и освобождения
памяти для переменной типа PChar (в приведенном ниже коже это переменная
PData). Можете проверить его работоспособность:
PData := PChar(Data);
Inc(PData,FSize) ; // установка указателя в конец данных файла
PData^ := #0;
// установка терминатора в конец файла
If UCase then
AnsiStrUpper(PChar(Data))
else
AnsiStrLovar(PChar(Data)) ;
А теперь основное практическое задание.
Вам следует разработать программу, которая, используя технику отображения файла в память, реверсирует байты dbf-файла, а именно: перемещает заголовок файла в произвольное место файла данных, меняя байты местами, - то
есть делая его непригодным для обработки средствами СУБД, но эта же программа выполняет его восстановление, производя обратную операцию. Удобно
проделать это в рамках одной программы, используя управляющий компонент
DBGrid и драйвер ODBC для файлов Visual FoxPro. Вместе с дополнительными
возможностями кодировки “на лету” это простой аналог защиты файла, хотя и
не очень надежный и не гарантирующий от возможного взлома.
18
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для примера ниже приведен текст реально действующей процедуры (мы
назвали ее Noise, т.е.“шум”), которая искажает файл данных перед его поставкой пользователю. Вторая процедура (DeNoise) выполняет симметричные преобразования, восстанавливая файл перед его загрузкой в компонент ClientDataSet в методе FormCreate. После загрузки файл повторно искажается. На системной дате файла это никак не отражается, причем все происходит очень быстро.
Одно из трех использованных нами преобразований - это то, что вам предстоит
проделать самим.
19
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
20
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
3. Консольные приложения. Программы работы с каталогами
3.1. Слияние файлов
Цель занятия: ознакомление с техникой создания консольных приложений и работой с пользовательским файлом настроек на примере слияния файлов.
Иногда возникает вопрос – можно ли использовать команды вводавывода и приемы отладки из Turbo Pascal в среде Delphi? Ответ положительный. Кроме того, есть самостоятельный вид приложений Delphi, называемых
консольными, которые именно так и разрабатываются (заметим, что под термином «консоль» в свое время понимали комплект из терминала и клавиатуры).
В силу отсутствия элементов интерфейса и графического диалога с пользователем, консольные приложения значительно компактнее и могут использовать
знакомые по Turbo Pascal операторы Read, ReadLN, Write, WriteLN, а могут вообще не иметь команд ввода – вывода, считывая необходимые параметры из
файла настроек, обычно называемого файлом инициализации или просто iniфайлом. Ini-файл - это обычный текстовый файл, который подготавливается в
любом текстовом редакторе и содержит настройки программы.
В качестве примера разработки консольного приложения рассмотрим
создание компактной программы, позволяющей администратору криптоключей
объединять в единый файл произвольное число файлов с целью идентификации
пользователей из различных территориально распределенных подразделений
крупной организации. Предполагается, что структура файлов идентична, они
являются бинарными (не текстовыми или состоящими из записей, а могущими
содержать произвольный набор байтов), с обязательным "довеском" в качестве
21
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
подписи администратора частной криптосети. Длина логического информационного блока является фиксированной и составляет, допустим, 288 байт. Длина
подписи администратора в каждом файле является переменной, но заведомо
меньшей 288 байт. К сожалению, подпись имеет DOS-формат, что стало поводом к разработке еще одной служебной программы на Delphi, позволяющей
распознать DOC/RTF документ, создать его дубль с подписью, конвертированной в кодовую страницу 1251 Windows с одновременным вызовом редактора
MS Word для просмотра и печати по технологии COM.
Задача формулируется следующим образом. Разработать алгоритм
слияния произвольного числа файлов, расположенных в одном каталоге, в некоторый файл этого каталога, учитывая, что длина информационных блоков
фиксирована, а все, что "в остатке" – служебная информация, которую следует
проигнорировать. Программа должна работать без диалога с пользователем и
выводить на консоль статистику процесса только по желанию пользователя.
Для решения этой небольшой частной проблемы нет необходимости привлекать визуальные компоненты VCL Delphi. Каркас нового консольного приложения создается стандартным способом (рис.5):
File ð New ð Console Application
Рис.5. Создание консольного приложения.
22
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
После выбора вида объекта "Console Application" среда Delphi создаст заготовку приложения следующего вида (здесь применялась версия Delphi 5.5, в
версии 7 этот текст будет иметь другой, более громоздкий вид):
Все остальное необходимо дописывать "от руки". В каталоге приложения
будет размещен файл с расширением .dpr. Он и будет содержать текст вашей
программы, при этом файла с расширением .pas не будет. Но во многом текст
приложения будет совпадать с текстом Pascal-программы. Даже операторные
скобки begin-end покажутся вам знакомыми.
Описательная часть приложения может быть определена, например, следующим образом:
23
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Как и в случае DOS-приложения для Turbo Pascal 7.0, для сканирования
каталога последовательно применяются две функции FindFirst() и FindNext() и
специальный тип данных TSearchRec, который представлен переменной с именем sr.
Описание констант – описателей атрибутов файлов в шестнадцатеричном виде - мы специально для справки разместили в тексте программы (см выше в комментариях.). Обязательно просмотрите в Help описание этих функций
и примеры их использования.
Для обработки факта отсутствия файла настроек (мы назвали его Config.ini) применен прием Turbo Pascal:
но с тем же успехом можно было использовать логическую функцию
FileExist(), что для Delphi является более естественным приемом. Обратите
внимание: в командах приходится использовать только латиницу, не пытайтесь
использовать русский текст, даже в константах, при этом операторы Pascal будут вам знакомы.
24
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для чтения текстового файла был применен стандартный прием. Ниже
приводится фрагмент текста для поиска значений переменных Stat, Path_Dir:
Этот текст содержит возможность коварной ошибки, которую трудно обнаружит. Мы рекомендуем применить к информационной строке "Row_Info"
функцию TrimRight() до расчета ее длины функцией Length(), так как в конец
строки в процессе ее подготовки редактором могли быть внесены невидимые
символы "пробела". Содержание файла было следующим:
Продолжение обработки Ini-файла имеет вид:
25
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Завершающий фрагмент текста полностью:
if Stat_Exist Then
begin
if Path_Exist Then WriteLN('Path_IN = ' + Path_Dir);
if Path_Out_Exist Then WriteLN('Path_Out = ' + Path_Dir_Out);
end;
if Path_Exist And Path_Out_Exist then
begin
FileCount:=0; z:=0;
FileAttrs := faArchive + faReadOnly;
// faReadOnly + faAnyFile;
// WriteLN('FileAttrs = '+ IntToStr(FileAttrs));
// WriteLN('faAnyFile = '+ IntToStr(faAnyFile));
// WriteLN('faReadOnly = '+ IntToStr(faReadOnly));
// WriteLN('faAnyFile + faReadOnly = '+ IntToStr(faAnyFile +
faReadOnly));
// WriteLN('faAnyFile And faReadOnly = '+ IntToStr(faAnyFile And
faReadOnly));
// WriteLN('faReadOnly And faAnyFile = '+ IntToStr(faReadOnly And
faAnyFile));
// WriteLN('faAnyFile And faAnyFile = '+ IntToStr(faAnyFile And faAnyFile));
// WriteLN('faReadOnly And faReadOnly = '+ IntToStr(faReadOnly And
faReadOnly));
// Readln;
{ пауза после завершения вывода }
//
if FindFirst('C:\TestFile\*.*', faAnyFile, sr) = 0 then
if FindFirst(Path_Dir + '\' + '*.*', FileAttrs, sr) = 0 then
begin
//
WriteLN ('!!! File Found');
// есть первый поиск
AssignFile(F,Path_Dir_Out + '\'+ 'DestFile.dat');
Rewrite(F,1);
// длина записи = 1
//
WriteLN('This file: ',sr.Name,'
Size = ',IntToStr(sr.Size));
{ ====== Обработка первого найденного === }
if (sr.Attr and FileAttrs) = sr.Attr then
begin
//
WriteLN ( '0. ' +sr.Name + ' sr.Attr = ' + IntToStr(sr.Attr) + ' FileAttrs
= ' + IntToStr(FileAttrs));
//
WriteLN ( '0. sr.Attr and FileAttrs ' + IntToStr( sr.Attr and FileAttrs));
FileCount := FileCount + 1;
z:=z+sr.Size;
26
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
//
WriteLN('0. File selected: ',sr.Name,' Size: ',IntToStr(sr.Size));
NRepeat:= sr.Size div 288;
if Stat_Exist then ShowInfo;
{$I-}
AssignFile (F_InPut, Path_Dir + '\' + sr.Name);
FileMode:=0;
Reset(F_Input,1);
// Устанавливается размер записи = 1 байт
For i:=1 to NRepeat do
begin
BlockRead (F_Input,Buf,SizeOf(Buf),NumRead);
BlockWrite(F,Buf,NumRead,NumWrite);
end;
CloseFile (F_InPut);
{$I+}
end;
// ===== Обработка последующих =======
While FindNext(sr) = 0 do
begin
if sr.Attr = 16 then goto Exit_Loop;
{ каталоги отбрасываю }
//
WriteLN ( '1. ' +sr.Name + ' sr.Attr = ' + IntToStr(sr.Attr) + ' FileAttrs
= ' + IntToStr(FileAttrs));
//
WriteLN ( '1. sr.Attr and FileAttrs ' + IntToStr( sr.Attr and FileAttrs));
if (sr.Attr and FileAttrs) = sr.Attr then
begin
//
WriteLN ('2. sr.Attr = '+ IntToStr(sr.Attr) + ' Name = ' + sr.Name);
if (sr.Name <> '..') And (sr.Name <> '.') Then //And Not(sr.Attr And
faReadOnly > 0) then // это не каталог и не …
begin
//
WriteLN ('3. sr.Attr = '+ IntToStr(sr.Attr) + ' Name = ' + sr.Name);
if (UpperCase(sr.Name)='ERRORLOG.DBF') Or (sr.Size = 0) Then
goto Exit_Loop;
FileCount := FileCount + 1;
// ==== начало обработки текущего найденного файла ====
z:=z+sr.Size;
//
WriteLN('File selected: ',sr.Name,' Size: ',IntToStr(sr.Size));
//
WriteLN ('4. sr.Attr = '+ IntToStr(sr.Attr) + ' Name = ' + sr.Name);
NRepeat:= sr.Size div 288;
if Stat_Exist then ShowInfo;
{$I-}
AssignFile (F_InPut, Path_Dir + '\' + sr.Name);
FileMode:=0;
// режим открытия - ReadOnly
Reset(F_Input,1);
// размер записи = 1 байт
For i:=1 to NRepeat do
27
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
begin
BlockRead (F_Input,Buf,SizeOf(Buf),NumRead);
BlockWrite(F,Buf,NumRead,NumWrite);
end;
CloseFile (F_InPut);
{$I+}
end;
// if (sr.Name <> '..') And ...
end;
// if (sr.Attr and FileAttrs) = sr.Attr then
Exit_Loop:
end;
// While FindNext(sr)= 0
if Stat_Exist Then WriteLN ('Number of files: ' + IntToStr(FileCount) +
' Total_Size: ' + FloatToStrF(z,ffFixed,15,0) + ' byte ' +
'(including Admins Sign !)');
FindClose(sr);
end;
// if FindFirst(...
end;
// if Path_Exist And ...
CloseFile (F);
if Stat_Exist Then WriteLN( 'Today is ' + DateToStr(Date));
if Stat_Exist Then
begin
WriteLN;
WriteLN ('Ok'' Press "Enter" key, please');
Readln; { пауза после завершения вывода }
end;
end.
Вывод программы для режима Stat = 1 имеет следующий вид:
28
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для ускорения процесса чтения-записи данных применены операции поблочного чтения-записи BlockRead и BlockWrite с длиной блока, совпадающей
с естественной длиной фрагмента информации в 288 байт. Для входных и выходных файлов длина записи установлена в 1 (устанавливалась перед их открытием командой Reset), как подходящая для любых типов файлов (здесь мы рассматриваем файлы как нетипизированные, то есть как простую последовательность байтов, а не как файлы из записей).
Для улучшения "читабельности" кода и его сокращения вывод промежуточной информации вынесен в подпрограмму–процедуру "ShowInfo":
Текст этой процедуры размещен для простоты не в отдельном модуле, а
просто перед основным телом программы, как в Turbo Pascal версии 3.
3.2. Просмотр и удаление файлов
с учетом даты создания/модификации
Цель занятия: повторная работа с ini-файлом и сканирование файлов каталога, анализ их атрибутов с целью выявления файлов с датой создания ранее
указанной.
Сканирование, или просто перебор файлов каталога, - одна из типовых
задач, встречающихся в практике работы программиста. Она возникает, например, тогда, когда необходимо по таймеру, используя возможности штатного
планировщика заданий ОС Windows, производить чистку некоторого каталога
от "старых" файлов. С тем же успехом можно копировать эти файлы для их автоматической архивации, удалять временные файлы и т.д. Для этого необходимо научиться анализировать атрибуты файлов, присваиваемые им операционной системой.
Создадим каркас нового консольного приложения стандартным способом:
File ð New ð Console Application
Ниже приведен пример определения раздела описаний приложения.
29
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Как и в предыдущем примере, для сканирования каталога применяются
две функции: FindFirst() и FindNext() и специальный тип данных TSearchRec,
который представлен переменной sr.
30
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Обратите внимание на функцию TrimRight(), использованную ниже для отсечения хвостовых пробелов.
(* === WriteLN (‘/// File Found’);
/ / есть первый поиск
WriteLN (FileAttrs);
/ / это - 63
WriteLN (sr.Attr);
/ / это - 16
WriteLN (FileAttrs And sr.Attr);
/ / это – также 16
ReadLN; === *)
While FindNext(sr) = 0 do
begin
if (sr.Attr And FileAttrs) = sr.Attr then
{ включится и сам каталог }
begin
Inc (FileCount) ;
/ / или FileCount := FileCount + 1;
if ( sr.Name <> ‘. .’) And ( sr.Name <> ‘.’) And Not (sr.Attr And
faReadOnly > 0) then
/ / это не каталог и …
begin
31
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
//
//
WriteLN(‘File found: ‘,sr.Name, ‘ Size: ‘, IntToStr(sr.Size),
‘ DateTime: ‘, DateTimeToStr(FileDateTodateTime(sr.Time)));
Y:= FileDateToDateTime(sr.Time);
//
//
//
//
WriteLN (‘Now = ‘,Now,’
‘,DateTimeToStr(Now));
WriteLN (‘Delta = ‘ + FloatToStrF(Abs(Now – Y),ffFixed,,) + ‘ days’ );
if Bound_Exist And (Abs(Now – Y) > Bound) Then
begin
WriteLN (‘ Delete file ‘ + Path_Dir + ‘\’ + sr.Name + ‘ for ‘ +
FloatToStrF(Abs(Now – Y),ffFixed,10,5) + ‘ days’);
Команда DeleteFile() для удаления текущего файла, удовлетворяющего условию
поиска, временно заключена в комментарии.
3.3. Копирование дерева каталогов
Цель занятия: работа с ini-файлом и копирование каталога, включая
подкаталоги, с помощью функций Win API.
Копирование каталога – одна из частых операций администратора баз
данных, которая в программировании не очень проста, так как требует циклического просмотра дерева каталогов и файлов. Можно обратиться к опыту предыдущего урока, а можно воспользоваться готовой функцией ShFileOperation()
из состава файловой оболочки Windows, с параметрами которой следует ознакомиться в Help. Для работы с ней необходимо знать структуру данных
ShFileOpStruct из модуля ShellAPI.pas. К счастью, некоторые параметры данной
структуры несущественны и их можно просто проигнорировать, а определение
остальных параметров будет рассмотрено в приведенном ниже примере..
32
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Задача копирования каталога - это реальная задача, которая именно в таком виде была реализована и несколько лет использовалась для перемещения
(дублирования) данных ежедневно решаемой задачи на файл-сервер вместо их
архивации. Можно включить программу в состав запускаемых задач штатного
планировщика заданий, обеспечив, например, завершение операционного дня
некоторого бухгалтерского комплекса программ. Обратите внимание на то, как
просто решается в Delphi проблема приведения строковых данных типа String
(или ShortString) к типу нуль-терминированных строк Win32 API. Напомним,
что данные типа String в функции Win32 API передавать нельзя. Следует приводить их к совместимому со строковыми данными Win API типу PСhar.
А теперь собственно задача: для конкретных каталогов источника и результата разработать как можно более компактную программу копирования.
Наличие каталога–мишени не должно быть обязательным, в случае его отсутствия этот каталог должен создаваться автоматически.
Для удобства пользователя программы мы воспользуемся стандартной
функцией. Предусмотрим ее настройку на возможность копирования в существующий каталог без предупреждения и создание, при необходимости, копий
копируемого дерева фрагмента файловой структуры, если это позволяет свободное пространство жесткого диска. Усложнять задачу оценкой свободного
дискового пространства, что придало бы программе большую завершенность,
не будем.
Заголовок программы имеет вид:
Раздел описания переменных:
33
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Вызов функции ShFileOperation() вместе со строками определения параметров структуры ShFileOpStruct
вынесен в отдельную процедуру CopyDirectoryTree():
procedure CopyDirectoryTree ( const AFromDir, AToDir: String; Copy: Boolean );
Это сделано для того, чтобы продемонстрировать аналогию с Turbo Pascal. Вы можете не выделять вызов, если впоследствии не собираетесь размещать процедуру в отдельном модуле Unit.
Фрагмент Help с описанием структуры ShFileOpStruct имеет вид:
Отметим, что поскольку приложение является консольным, то никакого
Hwnd - обработчика окна быть не может, а он требуется и в списке параметров
стоит первым. Ситуация разрешается передачей функции значения, равного 0.
Полностью процедура имеет следующий код:
34
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
То, что вынесено в комментарии - использование процедур GetMem,
FillChar, StrCopy и явное освобождение памяти FreeMem, - заимствовано нами из примера подобной программы, заслуживающей доверия. Можно использовать такой вариант, а можно сделать так, как в приведенном выше фрагменте,
35
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
то есть явное приведение типа строк Delphi функцией PChar в строке определения нужных параметров:
pFROM := PChar(AFromDir);
pTo := PChar(AToDir);
Следует помнить, что если бы мы использовали оператор явного распределения памяти GetMem(), то обязаны были бы освободить память, причем
в том же объеме, чтобы не вызвать ее утечки. Возможность корректного выполнения этой операции в Delphi показывает фрагмент кода, помещенный в
комментарии.
Дополнительная единица в команде распределения памяти GetMem()
нужна потому, что строковые переменные типа PChar имеют в качестве ограничителя длины символ с кодом 0 (говорят, что они "нуль–терминированы").
Можете обратиться к описанию процедуры StrCopy() в Help. Следовательно,
если в Turbo Pascal можно было при желании регулировать длину коротких
строк ShotString изменением нулевого байта, то теперь это можно делать размещением в строке типа PChar "нулевого" символа. Именно это и делает процедура FillChar(), но только по всем символам сразу (это процедуразаполнитель):
Напомним, что знак ^ означает операцию "разадресации", то есть позволяет получить адрес начала соответствующих переменных в памяти. Это еще
раз подтверждает, что все переменные типа PChar являются указателями.
Завершающий код приложения:
(* ================================================== *)
begin
// Insert user code here
AssignFile (IniList,'Config.ini');
{$i-} Reset (IniList); {$i+}
if IOResult <> 0 Then { нет файла настроек !}
begin WriteLN ('File "Config.ini" not exist'); ReadLN; Exit; end
else
begin
// WriteLN ('Ok, List exist');
With_Copy:= False;
// значение по умолчанию
While not eof(IniList)do
begin
ReadLN(Inilist,Row_Info);
if Copy(UpperCase(Row_Info),1,4)='STAT' Then
36
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
begin
{ WriteLN (' Stat searched '); == }
Stat:=Copy(Row_Info,6,1);
if Stat = '1' Then Stat_Exist:=True;
end;
if Copy(UpperCase(Row_Info),1,7)='PATH_IN' Then
begin
{ WriteLN (' Path_From searched Ok'); == }
Path_Exist:=True; // описание местоположения входных файлов найдено
{ WriteLN (Length(Row_Info)); == }
Path_Dir_From:=Copy(Row_Info,9,Length(TrimRight(Row_Info))-8);
end;
if Copy(UpperCase(Row_Info),1,8)='PATH_OUT' Then
begin
{ WriteLN (' Path_To searched '); == }
Path_OUT_Exist:=True;
Path_Dir_To:=Copy(Row_Info,10,Length(TrimRight(Row_Info))-9);
end;
if Copy(UpperCase(Row_Info),1,8)='WITHCOPY' Then
With_Copy:= True;
end;
CloseFile(IniList);
end;
if Stat_Exist Then
begin
if Path_Exist Then WriteLN('Path_IN = ' + Path_Dir_From);
if Path_Out_Exist Then WriteLN('Path_Out = ' + Path_Dir_To);
end;
if Path_Exist And Path_Out_Exist then
begin
//
CopyDirectoryTree (Handle,'C:\CopyDirectory','E:\My_Copy_Dir');
CopyDirectoryTree (Path_Dir_From,Path_Dir_To, With_Copy);
end;
end.
Вывод программы для случая STAT = 1 выглядит следующим образом:
37
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
А такой вид имел каталог – мишень после четырех запусков программы
копирования:
Видны четыре копии исходного каталога-примера CopyDir_Console с подкаталогом SubDir.
4. Разработка собственных компонентов.
4.1. Свойства, события, обработчики событий и диспетчеризация событий.
Переопределение (overriding) методов классов
Цель занятия: знакомство с технологией разработки визуальных компонентов на примере класса "TCloseButton"; разработка нового свойства - события, позволяющего перехватить перемещение курсора мыши над кнопкой и определить ее координаты; изучение диспетчеризации событий и безопасного вызова обработчиков событий.
Написание компонентов
позволяет в полной мере
использовать возможности
ООП,
поскольку
большинство компонентов
являются
наследниками
каких-либо базовых классов:
TControl,
TWinControl, TGraphicControl,
TCustomControl и так далее. Важно определить
класс, с которым следует
начать работать. Для этого
полезно ознакомиться с
текстом наиболее часто используемых компонентов, представленных в библиотеке VCL Delphi. С уверенностью можно сказать, что для невизуальных компонентов не следует брать Windows-базированные компоненты, а вполне подойдет, например, класс TGraphicControl.
38
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
На тестовой форме видно, как реагирует кнопка CloseButton, построенная
в динамике: она выдала сообщение о том, что метод Click() выполнен. Кнопка
отреагировала на введенное нами новое событие OnClickRelease до закрытия
формы командой Close, но до этого она выводила в заголовок формы координаты мыши, что дает полный контроль над положением курсора. Мы не предлагаем сделать "убегающую" от курсора кнопку, что, как видите, сделать с таким
компонентом при наличии его кода не очень сложно. Подобное событие "MouseInOver" имеется у другого класса кнопок - TSpeedButton, но пусть наша кнопка будет наследницей простой TButton.
Процесс разработки компонента включает следующие этапы.
1.
Выбор класса-предка компонента, члены которого (поля и методы)
будут наследоваться.
2.
Выделение и описание (вначале – словесное) новых свойств и событий, соответствующих функциональности нового класса. Определение степени открытости методов.
3.
Формирование заготовки модуля нового компонента.
4.
Определение, при необходимости, новых нестандартных типов методов (их имен, формальных параметров) для описания обработчиков событий.
5.
Определение конструктора нового компонента путем переопределения (ключевое слово override) конструктора базового класса и реализация
кода нового конструктора, возможно, переопределяющего наследуемые значения свойств. Аналогично - c переопределением деструктора.
6.
Возможная, хотя не обязательная, разработка умалчиваемого кода
обработчиков событий. При этом, если тип события не является стандартным,
следует описать для пользователя компонента сигнатуру обработчика события,
то есть выполнить описание имени метода и списка передаваемых ему параметров. Другое дело, что тогда нечего будет наследовать по умолчанию.
7.
Разработка методов диспетчеризации (что желательно) для безопасного использования нового компонента и при отсутствии реализации умалчиваемых методов. К этой проблеме относится и определение места кода, в котором новое событие будет возникать (скорее всего, мы "привяжем" новое событие к уже существующему стандартному, заставив предварительно выполниться умалчиваемый код директивой inherited).
8.
Тестирование компонента.
9.
Регистрация компонента в среде Delphi.
Задание. Используя в качестве заготовки оттестированный в лабораторной работе №1 класс TCloseButton, а точнее - текст его модуля, разработать
компонент - кнопку, не только закрывающую форму, но и реагирующую на два
новых события:
39
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
1) перехватывающую момент перед закрытием формы командой Close и каким-либо образом сигнализирующую об этом (в тестовом примере выводится
сообщение "Тест CloseButton", но обработчик события OnClickRelease доступен
во время проектирования в окне Object Inspector и может получить любой код,
который пожелает разработчик – пользователь компонента; в противном случае будет выполнен наследуемый, предусмотренный вами код);
2) в момент прохождения курсора мыши над любой кнопкой, порожденной
из этого компонента, активизироваться и передавать координаты мыши пользователю в параметрах метода – обработчика этого события.
Обратите внимание, что эти свойства кнопки появляются у нее независимо от того, создана она на форме программно или размещена буксировкой с палитры компонентов. Учтем это и будем в коде компонента разделять время
проектирования и время выполнения. Нет никакой необходимости реагировать
на события мыши в Design-время.
Сначала приведем описание нового класса, взятое из нашего модуля компонента:
На примере первого из проектируемых событий – OnClickRelease - сделаем следующее.
1.
В качестве базового выберем класс простых кнопок TButton.
2.
Конкретизируем задание, ограничившись тем, что это событие будет просто извещающим, то есть иметь стандартный тип TNotifyEvent без каких-либо параметров. Это событие должно предшествовать закрытию формы.
Поскольку закрытие формы мы собираемся делать по щелчку на кнопке, то ло-
40
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
гично привязать новое событие к этому стандартному событию OnClick. В нашем случае просто выведем извещающее сообщение. Тогда нужно будет переопределить метод Click, дописав его своим кодом, что и продекларировано в
описании нового класса директивой override:
Procedure Click; override;
3.
Для описания события OnClickRelease в раздел Private закрытых
данных нового класса разместим поле FОnClickRelease типа TNotifyEvent.
Эта переменная имеет процедурный тип.
4.
В разделе Published нового класса разместим описание новых
свойств-событий. Для события OnClickRelease это строка, связывающая закрытое поле FOnClickRelease класса c событием OnClickRelease.:
Property OnClickRelease: TNotifyEvent read FOnClickRelease
write FOnClickRelease
Новое событие будет в числе доступных в Object Inspector и имеет обработчик, предложенный нами. Программист может его переопределить.
5.
В раздел Public (и только там!) разместим строку о намерении переопределения конструктора Create базового класса (директива override):
Constructor Create (AOwner: TComponent); override;
Реализацию конструктора можно видеть в разделе implementation нового класса.
6. В разделе protected (защищенный) определим описание процедуры
диспетчеризации нового события, которая будет вызываться в определенном
месте кода метода Click() и вызывать метод обработчика события, в целях безопасности проверив его существование:
Procedure ClickRelease;
Обратите внимание на соответствие имен поля, свойства и метода диспетчеризации, следующее принятому в ООП соглашению: FOnClickRelease,
ОnClickRelease и ClickRelease.
7. В разделе Implementation реализации класса обеспечим вызов этого
метода ClickRelease в переопределенном событии OnClick().
8. В разделе protected определим пользовательский метод myAction обработки нового события
procedure myAction (Sender: TObject);
Этот метод, совпадая по структуре с процедурой нужного стандартного
типа, а именно TNotifyEvent (также имеет только один параметр - а именно,
ссылку на объект, вызвавший событие), будет присвоен полю FOnClickRelease
(напомним, что это поле имеет процедурный тип) в коде реализации метода
Create нового класса.
41
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Для второго события последовательность действий аналогична, но тип
обработчика пришлось ввести специально, так как он не является стандартным
(назван нами TMouseMoveOver, имеет три параметра) и совместим с методом,
так как в его описании присутствует отличительный признак …of object. Описание типа введено до его использования в описании класса.
Последовательность действий при создания компонента
Итак, события назовем OnClickRelease и OnMouseOver. Они должны
появиться в коде модуля компонента в "скрытом" разделе Private в виде соответствующих полей FOnClickRelease и FOnMouseOver (название начинается
с F) класса компонента:
События – это те же свойства, это подчеркивается тем, что они, как и
обычные поля объекта, будут доступны через соответствующие свойстваproperty, представленные в описании класса.
Вот что добавилось в описание класса TCloseButton по сравнению с его
описанием в задании №1 в соответствии с этими новыми полями–событиями:
Создание заготовки компонента в среде Delphi IDE
1. Выбрать команду File ð New , и затем на вкладке New выбрать значок
Component:
2. В открывшемся окне диалога выбрать в списке Ancestor type (Тип родителя): TButton
3. В поле Class Name ввести: TCloseButton
4. В поле Palette Page (страница палитры) оставить предлагаемое Custom.
5. В поле Unit File Name ввести имя и путь поиска для будущего модуля
компонента, но можно оставить и предлагаемое средой Delphi.
42
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Delphi создаст “каркас”- начальный текст модуля проектируемого компонента c четырьмя разделами описания класса TCloseButton и процедурой регистрации. Компонент полностью наследует все члены класса TButton (свойства, события и методы):
Раздел Private будет потом содержать так называемые "частные, личные,
внутренние" данные, скрывающие особенности реализации нового класса.
Раздел Protected определяет интерфейсную часть компонента, защищаемые данные; содержит сведения, недоступные для изменения программистам пользователям класса, но доступные для изменения в классах-наследниках.
Раздел Public содержит сведения интерфейса времени выполнения, сведения с максимальной степенью доступности.
Раздел Published содержит сведения интерфейса времени проектирования. В этом разделе будут находиться данные, доступные для изменения уже во
время визуального проектирования в окне Object Inspector.
Впоследствии вы добавите в заготовку этого класса свойства, методы и
события. Например, вот что добавлено из описания класса TCloseButton, взятого из лабораторной работы №1:
Реализация конструктора, которая ранее была использована в лабораторной работе №1, имела вид:
43
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
В этот код были внесены следующие изменения:
• заголовок кнопки изменен на "Default Value";
• свойствам-событиям присвоены соответствующие значения процедур методов реакции на события. Эти процедуры написаны нами и будут использоваться по умолчанию. Они имеют соответствующий процедурный
тип. Если бы назначения не было, ничего бы не происходило, но поскольку события описаны в разделе Published, то пользователи этого компонента смогут сами дописать нужный код, а каркас процедур создаст,
как обычно, среда Delphi, что впоследствии вы и сами сможете проверить.
Так выглядит новое, дополненное назначениями окончание метода Create:
Обработчики событий myAction и myActionForMouseOver, упомянутые
выше в правой части операторов присваивания, имеют текст:
44
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Полезно открыть комментарии и испытать в работе очень удобную функцию Win32 API MessageBox(), более информативную, чем ShowMessage().
О свойствах. Принято выделять простые свойства: числа, строки, символы, которые могут непосредственно редактироваться в окне Object Inspector.
Они также могут иметь перечислимый тип (например, свойство Align, имеющееся у большинства визуальных компонентов), тип множества, свойстваобъекта. Так, например, можно вводить простые свойства:
TmyClass = class (TCustomControl)
private
// внутренние поля
FintegerProp: Integer;
FStringProp: String;
FCharProp: Char;
published
property IntegerProp: Integer read FintegerProp write FintegerProp;
property IStringProp: String read FStringProp write FStringProp;
property CharProp: Char read FCharProp write FCharProp;
Используемый здесь синтаксис является типичным. Обратите внимание,
что имена свойств отличаются от имени поля отсутствием буквы F. Тип свойства совпадает с типом поля, которое оно представляет.
В силу соглашений, поля компонента раздела Private (их можно понимать
как поля компонента, по аналогии с полями пользовательского типа -записи
языка Pascal), принято начинать с буквы F (от слова Field – "поле"). Существует
правило, согласно которому прямой доступ к полям компонента не должен
предоставляться, а производится через соответствующие каждому полю свойства (property). Если ключевое слово write отсутствует, это означает, что свойство доступно только для чтения.
Для чтения и/или записи могут быть использованы более сложные конструкции - функции и процедуры, включающие всевозможные проверки и пре-
45
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
добработку данных. В этом случае по принятому в Delphi соглашению они начинаются с префиксов Get (считать значение ) и Set (установить значение).
Пользователь компонента, меняя его свойства в design или run-time, не вызывает явно ни Get-функцию, ни Set-процедуру. В Delphi такой вызов-подстановка
неявно выполняется компилятором, например, в операторе присваивания свойству нового значения.
Понятие и происхождение событий
Под событием в самом общем виде будем понимать любое происшествие, вызванное вмешательством пользователя, операционной системы или логикой работы программы. Совокупность события и кода, выполняющегося в
ответ на это событие, называется свойством - событием и реализуется в виде
указателя (т.е. путем ссылки) на некоторый метод, называемый в этом случае
обработчиком события.
Например,
• пользователь щелкает кнопкой мыши;
• в систему (OS) Windows посылается сообщение WM_MOUSEDOWN.
• OS Windows передает это сообщение тому элементу управления, для которого оно предназначено, например, некоторой кнопке;
• элемент управления, проверив наличие кода, может ответить на это событие.
Компонент должен проверить, ссылается ли свойство-событие на какойлибо код, предоставленный пользователем компонента. Для этого свойствасобытия имеют соответствующий метод диспетчеризации (event-dispatching
method), обычно размещающийся в защищенном разделе protected.
В частности, событие OnClick – одно из стандартных свойств-событий
Delphi - имеет метод диспетчеризации Click() и так представлено в одном из базовых классов, TControl:
TControl = class (TComponent)
private
FOnClick: TNotifyEvent;
protected
procedure Click; dynamic;
property OnClick: TNotifyEvent read FOnClick write FOnClick;
end;
В разделе реализации класса метод диспетчеризации TСontrol.Click()
имеет код:
procedure TControl.Click;
begin
46
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
if Assigned (FOnClick) then FOnClick(Self);
end;
Естественно, где-то этому полю FOnClick класса TCloseButton должна
быть "присвоена" пользовательская процедура того же типа (здесь - типа TNotifyEvent), чтобы что-то действительно выполнялось. Тогда происходит присвоение процедурной переменной ссылки (указателя) на метод, что допустимо в Object Pascal. В реализации нового класса мы выполняем это присвоение для двух
событий при создании кнопки в методе Create (слева - процедурная переменная, справа - ссылка на метод):
........
FonClickRelease:= myAction;
FonMouseOver:=myActionForMouseOver;
......
Полностью модифицированный метод Create имеет код:
Если свойство-событие имеет нестандартный тип, введенный разработчиком компонента, то сигнатура метода-обработчика должна быть соответствующей. Именно так пришлось поступить при проектировании второго события OnMouseOver, введя в обращение новый тип метода под названием
TMouseMoveOver (в модуле он приведен перед описанием класса, в котором
используется):
47
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Этот тип процедур-методов позволяет вернуть целочисленные координаты мыши, которые можно получить, если совместить второе проектируемое событие со стандартным событием OnMouseMove. Это событие новый класс наследует от одного из предков класса TButton, а именно TControl (см. в Help иерархию классов TButton, представленную на рис.6).
Рис. 6. Иерархия классов TButton.
Соответственно, для реализации события "OnMouseOver" (мышь –
"над…" ) в описании нового класса пришлось переопределить (override) стандартный метод MouseMove:
48
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Так выглядит (см ниже) наследуемый и переопределенный метод MouseMove(); привязка к нему позволила получить координаты мыши (но можно было бы распознать и нажатую кнопку мыши, проанализировав параметр Shift):
На заметку: процедура CreateWnd, упомянутая в работе №1, не использовалась вместо Create, но интересна тем, что выполняется после метода Create и
в одном из первоисточников использовалась для вывода информации о разработчике в момент размещения компонента на форме в Design-время.
Следует помнить, что свойства-события являются не более чем указателями на методы. В коде выше проверяется непустота этого указателя, и если
он действителен, то вызывается процедура, назначенная пользователем компонента (т.е. разработчиком).
Свойство FOnClick имеет тип TNotifyEvent – "извещающее" сообщение,
оно определяется Delphi так:
TNotifyEvent = procedure (Sender: Tobject ) of object;
Это процедура, имеющая на первый взгляд один параметр Sender (получатель сообщения), методом ее делает директива of object. Как и для всех методов, в процедуру передается неявно еще один параметр - Self, не указанный в
списке параметров, передаваемых в процедуре. Этот параметр - ссылка на объект, которому метод принадлежит (параметр полезен хотя бы тем, что позволяет корректно сослаться на поля объекта "внутри" его методов путем указания
префикса Self).
49
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Итак, разработчик компонента пишет программный текст, определяющий
свойство-событие, метод диспетчеризации, а программист - пользователь компонента, создает обработчик события.
Для первого события "OnClickRelease" дополним заготовку кода нового
класса следующим текстом:
Так как это событие возникает после выполнения щелчка на кнопке
CloseButton, то вызов диспетчера события включим в код метода Click(), для
чего наследуемый из класса TButton код процедуры Click следует переопределить. Отсюда директива override в описании класса TCloseButton:
В реализации переопределенного метода используем оператор inherited,
позволяющий выполнить код метода таким, каким он определен в классеродителе, и лишь затем вызовем метод диспетчеризации ClickRelease:
Метод диспетчеризации ClickRelease определен (в числе двух методов) в
описании класса TCloseButton в разделе protected:
50
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Назначение свойствам-событиям пользовательских обработчиков выполнено в конце кода метода Create, в конце текста создания кнопки и определения
ее визуальных свойств так, как это сделано в методе Create на занятии №1:
В описании класса TCloseButton метод myAction представлен так:
Реализация этого метода:
Логический оператор позволяет определить, находится ли компонент в
процессе проектирования формы (например, только что опущен на форму) или
во времени выполнения программы. В Help можно ознакомиться с полным набором констант, характеризующих множество возможных состояний компонента ComponentState. Переопределенная директивой override наследуемая
процедура Click() имеет вид:
Видно, что вначале используется наследуемое поведение употреблением
inherited, и лишь затем вызывается метод диспетчеризации ClickRelease и оператор Close.
51
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Диспетчерская процедура ClickRelease() имеет вид:
Она лишь делает проверку наличия обработчика и вызывает метод поля
FonClickRelease, для которого будет сделано назначение процедуры пользователя myAction при создании экземпляра кнопки либо динамически, либо в
процессе размещения ее с палитры "вручную".
На тестовой форме (в процессе выполнения) одна кнопка, "Default
Caption", создается динамически, в коде, а другая, "Caption - в design-time" –
размещена в дизайнере формы на произвольном месте и с новым заголовком.
После ввода текста компонента и проверочной компиляции клавишами Ctrl+F9 необходимо инсталлировать новый компонент и поместить его в какой-либо пакет, желательно тематический. Для этого следует воспользоваться
пунктом меню
Component ð Install Component
Для нашего случая воспользуемся существующим пакетом Dclusr50.dpk
(для версии Delphi 5.0), который предлагается по умолчанию. В диалоге установки нового компонента нужно определить вручную поле "Unit file name", но
местоположение текста удобно ввести путем поиска кнопкой "Browse".
52
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
В примере ниже регистрируется компонент “TCloseButton”:
После успешной установки следует проверить компонент на тестовой
форме, причем как путем размещения на форме в Design-time, так и создания в
динамике. Текст метода Create тестовой формы может быть следующим:
53
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Непосредственная обработка событий
Вы можете непосредственно реагировать, например, на события мыши,
вообще не привлекая VCL Delphi для предварительной обработки сообщений
Windows до формы удобных параметров, получаемых в событии
OnMouseMove. Непосредственно захватит сообщение процедура, объявленная
в разделе Private модуля компонента так:
procedure myMouseMove (var M: TWMMouse ); message wm_MouseMove;
Ключевое слово message сообщает Delphi о том, что вы сами хотите отреагировать на сообщение OS Windows. Константа wm_MouseMove представляет сообщение, которое вы желаете перехватить - очевидно, что это сообщение о перемещении мыши (все сообщения Windows можете просмотреть в Messages.pas из поставки Delphi).
Реализация метода myMouseMove может иметь такой вид:
Если заблокировать оператор inherited, то VCL Delphi вообще не получит
сообщений о перемещении мышки, и поэтому метод OnMouseMove не будет
вызван. Тип аргумента TWMMouse среда Delphi определяет так, как представлено на рис.7 (фрагмент Help).
Как видно, в этом конкретном случае перемещения мыши все можно решить гораздо проще, не вводя никаких новых событий, а получая координаты
мыши непосредственно из сообщения Windows. Но тогда нужно научиться обрабатывать сообщения Windows непосредственно.
54
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.7. Определение типа аргумента TWMMouse.
4.2.
Разработка невизуальных компонентов
Цель занятия: ознакомление с технологией разработки невизуального
компонента “с нуля” на примере класса “THalfMinute”, тестирование компонента.
В данном случае в качестве базового используем стандартный компонент
TComponent, обладающий минимальной функциональностью (просмотрите
для него в Help иерархию классов) и не предоставляющий возможности “отрисовки” на форме.
55
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
В качестве сценария предположим, что необходимо выполнять некоторое
действие по системным часам, то есть в установленные моменты времени, например, раз в 30 секунд. Стандартный компонент TTimer, конечно, можно использовать для генерации отклика на это событие (напомним, что только одно
событие - OnTimer - и имеет этот компонент), но можно разработать новый
компонент, который бы давал возможность пользователю-программисту написать собственный текст обработки именно этого события (назовем его OnHalfMinute). Очевидно, что функциональность компонента можно усложнить,
заставив событие происходить либо в любые равные промежутки времени, либо в конкретные моменты времени, например, считывая их значения из текстового Ini-файла. Подобная функциональность нужна, например, фоновым агентам, сканирующим некоторый каталог сервера через равные промежутки времени и делающим рассылку электронной почты из определенной “папки исходящих” и т.д.
Код компонента THalfMinute с подробными комментариями:
{ Copyright © 1999 by Delphi 5 Developer’s Guide - Xavier Pacheco and
Steve Teixeira }
{ Изменения внесены Е.Шигаловым в июле-августе 2004 г. }
unit Halfmin;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
56
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Forms, Dialogs, ExtCtrls;
type
{ Определяем процедуру TTimeEvent (точнее, ее тип) для обработчика
нового события.
Свойство-событие будет иметь именно этот процедурный тип, предусматривающий передачу 2-х параметров:
•
объекта, генерирующего это собитие;
•
значение типа TDateTime, представляющего время генерации события.
В случае нашего компонента событие будет происходить каждые полминуты }
TTimeEvent = procedure(Sender: TObject; TheTime: TDateTime) of object;
THalfMinute = class(TComponent)
private
FTimer: TTimer;
{ Определяем поле, которое будет хранить ссылку на пользовательскую
процедуру - обработчик события. Эта процедура должна иметь тип TTimeEvent,
определенный выше. Поле назовем FOnHalfMinute }
FOnHalfMinute: TTimeEvent;
FOldSecond, FSecond: Word;
// Просто используемые переменные
{Определим процедуру FTimerTimer, которая будет связана с событием
таймера FTimer.OnTimer (будет назначаться свойству-событию). Она должна
иметь тип TNotifyEvent, как и свойство-событие OnTimer. }
procedure FTimerTimer(Sender: TObject);
protected
{ Определение DoHalfMinute, метода диспетчеризации события OnHalfMinute }
procedure DoHalfMinute(TheTime: TDateTime); dynamic;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
( Определим реальное свойство-событие, которое будет отображаться в
окне Object Inspector }
property OnHalfMinute: TTimeEvent read FOnHalfMinute
write FOnHalfMinute;
end;
// конец описания класса THalfMinute
procedure Register;
implementation
procedure Register;
begin
RegisterComponents(‘Custom’, [THalfMinute]);
end;
57
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
constructor THalfMinute.Create(AOwner: TComponent);
{ Конструктор Create создает экземпляр компонента типа TTimer с именем FTimer, а затем задает его некоторые свойства: Interval и событие OnTimer,
которому назначается обработчик - метод FTimerTimer проектируемого компонента.
Заметьте, что свойство Enabled устанавливается равным True при выполнении программы, и False - во время разработки. }
begin
inherited Create(AOwner);
// Если компонент в режиме проектирования, то поле FTimer не активизируется
if not (csDesigning in ComponentState) then
begin
FTimer := TTimer.Create(self);
FTimer.Enabled := True;
// Установка остальных свойств, включая обработчик события
FTimer.OnTimer
FTimer.Interval := 500;
FTimer.OnTimer := FTimerTimer;
// вот и установка обработчика
end;
end;
destructor THalfMinute.Destroy;
begin
FTimer.Free;
// объект FTimer создан программно, потому - ручное
освобожд.
inherited Destroy;
end;
procedure THalfMinute.FTimerTimer(Sender: TObject);
{ Этот метод служит обработчиком события FTimer.OnTimer и назначается ему динамически в конструкторе THalfMinute.Create.
Метод считывает системное время, проверяет его кратность 30 секундам.
Если так, то вызывается метод диспетчеризации DoHalfMinute события OnHalfMinute.}
var
DT: TDateTime;
Temp: Word;
begin
DT := Now;
// Получаем системное время
FOldSecond := FSecond; // Сохраняем старое значение секунд
// Получаем секундную составляющую системного времени
58
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
DecodeTime(DT, Temp, Temp, FSecond, Temp);
{ Если сейчас не то же самое время, когда метод вызывался в последний
раз, и оно кратно 30 секундам, то вызывается метод диспетчеризации для OnHalfMinute}
if FSecond <> FOldSecond then
if ((FSecond = 30) or (FSecond = 0)) then
DoHalfMinute(DT);
// === Пример(он кажется более понятным): если требуется сигнализир.
раз в 5 секунд:
// If (GetTickCount div 1000) mod 5=0 Then
// DoHalfMinute(DT);
end;
procedure THalfMinute.DoHalfMinute(TheTime: TDateTime);
{Это метод диспетчеризации события OnHalfMinute. Он проверяет, назначил ли пользователь компонента обработчик этому событию, и если да - то
вызывает его код }
begin
if Assigned(FOnHalfMinute) then
FOnHalfMinute(Self, TheTime);
end;
end.
В отличие от компонента TCloseButton, никакого умалчиваемого поведения этот компонент не несет, так как никакой процедуры полю FOnHalfMinute
мы не назначали. Но использовать компоненту безопасно и в этом случае, так
как метод диспетчеризации DoHalfMinute не допускает выполнения того кода,
которого нет:
if Assigned(FOnHalfMinute) then …
Следует обратить внимание, что если в конструкторе Create вначале выполняются наследуемые действия (inherited), а затем – специфические для данного компонента, то в методе
Destructor, и это как правило,
наоборот:
В данном случае объект
таймер создан программно, и по-
59
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
этому должен быть разрушен “вручную”:
FTimer.Free;
Для тестирования компонента HalfMinute служит небольшое приложение из двух форм. Первая форма в компоненте TListBox просто выводит дату и
время перехвата спроектированного события, которое в целях демонстрации
выводится и в заголовок кнопки. Вторая форма введена с целью получения
практики работы со списком, но отладочный вывод (без останова программы в
сообщении ShowMessage() или MessageBox()) можно было сделать, используя в
качестве места вывода заголовок формы.
Вторая форма (ее имя Show_DateTime) создается автоматически, так
как находится после создания в дизайнере, по умолчанию, в числе AutoCreate,
но для того, чтобы сделать ее видимой, применен метод Show в следующем коде события Activate главной формы:
Для Windows 2000/XP метод Show, возможно, потребуется исключить,
заменив кодом:
Visible:=True; // ранее было Show;
Для того, чтобы закрывая первую, закрыть и вторую форму и корректно
освободить ресурсы:
60
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Переменная CanClose (см. Help) используется Delphi для того, чтобы до
выполнения метода Close формы еще в процессе запроса на закрытие дать возможность разработчику освободить системные ресурсы (закрыть файлы, подчиненные формы, уничтожить созданные программно объекты, для которых
форма не назначена в качестве родителя и так далее).
Код обработчика события HalfMinute (см выше), вписанный нами в заготовку, полученную после щелчка на событии OmHalfMinute, заполняет список
второй формы, прост и служит лишь для демонстрации того, что событие действительно происходит каждые 30 сек. Для обеспечения возможности ссылки
на объект другой формы (здесь - списка ListBox формы Show_DateTime из кода
формы MainForm) в список модулей Uses среда Delphi предложила, а мы не отказались, включить модуль ShowMsg второй формы:
Ваше имя второго модуля почти наверное будет другим.
Соответственно, в разделе implementation второй формы включена ссылка на модуль HalfMinute главной формы:
Обеспечение перекрестных ссылок модулей - обычная практика в программировании с большим числом форм и с этим вы уже столкивались в некоторых лабораторных работах.
В целом, приложение в Run-time выглядит следующим образом (рис.8):
61
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.8. Вид приложения в Run-time.
Для получения dcu-модуля компонента и размещения его на страничке
палитры "Custom" скопируйте, например, pascal-текст модуля, приведенный
выше, в подкаталог Component текущего каталога лабораторной работы (у нас
это C:\Labs_OOP\Lesson_1_2\Component). На этот каталог у вас должны быть
полные права.
Для размещения компонента в пакете Dclusr.dpk, предлагаемом Delphi
по умолчанию, и после выбора "Unit file name", местоположения pas-файла в
окне Browse, форма Install Component, в нашем случае - для Delphi 5.5, имела
следующий вид:
62
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
После размещения на пустой форме и щелчка на строке OnHalfMinute
страницы Events окна Object Inspector генерируется следующий "пустой" код:
Никакого умалчиваемого поведения нет (можно проверить вставкой директивы inherited и пробным выполнением по F9), так как оно не было предусмотрено нами в тексте модуля компонента, но каркас процедуры показывает,
что в нужный момент разработчик получит в переменной "TheTime" дату и
время генерации события. Как распорядиться полученной информацией – это
уже дело программиста.
Мы распорядились так:
4.3. Построение компонента с нуля. Компонент Clock
Цель занятия: введение в построение компонента, производного от некоторого базового, обладающего минимальным набором возможностей.
Внешний вид программы, тестирующей разработанный компонент, представлен на рис.9.
63
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Рис.9. Внешний вид программы, тестирующей разработанный компонент.
Существуют три класса таких объектов, которые можно было бы назвать
“абстрактными”, так как вам никогда не пришло бы в голову установить экземпляр одного из них. Они существуют для того, чтобы порождать новые классы
объектов.
1.
Классы TWinControl и TCustomControl являются базовыми для
элементов управления Windows. Они воспринимают входное воздействие и обладают дескриптором (handle), который могут передавать при обращении к
функциям Win32 API. Дескриптор – это 32-разрядное число, указывающее на
определенный экземпляр объекта в системе Windows (три типа объектов OC –
объекты ядра, USER и GDI). Существуют они в своем собственном окне. Класс
TWinControl имеет следующие три основные особенности: объекты этого класса имеют дескриптор, могут получить фокус ввода и могут являться родительским элементом для других компонентов. Класс TCustomControl является производным от TWinControl. Основное различие этих классов состоит в том, что
класс TCustomControl обладает методом Paint, то есть умеет отрисовать себя, а
класс TWinControl этого метода не имеет.
2.
Класс TGraficControl подходит для построения компонентов, не
требующих восприятия входных воздействий (имеется ввиду – от клавиатуры,
от мыши сообщения доходят), не могущих содержать других компонентов и не
требующих обработчика. Его потомки существуют в окне своего родителя, используют его дескриптор и контекст родительского устройства (DC). Они обладают полями Handle (дескриптор) и Canvas (полотно), но в действительности
они принадлежат их родителю. Примерами являются объекты TLabel и TShape.
3.
Класс TComponent подходит для построения на основе его классов
невизуальных компонентов.
Построим небольшой компонент с основания. Модуль CLOCK, показанный на рис.9, – это небольшие часы, которые вы размещаете на форме, запускаете и останавливаете по желанию, меняете цвет фона. Естественно в Windows
использовать стандартный таймер. Поскольку таймеры для запуска и останова
64
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
требуют наличия Handle (мы не собираемся размещать их на форме предварительно, а создадим динамически) и пересылают сообщения wm_Paint в окно,
владеющее им, то TGraficControl не является хорошим выбором для базового
класса. Так как мы собираемся перерисовывать изображение часов с ходом
времени, то нам потребуется метод Paint. Поэтому в качестве базового класса
выберем TCustomControl.
Таким образом, запустив программу, вы можете создать часы и сделать
их видимыми на форме, затем запустить часы и, если необходимо, изменить их
цвет. Полностью код компонента таков:
Раздел описания класса TClock:
Класс имеет три скрытых private поля:
FTimer: Integer - поле порядкового номера таймера Windows, можно положить равным 1,2,3 и так далее;
FRunning: Boolean - логическое поле, определяющее, должны ли идти
часы;
FColor: TColor - переменная, определяющая цвет кисти, объекта
brush.полотна (Canvas).
65
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Если свойство Running определяет состояние часов – запущены они или
нет, то программное управление таймерами Windows осуществляется вызовом
двух функций Win API: для запуска используем SetTimer(), для остановки –
KillTimer().
Функция SetTimer имеет четыре параметра:
1. Handle является HWND, дескриптором текущего окна;
2. IDEvent – уникальный идентификатор таймера внутри окна, которое его создало; мы полагаем его равным 1;
3. Elapse – промежуток времени между обращениями к таймеру;
4. TimerFunc - функция обратного вызова, здесь не используется.
Типичное обращение к функции SetTimer выглядит следующим образом:
SetTimer(Handle, FTimer, 1000, nil);
Здесь Elapse = 1000, и обращение к таймеру делается каждые 1000 миллисекунд, т.е. раз в секунду.
Функция KillTimer() имеет два параметра, смысл их очевиден:
KillTimer(Handle, FTimer);
В нашем случае, когда функция обратного вызова не используется, события таймера передаются в окно посредством сообщений:
procedure WMTimer(var Message: TMessage); message WM_Timer;
Ответом на событие таймера является обработчик события – пользовательская процедура WmTimer(), имеющая следующий код:
procedure TClock.WMTimer(var Message: TMessage);
var
S: string;
begin
S := TimeToStr(Time);
Canvas.TextOut((Width div 2) - (Canvas.TextWidth(S) div 2), 45, S);
end;
Свойство Running представлено так:
property Running: Boolean read FRunning write SetRunning;
Процедура SetRunning() установки значения Running в True/False имеет
код:
66
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Если пользователь устанавливает Running в True, то неявно выполняется
процедура SetRunning и вызывается SetTimer(). В противном случае вызывается
KillTimer, и часы прекращают работу. Такая схема вызова имеет место независимо от того, меняется ли это свойство программно или вручную во время проектирования в редакторе свойства в окне Object Incpector (см. ниже). Это возможно, если модуль TClock делается полноценным компонентом и объект размещается на форме с помощью мыши из палитры VCL.
Для вызова KillTimer() логично было бы использовать метод Destroy объекта TClock как ответ на соответствующее событие, но окно, связанное с часами, к этому моменту уже разрушено и нет доступного дескриптора окна. Поэтому используется пришедшее до закрытия окна TClock сообщение
wm_Destroy:
О методе Paint. Он вызывается всегда, когда есть необходимость перерисовки окружности, определяющей область часов. За этим следит Windows, и
67
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
вам никогда не придется вызывать Paint непосредственно. Windows посылает
сообщение wm_Paint в окно TClock, определяющее необходимость перерисовки. TClock вызывает обработчик Paint для этого сообщения:
Таким же образом сообщение wm_Timer окну TClock преобразуется в вызов обработчика wmTimer, а затем уже в нем следует обращение Canvas.TextOut(10,40,GetTimeString);
Процедура wmTimer() представляет типичный метод-сообщение, о чем
говорит ее определение в описании класса TClock:
procedure WMTimer(var Message: TMessage); message WM_Timer;
Процедура выводит состояние активизированного системного таймера на
полотне объекта TClock методом TextOut. Для преобразования значения функции Time в текстовую форму применена функция TimeToStr:
Выключение таймера делается в момент получения окном компонента
сообщения WM_Destroy от системы о намерении разрушить окно. Описание
этого метода-сообщения, размещенное в описании класса TClock, имеет вид:
procedure WMDestroy(var Message: TMessage); message wm_Destroy;
Обработчик события wm_Destroy из раздела implementation имеет код:
Свойство Color, дающее доступ пользователю к полю FColor, определяется так:
68
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
property Color: TColor read FColor write SetColor;
То есть оно допускает непосредственное чтение из поля FColor и запись
нового значения посредством процедуры SetColor(). Код этого метода:
Процедура InvalidateRect() Win32 API заставляет определенное в первом
параметре окно полностью перерисовать себя после установки цвета командой
FColor := Color;. В порядке эксперимента временно удалите этот оператор – со
сметой цвета перестанет корректироваться цвет заливки круга.
Определение InvalidateRect():
Procedure InvalidateRect(Wnd: HWnd; Rect: PRect; Erase: Boolean);
Средний параметр является указателем на структуру TRect, которая может быть использована для определения прямоугольной обрасти, подлежащей
перерисовке. Если третий параметр установлен равным False, то изменяются
только те части окна, которые вы специально перерисовываете. Ниже – результат эксперимента (справа – часы, установленные из палитры):
Для полной перерисовки, как в нашем случае, вполне можно использовать собственную функцию Invalidate Delphi, совету проверить.
Переписанный конструктор Create имеет вид:
69
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Поле FTimer определяет номер используемого системного таймера,
равный 1, остальные назначения очевидны и определяют, в том числе, и наследуемые от TControl свойства Width и Height (ширина и высота).
Только после отладки модуля компонента для добавления его к палитре компонентов нужно его предварительно зарегистрировать. При фрагменте
кода компонента, приведенного ниже, это произойдет автоматически, имя новой странички указано текстовой константой ‘Unleash’.
В заключение приведем полный код модуля компонента TClock:
70
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
71
Copyright ОАО «ЦКБ «БИБКОМ» & ООО «Aгентство Kнига-Cервис»
Список литературы
1. Петров В.Н. Информационные системы. – СПб.: Питер, 2002.
2. Фаронов В.В. Программирование баз данных в Delphi 7: Учебный курс. СПб.: Питер, 2004.
3. Стив Тейксейра, Ксавье Пачеко. Delphi 5. Руководство разработчика, том 1.
Основные методы и технологии программирования: Пер. с англ.: Учебное
пособие - М.: Издательский дом “Вильямс”, 2000.
4. Чарльз Калверт. Delphi2. Энциклопедия пользователя: Пер. c англ.– К.:
НИПФ “ДиаСофт Лтд.”, 1996.
5. Методические указания к лабораторным работам по объектно-ориентированному программированию. / Сост. Шигалов Е.К., Бобкова В.А. Иван. гос.
хим.-технол. ун-т, 2003.
Содержание
1. Файловые потоки (FileStream). Создание объекта программным путем. Сохранение объекта в файле и считывание его из файла . . . . . . . . . . . . . . . . . . .3
2. Работа с файлами, отображенными в память . . . . . . . . . . . . . . . . . . . . . . . . . . 9
3. Консольные приложения. Программы работы с каталогами . . . . . . . . . . . . . 21
3.1. Слияние файлов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .21
3.2. Просмотр и удаление файлов с учетом даты создания/модификации . . 29
3.3. Копирование дерева каталогов . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .32
4. Разработка собственных компонентов . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . 38
4.1. Свойства, события, обработчики событий и диспетчеризация событий.
Переопределение (overriding) методов класса . . . . . . . . . . . . . . . . . . . . 38
4.2. Разработка невизуальных компонентов . . . . . . . . . . . . . . . . . . . . . . . . . . .55
4.3. Построение компонента с нуля. Компонент Clock . . . . . . . . . . . . . . . . . .63
Список литературы . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 72
72
Документ
Категория
Информатика
Просмотров
23
Размер файла
967 Кб
Теги
2036, технология, объектно, программирование
1/--страниц
Пожаловаться на содержимое документа