Issuu on Google+

I t W T o b p ГО i]

OOIIOiDJI

DELPHI Font.Chaib, Fo

iRoman' tRoman'

Font d ar

'i Roman' T I В

VV'/dl = 4 5

И ICe

«

m

iow » FprmSh n e ?S tyOnTop OEFAUl CHARStiT Г or-'. Co1 т • c/Vt/low f ont.Height = 1-II M6 Sans Se Fa t Sr.'e » i t 1 Fa/ . -yt Labe42 TLabc Left » 16 Top 10< Width » 24 Hefffit = 13 DFC

WlT_CHARSET

Font.Color • cIYrHkw Font He^ht ' -11 Fc * )me MS Sar s Sfer/f Font.Style -[] Lett = 24 Top = 56 Wj'df'i = 2 5 IhM^ftt = 2 5 Cotn on = '+" Font.C I' r = с Wed Fo"?tHeight = 19 Font iitvifi = [fsBoid] ParentFont = Fafse Bru»h:Style = bsClear Font.Color = clBlue Font.Height = -11 Forf.". ime = 'MS Sans $6r/f Роп(.51у1ё = [] Pen.Style = psCleer


УДК 004.43 ББК 32.973-018.1 Е 60

Рецензенты: д-р техн. наук В.Н. Богатикое (Институт информатики и математического моделирования технологических процессов РАН); д-р техн. наук В.Н. Захаров (Институт проблем комплексного освоения недр РАН)

Е 60

Емельянов, В.И. Основы программирования на Delphi: Учеб. пособие для вузов/В.И. Емельянов, В.И. Воробьев, Т.П. Тюрина; Под ред. В.М. Черненького. — М.: Высш. шк., 2005.— 231 е.: ил. ISBN 5-06-004869-1 Пособие содержит основные начальные сведения по программированию на Delphi, более 20 заданий, рекомендованных к обязательному выполнению, и составляет материал курса «Алгоритмические языки программирования». Для студентов вузов, обучающихся по направлению «Информатика и вычислительная техника». Может быть рекомендовано студентам других направлений и всем желающим изучить основы программирования на Delphi. УДК 004.43 ББК 32.973-018.1

ISBN 5-06-004869-1

© ФГУП

«Издательство «Высшая школа», 2005

Оригинал-макет данного издания является собственностью издательства «Высшая школа», и его репродуцирование (воспроизведение) любым способом без согласия издательства запрещается.


ВВЕДЕНИЕ Основной задачей изучения дисциплины "Алгоритмические языки и технология программирования" является обучение студентов методике разработки программ, основам алгоритмизации, методам отладки, тестирования и построения приложений на ЭВМ. В качестве базового языка программирования выбран Object Pascal (Паскаль). Достоинства языка Паскаль общепризнанны: рациональность, лаконичность, полное соответствие идее структурного программирования, наличие мощной системы программирования и пакетов прикладных программ для решения широкого круга задач. В настоящее время продолжают оказывать решающее влияние на состояние и развитие всех областей современного программирования идеи визуального, объектно-ориентированного и событийно управляемого программирования. Классическим выражением этих идей является среда программирования DELPHI и язык программирования OBJECT PASCAL. Форма обучения программированию достаточно проста: обучение в работе. Такой способ не только ускоряет процесс обучения, но и помогает быстрее запомнить материал. Когда выполняется некоторое действие и можно увидеть его результат, то надолго запоминается, что было сделано и как. Чтение книг по программированию - это хорошо, но гораздо лучше практика в программировании. Соответственно предлагаемое пособие - это фундамент Вашего успеха. Для целей первоначального обучения наиболее приемлемым представляется разумное сочетание обучения программированию с изложением языка. Детально ориентированная на синтаксис методика учит не программированию, а кодированию. Не должно быть ни стремления к полноте, например, в описании языка, ни следования заповеди "от частного к общему", т. е. систематическому, формальному изложению предшествует предварительное неформальное знакомство с большинством языковых средств. Данное учебное пособие содержит большое количество примеров. Теоретический материал изложен исходя из постепенного усвоения тех или иных языковых средств решения задач. Отличием данного пособия от многочисленных книг, посвященных программированию на DELPHI, является подробное описание реального процесса создания программ. Это означает, 3


что в центре внимания авторов пособия находились, прежде всего, технологии разработки приложений. В данном пособии рассмотрена среда визуального программирования Delphi версии 7.0. Все примеры выполнены в операционной среде Windows ХР.


ОСНОВЫ DELPHI Программное обеспечение представляет собой один из видов интеллектуальной собственности, разработка которой превратилась в индустрию, которая развивается по собственным строго определенным законам. Скорость создания и надежность программного обеспечения напрямую зависят от основного рабочего инструмента — используемой среды разработки. При этом важны не только достоинства текущей версии, но и базовые идеи, заложенные в концепцию инструментария. Особенностью программирования в Windows является наличие многочисленных технологий, используемых при создании приложений различных типов, например офисных, для работы с базами данных, в среде "клиентсервер", в Интернете и т.д. Среда разработки приложений Delphi для самых разнообразных предприятий и организаций, являющаяся интеллектуальной собственностью фирмы Borland, на протяжении ряда лет успешно выдерживает (и выигрывает!) жесткую конкуренцию с другими системами подобного типа. В среде Delphi могут работать не только профессионалы. Среда разработки сохраняет простоту и наглядность процесса создания приложений, основанного на использовании технологий визуального программирования. Объектно-ориентированный компонентный подход позволяет легко и быстро создавать не только интерфейс программ, но и достаточно сложные механизмы доступа к данным, а также повторять и тиражировать удачные программные решения. ОБЩАЯ ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ Компьютерное программирование часто рассматривается как вид искусства. Однако многие институты преподают программирование как науку. Исследовательские учреждения давно ищут пути увеличения эффективности труда и уровня профессионализма разработчиков программ, т. е. интеллектуальной собственности, которая имеет свой особый статус и свое правовое поле. Существуют различные подходы к программированию. Одним из них является структурное программирование, представляющее попытку превра5


тить искусство программирования в обычную науку. Основной принцип структурного программирования заключается в создании некоторых аналитических моделей решаемой задачи перед началом кодирования. Двумя наиболее используемыми старыми моделями являются разработки "сверху вниз" и "снизу вверх". Разработка "сверху вниз" означает, что программа создается исходя из принципа первоочередного решения главных задач. Новые более детальные уровни разрабатывают по мере успешной реализации предыдущих более высоких уровней. Идея разработки "снизу вверх" заключается в том, что все хорошие программы состоят из хорошо отлаженных процедур и функций, объединенных логично и разумно. Новые уровни программы разрабатывают с использованием уже готовых функций и процедур, пока программа не будет готова. В настоящее время значительным сдвигом на пути разработки технологий программирования является объектно-ориентированное программирование (ООП). ООП задумывалось как некая мощная концепция, уменьшающая время разработки и отладки программ. ООП описывает программы в терминах логических сущностей и взаимодействий между ними. Основным элементом программирования является класс, от которого порождаются многочисленные объекты со своими инкапсулированными данными и интерфейсами, позволяющими выполнять определенные встроенные действия. Отличительной особенностью ООП является мощный механизм эволюционного развития имеющихся многочисленных базовых классов путем наследования и встраивания новых интерфейсов в последующие поколения классов. Концепция ООП соответствует внутренней логике функционирования операционной системы (ОС) Windows. Поэтому приложения, разработанные на основе ООП (в том числе и вереде Delphi), приобретают большую надежность кода и возможность широкого использования однажды построенных классов. ЯЗЫК ПРОГРАММИРОВАНИЯ

Delphi - это могучая среда разработки самых разнообразных программных приложений, являющаяся одновременно и редактором и компилятором. Данная среда ориентирована на работу, прежде всего, в операционных системах Windows. Однако с помощью Delphi можно разрабатывать приложения для Linux и строить кроссплатформенные приложения Windows- Linux. В основе любой программы на Delphi лежит язык Object Pascal - язык программирования высокого уровня, позволяющий использовать такие объектно-ориентированные языковые средства, как динамические массивы, перегрузка методов, параметры, заданные по умолчанию, файловые потоки, многопоточные приложения и многое другое. 6


В качестве главного момента можно отметить следующее: изучение Delphi следует вести в двух направлениях: изучение среды разработки и изучение языка программирования. ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ

В Delphi разрабатываются приложения, работающие под управлением операциионной системы Windows. Эти приложения используют библиотеку базовых классов объектов (библиотеку компонентов). Таким образом, приложение представляет собой совокупность объектов, которыми являются само приложение и все его компоненты: окна, кнопки, меню и т.д. Объектприложение разрабатывается как программа-проект, которая взаимодействует с другими объектами. Это взаимодействие, как и положено в объектноориентированном мире, выражается в сообщениях, посылаемых друг другу объектами. Идея обмена сообщениями легла в основу операционной системы Windows, где объекты-окна посылают и получают сообщения. Основные принципы и понятия объектно-ориентированного программирования: класс, поля, методы, свойства, инкапсуляция, объект, наследование, полиморфизм. Класс является обобщением понятия типа данных и задает свойства и поведение объектов класса, являющихся его построенными экземплярами. Каждый объект принадлежит некоторому классу. Отношение между объектом и классом такое же, как между переменной и ее типом. С формальной точки зрения класс - это объединение данных и обрабатывающих их процедур (инкапсуляция). Данные класса называются полями, или переменными класса, а процедуры обработки полей-методами класса. Поля определяют свойства объекта (экземпляра класса) или состояние объекта, методы определяют поведение объекта. Для придания гибкости доступа к полям существуют такие элементы класса, как свойства. Свойства реализуют механизм доступа к полям. Каждому свойству соответствует свое поле, содержащее некоторое текущее значение. Для чтения и записи этих значений разрабатываются специальные методы. Если определен класс А, то можно определить новый класс В, наследующий свойства и поведение объектов класса Л. Это значит, что в классе В неявно определены все поля и методы класса А. Класс В, являясь наследником базового (родительского, предка) класса А, называется производным (дочерним, потомком) по отношению к классу А. В производном классе можно задать новые свойства и новое поведение (эволюционное развитие), определив новые поля и методы. Метод базового класса иногда полезно объявить как виртуальный. Виртуальность обеспечивает возможность написания полиморфного метода, 7


который может обеспечить разнообразное (полиморфное) поведение виртуального метода в зависимости от использования его тем или иным объектом. ВИЗУАЛЬНОЕ ПРОГРАММИРОВАНИЕ

Визуальное программирование позволяет разрабатывать все элементы приложения, непосредственно наблюдая результаты своей работы на экране. Иными словами, разработчик увидит, как будет работать его приложение еще до того, как полностью будет написана программа. Реализация визуального программирования стала возможной в связи с развитием графического интерфейса GUI (Graphical User Interface). Основным понятием визуального программирования является интерфейсный элемент, который в терминах объектно-ориентированного программирования является объектом. Объекты - кнопки, строки, списки и т.д. — являются кирпичиками, из которых строится приложение. Среда визуального программирования позволяет увидеть объекты на экране монитора до выполнения программы. Изменение внешнего вида объекта выполняется с помощью таких его свойств, как высота, ширина, положение, цвет и др. Свойства - это атрибуты объекта, которые составляют его индивидуальность и помогают описать его. В то же время свойства обеспечивают возможность реагировать на внешние события. Жизнь объектов состоит в обмене сообщениями. Сообщения инициируются событиями, которые происходят во внешнем по отношению к приложению мире: при действиях пользователя, операционной системы или другого приложения. События могут возникать, например, при нажатии на кнопку мыши или клавиши на клавиатуре. Сообщение может быть проигнорировано или обработано. Для обработки сообщения необходимо запрограммировать соответствующий обработчик события. Соответственно в ООП, в том числе и Delphi, основным стилем программирования является управляемое событиями программирование. СОБЫТИЙНО УПРАВЛЯЕМОЕ ПРОГРАММИРОВАНИЕ

Рассмотрим простую и естественную модель событийно управляемого и визуального программирования, характерную для Delphi. В типовой модели у приложения три составляющие: визуальная, системная и обработчики событий (рис.1). Визуальная составляющая задает образ на экране, с которым будет работать пользователь, т. е. визуальная составляющая определяет интерфейс пользователя. Большинство элементов интерфейса (кнопки, окна, списки и др.) стандартизированы. Поэтому в разных средах разработки (Visual Basic, Visual С++, Delphi и др.) визуальный инструментарий содержит одни и те же элементы интерфейса (элементы управления). 8


Обработчик событий 1 Обработчик событий 2

Обработчик событий n -1 Обработчик событий п Рис. 1

Элементы управления являются объектами. Их свойства и поведение определяются полями и методами соответствующих базовых классов. Классы, порождающие интерфейсные элементы управления, в Delphi называют компонентами. Все визуальные компоненты находятся в библиотеке визуальных компонентов Delphi VCL (Visual Component Library). Кроме визуальных компонентов в этой библиотеке есть невизуальные компоненты, не имеющие образа на экране монитора Пользователь - возмутитель спокойствия в мире объектов. Он нажимает на кнопки, выбирает элементы списков, печатает тексты в окнах редактирования. Каждому его действию соответствует некоторое событие. Системная составляющая определяет тип и параметры события и формирует сообщение объекту, с которым связано событие. Иначе говоря, системная составляющая находит нужный объект и запускает соответствующий обработчик события, т. е. соответствующий метод данного объекта. В обработке события программист может предусмотреть самые разные действия: изменение свойств объектов, добавление или удаление интерфейсных объектов и т. д. В большинстве приложений нет необходимости программировать системную составляющую. Кроме того, такое программирование часто требует владения функциями Windows API (Application Programm Interface, функции из открытых библиотек Windows). Таким образом, чтобы создать приложение, необходимо выполнить два взаимосвязанных этапа: разработать с помощью визуального инструментария интерфейс пользователя и написать реакции на действия пользователя, т. е. для каждого возможного события написать обрабатывающий его метод. Полученный интерфейс определяет способ взаимодействия пользователя и приложения, т.е. управление приложением, и внешний вид форм, из которых состоит разрабатываемый проект. Функциональность, т.е. конкретные решаемые задачи, определяется разрабатываемыми обработчиками событий. 9


WINDOWS-ПРИЛОЖЕНИЕ Основньм объектом объектно-ориентированной операционной системы Windows является окно. В дополнение к обычным свойствам объекта оно имеет графический образ на экране дисплея, с которым взаимодействует пользователь. Параметры Windows-окна определяют такие свойства, как тип, размер, положение на экране и т. д. В многозадачной и многооконной операционной системе Windows одновременно можно запустить несколько приложений, с каждым из которых может быть связано несколько окон. В каждом приложении имеется, как минимум, одно окно. События, возникающие в процессе работы компьютера (инициированные пользователем или связанные с посылкой сообщений от одного приложения к другому, от одного окна к другому того же приложения), приводят к возникновению сообщений, из которых операционная система (Windows) организует системную очередь сообщений. Далее сообщения распределяются по приложениям и создается для каждого приложения своя очередь. В этой очереди группируются сообщения от разнообразных источников: мыши, клавиатуры, таймера, других приложений и от самой операционной системы. В этой схеме есть исключения, так как некоторые сообщения напрямую направляются окну, например, сообщение \VM_DESTROY, уведомляющее о закрытии данного окна. Windows накладывает довольно жесткие ограничения на структуру приложений - каждое имеет главную процедуру (в Delphi это программапроект), одинаково устроенную для всех приложений. Главная процедура начинает работу с регистрации класса окна приложения, затем создает и рисует на экране главное окно и, возможно, другие окна. После создания объектов-окон, связанных с приложением, запускается цикл обработки очереди сообщений приложения, который иногда называют основным циклом обработки сообщений. СРЕДА ПРОГРАММИРОВАНИЯ Интегрированная среда разработки приложений в Delphi называется IDE (Integrated Development Environment). Под этим названием скрывается целая коллекция окон, меню и программ, которые позволяют проектировать интерфейс, связывать код с каждым экранным элементом и полностью отлаживать приложение внутри Delphi. При входе в IDE стандартно появляются 4 окна: главное окно (рис.2), окно инспектора объектов (Object Inspector), окно форм (Form Designer) и окно редактора кода (Code Editor). Главное окно является управляющим центром IDE. Оно управляет файлами, включаемыми в приложение, и выполняет всю работу с их сопровож10


дением, компиляцией и отладкой. Оно состоит из трех отдельных элементов: панели меню (Menubar), панели инструментов (Speedbar) и палитры компонентов (Component Palette). / Delphi 7 - Рш fecf! ш

ж 1 |

р

р

^

ш

й|

f y j f c Р®.

J

£ ШШт*

Irf Н

Рис.2

Окно инспектора объектов оформлено в виде ( |ecl двухстраничного блокнота (рис. 3). Это окно ис- Forml л пользуется для настройки компонента или формы. Первая страница используется для настройки в свойств (Properties), вторая — для настройки собыйбйгёОгМ тий (Events). AlptjfiBJend: jfjfc& Форма является контейнером ��нтерфейсных элементов. Окно форм (проектировщик форм) используется в процессе разработки интерфейса SArichoes jiadSfcaSnpj fegfert ifrue (рис. 4). Вместе с инспектором объектов проектировщик форм позволяет добавлять компоненты в iystemMerau. форму, модифицировать их, связывать обработчики событий с программным кодом на Object Pascal, используя окно редактора кода. По умолчанию для каждого нового проекта Ш0Ш № создается одно окно, которое называется формой. В дальнейшем к проекту могут быть добавлены Рис 3 другие формы. Но только одна форма может быть активной.

Окно редактора кода (Code Editor, рис.5, справа) позволяет редактировать коды всех модулей (программных единиц), входящих в разрабатываемое приложение. Это окно оформлено в виде многостраничного блокнота. 11


Каждая страница отображает код той или иной программной единицы, входящей в приложение. С помощью проектировщика форм и инспектора объектов представляются все виды частей "фасада" приложения, но без редактора кода нельзя связать эти части в единое целое.

Рис. 5

По умолчанию слева от редактора кода находится окно Code Explorer (см. рис. 5). Оно используется для поиска в редакторе кода какого-либо программного элемента: типа, класса, метода и т.д. Это окно можно выделить в самостоятельное и перенести в другое место. IDE Delphi обладает большой гибкостью в настройке рабочей среды. Для настройки используется диалоговое окно Environment Options, которое выбирается, раскрывая пункты меню Tools - Options. ПЕРВОНАЧАЛЬНЫЕ СВЕДЕНИЯ О ПРОЕКТЕ ПРИЛОЖЕНИЯ Основу любой разработки в Delphi составляет проект, объединяющий различные элементы в единое целое и обеспечивающий требуемую структуру для Windows-пршюжения. В простейшем случае в проект входят модули и формы различных типов. Как правило, из одного проекта получается одно приложение. Самый маленький проект состоит из главного файла (программыпроекта), который имеет расширение DPR. Кроме этого файла в проект входят файлы с расширениями CFG (настройки проекта), DOF (опции проекта), DSK (настройки среды), RES (файл ресурсов, в котором хранятся курсоры, значки, строки сообщений и др.). Главный файл проекта имеет стандартную форму и автоматически генерируется средой Delphi. Обычный проект, кроме главного файла, включает в себя, как минимум, одну форму и связанную с этой формой программу модуля. Форма служит для размещения на ней интерфейсных элементов. Программа модуля позво12


ляет на языке Object Pascal связать эти элементы в единое целое. Форма хранится в файле с расширением DFM (файл ресурсов формы), а программа модуля - в файле с расширением PAS. Модуль - это текстовый файл с исходным программным кодом. С модулем может быть связана форма, представляющая отдельное окно. Программная заготовка для модуля генерируется автоматически при построении формы. В состав проекта могут входить модули, не имеющие связанных с ними форм. При входе в IDE Delphi автоматически строится стандартный проект, включающий одну форму и связанный с ней один модуль. Если требуется выбрать какой-либо другой вариант проекта, то используется репозиторий (специальное хранилище, Repository, рис. 6).

Batch File

Control Panel Contid Panel Application Module

Frame

Package

г .

CIX Application

Component

Console Appfcatron

Data Module

DLL Wizard

Form

Project Group

Report

ResouceDLL Wizard

Г

Ш.

Omi

Рис. 6

В репозитории содержатся не только варианты проектов, но и элементы, которые можно добавлять в проект. Открыть репозиторий можно при помощи команды New-Other меню File. Если перед этим закрыть проект, предлагаемый по умолчанию (Close All в меню File), и выбрать в репозитории Application, то получим стандартный проект, т.е. такой же, какой в самом начале был закрыт. Обычно все файлы проекта располагают в одном каталоге. Сборка всего проекта выполняется при его компиляции (Ctrl+F9). При этом имя создаваемого приложения (ЕХЕ-файл) или динамически загружаемой библиотеки (DLL-файл) совпадает с названием проекта. Для каждого модуля создается файл кода с расширением DCU. Для управления составными частями проекта служит диспетчер проектов (Project Manager, рис. 7), который вызывается командой Project Manager меню View главного окна Delphi. 13


И ). <t Kyvv*

В

% lPfoject2.exe

~ J

--»- •••J..

Йе*. _ «ЛЦ i is-ЩяВ! В Project? «ню B-gjJ Uratl • В UnM.pas - S Foiml

C:\Program R]es\Borlarid\DelphiAProfects C:\Progtam Files\Borland\DelphiAProjects C:\Progtam Files\Bo[land\Delphi7\Pfoject$ Q\ProgramFtesSBoiland\Delphi7\Proiects D.4P[osiamFiles\Bcjla-id\Delp(ii7\PiC4ecb

®

f i

Рис. 7 В общем случае в Delphi всегда создается группа проектов. Простой проект представляет группу из одного проекта. ВСТРОЕННЫЙ ОТЛАДЧИК Интегрированная среда разработки включает встроенный отладчик приложений, в значительной мере облегчающий поиск и устранение ошибок. Средства отладчика доступны через команды пункта меню View-Debug Windows. Простейшим вариантом отладки является использование клавиши пошагового выполнения программы F7 и просмотр значений переменных в окне Watch (пункты меню Run: Trace Info и Add Watch). Если требуется отладка начиная с конкретного оператора, то устанавливают для него Breakpoint (в пункте Run). До точки останова можно пройти, используя F4, а далее, применяя, например, F7. ИСПОЛЬЗОВАНИЕ ВСТРОЕННЫХ КЛАССОВ На первых порах разработчика программ интересуют не все классы, а компоненты. Компоненты — неотъемлемая часть прикладной программы. Они формируют основу интерфейса пользователя. Компоненты для разработки интерфейса и системные компоненты соответственно называются визуальными и невизуальными. Невизуальные компоненты используются для реализации логической части приложения и функциональных возможностей системной составляющей (см. рис. 1). Визуальные компоненты входят в палитру компонентов и позволяют строить пользовательский интерфейс приложения. ИЕРАРХИЯ КЛАССОВ Delphi имеет большое количество встроенных классов, которые образуют иерархическое дерево (рис. 8, слева). 14


! ирЬп ни! classes

Я

В- Ц TObject j В-ц^ TPeisistent В*§, TComponent • Private TControl B-^l TWinContra) ' жfin Protected В-Щ TScrobiaWinConttol j a USES r 'tin Assign i-j TCustomForm ! К Destroy ВTForni GetNamePath 1% TFotml ниSi Inherited

4

Рис. 8 Каждый класс обязательно является наследником другого класса, за исключением TObject. TObject является предком для всех классов. Предком для всех компонентов является класс TComponent. Иерархию и отдельные элементы классов (см. рис. 8, справа) можно увидеть с помощью Browse Objects, окно которого вызывается из меню: View-Browse. Каждый класс обладает набором своих методов, свойств и полей, да еще наследует массу подобных элементов. Поэтому ориентироваться во всех возможностях класса очень трудно. Помогает то обстоятельство, что многие свойства и методы имеют много общего и называются одинаково. Базовые встроенные классы следует рассматривать как некую библиотеку функций и возможностей, которые к тому же можно наследовать при разработке собственных классов. Рассмотрим кратко назначение некоторых основных компонентов в иерархии классов. Класс TObject реализует функции, которые обязательно будут выполняться в процессе построения любого объекта. Следует отметить, что круг общих для всех классов операций невелик. В первую очередь - это создание экземпляра класса и его уничтожение. Эти операции, например, для размещенных на форме объектов выполняются автоматически. Рассмотрим еще одну функцию данного класса. Каждый объект должен содержать некоторую информацию о себе, которая используется приложением и средой разработки. Класс TObject содержит ряд методов, обеспечивающих представление этой информации в потомках. Основное назначение класса TPersistent (в переводе означает: устойчивый, постоянный) заключается в возможности выполнения операции копирования содержимого одного объекта (Source) в другой (Self). Self - специальная переменная в объекте-приемнике. При этом используется метод Assign - простым присваиванием содержимое не копируется, а замещается с уничтожением внутренней структуры объекта-приемника. Класс TComponent используется в качестве основы для создания невизуальных компонентов и реализует основные механизмы, которые обеспечивают функционирование любого компонента. 15


Вслед за классом TComponent в иерархии классов (см. рис. 8) располагается группа из трех классов, которые обеспечивают создание различных визуальных компонентов. Визуальные компоненты — это разнообразные стандартные для Windows и специальные элементы управления. Понятно, что визуальные компоненты должны уметь отобразить себя на экране монитора и реагировать на целый ряд новых событий - реакция на мышь, клавиатуру, движение курсора и т.д. Для этого в них встроен специальный механизм, обеспечивающий взаимодействие компонентов с графической подсистемой операционной среды (GUI). Базовьм для всех визуальных компонентов является класс TControl, который инкапсулирует механизмы отображения компонентов на экране. В этом классе вводится множество новых свойств и методов. Для определения местоположения и размеров визуального компонента введены два свойства, с помощью которых задаются координаты левого верхнего угла: Тор (верхний) и Left (левый), а также два свойства, задающие размеры клиентской области: Height (высота) и Width (ширина). Значения свойств задаются в пикселах. Для определения местоположения компонента используется система координат рабочей области владельца (Owner - владелец) данного компонента, например формы. Имеется и ряд других свойств, определенных в компоненте TControl. Далее в иерархии классов стоит очень важный визуальный компонент TWinControl, который обеспечивает использование в Delphi оконных элементов управления. Главное отличие оконного элемента управления от других элементов - наличие дескриптора окна hWnd. Дескриптор окна - это специальный идентификатор, присваиваемый операционной системой всем объектам, которые должны обладать свойствами окна. Если элемент управ- ч ления имеет дескриптор окна, то он должен уметь выполнять следующие операции: • получать и передавать фокус управления во время выполнения приложения; • воспринимать управляющие воздействия от мыши и клавиатуры; • уметь размещать на себе другие элементы управления. ИСПОЛЬЗОВАНИЕ ПАЛИТРЫ КОМПОНЕНТОВ И ИНСПЕКТОРА ОБЪЕКТОВ

Палитра компонентов — это средство, которое используется для добавления компонентов на форму (рис. 9).

A R

Ы -jej * Рис.9

16

Ш

Размещаются компоненты с помощью мыши — вначале щелчок левой кнопкой на нужном компоненте палитры, а затем - на форме. Палитра компонентов группирует компоненты в соответствии с выполняемыми функциями и отображает каждую из этих групп на отдельной странице. Инспектор объектов — важнейший инстру- 11 Jv-Ct мент для работы с размещенными на форме компонентами (рис.10). Вначале выбирают на форме интерфейсный элемент, требующий редактирования. Далее с помощью страницы свойств (Properties) изменяют его внешний вид и некоторые доступные внутренние характеристики или с помощью страницы событий (Events) выбирают нужные свойства. Каждому компоненту и каждой форме соответствует свой определенный список событий, на которые они могут реагировать. Реакция объекта на какое-либо событие определяется специальной процедурой обработки события, которую программист должен разработать самостоятельно. Перед разработкой данной процедуры необходимо, чтобы среда Delphi сгенерировала заготовку для выбранного события. Для этого с помощью Рис. 10 мыши на странице Events инспектора объектов выбирают нужное событие. После двойного щелчка в поле справа от названия (см. рис. 10) появляется программная заготовка будущего обработчика выбранного события. Далее с помощью редактора добавляется требуемый код. ИСПОЛЬЗОВАНИЕ ГРАФИКИ Программисты стараются придать внешнему виду своих приложений максимум привлекательности, так как 80% информации мозг человека получает по зрительному каналу. Поэтому в Delphi имеются развитые средства для работы с графическими возможностями Windows. ОСНОВНЫЕ ИНСТРУМЕНТЫ

В стандартном графическом интерфейсе Microsoft Windows GDI (Graphics Device Interface включает набор программ, воспроизводящих графику) основой для рисования служит HDC (Handle Device context) - дескриптор контекста устройства - и связанные с ним шрифт, перо и кисть (контекст в данном случае - это стек плюс регистры процессора). Реализованные биб-

СПГГИ (ТУ) ГЛАВНАЯ

17


лиотечные GDI-функции являются аппаратно независимыми, т.е. приложение работает не с физическим, а с логическим устройством, имеющим высокие характеристики. Взаимодействие с устройствами вывода осуществляется с помощью драйверов. В состав библиотеки компонентов (VCL) Delphi входят графические надстройки, назначением которых является обеспечить удобный доступ ко всем свойствам указанных выше инструментов: шрифт (Font), перо (Реп), кисть (Brush). Шрифт Windows определяется классом TFont. В Delphi допускаются только горизонтально расположенные шрифты. Шрифты имеют множество характеристик, основные из которых приведены в табл. 1. Свойство Имя (Name) Стиль (Style) Цвет (Color) Вариант набора символов (Charset) Способ установки ширины шрифта (Pitch) Высота (Height) Размер (Size)

Описание

Таблица 1

Например, Arial Особенности начертания: жирный, курсив, подчеркнутый, перечеркнутый Цвет шрифта Например, кириллица Russian Charset или по умолчанию Default Charset Например, с переменной шириной символа, моноширинный, по умолчанию (определен именем) В пикселах В пунктах

Класс ТРеп инкапсулирует свойства пера GDI. Перо имеет следующие основные характеристики: • стиль (Style) линии (сплошная, пунктирная и т. д.); • толщина (Width) пера в пикселах; • цвет (Color) пера; • идентификатор растровой операции (Mode), определяющей взаимодействие пера с поверхностью изображения. В Windows существуют ограничения на стили линий - пунктирные и штрихпунктирные (psDash, psDot, psDashDot, psDashDotDot) могут быть установлены только для линий единичной толщины. Более толстые линии должны быть сплошными. В Delphi это ограничение также сохраняется. Класс TBrush инкапсулирует свойства кисти - инструмента для заливки областей. Кисть имеет такие характеристики, как цвет (Color) и стиль (Style). Последний определяет фактуру закраски, например сплошную. Шрифт, перо и кисть не мотут использоваться самостоятельно. Они являются составными частями класса TCanvas. Этот класс объединяет в себе "холст", рабочие инструменты (перо, кисть, шрифт), а также набор функций по рисованию геометрических фигур. Свойство типа TCanvas называют канвой. Канва входит в качестве свойства во многие компоненты, в частности, TImage. Изображение на канву компонента TImage можно переносить из файла с помощью свойства Picture. При этом можно загружать растровые 18


изображения (битовые карты, BMP), пиктограммы в виде иконок (файлы ICO), из метафайлов (стандартный формат WMF и расширенный EMF) и из сжатых в формате JPEG изображений (файлы JPG и JPEG). ГРАФИЧЕСКИЕ ДАННЫЕ И ПАЛИТРА

Традиционно графические данные подразделяются на два типа: векторные и растровые. В компьютерной графике векторные данные обычно используются для представления прямых, многоугольников, кривых (или любых объектов, созданных на их основе) с помощью определенных в численном виде контрольных ключевых точек. Программа воспроизводит линии посредством соединения ключевых точек. С векторными данными всегда связаны информация об атрибутах (цвете, толщине линий) и набор соглашений (или правил), позволяющих программе начертить требуемые объекты. Понятие вектор в данном случае определяет отрезок линии. Растровые данные представляют набор числовых значений, определяющих цвета отдельных точек, расположенных на правильной сетке и формирующих образ. Таким образом, растр (bitmap) - это массив точек, заданный с помощью массива числовых значений окраски отображаемого образа. Очень часто числовые значения (три или четыре числа на один пиксел), задающие тот или иной пиксел в растровой картинке, называют просто пикселом. Количество возможных цветов (глубину цвета) пиксела определяет битовая глубина. Однобитовый пиксел может быть одного из двух цветов, четырехбитовый - одного из 16 и т. д. На сегодняшний день, в основном, используется глубина цвета в 24 и 32 бита (возможны варианты в 1, 2, 4, 8, 15,16 бит). Изображения в растровом формате представляют собой набор точек, организованных в виде последовательности строк, называемых строками развертки. Значения в формате представления (например, записанные в файл) точек растра обычно упорядочены таким образом, чтобы их легко можно было отобразить практически на любом растровом устройстве. Чтобы передать цвет, нужно задать несколько значений (обычно три), определяющих интенсивность каждого из основных цветов (цветовых каналов), которые смешивают для получения составных цветов. Конкретный цвет представляет собой точку в цветовом пространстве. Наиболее распространенным способом передачи цвета является модель RGB. В этой модели основньми являются цвета: красный (Red), зеленый (Green) и синий (Blue). Цвет задается посредством RGB-триплета. Каждый пиксел физически определяется триадой веществ. Обычно используются оксиды: цинка (зеленый), меди (синий), европия (красный). При сохранении цветовых данных в файле триплет записывают в виде чисел. Если глубина 2»

19


цвета равна 24 битам, например, по 8 бит на каждый основной цвет, то диапазон значений цвета для каждого байта равен 0...255. Причем, чаще всего, принимают, что пикселные значения (0, 0, 0) соответствуют черному цвету, (255, 255, 255) - белому, (255, 0, 0) - красному, а, например, триплет (127, 127,127) определяет один из вариантов серого. Таким образом, теоретически данная схема описывает около 16,7 млн составных цветов. Довольно часто набор цветов, который задается в файле, отличается от того, который может быть отображен на конкретном устройстве. Задача согласования наборов цветов обычно решается программой визуализации, которая осуществляет преобразование цветов, заданных в файле, в цвета устройства отображения. Если количество цветов, заданных пикселными значениями в файле, меньше количества цветов, которое может отображать устройство вывода, то проблем обычно не возникает, в противном случае происходит потеря данных, и могут появиться нежелательные эффекты. В любом варианте программа визуализации должна выполнить определенную работу, сопоставляя наборы цветов источника и адресата. Значения триплета непосредственно передаются устройству отображения. При записи этих значений в файл может осуществляться кодировка с помощью палитр. Палитра представляет собой одномерный массив цветовых величин. С помощью палитры цвета задаются косвенно посредством указания их позиций (индексов) в массиве. Это позволяет сократить объем файлов. Например, 4-битовые пикселные данные могут использоваться для представления изображений, содержащих 16 цветов. Определив для этих 16 цветов палитру, данные в файл записываются в виде чисел из диапазона 0...15. Программа визуализации, читая данные из файла, перекодирует их в соответствии с палитрой в триплеты, которые и используются для окрашивания точек на устройстве вывода. При глубине цвета 24 и выше палитра не используется. В настоящее время часто используется глубина цвета в 32 бита. Данный режим работы графического устройства похож на режим в 24 бита, но в нем добавлен четвертый 8-битовый канал (альфа-канал), содержащий дополнительную информацию о прозрачности каждой точки. Существует режим pfCustom, предназначенный для реализации программистом собственных конструкций. НЕКОТОРЫЕ ОБЩИЕ СВОЙСТВА КОМПОНЕНТОВ Рассмотрим некоторые общие свойства компонентов и формы. Эти свойства задаются с помощью страницы Properties инспектора объектов или программным путем. Align — задает тип выравнивания компонента внутри формы (по умолчанию равно aINone). 20


Caption — заголовок компонента (надпись на компоненте). Могут использоватья русские буквы. Color — цвет фона для формы или компонента. Цвет можно задавать при помощи обозначений, например зеленый - clGreen, или шестнадцатеричных констант (зеленый - $008000). Height - вертикальный размер в пикселах. Enabled - если это свойство равно True, то компонент реагирует на действия пользователя (сообщения мыши, клавиатуры), иначе эти сообщения игнорируются. Hint — задает текст (подсказка), который будет отображаться при нахождении курсора в области компонента. Left — горизонтальная координата левого угла компонента относительно формы в пикселах. Для формы это значение указывается относительно экрана дисплея. Name - задает имя компонента (идентификатор), которое будет использоваться в программе. ParentColor — если значение этого свойства равно True, то компонент будет отображаться цветом родительского компонента, иначе используется собственное свойство Color. TabOrder - задает порядок получения компонентами на форме фокуса при нажатии клавиши Tab. По умолчанию этот порядок определяется последовательностью размещения компонентов на форме. Для изменения этого порядка необходимо явно изменить значения свойства TabOrder компонентов. Следует отметить, что компонент, значение TabOrder которого равно 0, получает фокус при отображении формы. Использование свойства TabOrder зависит от значения свойства TabStop. TabStop - это свойство позволяет указать, может ли компонент получать фокус или нет. Тор - вертикальная координата левого верхнего угла интерфейсного элемента относительно формы. Visible - определяет видимость компонента. Width - горизонтальный размер интерфейсного элемента или формы в пикселах. Сведения о перечисленных выше свойствах помогут сориентироваться при первом знакомстве со средой Delphi. СОХРАНЕНИЕ ПРОЕКТА Сохранение файлов проекта необходимо для любой среды программирования и Delphi не является исключением. Так как проект в Delphi включает в себя большое число файлов, необходимо сохранять каждую группу файлов 21


отдельно. В простом проекте таких групп две: группа файлов программыпроекта и группа файлов модуля и формы. Перед сохранением проекта необходимо дать имена всем группам файлов. Для нового проекта среда Delphi дает файлам имена по умолчанию файлам группы программы-проекта присваивается имя Projectl.*, где символ * означает расширение файла; файлам группы модуля и формы присваивается имя Unitl.*. При сохранении файлов эти имена можно изменить. Сохранение файлов программы-проекта осуществляется командой Save Project as пункта меню File. Сохранение файлов модуля и формы осуществляется командой Save as пункта меню File. Можно использовать команду Save All, с помощью которой последовательно вызываются команды сохранения файлов модуля и формы и программы-проекта. При сохранении файлов не надо забывать о задании нужного каталога, в котором будут храниться файлы. ПОСТРОЕНИЕ ПРОСТЕЙШЕГО ПРОЕКТА Создадим простейший проект, который просто демонстрирует последовательность действий при работе в среде Delphi. Данный проект будет иметь одну форму (рис. 11), на которой расположены два интерфейсных элемента (две простые кнопки). ' ыло Forml i v » ним hj II; > стьммин n;« -hi I. 'х1 77-.* •-ХГ ШГ ' - • "V . i й '' 1

Операция I

• •

уЗшоя ,

. Л

' !:Л

Рис. 11

Первая кнопка будет использоваться для запуска приложения и выполнения каких либо действий (кнопка "Операция"), а вторая - для закрытия программы (кнопка "Выход"). Для построения этого проекта в среде Delphi необходимо выполнить следующие действия: 1) открыть новый проект: File - New Application; 2) изменить заголовок (Forml) главной формы, выбрав в инспекторе объектов свойство Caption. Имя формы (Name) изменять не надо; 3) сохранить проект на диске, например, с помощью команд меню File Save All, выбрав требуемый каталог и присвоив осмысленные имена файлам; 4) поместить на форму первую кнопку. Для этого в палитре компонентов открыть страницу Standard, мышью выбрать компонент Button и щелкнуть 22


левой кнопкой. Поместить курсор мыши на форму и щелкнуть левой кнопкой - объект Button 1 появится на форме; 5) изменить название этой кнопки. Для этого проделать то же самое, что и для формы. В инспекторе объектов выбрать Caption (для Buttonl) и изменить название «Buttonl» на «Операция»; 6) добавить вторую кнопку и изменить ее название (Caption) «Button2» на «Выход» - повторить п. 3 и 4. Name, как и в п. 5, изменять не надо; 7) с помощью мыши выровнять расположение этих кнопок на форме и, если надо, изменить их размер. Добавить ко второй кнопке обработчик события: нажатие левой кнопки мыши (или клавиши Enter), с помощью которого будет осуществляться выход из программы. С помощью мыши пометим вторую кнопку и на странице событий в инспекторе объектов выберем событие OnClick. Выполним двойной щелчок левой кнопкой мыши в поле справа от имени OnClick - появится редактор кода с программной заготовкой требуемого обработчика событий (рис.12). Между begin и end необходимо вставить имя процедуры (подпрограммы) Close; Ё Unitl. pas O i l ? irj «• • 4 л LM1 1 I _ , -.....-^v..-: — , г -Г I procedure TForrril. Button2 С lick (Sender: TObject) ; a «begin 1 1 ' end; 7

a

i

Ш Ш f i f es ^Diagram / Л .

Щт Рис. 12

i

8) запустить программу на выполнение, например, командой Run в пункте меню Run или с помощью клавиши F9, или с помощью зеленого треугольничка на панели инструментов. Проверить работоспособность кнопки «Закрыть» (сделать щелчок мышью на этой кнопке). Произойдет выход из приложения и возврат в среду Delphi; 9) повторно сохранить проект на диске, чтобы записать все выполненные действия. Это можно сделать и при выходе из Delphi. ПОНЯТИЕ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ Исключительная ситуация - это изменение условий работы приложения, которые мотут быть причиной невозможности дальнейшего его выполнения. Любое взаимодействие с операционной системой на предмет получения ресурсов: места на диске, памяти, открытие файла — может завершиться неудачно. Любое вычисление может закончиться делением на нуль или переполнением. Платой за надежную работу программы в таких условиях слу23


жит введение многочисленных проверок, способных предотвратить некорректные действия в случае возникновения нештатной ситуации. Обычно для корректного выхода из ситуации нужно отменить целую последовательность действий, предшествующих неудачному действию. Выход из нештатной ситуации предложен разработчиками Windows - это структурированная обработка исключительных ситуаций, которая реализована и в Delphi. Структурированная обработка исключительных ситуаций в Delphi осуществляется с помощью множества специальных классов. Предком всех таких классов является объектный тип (класс) Exception. Чтобы выделить имена классов, связанных с исключительными ситуациями, первой буквой их имен является буква Е. Имена других классов начинаются с буквы Т. В Delphi исключительные ситуации представлены в форме объектов. При возникновении исключительной ситуации в памяти создается объект обработки исключительной ситуации. Для удаления этого объекта должен быть создан обработчик исключительной ситуации. Для работы с такими объектами разработаны специальные конструкции языка, которые позволяют написать дополнительный код обработки возможных исключительных ситуаций. Приложение обычно имеет один глобальный обработчик и несколько локальных специализированных процедур-обработчиков, реагирующих на соответствующие конкретные исключения. Если исключение не имеет своего локального обработчика, то приложением вызывается глобальный обработчик.

ВВЕДЕНИЕ В OBJECT PASCAL Благодаря языку Object Pascal среда Delphi стала системой быстрой разработки приложений. Разработчику приложений необходимо потрудиться, чтобы язык Object Pascal стал настоящим инструментом в его руках. Необходимо изучить способы организации данных, научиться создавать собственные типы, объединять группы операторов в эффективные многократно используемые модули, объединять наборы типов данных, процедур и функций, образуя внешние библиотеки, которые совместно используются различными программами. СТРУКТУРА ПРИЛОЖЕНИЯ Выше было отмечено, что приложение - это "ловушка" событий плюс обработчики этих событий. Обработчик события реализует какие-либо действия. Самым простым событием является нажатие на клавишу клавиа��уры 24


или кнопку мыши. Более сложные события формируются системой Windows или текущим приложением. Для того чтобы обеспечить требуемую реакцию на то или иное событие, необходимо разработать интерфейс пользователя. Пользовательский интерфейс — это некоторая графическая среда, через которую передаются события внутрь приложения. Простейший интерфейс состоит из меню. Более сложные интерфейсы включают в себя различные кнопки, панели, диалоговые окна и т.п. Для разработки интерфейса служит палитра компонентов, которая содержит более 100 различных элементов. Внешний вид интерфейсных элементов может задаваться программно или с помощью инспектора объектов. Приложение обеспечивает решение всей задачи и по мере необходимости обращается к средствам модулей. Модули содержат отдельные элементы - это типы данных, константы, переменные, подпрограммы и др., которые необходимы для выполнения каких-либо операций. Модули фактически выступают в качестве библиотек этих элементов. В простейшем случае приложение может состоять из одной программы-проекта (используется очень редко). Приложение кроме модулей может иметь в своем составе динамические библиотеки (DLL). Программа-проект, модули и DLL называются программными единицами. Рассмотрим, из каких основных частей состоит программная единица, т.е. какова структура различных программных единиц. СТРУКТУРА ПРОГРАММЫ-ПРОЕКТА

Программа-проект определяется заголовком. Заголовок состоит из зарезервированного слова Program и имени программы, например Program P r o j e c t l ; Uses Forms, U n i t l i n ' U n i t l . p a s ' {Forml}; {$R *•RES} begin Application.Initialize; Application.CreateForm(TForml, Application.Run; end.

Forml);

Имя программы совпадает с именем файла, в который она записывается. Непосредственно менять имя программы-проекта не следует - необходимо просто сохранить ее с другим именем. Текст программы-проекта очень редко приходится корректировать — Delphi создает его автоматически. Текст программы-проекта записывается в файл Projectl.dpr. Данная программа использует встроенный модуль Forms и модуль Unitl, который должен разработать программист. В фигурных скобках можно задавать комментарии, или директивы. Признаком директивы является наличие 25


символа $. Используемая в данном случае директива SR читает файл ресурсов и подключает их к проекту. Вместо символа * в данном случае будет подставлено имя Projectl. Программа-проект стандартно вызывает три метода класса TApplication: Initialize, CreateForm и Run. СТРУКТУРА МОДУЛЯ

Модуль является программной единицей для хранения элементов программирования. Модули бывают двух типов: • стандартные, заранее созданные разработчиками Delphi и включенные в среду программирования; • модули разработчика, создаваемые программистом. Модуль состоит из следующих частей: • заголовок (Unit); • интерфейс (interface); • исполнительная часть (Implementation); • секция инициализации (Initialization); • секция завершения (Finalization); • ключевое слово end с точкой. Особенности этих частей заключаются в том, что каждая из них может присутствовать только один раз, их последовательность только такая, как указано выше. Секции инициализации и завершения могут отсутствовать. Заголовок начинается с ключевого слова Unit, за которым следует имя. Имя модуля используется как имя файла, в который записывается текст программы. Delphi по умолчанию присваивает имя Unitl. В дальнейшем это имя можно изменить при сохранении файла. Интерфейс модуля - часть модуля, доступная для других программных единиц. Интерфейс начинается с зарезервированного слова Interface. Заканчивается интерфейсная секция началом исполнительной части. Исполнительная часть модуля начинается с ключевого слова Implementation, а завершается либо началом секции инициализации, если она есть, либо зарезервированным словом end. В этой секции определяются все процедуры и функции, методы классов и обработчики событий, объявленные в интерфейсной секции. Дополнительно могут содержаться определения программных элементов, которые не объявлены в интерфейсной части. Эти процедуры и функции являются недоступными для других программных единиц и используются для выполнения каких-либо внутренних действий в данной секции. Секция инициализации содержит операторы, которые выполняют при запуске программной единицы какие-либо вспомогательные действия: открываются файлы, инициализируются переменные и т. д. Выполняются эти

26


операторы только один раз. Начинается эта секция ключевым словом Initialization. Эта секция необязательна. Секция завершения содержит операторы, которые выполняются при завершении приложения. Они исполняются один раз. Обычно в этой секции удаляются какие-либо временные файлы, записывается какая-либо информация и т.п. Начинается эта секция с ключевого слова Finalization. Эта секция необязательна, однако, она должна присутствовать, хотя бы пустая, если есть секция инициализации. Примером встроенного библиотечного модуля является модуль Forms, который содержит методы, позволяющие построить форму. Ниже приводится пример модуля: Unit Unitl; Interface Use s {Подключаемые модули} Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s ; Type {Объявления типов используемых переменных} TForml = c l a s s ( T F o r m ) Editl: TEdit; Buttonl: TButton; Button2: TButton; procedure ButtonlClick(Sender: TObject); procedure Button2Click(Sender: TObject); end; var {Объявления переменных} Forml: TForml; F: T e x t F i l e ; S: S t r i n g ; Implementation {$R *.DFM} {Загрузка файла ресурсов формы} procedure TForml.ButtonlClick(Sender: TObject); begin

S:=Editl.Text;

{Чтение строки S}

Editl.Text:='' ; end; procedure TForml.Button2Click(Sender: TObject); begin Close; end; Initialization AssignFile(F, ' M y f i l e . t x t ' ) ; Rewrite (F); {Открытиефайла} Finalization

WriteLn (F, S ) ;

{Запись в файл строки S} 27


CloseFile(F); end.

{Закрытие файла}

Текст этой программы записывается в файл Unitl.pas, автоматически создается файл ресурсов для формы, который будет иметь имя Unitl.dfm. Данная программа открывает файл Myfile.txt и записывает туда строку S, которая вводится с клавиатуры, используя объект Editl. Структура DLL будет описана далее. При запуске приложения на выполнение сначала формируется выполняемый файл. Этот процесс делится на две стадии. Во-первых, компилятор транслирует исходный текст кода программы и формирует двоичный объектный код (в частности, создается файл Unitl.dcu для откомпилированных файлов Unitl.pas и Unitl.dfm в данном случае). Во-вторых, компоновщик связывает полученный объектный код со встроенными библиотечными объектными кодами модулей. В результате создается исполняемый файл, в данном случае Projectl.exe. ПРИМЕР ПРИЛОЖЕНИЯ 1

Создадим приложение, выполняющее какие-либо "полезные" действия. Для этого продолжим работу над проектом, форма которого представлена на рис. 11. Пусть приложение будет выполнять сложение двух чисел: s = а + Ь. Создадим форму, приведенную на рис. 13.

Рис. 13

В данном проекте использовались два новых компонента TLabel и TEdit (по сравнению с рис. 11). Компонент TLabel позволяет отобразить статический, нередактируемый текст. Используя этот компонент, задают заголовки для других интерфейсных элементов. Текст заголовка вводится с помощью инспектора объектов (свойство Caption). Если свойство AutoSize равно "True", то размер компонента динамически изменяется в зависимости от размера текста. Для того чтобы текст располагался в нескольких строчках, нужно свойству WordWrap задать True. Компонент TEdit понадобился для создания полей ввода. С помощью этого компонента строятся строки редактирования - прямоугольные окна, в 28


которых возможен ввод и редактирование текста. TEdit — однострочный редактор. Этот редактор содержит свойство Text, с помощью которого можно передавать данные внутрь приложения. Числа вводятся в виде текста, полученный текст программно преобразуется в числа с помощью функций преобразования. Окончание ввода осуществляется путем передачи ф о ^ с а другому компоненту, используя нажатие клавиши Tab. Если свойству' Readonly задать значение True, то можно запретить ввод данных в данное поле ввода и использовать компонент TEdit для вывода результатов расчетов. В качестве события, инициализирующего выполнение сложения, выберем нажатие на первую кнопку Buttonl (кнопка "Расчет"). Выберем подходящее встроенное для кнопки Buttonl событие - событие OnClick. В рассматриваемом примере используются Labell, Label2, Label3, Editl, Edit2, Edit3, Buttonl, Button2. Так как вводятся числа, то первоначально в свойстве Text интерфейсных элементов Editl и Edit2 нужно удалить с помощью инспектора объектов имеющийся текст. Далее необходимо задать свойство TabOrder для всех элементов - оно задает порядок "обегания" всех объектов на форме с помощью клавиши Tab. После запуска приложения фокус должен бьггь на Editl. Поэтому необходимо задать свойство TabOrder для элемента Editl равным 0. Для Edit2 свойство TabOrder необходимо задать равным 1, Buttonl - 2, Button2 — 3. Далее запрограммируем обработчик события на нажатие кнопки " Расчет". Д ля этого в редакторе кода необходимо "вызвать" заготовку требуемого обработчика. Заготовка события OnClick в редакторе кода будет сгенерирована, если выполнить двойной щелчок с помощью левой клавиши мыши на элементе Buttonl. Между begin и end нужно написать код обработки события, выполняющий следующее: • взять строку E d i t l . Text и преобразовать ее в число; • взять строку E d i t 2 . Text и преобразовать ее в число; • выполнить сложение этих чисел; • полученный числовой результат преобразов��ть в текст и этот текст поместить в свойство E d i t 3 . Text для отображения на экране дисплея. Примем, что в данном случае будем работать с целыми числами. Решим проблему ограничения ввода только цифр. Этого можно достичь, если задать обработчик событий OnKeyPress. Этот обработчик обрабатывает каждое нажатие клавиши на клавиатуре при вводе текста (в данном случае чисел). Таких обработчиков нужно два: для Editl и Edit2. Вначале сформируем обработчик OnKeyPress для Editl. Для этого необходимо выполнить следующее: мышью выбрать Editl на форме, в инспекторе объектов перейти на страницу событий (Events), выбрать OnKeyPress и выполнить двойное нажатие левой кнопкой мыши в поле справа от названия события. Появится редактор кода, в котором будет заготовка нужного обработчика. Введем текст: 29


If not (key in ['0'..'9']) then Key:=#0;.

Этот оператор "отсеет" все символы, кроме цифр. Фактически этот обработчик исполняет роль фильтра, заменяя введенные нецифровые символы на самый первый символ (#0) из таблицы ASCII, который не имеет графического начертания. Теперь сформируем обработчик OnKeyPress для Edit2. Так как он будет таким же, что и для Editl, поступим следующим образом. Выберем Edit2, перейдем на страницу событий в инспекторе объектов, мышью выберем OnKeyPress (справа в этой строчке будет стрелка). Мышью нажмем на стрелку - появится в окошке строка EditlKeyPress. Выберем мышью эту строку - один обработчик события OnKeyPress будет работать на два элемента Editl и Edit2. Ниже приведен полный текст модуля Unitl. unit U n i t l ; interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s ; type TForml = c l a s s ( T F o r m ) {Список используемых интерфейсных элементов} Label1: TLabel; E d i t l : TEdit; Label2: TLabel; Edit2: TEdit; Label3: TLabel; Edit3: TEdit; Buttonl: TButton; Button2: TButton; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure Button2Click(Sender: T O b j e c t ) ; procedure EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; end; var Forml: T F o r m l ; implementation {$R * . d f m } {Программные коды используемых обработчиков событий} procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; begin Edit3.Text:=XntToStr(StrToInt(Editl.Text)+ StrToInt(Edit2.Text)) ; end; procedure TForml.Button2Click(Sender: TObj e c t ) ; begin Close;

30

Graphics,


end; procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ • 0 • . . 1 9 ' , 1 - • , # 8 ] ) t h e n key:=#0; end; end.

В обработчике OnKeyPress фильтр был расширен, чтобы появилась возможность ввода следующих дополнительных символов:'-', #8 (Backspace). Самым замечательным в приведенной выше программе является то, что разработчик должен набрать всего три строки — это строки между begin и end в трех обработчиках событий. ОПИСАНИЯ ПРОГРАММНЫХ ЭЛЕМЕНТОВ Любая программа состоит из программных элементов и операторов управления этими элементами. К программным элементам относятся константы, типы, переменные, процедуры, функции, объекты и некоторые другие. К операторам управления относятся следующие операторы: присваивания, построения разветвлений в расчетах, построения циклических вычислений и др. С помощью объявления в программу вводится какой-либо программный элемент. Ему присваивается имя и, возможно, начальное значение. Object Pascal является строго типизированным языком. Это означает, что каждому программному элементу до первого применения необходимо задать его тип, чтобы сообщить компилятору, к какого вида сущностям относится объявленный программный элемент. Эта сущность определяет, какие операции можно применить и как эти операции должны интерпретироваться. ПРОГРАММНЫЕ ЭЛЕМЕНТЫ И АДРЕСА ПАМЯТИ

Каждый программный элемент занимает какую-либо область памяти. Компилятор неявно заменяет каждое имя определенным адресом в памяти. Иногда имя определяет не сам элемент, а "нечто в памяти". В этом случае имя представляет собой явно указываемый адрес размещения некоторого программного элемента. Такой программный элемент называется ссылкой, или указателем. Таким образом, имена могут представлять или программные элементы или адреса размещения этих элементов. ОБЛАСТИ ВИДИМОСТИ

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


например, в процедуре кого-либо обработчика событий (такое имя часто Edit3.Text:=IntToStr(s); называют локальным - Sender в примере 1), область видимости простираand; • ется только в блоке процедуры. Имя, описанное, например, в интерфейсной секции модуля (такое имя называют глобальным — TForm, Forml в примере ИСПОЛЬЗОВАНИЕ Г Л О Б А Л Ь Н Ы Х П Е Р Е М Е Н Н Ы Х В П Р И М Е Р Е 1 1, имена в подключаемых модулях, например в Forms), имеет область видимости, простирающуюся на весь модуль. Области видимости иногда задают Как можно заметить из сравнения двух приведенных выше вариантов с помощью ключевых слов Private, Public, Protected. >ешения примера 1, применение локальных переменных привело к невольному изменениию программного кода. Еще один вариант решения можно ПРАВИЛА ЗАПИСИ И М Е Н юлучить, используя глобальные переменные. Их применение изменяет труктуру приложения - добавляются новые обработчики событий. Имя (идентификатор) состоит из последовательности букв и цифр. Глобальные переменные (var a,b,s:integer;) объявляются в интерПервый символ должен быть буквой. Символ подчеркивания "_" считается е с н о й секции модуля Unitl (пример 1). Значения глобальным переменным буквой. Количество символов в имени может быть достаточно большим, но i и b присваиваются на уровне работы соответствующих компонентов, в значимыми являются первые 63 символа. Буквы в верхнем и нижнем регист- ьанном случае Editl и Edit2. Это означает, что при выходе, например, из ре неразличимы в имени, однако, для удобочитаемости программ следует лемента Editl (при потере фокуса элементом Editl) необходимо присвоить использовать прописные и строчные буквы. начение соответствующей числовой переменной. Такое присваивание можю выполнить с помощью обработчика OnExit. Таким образом, структурно ВРЕМЯ Ж И З Н И ИДЕНТИФИКАТОРОВ ,риложен ие дополняется двумя обработчиками EditlExit и Edit2Exit. Ниже |редставлены программные коды этих обработчиков: Если программист не указал иного, то программный элемент, например локальный, создается, когда встречается его объявление, и уничтожается, procedure TForml.EditlExit(Sender: TObject); когда его имя выходит из области видимости. Элементы с глобальными hegin a:=StrTolnt(Editl.Text); именами создаются и инициализируются автоматически при объявлении или ind; с помощью инспектора объектов. Иногда они создаются не автоматически, а явно в программе (очень часто один раз) и "живут" до завершения програм- rocedure TForml.Edit2Exxt(Sender: TObject); >=gin мы. Если элемент создан явно программистом, то при выходе из области b:=StrToInt(Edit2.Text); видимости его явно необходимо уничтожить, иначе можно непроизводи- nd;. тельно занять всю память. Соответственно в обработчике Buttonicixck необходимо убрать трочки вычисления значений переменных а и Ъ. Учитывая еще, что переИСПОЛЬЗОВАНИЕ Л О К А Л Ь Н Ы Х П Е Р Е М Е Н Н Ы Х В ПРИМЕРЕ 1 генная s объявлена глобально, перепишем код рассматриваемого обработРассмотрим другие варианты решения примера 1. Вначале исследуем ика событий: понятие "локальных переменных", введенное выше. Использование ло- irocedure TForml.Buttonicixck(Sender: TObject); кальных переменных, в данном случае, повышает "наглядность" программ- egin s:=a+b; ного кода. Для реализации этой идеи достаточно изменить один обработчик Edit3.Text:=IntToStr(s); нажатие на кнопку Buttonl (рис. 13): ad;.

procedure TForml.ButtonlClick(Sender: TObject); {объявление локальных переменных} var a,b,s:integer; begin a:=StrToInt(Editl.Text); b:=StrToInt(Edit2.Text); s:=a+b;

32

ТИПЫ Тип в простейшем случае определяет вид конкретно представляемой в амяти информации, например переменных, констант. В более широком мысле тип есть конкретное представление некоторой концепции (понятия), -4758

33


применяемой к тем или иным данным. Например, имеющийся тип doubli1е*«менных и констант. Переменная есть программный элемент, который его операциями +, -, *, / и встроенными математическими функциями в< 1(0жет в программе принимать различные значения, в отличие от констант, числения логарифма, синуса и т. д. обеспечивает ограниченную, но конкро оторые в программе сохраняют постоянные значения. Имеются следующие группы простых типов: ную версию математического понятия действительного числа. Язык пр< граммирования имеет множество встроенных типов, а также предоставляв • целые; инструменты построения собственных типов. Собственный тип создаст* • логические (булевы); для того, чтобы дать специальное и конкретное определение понятия, кит • символьные; рому среди встроенных типов ничто не отвечает, например, построить о • перечни; сутствующий в Object Pascal тип, позволяющий работать с комплексным • интервальные (диапазоны); числами. Хорошо выбранные типы делают программу более четкой и корт • вещественные; кой. • дата-время. Итак, каждое имя в программе имеет ассоциированный с ним тип. Эл Среди простых типов перечень и интервальный тип определяются протип определяет: граммистом, а остальные встроенные. Первые пять типов образуют группу • структуру программного элемента, т. е. ту или иную интерпретаци ак называемых порядковых типов. памяти; Порядковый тип характеризуется важной отличительной особенностью. • множество допустимых значений; •Саждое значение порядкового типа (основное) имеет ассоциированное с ним • совокупность возможных операций с ним. фугое значение, представляющее собой порядковый номер: для первого В Object Pascal можно выделить следующие типы: яачения — 0, для второго - 1 и т.д. Порядковый номер целого значения ранен самому значению. Для порядковых типов существуют встроенные стан• простые; шртные операции выполнения некоторых действий (табл. 2). • структурированные; • указатели; Операция Выполняемые действия • классы; Low(T) Минимальное значение типа T • вариантный тип. High(T) Максимальное значение типа Т Самую обширную группу представляют структурированные тищ Ord(X) Порядковый номер значения X Структурированные типы данных определяют наборы однотипных или раз Pred(X) Возвращает предыдущее значение для X Succ(X) Возвращает следующее значение для X нотипных компонентов. Типы компонентов образуются из других тип»] Dec(X) Уменьшает X на единицу данных. Можно выделить следующие структурированные типы: Inc(X) Увеличивает X на единицу • массивы; • строки; ЦЕЛЫЕ ТИПЫ • записи; • множества; В программных элементах целых типов память интерпретируется в ви• файлы. е информации, представляющей собой целые числа (табл. 3). Задание типа тому или иному элементу в программе необходимо для т го, чтобы компилятор мог выделить соответствующую память и установи! Тип Диапазон значений Физический формат (байга) Shortlnt -128... 127 1 механизм доступа к самому элементу и его компонентам. ПРОСТЫЕ ТИПЫ Простой тип определяет множество значений программного элемент! структура которого представляет собой единственную величину. Прост* типы (как и другие типы) часто используются в программе для задания ш 34

Byte Smalllnt Word Longlnt LongWord Int64 Integer Cardinal

0 .255 -32 768.. 32 767 0... 65 535 -2 147 483 648.. . 2 147 483 647 0...4 29463967 295 2< -' 2 1 Зависит от процессора, стандартно занимают 4 байта

1 2 2 4 4 8

35


i Целые типы подразделяются на физические и общие. Физические тип занимают строго определенный объем физической памяти. Общие типы Ь teger и Cardinal метут занимать различный объем памяти, в зависимости < типа микропроцессора и операционной среды таким образом, чтобы доси галась максимальная эффективность. Целые числа могут записываться как десятичной, так и в шестнадцатеричной системе счисления. В последне случае перед числом ставится знак $, а допустимый диапазон значений буде $00000000 ... SFFFFFFFF. Целые типы можно складывать, вычитать, умножать. Кроме того, можц выполнять действия, представленные в табл. 4. Операция ABS(X)

XdivY XmodY ODD(X) SQR(X) SQRT(X)

Результат

Таблица 4

Модуль X Нахождение целой части от деления Вычисление остатка от деления Проверяет на нечетность аргумент X Возводит аргумент X в квадрат Извлекает квадратный корень из аргумента X

Физические типы следует применять, когда в первую очередь важщ именно диапазон значений, зависящий от занимаемой памяти, и сам объе) памяти. Тип Integer является обобщением всех знаковых целых чисел, Cardinal - всех беззнаковых. Целый тип является порядковым типом, поэтому с ним возможны one рации, приведенные в табл. 2. СИМВОЛЬНЫЕ типы Смысл символьного типа очевиден при выводе символов на экран ил принтер. Обычно символьные типы задают некоторые стандартные cxeMi кодирования и декодирования информации для обмена символьными да! ными. Символьная схема должна воспроизводиться так же, как ее определи ет операционная система. Существуют две схемы: набор 8-битовых симво лов, известный как расширенный (extended) ANSI-стандарт (American Na tional Standards Institute - Американский национальный институт стандар тов), и схема, соответствующая международному стандарту UNICODE j представлящая набор 1б-битовых символов, в котором первые 256 симв4 лов совпадают с символами, определенными ANSI-стандартом. В Delphi определены два физических символьных типа и один общий! Два физических типа ANSIChar и WideChar реализуют соответственн ANSI-стандарт и UNICODE-схему. Общий тип, именуемый Char, обычн соответствует типу AnsiChar. Каждый символ имеет свой собственный по рядковый номер. Номер символа можно извлечь с помощью функцм Ord(C), где С — какой-либо символ. 36

Значения переменным символьного типа присваивают, записывая тот или иной символ в апострофах (например, с := 'А';) или используя порядковый номер того или иного символа (например, С := #65;, где 65 порядковый номер символа А в наборе символов; : = - знак присваивания). ЛОГИЧЕСКИЕ Т И П Ы

Минимальной единицей измерения информации является бит, два значения которого: 0,1 — можно использовать для записи информации о чемлибо, представляющем одно из двух: да (истина, true, 1) или нет (ложь, false, 0). Информация о чем-либо, что можно представить как истина или ложь, хранится в переменных логического или булевого типа. Для совместимости с языком С++ или другими языками программирования и различными операционными системами имеются три физических булевых типа. Предпочтительней использовать общий Boolean (1 байт) тип. Другие типы именуются следующим образом: ByteBool (1 байт), WordBool (2 байта) и LongBool (4 байта). Переменным булевого типа можно присваивать только значения true или false. Если память, соответствующая логической переменной типа Boolean, содержит 0, то ее значение равно false, если в памяти содержится 1, то значение логической переменной равно true. В данном случае тип Boolean задает приведенную выше схему интерпретации содержимого памяти. Переменные физических булевых типов интерпретируют содержимое памяти по-другому: если в памяти содержится 0, значение переменной равно false, при любом другом значении значение переменной равно true. Однако функция Ord всегда возвращает значение 1, если переменная равна true, и 0, если значение переменной равно false. Таким образом, порядковый номер false равен 0, a true — 1. С помощью булевых типов выполняются сравнения. Это их основное назначение. ТИП ПЕРЕЧЕНЬ

Запишем пример объявления типа перечень (иногда этот тип неправильно называют перечисляемым или перечислимым): Type EnumType = (Valuel, Value2, Value3);.

Обычно данные типа перечень содержат дискретные значения, представляя их не числами, а именами (идентификаторами). Простейшим встроенным типом перечень является тип Boolean, который можно определить следующим образом: Type Boolean = (false, true);.

Единственное, что отличает тип Boolean от типа перечень, это множество операций сравнения, которые встроены в рассматриваемый тип. Тип перечень - это просто список уникальных имен или идентификаторов, зарезер37


вированных программистом для каких-то конкретных целей. Например,] можно создать список цветов: Type MyColor = (Red, Green, Blue);. В этом типе объявлены четыре идентификатора: MyColor обозначает соответствующий тип, Red, Green, Blue — значения этого типа. Учитывая, что тип перечень относится к порядковому типу, можно извлечь порядковый номер с помощью функции Ord для каждого значения (Ord(Red) = О, Ord(Green) = 1, Ord(Blue) = 2). ИНТЕРВАЛЬНЫЙ Т И П

Интервальный тип (диапазон) позволяет объявить переменные, содержащие информацию из некоторого заданного поддиапазона для какого-то исходного базового типа. Базовый тип должен быть порядковым, кроме типа диапазон. Синтаксис объявления интервального типа имеет следующий вид: Туре D = MinValue. .MaxValue;, где константы: MinValue и MaxValue опреде-

ляют соответственно минимальное и максимальное значения среди всех возможных значений для данного типа. Например: Type Day = 1..31;. В Е Щ Е С Т В Е Н Н Ы Й ТИП

С помощью вещественных типов (табл. 5) вводятся переменные, содержащие числа, которые состоят из целой и дробной частей. Таблица 5 Тип

Single Double Extended Comp

Currency Real

Пороговое значение

1,5-lff45 5-Ю324 3,6-lff4951 1 0,0001 5-10"314

Максимальное no модулю значение

3,4-1038 1,7-10308 1,1-104932 263-l 9,2-10" 1,7-10м*

Число значащих цифр

7-8 15-16 19-20 19-20 19-20 15-16

Размер в байтах

4 8 10 8 8 8

Все вещественные типы различаются пороговым (минимальным положительным или отрицательным) и максимальным по модулю значениями. Все типы могут представлять число 0. Если при выполнении вычислений образуется число, меньшее порогового, то в память запишется 0 - произошло исчезновение порядка. Особенность вещественных типов заключается в том, что они представляют некоторое подмножество математических вещественных чисел, которое можно представить в формате с плавающей запятой и фиксированным числом цифр, т. е. точное представление чисел не всегда возможно. Удивительно, но такое простое число, как 0.1, записывается с некоторой погрешностью, пусть очень небольшой. Из-за этого представление чисел с плаваю38


щей запятой оказывается неудобным, когда сохраняется и печатается фиксированное число десятичных разрядов, например при вычислениях с денежными величинами. Для частичного решения этой проблемы введен тип Сошр, фактически представляющий собой целые числа, т.е. вещественные числа, дробная часть которых равна 0. Некоторые операции с этим типом приведены в табл. 6. Таблица 6 Операция

Abs(X) ArcTan(X) Cos(X) Ехр(Х) Frac(X) Int(X) Ln(X) Pi Round(X) Sin(X) Sqr(X) Sqrt(X) Trunc(X)

Результат

Абсолютная величина X Арктангенс X Косинус X Экспоненциальная функция от X Дробная часть X Целая часть от X (результат — вещественный) Натуральный логарифм от X Число Пи (3,1416...) Ближайшее к X целое (результат — целый) Синус X Квадрат X, т. е. Х*Х Квадратный корень от X Отсекает дробную часть X (результат - целый)

Тип Currency (денежный) введен для удобства работы с денежными суммами. Физически значения в соответствии с этим типом записываются как целые числа в память того же объема, что и в случае типа Сотр. Однако в ЭТОТ тип встроено автоматическое представление чисел в виде рублей и ��опеек. Компилятор не забывает умножать их на 10000 перед записью в память и делить их на 10000 при выполнении вычислений - таким образом, обеспечивается точность расчетов с четырьмя знаками после запятой. Т И П ДАТА-ВРЕМЯ

Стандартный тип дата-время (TDateTime), определенный в модуле System, совпадает с типом Double. Например, если определена константа типа дата-время 35065.75, то это число означает 1 января 1996 г., 18.00. Способ преобразования этого числа в дату-время встроен в тип. Целая часть числа типа TDateTime задает число дней, прошедшее с 30 декабря 1899 года, а дробная часть - прошедшую долю текущих суток. Символьное представление типа дата-время определяется установками Windows. Различные функции преобразования находятся в модуле SysUtils. ВЫРАЖЕНИЯ Многие операторы содержат в своем составе конструкции, называемые выражениями. Выражение - это синтаксическая единица языка, опреде39


лякмцая способ вычисления некоторого значения. В выражении выполняются некоторые действия над параметрами. Рассмотрим множество операций и параметров, используемых в выражениях. КОНСТАНТЫ

Константами называют параметры, значения которых не могут изменяться в процессе выполнения программы. В выражениях возможно двоякое использование констант: • непосредственное использование значения константы; • использование имени константы. В качестве констант могут использоваться значения различных типов. Возможно не только десятичное представление числовых констант, но и шестнадцатеричное, например SF5, где первый символ указывает, что далее следует шестнадцатеричная константа. Для задания имени константы используется следующее объявление: const

<имя>

=

<значение>; — например, const

а

=

5;

s

=

' Pascal';. В описании const можно использовать константные выражения (например, const b = Рх/4;). Все вычисления константных выражений выполняются на стадии компиляции. Код программы не увеличивается при использовании поименованных констант. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ

Данное название не совсем удачное, так как типизированная константа представляет собой фактически переменную. Название это появилось потому, что типизированная константа определяется ключевым словом Const, например Const Maximum: integer = 100;. Н е будет ошибки,

если вместо данной строки записать: Var Maximum: integer = 100;. Таким образом, типизированные константы используются для задания начального значения переменных. Использование типизированных констант зависит от директивы компилятора {$J}. Если она выключена {$J-}, типизированные константы становятся просто константами, если она включена {$J+} (установлено по умолчанию) - типизированная константа представляет собой инициализированную переменную. ПЕРЕМЕННЫЕ

Переменными называют параметры, значения которых могут изменяться в процессе выполнения программы. Запишем следующие примеры объявления переменных: 40


Var

a: integer; x,y: single; operat: (plus, minus, mult, divide);.

В данном случае объявлены четыре переменных: а - целого типа, ж, у — вещественного типа, operat — типа перечень. ОПЕРАЦИИ

Можно выделить следующие операции: • арифметические; • логические; • операции над битами; • операции отношения; • операции со строками; • операции с множествами; • операции над объектами; • адресная операция Арифметические операции для вещественных чисел определяются с помощью символов: +, *, /. Для целых чисел определены пять операций: +, -, *, div (деление нацело), mod (остаток от деления), например, A div В; {если А = 7, а В = 3, то результат равен 2], с mod 3 ; {если С = 10, то результат равен 1}. Логические операции применяются к операндам логического типа, как, например, приведено в табл. 7 для унарной операции отрицания not. Таблица 7 Значение операнда X

Результат операции not X

True False

False True

Результат вычисления логической операции получается также логического типа. Определяются эти операции специальными таблицами. Рассмотрим три бинарные логические операции: and — логическое И (конъюнкция); or - логическое ИЛИ (дизъюнкция); хог - ИСКЛЮЧАЮЩЕЕ ИЛИ (табл. 8). Таблица 8 True False True False

X

Значения операндов

True True False False

Y

j

XandY True False False False

Результаты операций

XorY True True True False

X»rY False True True False

Существуют 2 варианта вычисления логических выражений, задаваемые директивой {$В}: полное {$В+} и укороченное {SB-}. Полное вычисление означает, что выполняются все запрограммированные действия, даже если 41


на промежуточном этапе становится известным значение всего выражения. При укороченном вычислении все расчеты прекращаются, как только становится известным значение заданного выражения. Например, если в выражении (A and В) or (х xor Y) значение (A and В) равно true (когда операция or дает true, см. табл. 8), то при укороченной форме вычисления прекращаются без расчета второй части (х xor- Y) исходного выражения, так как полученное значение true уже не изменится. Операции над битами выполняются над целыми числами. Существуют следующие битовые операции: • not X - инверсия всех битов числа X; • X and V - побитовое логическое умножение чисел X и ¥ ; • X or Y - побитовое логическое ИЛИ над X и Y; • X xor Y - побитовое исключающее ИЛИ над X и Y; • X shr J - сдвиг содержимого числа X на J бит вправо; • X shl J - сдвиг числа X на J бит влево. Операции сдвига часто используются для умножения и деления целых чисел на 2, 22, 2 3 и т. д. Например, пусть X = 3 (в двоичной форме 0011). Выполним X shl 1 - 0011 сдвигаем влево на 1 разряд, получим 0110. Двоичное число 0110 равно 6 в десятичной системе, т. е. выполнено умножение на 2. Операции отношения предназначены для сравнения двух величин. Величины должны бьггь сравнимых типов. Результат сравнения имеет логический тип. Существуют шесть операций: = — равно, < - меньше, <= — меньше или равно, О — не равно, > — больше, >= - больше или равно. Адресная операция @ позволяет определить адрес расположения переменной, типизированной константы, подпрограммы и некоторых других программных элементов в памяти. Остальные операции будут рассмотрены далее. ФУНКЦИИ Функция представляет собой специальную подпрограмму, предназначенную для вычисления какого-либо параметра, исходя из значений ее аргументов. Имеется большое количество встроенных стандартных функций. Возможно создание собственных функций, которые используются наряду со стандартными. Любая функция задается своим именем с указанием после него в круглых скобках перечня аргументов, например sin(X). ПОРЯДОК ВЫЧИСЛЕНИЯ В Ы Р А Ж Е Н И Й

Выражения вычисляются в определенном порядке в соответствии с приоритетами (табл. 9) выполняемых операций. 42


Приоритет

1 2 3 4 5 6

Таблица 9 Операции ( ) - круглые скобки Вычисление функций (5J, not, унарные +, *, /, div, mod. and, shl, shr, as +, or, xor =, o , >, <, <=, >=, is, in

Для того чтобы изменить приоритет выполняемых действий, используются круглые скобки. Действия выполняются чаше всего слева направо. Компилятор в целях оптимизации может нарушить этот порядок. ВИДЫ ОПЕРАТОРОВ С помощью операторов осуществляется контроль за потоком вычислений и проводятся различные манипуляции с данными. Операторами описываются выполняемые программой алгоритмические действия, которые необходимы для решения задачи. Все операторы условно можно разделить на две группы: простые и структурированные. Простыми называют операторы, которые не содержат внутри себя других операторов. Структурированными являются такие операторы, которые состоят из других операторов. К ним относятся: • составной оператор; • операторы условного перехода; • операторы цикла; • оператор над записями; • операторы обработки исключительных ситуаций. Иногда объявления программных элементов называют операторами объявления, которые, в отличие от "настоящих" операторов, являются неисполнимыми. ПРОСТЫЕ ОПЕРАТОРЫ Таких операторов четыре: оператор присваивания, обращение к процедуре, оператор безусловного перехода и пустой оператор. Оператор присваивания является средством изменения содержимого памяти. Синтаксически с помощью этого оператора переменной присваивается значение какого-либо выражения. Записывается этот оператор следующим образом: Y := «выражение»;, где Y - переменная, := - знак присваивания. Например, Х:= 5; Y:= sin(X); С := 'А';. 43


Выражение вычисляется независимо от типа переменной. Однако чтоб присваивание было корректным, необходимо, чтобы тип переменной бы совместим с типом выражения. Например, переменной вещественного тщ] можно присваивать выражения целого типа, но переменной целого тип нельзя присваивать значения выражений вещественного типа. Обращение к процедуре позволяет выполнить вычисления, задаваем и подпрограммой вида "процедура". Как и функция, процедура имеет имя может иметь список аргументов. При вызове вычислений из процедуры за писывается имя нужной процедуры, а затем справа от имени в круглы скобках записывается список параметров-аргументов. Например, ProcName (X, Y);. Существует большое количество встроенных стандарт ных процедур, например Dec(X) и 1пс(Х) (см. табл. 1). Программист може составлять собственные процедуры. Оператор безусловного перехода записывается в следующем виде: GOT< <метка>;. Этот оператор позволяет изменить последовательный поряда выполнения операторов и перейти к выполнению программы, начиная с за данного оператора. Оператор, на который происходит переход, должен быт помечен меткой. Метки бывают двух видов: или какое-либо число 1...999 или обычный идентификатор: А, В25, Point. Все метки должны быть описа ны в объявлении Label: 56, А, В25, Point;. Одной меткой можно по метить только один оператор: Point: * := 2*х;. Использование операто pa GOTO считается плохим стилем программирования. Пустой оператор не выполняет никаких действий и никак не отображается в программе, разве только точкой с запятой. СОСТАВНОЙ ОПЕРАТОР Составной оператор представляет собой совокупность последовательно выполняемых операторов, заключенных в так называемые операторные скобки begin и end, т. е. Begin <Оператор 1>; . . . СОператор N>; End;

Этот оператор, как правило, используется в составе других операторов.

ОПЕРАТОРЫ УСЛОВНОГО ПЕРЕХОДА Операторы условного перехода проверяют, какие операторы далее следует выполнять и, таким образом, строить разветвляющиеся вычислитель44


ные процессы. Сначала рассматривается некоторое условие, а затем, в зависимости от того, ложно это условие или истинно, осуществляется переход на в ы п о л н е н и е тех или иных действий. Существует два оператора условного перехода: IF и CASE. В операторе IF осуществляется выбор одного из двух вариантов расчета. В операторе CASE осуществляется выбор одного из нескольких вариантов расчета. ОПЕРАТОР IF Возможны 2 варианта записи этого оператора: IF <условие> THEN Соператор 1>; IF <условие> THEN Соператор 1> ELSE <оператор 2>;.

Условие ветвления для оператора IF задается логическим выражением. Если <условне> возвращает true, то выполняется соператор 1>. Если сусловие> возвращает false, то соператор 1> не выполняется, а выполняется «шератор 2>, если таковой задан. Например, IF X с О THEN Y: = - X ; IF X > 25 THEN Y: = X + 5 ELSE Y: = A + X;.

В первом операторе присваивание Y: = -X выполняется, если логическое выражение х с о истинно. Во втором случае имеется выбор, какой оператор выполнить. ПРИМЕР ПРИЛОЖЕНИЯ 2

Ввести целое число X и вывести на экран символ, соответствующий этому числу в кодировке ANSI (рис. 14). I

ПРИМЕР 2

Рис. 14

Пусть Xтипа Word, значит, значение Xлежит в диапазоне 0...65535. В таблице ANSI всего 256 символов, они пронумерованы от 0 до 255. Часть этих символов является управляющими (0...31) и не все из них можно увидеть на экране дисплея. Остальные символы (32...255), кроме пробела (сим45


вол 32), имеют графическое представление. С помощью оператора IF задам ная выше ситуация описывается следующим образом: IF (X > 31) AND (X <= 255) THEN <вывод символа> ELSE IF (X <= 31) THEN <вывод 'Управляющий символ'> ELSE <вывод 'Символа нет'>;.

В этом примере используются 2 новых компонента: TPanel и TBitBtn, На форме (см. рис. 14) изображены 7 объектов: 2 метки Labell, Label2, 2 однострочных редактора Editl, Edit2, панель Panell, простая кнопка Button 1 и комбинированная кнопка BitBtnl. Для Panell установлено свойст! во Align равным alBottom, для LabeI2 - свойство WordWrap равно true и Alignment равно taCenter, чтобы название (Caption) записывалось в две. строки и выравнивалось по центру, для BitBtnl свойство Kind выбрано равным bkClose. Элемент управления TPanel представляет собой вдавленный или выпук-J лый (Beveled) прямоугольник, в котором может содержаться несколько элементов управления. Таким образом, этот компонент используется в качестве контейнера других элементов. Выпуклость и вогнутость определяется следующими свойствами: Bevellnner - вогнутый, BevelOuter - выпуклый,' BevelWidth - ширина (глубина). Компонент TBitBtn, называемый комбинированной кнопкой, очень поч хож на TButton. Находится на второй странице Additional палитры компм нентов. Этот компонент вместе с текстом позволяет разместить на себе маI ленький рисунок (в виде пиктограммы), причем позволяет размещать одно-! временно до 4 рисунков. Эти рисунки могут выбираться в процессе выпол-1 нения программы, в зависимости от возникшей ситуации. Рисунок задается с помощью свойства Glyph и должен быть предварительно создан с помощью! любого графического редактора, например, встроенного в Delphi. Рисуном должен представлять собой файл типа BMP. Количество рисунков в файле определяется свойством NumGlyphs. В свойстве Kind существуют встроен-1 ные кнопки с заданными операциями и соответсвующими этим операциям рисунками. В данном случае выберем операцию выхода из обработчика со-] бытий. При задании значений свойств TabOrder нужно учитывать, что объест Panell содержит внутри себя 2 объекта и по отношению к форме все эти, объекты являются одним элементом. Поэтому свойство TabOrder объекта! Editl должно быть равно 0, объекта Panell - 1. Нет необходимости задавать1 значение свойству TabOrder для Edit2. Поэтому следует присвоить связанному с TabOrder свойству TabStop этого объекта значение false. TabOrder у элементов Button 1 и BitBtnl задаются по отношению к контейнеру Panell равными соответственно 0 и 1. Интерфейс разработан. Теперь в соответствии со схемой рис. 1 выберем необходимые обработчики событий. Для объекта Editl следует организовать 46


ввод только цифровых символов, д л я Buttonl и BitBtnl необходима обработка нажатий на эти кнопки. Таким образом, потребуется разработать т р и обработчика событий. Текст программы модуля приводится ниже. unit p r i m 2 ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) {список интерфейсных элементов) Pane11: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Labell: TLabel; Editl: TEdit; Label2: TLabel; Edit2: TEdit; {объявления обработчиков событий} procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; end; var Forml: T F o r m l ; implementation J$R *.DFM) (программный код обработчиков событий) procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ • 0 1 . . 1 9 " , # 8 ] ) t h e n k e y : = # 0 ; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var X:word; begin X := S t r T o I n t ( E d i t l . T e x t ) ; i f (X > 31) a n d (X<=255) t h e n E d i t 2 . t e x t : = C h r ( X ) else i f X <= 3 1 t h e n E d i t 2 . t e x t : = "Управляющий с и м в о л ' e l s e E d i t 2 . t e x t := 'Символа н е т ' ; end; end. ОПЕРАТОР CASE Оператор C A S E имеет с л е д у ю щ и й синтаксис записи: CASE

<селектор>

OF 47


С1: <оператор 1>; C2: <оператор 2>; CN: <оператор N>; ELSE <оператор>; End;.

Условие ветвления - <селектор> - для оператора CASE задается выра жением порядкового типа. С1 ... CN - CASE-константы должны совпала! по типу с селектором, соператор 1> ... <оператор N> — это операторы, и которых должен выполниться только один — тот, у которого соответствуй щая CASE-константа совпадет с селектором. Если не обнаружено ни одной совпадения селектора с какой-либо CASE-константой, выполняется опера тор, записанный после ELSE. Ветвь ELSE может отсутствовать. Тогда, есл| селектор не совпадет ни с одной CASE-константой, оператор CASE буде пропущен, т. е. не выполнится ни одного оператора внутри CASE. Например, VAR

L: integer;

CASE L OF 0: Label1.Caption := 'Число нуль'; 2,4,6,8: Label1.Caption := 'Четкая цифра'; 1,3,5,7,9: Labell.Caption := 'Нечетная цифра'; 10..100: Labell.Caption : = 'Число от 10 до 100'; ELSE Labell.Caption := 'Число L < 0 или L > 100'; End;. ПРИМЕР ПРИЛОЖЕНИЯ 3

Усложним пример 1. Пусть вводятся два числа и знак операции, которую нужно выполнить с этими числами (+, -, /, *). Пусть числа (а, Ь) будут типа Integer. Следует обратить внимание на то, что А/Ъ дает вещественное число. Интерфейс можно представить в виде формы (рис. 15), содержащей следующие объекты: последовательно расположенные Labell, Editl, LabeI3, Edit3, Label2, Edit2; Bevell с элементами Labei4 и Edit4; Panell, на которой расположены две кнопки Buttonl и BitBtnl. Компонент TBevel расположен на странице Additional палитры компонентов. С его помощью на форме можно отображать различные прямоугольные области в виде углублений, рамок или просто линий, что позволяет придавать сегментам форм трехмерный вид. Свойство Shape (очертание) определяет, отображается ли рамка, углубление или линия. Для Bevell установлено значение этого свойства, равное bsSpacer. Компонент Edit3 служит для ввода знака арифметической операции. Для записи знака вводимой операции выберем переменную типа перечень Operat (Operat = (Plus, Minus, Mult, Divide, None)).

48


Рис. 15

Переменные а, Ь, Operat объявим глобально, потому что они будут использоваться для обмена информацией между различными обработчиками событий. Понадобятся следующие обработчики событий: • ограничение ввода с помощью OnKeyPress для Editl, Edit2, Edit3; • присваивание значений глобальным переменным — событие возникает при потере каким-либо элементом фокуса (OnExit) — 3 обработчика; • обработка нажатия на кнопку Buttonl. Для отображения результатов расчета служит объект Edit4. Так как при делении получается вещественное число, для отображения этого числа на экране понадобится функция преобразования этого числа (Z) в строку FIoatToStr(Z). Эта функция позволяет выполнить неформатированное преобразование вещественного числа в строку (кстати, StrToFloat(S) преобразует строку (S) в вещественное число). Для форматированного преобразования используется процедура FormatFloat, которая преобразует вещественное число в заданный вид в соответствии с заданным форматом. Рассмотрим один формат преобразования вещественного числа - '0.00'. Этот формат позволяет представить число на экране с двумя знаками после запятой. Процедура имеет два параметра, например FormatFloat('0.00', Z), где Z - вещественное число. Теперь можно привести текст программы модуля, в котором демонстрируется применение типа перечень. unit p r i m 3 ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panel1: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Bevell: TBevel; 4 — 4758

49


Editl: TEdit; Labell: TLabel; TEdit; Edit2: Label2: TLabel; TEdit; Edit3: TLabel; Label3: Label4: TLabel; Edit4: TEdit; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) procedure Edit3KeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) procedure ButtonlClick(Sender: T O b j e c t ) ; procedure E d i t l E x i t ( S e n d e r : T O b j e c t ) ; procedure E d i t 2 E x i t ( S e n d e r : T O b j e c t ) ; procedure E d ± t 3 E x i t ( S e n d e r : T O b j e c t ) ; end; VarOperat = ( P l u s , M i n u s , M u l t , D i v i d e , N o n e ) ; var Forml: T F o r m l ; a,b: integer; Operat: V a r O p e r a t = N o n e ; implementation {$R *.DFM) procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ • 0 ' . . 1 9 ' , ' - ' , #8]) t h e n k e y : = # 0 ; end; procedure TForml.Edit3KeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin if Length(Edit3.Text) = 0 then begin i f not (key i n [' + ' , ' - ' , ' * ' , 1 / ' , # 8 ] ) t h e n key:=#0; end e l s e k e y : = # 0 ; end; procedure TForml.EditlExit(Sender: T O b j e c t ) ; begin a:=StrToInt(Editl.Text); end; procedure TForml.Edit2Exit(Sender: T O b j e c t ) ; begin b:=StrToInt(Edit2.Text); end; procedure TForml.Edit3Exit(Sender: T O b j e c t ) ; var С:Char; begin if Length(Edit3.Text) > 0 then begin С:=Edit3.Text[1 ] ; c a s e С of ' + ' : Operat:=Plus;

50


1

- 1 : Operat:=Minus; ' * ' : Operat:=MuIt; ' / 1 : Operat:=Divide; end; end e l s e Operat:=None; end; procedure TForml.Buttonicixck(Sender: T O b j e c t ) ; var Z:Real; begin Z 0.0; c a s e O p e r a t of P l u s : Z:=a+b; Minus: Z:=a-b; Mult: Z:=a*b; Divide: i f b=0 t h e n b e g i n E d i t 4 . T e x t : = ' Д е л е н и е на 0 ' ; Exit; end e l s e Z := a / b ; None: b e g i n E d i t 4 . T e x t := 'Операции н е т ' ; Exit; end; end; E d i t 4 . T e x t := F o r m a t F l o a t ( ' 0 . 0 0 ' , Z ) ; end; end.

Необходимо отметить, что обработчик OnKeyPress для Edit3 содержит объявление переменной С символьного типа для записи вводимого знака арифметической операции, так как непосредственное использование строки Edit3. Text в качестве селектора оператора Case запрещено. Кроме того, в этом обработчике с помощью функции Length (S) (длина строки s) проверяется, был ли осуществлен ввод символа. Обработчик событий OnKeyPress для Edit3 усложнен, - разрешается ввод только одного символа. Дополнительные ключевые слова begin и end (составной оператор) в этом обработчике необходимы, чтобы снять неоднозначность использования else - к какому if данное ключевое слово относится. Кроме указанного выше составного оператора, следует обратить внимание на другие составные операторы данной программы. В обработчике ButtonlClick используется процедура Exit для непосредственного выхода из обработчика. Обработчик для Editl одновременно используется и для Edit2, как и в предыдущем примере 2. Кроме того, следует обратить внимание, что, кроме цифровых символов, этот обработчик разрешает ввод символа "минус" для отрицательных чисел. 4»

51


ИСПОЛЬЗОВАНИЕ ENTER В ПРИМЕРЕ 3

Часто при окончании ввода в элементе редактирования и переходе к следующему управляющему элементу оказывается более удобным использование клавиши Enter вместо Tab. Рассмотрим этот вопрос на примере 3. Стандартным подходом в данном случае при обнаружении нажатия клавиши Enter является подход с применением передачи фокуса с помощью метода SetFocus или свойства ActiveControl. Все, что нужно сделать в примере 3, переписать обработчики OnKeyPress в следующем виде: procedure TForml.EditlKeyPress(Sender:

TObject; v a r Key: C h a r ) ;

begin if key=#13 then if Sender = Editl then Forml.ActiveControl:=Edit3 else Buttonl.SetFocus; i f n o t (key i n [ • 0 ' . . ' 9 ' , ' - ' , #8]) t h e n k e y : = # 0 ; end; procedure TForml.Edit3KeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin if key=#13 then Forml.ActiveControl:=Edit2; i f Length(Edit3.Text) = 0 then begin i f n o t (key i n [ ' + ' , ' - ' , ' * ' , ' / 1 , # 8 ] ) t h e n k e y : = # 0 ; end e l s e key:=#0; end; .

Другим подходом является применение процедуры SelectNext, которая может передавать фокус следующему или предыдущему управляющему элементу. Рассматриваемая процедура имеет следующее описание: SelectNext (CurControl:

TwinControl;

GoForward,

CheckTabStop:

Boolean), где CurControl указывает на оконный управляющий элемент, относительно которого выполняется передача фокуса; параметр GoForward определяет направление передачи фокуса: следующий элемент (True) или предыдущий (False); параметр CheckTabStop определяет, нужно ли учитывать значение свойства TabStop управляющего элемента, который должен получить фокус. Применение данной процедуры может оказаться проще, чем предыдущий подход. Например, перепишем первый оператор в обработчике EditlKeyPress примера 3: if key=#13 then SelectNext(Sender as TffinControl, true,true);.

В данном случае Sender имеет тип Tobject, а необходим тип TWinControl, поэтому выполнено явное преобразование типов с помощью операции as. В заключение одно замечание. Чтобы был осуществлен правильный пе52


реход от E d i t 2 к B u t t o n l (а не к E d i t 4 ) , необходимо установить для E d i t 4 свойство T a b s t o p = f a l s e .

Возможен еще один подход обработки нажатия клавиши Enter с помощью SelectNext — можно использовать обработчик OnKeyPress самой формы. Для этого необходимо установить свойство формы KeyPreview = true, чтобы обработчик формы получил сообщение о нажатии клавиш первым.

ОПЕРАТОРЫ ЦИКЛА Операторы цикла используются для программирования циклических вычислительных процессов. Циклический вычислительный процесс представляет собой неоднократно повторяющиеся вычисления при различных значениях исходных данных. Например, пусть требуется построить график Y = f(X) на отрезке [а;Ь] с шагом Н. Для этого необходимо провести циклические, повторяющиеся вычисления в соответствии с табл. 10. Таблица 10

X

А*)

а a+h Ь

Число итераций п—

Y=№

Ь-а

+1

h

Однократное выполнение расчета внутри цикла называется итерацией. Существуют три оператора цикла: операторы For, While и Repeat. ОПЕРАТОР ЦИКЛА FOR Оператор For позволяет организовать выполнение какого-либо другого оператора заранее заданное число раз. Существуют 2 варианта этого оператора: For <управляющая леременная> := <start> to <Finish> do <oneратор>; For <управлякжцая переменная> := <Start> downto <Finish> do <оператор>;.

Управляющая переменная позволяет ограничить количество расчетов заданного оператора, которое зависит от значений выражений < s t a r t > и <Finish>. Выражение < s t a r t > определяет начальное значение управляющей переменной, выражение <Finish> - конечное значение. При каждой итерации управляющая переменная увеличивается на единицу в первом опе53


раторе и уменьшает свое значение на единицу во втором операторе. Как только значение управляющей переменной превзойдет значение <Finish> в первом операторе (станет меньше во втором операторе), цикл прекращается. Выражения <start> и <Finish> должны возвращать значения порядкового типа, соответственно «управляющая переменная> должна также иметь порядковый тип. Например, FOR Х:= 1 ТО 10 DO Y:=sin(X); FOR I:= 10 DOWNTO 1 DO Y:=sin(X);.

Если в самом начале цикла значение <Finish> будет больше, чем значение <start> для первого оператора, или значение <Finish> меньше, чем <start> для второго оператора, то не будет выполнено ни одной итерации. В Delphi скорость выполнения циклических вычислений оптимизирована, поэтому имеют место ограничения на управляющую переменную, которая должна: • иметь порядковый тип; • быть объявлена в том же блоке, в котором помещен цикл, т. е. должна быть локальной. ПРИМЕР ПРИЛОЖЕНИЯ 4

Пусть требуется рассчитать табл. 10 и построить на экране дисплея графику -f(x) = sinfc). Шаг изменения координат хну графи ка зададим в пикселах и пусть, h = 1. В данном случае имеют место два типа графиков: физический и дисплейный. Пусть X, Y,H - координаты и шаг физического графика, x,y,h координаты и шаг дисплейного графика. Прежде чем рисовать график, необходимо выполнить преобразование физических координат в дисплейные (или масштабирование) так, чтобы дисплейный график верно отражал процесс, смоделированный физическим графиком. Физический график начинается в точке х = а и заканчивается в точке х = Ъ. Пусть дисплейный график начинается в точке X = хп и продолжается до точки X — (рис. 16). Выберем следующую схему построения дисплейного графика: • используя заданное h, находим текущее значение х; • рассчитываем Н и находим значение физической переменной X; • определяем Y; • преобразуем У в у, • на экране дисплея отображаем полученную точку (лс, у). Введем масштаб ту и запишем соотношение^ = myY. Так как данное соотношение справедливо для любой точки трафика, необходимо, чтобы оно выполнялось и при значении jVax = т у Ктах (все значения^ будут воспроизводиться на экране). Из этого соотношения можно определить масштаб Шу, 54


у т.е. tny = " " . Максимальное значение Утзх в данном случае для функции sm(X) равно 1, а для дисплейного графика j'max задается, в общем случае, произвольно.

Теперь найдем соотношение между х и X (или между Л и Я, что одно и то же). Примем, чтобы количество точек по оси абсцисс для дисплейного и физического графиков было одинаковым. Чтобы это выполнялось, необходимо согласовать значение шага Н для физического графика с выбранным h для дисплейного графика. Запишем Ь~а _хк-х„ Н ~ h ' h—а ^ Ъ—а • откуда следует Н = h > обозначив тх = , получим требуемое хк-хп хк-х„ соотношение Н = mxh.

55


Для размещения графика понадобится компонент TImage (страница Additional). Форма примера 4 приводится на рис. 17. Какие объекты используются в данном примере, можно найти в тексте программы.

[ J

ПРИМЕР 4

е

~

M

.-: :

ШМ

..

j

^ i

I i

.

А

I.Jtbu J Рис. 17

Компонент TImage используется для рисования графических объектов и геометрических фигур. Полезным свойством компонента TImage является Canvas (холст), которое включает, в частности, свойства Pen, Brush. Канва имеет методы: MoveTo (используется для перемещения невидимого графического курсора в заданную точку) и LineTo (позволяет рисовать линии). Эти два метода используются для рисования графика на канве TImage. Координаты задаются в пикселах, горизонтальная ось направлена слева направо, вертикальная - сверху вниз. Вариант решения задачи приводится на рис. 18. Выбраны следующие размеры для Imagel: Width = 305, Height = 154. Ось абсцисс проведена при значении у0 — 75.

Рис. 18

Текст программы приведен ниже. 56


u n i t prim4; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , Controls, F o r m s , D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; ^ype TForml = c l a s s ( T F o r m ) Panel 1: TPanel; Buttonl: T B u t t o n ; BitBtnl: TBitBtn; Editl: TEdit; Labell: TLabel; Edit2: TEdit; Label2: TLabel; I m a g e l : TImage; Label3: TLabel; Bevel1: TBevel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) procedure ButtonlClick(Sender: T O b j e c t ) ; end; var Forml: T F o r m l ; implementation {$R *.DFM} procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ ' 0 ' . . ' 9 ' , • - ' , 1 . • , # 8 ] ) t h e n k e y : = # 0 ; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var a,b:real; xn, x k , y O , y k : i n t e g e r ; mx,my:real; x,y,ymax:real; i:integer; begin xn:=15; xk:=280; y0:=75; yk:=70; a:=StrToFloat(Editl.Text); b:=StrToFloat(Edit2-Text); ymax:=1.0; { З а д а д и м ц в е т и толщину п е р а ) Imagel.Canvas.Pen.Color:=clBlack; Image1.Canvas.Pen.Width:=1; ( З а к р а с и м ц в е т о м фона предыдущий г р а ф и к ) Imagel.Canvas.Brush.Color:=clWhite; Imagel.Canvas.Rectangle(0,0,Imagel.Width, Imagel.Height) {Рисуем оси координат) Imagel.Canvas.MoveTo(xn,yO);


Imagel.Canvas.LineTo(xk,yO); Imagel.Canvas.MoveTo(xn,yO+yk); Imagel.Canvas.LineTo(xn,yO-yk); {Проверяем, в в е д е н о ли b < a} i f b<=a t h e n b e g i n L a b e l 3 . C a p t i o n : = 'Графика н е т ' ; Exit; end e l s e L a b e l 3 . C a p t i o n : = ' ' ; { У с т а н а в л и в а е м ц в е т и толщину к а р а н д а ш а д л я г р а ф и к а } Imagel.Canvas.Pen.Color:=clRed; Imagel.Canvas.Pen.Width:=2; mx:=(b-a)/(xk-xn); my:=yk/ymax; {Устанавливаем начальную точку графика} Imagel.Canvas.MoveTo(xn,yO-round(my*sin(a))); {Строим г р а ф и к в ц и к л е for} for i : = l t o x k - x n d o b e g i n x:=a+i*mx; y:=my*sin (x); Imagel.Canvas.LineTo(xn+i,yO-round(y)); end; end; end.

В обработчике событий OnKeyPress учтено, что вводятся вещественные числа. Используемая функция Round (Z) преобразует вещественное число z в целое с предварительным округлением. Для того чтобы возможно было неоднократное построение графика, необходимо стирать предыдущий график. ОПЕРАТОР ЦИКЛА WHILE В отличие от оператора For, оператор цикла While используется, когда заранее неизвестно число выполняемых итераций. Этот оператор записывается так: w h i l e «логическое выражекие> do <оператор>;. Итерации выполняются до тех пор, пока логическое выражение остается истинным. Если с самого начала значение логического выражения ложно, то оператор While будет пропущен. ПРИМЕР П Р И Л О Ж Е Н И Я 5

Найти все делители целого числа X, кроме единицы и самого числа. Воспользуемся простейшим алгоритмом. Будем перебирать все делители, начиная с числа 2, и проверять, делится ли нацело заданное число X. Очевидно, последний делитель, который следует проверять, равен половине 58


числа X. Если ни одного делителя не найдено, то число простое. Будем исследовать только положительные числа. Воспользуемся в этом примере компонентом TListBox. Форма с вариантом решения задачи приведена на рис. 19. ПРИМЕР 5 Список делителей 1-йделитель=2 2-й делитель=3 3-й делигель=6 4-й делит ель=1531 5-й делитель=3062 Б-й делитель=4593

r~pS

JUbWM Рис. 19

Интерфейсный элемент TListBox позволяет представить список объектов (свойство Items), чаще всего строк (класс TStrings), из которых можно выбрать один элемент. Если список не умещается на экране, то возможна его прокрутка по вертикали. Список можно представлять не только в один столбец - число столбцов задается свойством Columns. Если Columns > 1, то разрешается и горизонтальная прокрутка элементов. Элементы списка могут, например, динамически добавляться. При этом используется свойство Items с соответствующим методом Add (Items.Add) — добавить. Какой элемент выбран, запоминается в свойстве Itemlndex. Можно задать одновременный выбор нескольких элементов в списке с помощью свойства MultiSelect. Компонент имеет много других разнообразных свойств, что делает его удобным для отображения данных (например, свойство Sorted позволяет представлять список в алфавитном порядке и др.). Ниже приводится программа для примера 5. unit p r i m 5 ; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , Classes,Graphics, C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Bevell: TBevel; Labell: TLabel;

59


E d i t l : TEdit; ListBoxl: TListBox; Label2: TLabel; Label3: TLabel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; end; var Forml: TForml; implementation {$R *.DFM} procedure T F o r m l . E d i t l K e y P r e s s ( S e n d e r : T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n ( ' 0 ' . . • 9 ' , # 8 ] ) t h e n k e y : = # 0 ; end; procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; var x,half,divider:word;

i:byte;

begin Label3.Caption: = ' ' ; ListBoxl.Items.Clear; x:=StrToInt(Editl.Text); h a l f : = x d i v 2; divider:=2; i:=0; While d i v i d e r <= h a l f d o b e g i n i f x mod d i v i d e r = 0 t h e n b e g i n inc(i); L i s t B o x l . I t e m s . A d d ( I n t T o S t r ( i ) + ' - й делитель=' + IntToStr(divider)); end; inc(divider); end; i f i=0 then Label3.Caption:='Число простое'; end; end.

В отличие от оператора цикла For, который автоматически изменяет управляющую переменную и управляет циклом, в операторе While программист должен сам организовать управление, заключающееся в следующем: • выбрать какую-либо управляющую переменную, в данном случае i — счетчик циклов; • присвоить ей начальное значение (например, i : = 0 ; ) ; • в операторе While записать условие продолжения выполнения цикла, например divider <= half; (как только это условие станет равным false, цикл закроется); 60


• внутри цикла осуществлять изменение управляющей переменной на каждой итерации, например inc (i) - увеличить значение i на единицу. ОПЕРАТОР ЦИКЛА REPEAT Оператор цикла Repeat записывается следующим образом: Repeat

<Оператор1>;

<Onepa>ropN>; Until <логическое выражение>;.

В этом цикле выполняются все операторы между Repeat и Ontil, пока логическое выражение не станет истинным. В отличие от оператора while, в котором логическое выражение определяет условие продолжения итераций, в операторе Repeat логическое выражение определяет условие окончания цикла. Независимо от значения логического выражения, хотя бы одна итерация обязательно должна выполниться. Как и в случае с циклом while, программист должен сам организовать управление итерациями в цикле Repeat. ПРИМЕР ПРИЛОЖЕНИЯ 6 Рассчитать с точностью £ сумму следующего ряда: Y" Y V^ V3 '

п

1 2

3

п\ где Х - какое-либо число из диапазона — 1 <= Х < = 1. Точность Е связана с вычислением Y следующим образом. Обозначим X" общий член ряда А = (-1)" +1 ,и = 1,2,.... Суммирование (вычисление Y) п продолжать до тех пор, пока значение а не станет меньше е. Вывести на экран историю расчета, т.е. какое значение на каждой итерации имели величины а и F, в следующем виде: итерация 1 а = у = ; итерация 2 а = у = и т.д. На рис. 20 построен алгоритм расчета суммы ряда. В этом алгоритме введена дополнительная переменная Ъ для отслеживания знака очередного члена ряда. На рис. 21 приводится форма с вариантом расчета. Расположенные на форме компоненты можно определить, исходя из текста программы, который приводится ниже. Для решения данной задачи был использован компонент Т М е т о . Э Т О Т элемент управления служит для получения какого-либо текста пользователя 61


и его отображения на экране. ТМешо называют многострочным редактором (однострочным редактором является компонент TEdit). Доступ ко всему тексту может быть получен через свойство Text. Свойство Lines представляет весь текст в виде соЬ = -1 вокупности строк. Все строки в ТМето пронумерованы, начиная от нуля. Если •i на экране строки полностью не отобраЬ = -Ъх жаются (задана маленькая ширина Width), то с помощью свойства Wordп=п+1 Wrap можно управлять переносом строк. ТМето имеет свойство ScrollBars можно устанавливать полосы прокрутки. а = Ъ/п По умолчанию: ScrollBars установлено равным ssNone. Можно задать центрирование строк с помощью свойства AlignY= Y+ а ment. Свойство Lines содержит методы Add (добавить), Delete (удалить), Insert \а\ < (вставить). 1 Для Memol в инспекторе объектов установлено свойство WordWrap равным true, а свойство ScrollBars ssVertical. Y= О — r ~ п= О

щетьр

10.000002

Итерация 1 Итерация 2 Итерация 3 Итерация 4 Итерация 5 Итерация 6 Итерация 7 Итерация 8

iTsssri Рис. 21

Ниже приведена программа. 62

а=0.25000 ji=0.2500 а=-0.03125I >1=0.2188 а=0.00521 у=0.2240 а=-0.00098 11=0.2230 а=0.00020 11=0.2232 а=-0.00004 11=0.2231 а=0.00001 И 1 2 2 3 1 а=0.00000 1*0.2231


unit p r i m 6 ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , Controls, F o r m s , D i a l o g s , S t d c t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panell: TPanel; Button1: TButton; BitBtnl: TBitBtn; Bevell: TBevel; Memol: TMemo; Labell: TLabel; Label2: TLabel; E d i t l : TEdit; Edit2: TEdit; Label3: TLabel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) procedure ButtonlClick(Sender: T O b j e c t ) ; procedure E d i t l E x i t ( S e n d e r : T O b j e c t ) ; end; var Forml: T F o r m l ; implementation ($R *.DFM} procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n ( ' 0 ' . . ' 9 ' , • - ' , ' . • , # 8 ] ) t h e n k e y : = # 0 ; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var x,y,a,b,eps:real; n:byte; begin Memol.Lines.Clear;

у:=0.0; n:=0;

b:=-1.0; x:=StrToFloat(Editl.Text); eps:=StrToFloat(Edit2.Text); Repeat b:=-b*x; inc(n); a:=b/n; y:=y+a; Memol.Lines.Add('Итерация'+IntToStr(n)+' F o r m a t F l o a t ( ' 0 . 0 0 0 0 0 ' , a)+ ' y=' + FormatFloat('0.0000',y)); Until a b s ( a ) < e p s ; end;

a='

+


procedure TForml.EditlExit(Sender: T O b j e c t ) ; begin if Abs(StrToFloat(Editl.Text))>1.0 then if Application.MessageBox('Введите ч и с л о X ' , ' Ч и с л о / Х / < = 1 ' , MB_OK)=IDOK then E d i t 1 . S e t F o c u s ; end; end.

В приведенной программе обработчик события ButtonlClick написан по алгоритму рис. 20, кроме одного дополнения - внутрь цикла добавлена процедура формирования истории расчета с помощью объекта Memoi. Обработчик EditlExit отслеживает условие: Х п о модулю должно быть не больше 1. В случае невыполнения этого условия вызывается стандартное диалоговое окно объекта Application MessageBox. Синтаксически подпрограмма MessageBox оформлена в виде функции. При вызове функции MessageBox необходимо указать три параметра: текст-подсказка (что надо делать); заголовок диалогового окна; вариант внешнего вида окна (константа мв_ОК указывает, что окно с одной кнопкой). Editl. SetFocus — установить фокус на объект Editl.

ИСПОЛЬЗОВАНИЕ ПРОЦЕДУР BREAK И CONTINUE В циклах For, While и Repeat можно использовать две стандартные процедуры Break и Continue. Процедура Break позволяет досрочно выйти из цикла, не дожидаясь выполнения условия выхода. Процедура Continue позволяет начать новую итерацию цикла, даже если предыдущая не завершена. ПРИМЕР ПРИЛОЖЕНИЯ 7 Пусть с помощью генератора случайных чисел генерируется некоторый рад чисел к в диапазоне -п <= к <— п. Организовать список чисел до первого сгенерированного отрицательного числа включительно. Форма для этого примера приводится на рис. 22. На этой форме в виде трех черных прямоугольничков изображена метка, с помощью которой отображается ситуация, когда не задан диапазон и. Все остальные размещенные на форме компоненты указаны в тексте программы, который приводится ниже. В примере используется новый компонент TComboBox - свернутый список. Этот элемент представляет собой свернутый TListBox. Для того чтобы просмотреть список, необходимо раскрыть его, например, с помощью мыши. Список TComboBox состоит собственно из списка и редактируемого поля. В редактируемом поле может находиться какая-нибудь строка из списка или какая-нибудь строка, которую необходимо поместить в список. Дос64


туп к строке редактирования осуществляется с помощью свойства Text. Доступ к любому элементу списка осуществляется с помощью Items.Strings (свойство Items, Strings задает тип этого свойства), а для указания номера строки i используются квадратные скобки [ i ] . 7

ПРИМЕР 7

v

Г'-' '

If ,, ^

гт «

^Список чисел • »»

J

S:

:

gal lis .

.

r

JШ 1

1

Рис. 22

Генерирование числа осуществляется с помощью функции Random(L), которая генерирует случайные числа от 0 до L-1. Для инициализации генератора случайных чисел используется процедура Randomize. Она инициализирует генератор, т. е. дает точку отсчета для случайных чисел. unit p r i m 7 ; interface uses Windows, M e s s a g e s , S y s U t i l s , c l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d c t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Bevell: TBevel; Labell: TLabel; Label2: TLabel; Editl: TEdit; ComboBoxl: TcomboBox; Label3: TLabel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure E d i t l E x i t ( S e n d e r : T O b j e c t ) ; end; var Forml: T F o r m l ; implementation {$R * . DFM} procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin 5-4758

65


i f n o t (key i n [' 0 ' . . ' 9 ' , # 8 ] ) t h e n k e y : = # 0 ; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var k,i,n:integer; begin if Length(Editl.Text)=0 then E d i t l . T e x t : = ' 0 ' ; if Editl.Text="01 then begin L a b e l 3 . C a p t i o n : = ' Н е т диапазона n ' ; ComboBoxl.Visible:=false; end e l s e b e g i n ComboBoxl.Visible:=true; ComboBoxl.Clear; Label3.Caption:=''; n:=StrToInt(Editl.Text); Randomize; i:=0; While true do begin к : = R a n d o m ( 2 *n + 1) - n ; inc(i); C o m b o B o x l . I t e m s . A d d ( I n t T o S t r ( i ) + ' - e число= 1+ IntToStr(к)); i f k>=0 t h e n Continue e l s e b e g i n ComboBoxl.Text:=ComboBoxl.Items.Strings[i-1]; Break; end; end; end; end; procedure TForml.EditlExit(Sender: T O b j e c t ) ; begin if Abs(StrToInt(Editl.Text))>100 then i f A p p l i c a t i o n . M e s s a g e B o x ( ' В в е д и т е число X ' , ' Число | X | < 100 ',MB_OK)=IDOK t h e n E d i t l . S e t F o c u s ; end; end.

В данном случае используется бесконечный цикл While true.... очередное полученное число к неотрицательно, то осуществляется пере на начало цикла - процедура Break не выполняется. Если значение к отрицательным, то сначала в заголовок интерфейсного элемента Combo заносится последнее значение числа к (так как нумерация строк в спи начинается с 0, последняя строка имеет номер i-1), а затем уже выполня процедура выхода из цикла Break. В данной программе приведен пример использования свойства Visi Если значение этого свойства равно false (ComboBoxl .visible:=false), соответствующий объект не отображается на экране. Чтобы вернуть его и бражение, надо свойству Visible присвоить true. 66


Назначение обработчика EditlExit — вернуть фокус н а ввод числа и, •ели не соблюдается условие примера п < 100. Оформлен он так же, как и Соответствующий обработчик в примере 6.

МАССИВЫ Массив представляет собой фиксированное количество однотипных компонентов, снабженных индексами. Массивы соответствуют, например, векторам (одномерный массив), матрицам (двумерный массив) и др. СТАТИЧЕСКИЕ МАССИВЫ Тип статического массива объявляют следующим образом: туре <Имя> = Array [<"гип индекса>] of <тип элеметов>; (например, Туре Vekt = Array [1. .10] of byte;) .

В данном случае задан одномерный массив (вектор) состоящий из 10 элементов целого типа byte. Отдельные элементы этого массива в программе определяются с помощью индексов, например, если объявлено Var X: vekt;, то в выражениях можно использовать X [ 1 ], X [2 ] , . . . , х [ 10 ]. В квадратных скобках указывается значение индекса, т.е. номер компонента в массиве. Отсчет номеров ведется так, как указано в индексе при объявлении. Пусть объявлено: Var Z: array [-2.-2] of integer;. Это значит заданы элементы z [-2], z [-1], Z [0], Z [1], Z [2] целого типа integer. В общем случае тип индекса может быть любым порядковым типом. Так, в примере Var Y: array [ 'А' . . ' z ' ] of integer; объявлено 26 элементов (по количеству букв в английском алфавите): Y [ 'А' ] , у [ 'В' ], ..., у[ 'Z' ], выделено 26 ячеек памяти, в которые можно записывать данные типа integer, например X [ 'С' ] : =5;. Информацию можно также считывать из этих ячеек памяти: A: =cos (Pi + Х['С']) ;• Индекс можно определять и таким способом: var Y: array [char] of single;, т.е. определено 256 ячеек памяти, в которые можно записывать вещественные числа. Однако имеется ограничение при объявлении массивов: размер памяти, занимаемый одним массивом не должен превышать 2 Гбайта. Так, объявление var X:array[Longlnt] of integer; синтаксически верно, но превышен размер памяти, который должен быть выделен для этого массива (более 2 Гбайт). Аналогичные правила действуют и для двумерных массивов, например объявление var A: array[1..2, 1..3] of double; определяет двумер5*

67


ный массив (матрицу), который содержит 2 строки и 3 столбца (всего 6 эл ментов). Эти элементы записываются в памяти следующим образа А[1,1], А[1,2], А[1,3], а[2,1], А[2,2], А[2,3], т.е. сначала nq

вая строка матрицы, а затем вторая. Этот массив можно определить и так:, array[1. .21 of array[1..3] of real; ИЛИ Type ray[1..3] of real; Var A: array[1..2] of vekt;.

Vekt

=

a]

Разрешается объявлять и использовать многомерные массивы (до сен® мерных включительно). Для массивов можно задавать типизированные константы, т.е. задава начальные значения элементам массива. Например, требуется задать в пам ти значения элементов вектора q = (5.2, 6.0, -3.1, 0.8). Это можно осущес вить следующим образом: Const q:array[1. .4] of real= (5.2,6.0, 3.1,0.8) ;.

Начальные значения для двумерного массива, например матриц ГЗ 7 5] R= , можно задать следующим способом: 8 11 6 Type Matr= array[1..2, 1..3] of byte; Var R:Matr=((3, 7, 5), (8, 11, 6));.

При задании значения индекса для элемента какого-либо массива можц использовать выражение соответствующего типа, указанного при объявл нии. При этом необходимо следить, чтобы значение этого выражения не вь ходило за объявленные границы. Например, для элемента объявленной вьщ матрицы R можно записать R[Succ(i) ,Pred(j) ] (см. табл. 2), необходим лишь, чтобы выполнялись для заданной матрицы R условия: 1 с Succ(i) 2 и 1 < Pred(j) > 3.

Если задан массив символов, то типизированную константу можно oil ределять следующим образом: Const C:array[l. .5] of Char=*abcde' Если два или более массивов имеют один тип, т.е. объявлены с пома одного описания, то эти массивы можно присваивать друг другу. Наприм» если объявлено Type V= array[1..5] of real; Var A,B:V;, т о м о ;

записывать А: =в;. Однако при следующем описании тех же переменных At ВVar А:= array[1..5] of real;

В:= array[l..5] of real; запи-

сывать A: = В; нельзя. ДИНАМИЧЕСКИЕ МАССИВЫ Динамический массив, в отличие от статического, объявляется кш ссылка на некоторый адрес, по которому будут размещаться данные, например, Var

V = array of real;

VI = array of Integer;.

Каждая из объявленных переменных (v или v i ) может иметь значение какого-то адреса. Прежде чем располагать данные, этот адрес переменная 68


плена получить. Память под данные выделяется процедурой SetLength: SetLength(V,5);

SetLength(VI,10)

В данном случае объявлено 5 элементов одномерного массива вещественных чисел v и 10 элементов массива целых чисел v i . Нужное количество памяти выделится процедурой SetLength, эта же процедура присвоит конкретные значения адресов переменным v и v i . Имеются особенности в использовании динамических массивов. Первая — нумерация индекса всегда начинается с нуля. Вторая особенность касается правил выполнения операции присваивания, например, пусть объявлено: .jype Vekt

=

array

of

real;

Var

Vl,V2:Vekt;. Присваивание

vi: =v2; для статических массивов означает, что содержимое памяти VI заменяется содержимым памяти V2 (элементы массива v i получили значения элементов V2). Если учесть, что в случае с динамическими массивами VI и V2 являются ссылками (адресами), адрес VI заменяется на адрес V2. Таким образом, присваивание v i :=V2; привело к тому, что v l и V2 ссылаются теперь на один и тот же участок памяти, т.е. элементы массива VI равны элементам V2, но потерялся адрес VI, соответственно потерялся и выделенный участок памяти, начиная с адреса VI. Освободить память, выделенную под VI, можно, вызвав перед операцией присваивания v i :=V2; процедуру Finalize: Finalize (Vl); или просто присвоив Vl:=nil;. Динамические массивы могут быть и многомерными, например объявив var W : array of array of real;. Далее в программе можно задать размерности массива SetLength (W,5,10) ;. Динамические массивы позволяют, например, создавать треугольные матрицы, имеющие различные размерности индексов. Интерес к таким массивам в вычислительной математике очень большой. Для создания таких массивов сначала нужно задать размерность по первому индексу, например для объявленного выше двумерного массива w так: SetLength (W,3) ;. Это означает, что массив будет состоять из 3 строк. Теперь длину каждой строки зададим отдельно: SetLength (W[0J ,1) ; SetLength(W[lJ ,2) ; SetLength (W[2] ,3);. Если требуется освободить память из-под такого массива, то используется один вызов процедуры Finalize или одно присваивание W:=nil;. Начальный и конечный индексы динамического одномерного массива v можно определять функциями Low (V) (равно 0) и High (V). Так, например, в следующем операторе For i:=Low(V) to High(V) do . . . ц е л е с о образно не указывать непосредственно верхнюю границу для циклических вычислений, поскольку в программе можно неоднократно менять размерность динамических массивов.

69


ПРИМЕР ПРИЛОЖЕНИЯ 8

Задан одномерный массив чисел X = (0,2; 0,7; 1,8; 3,1; 4,2). Получиц таблицу косинусов для этих чисел, т.е. рассчитать cos(0,2), cos(0,7), .. cos(4,2). На рис. 23 представлен вариант решения этого примера.

*

ПРИ МЕР 8 -«С

••

0.2

——

IXL

• Ш1 0.9801

IGPFF

0.7

0.7648

3Зэяемене I i Ш^шМЙ

1.8

- 0.2272

3.1

- 0.9991

1

- 0.4903 . " - J P#«ier

|

*

Д выяад ^ Рис.23

Для решения задачи был использован новый компонент TStrmgGrid (таблица строк), который находится на странице Additional палитры компонентов. Данный компонент представляет собой двумерную таблицу. Число строк задается свойством RowCount, а число столбцов - ColCount. Размеры одной ячейки таблицы на экране задаются свойствами DefaultColHeight i DefaultRowYVidth в пикселах. Компонент может использоваться для имитации чего-либо похожего на таблицу. Доступ к отдельной ячейке таблицы осуществляется с помощью свойства Cells[i,j], где i — номер столбца, j - номер строки. Отсчет номеров столбца и строки начинается с нуля. Часть строк и столбцов, используемых в таблице, можно зафиксировать. Тогда они будут недоступны пользователю. Для этого нужно задать свойство FixedRows и FixedCols (фиксированное число строк и столбцов). Фиксированные колонки и строки закрашиваются в другой цвет. Используются они для заголовков. С помощью свойства goEdit можно запретить или разрешить редактирование ячеек таблицы. В данном примере для StrlngGridl установлено: ScrollBars = ssNone, RowCount = 6, ColCount = 3, FixedRows = 1, FixedCols = 1, GoEdit - false. Ниже приводится программа для данного примера. unit p r i m 8 ; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , c o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s , G r i d s ;

70


type TForml = c l a s s ( T F o r m ) panell: TPanel; Button1: TButton; BitBtnl: TBitBtn; StringGridl: TStringGrid; procedure ButtonlClick(Sender: T O b j e c t ) ; end; Forml: T F o r m l ; var implementation { $R *.DFM} procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; const n=5; x : a r r a y [ l . . n ] of r e a l = ( 0 . 2 , 0 . 7 , 1 . 8 , 3 . 1 , 4 . 2 ) ; i:integer; var begin f o r i : = l t o n do StringGridl.Cells[0,i]:=IntToStr(i)+" элемент'; StringGridl.Cells[1,0]: =' x'; StringGridl.Cells[2,0]: =' cos(x)1; f o r i : = l t o n do b e g i n StringGridl.Cells[1,i]:= FormatFloat(* 0.0',x[i]); StringGridl.Cells[2, i] := FormatFloat(' 0.0000',cos(x(i]));

end; end; end.

СТРОКИ В Delphi существует 5 типов строк: • SbortString - короткая строка; • AnsiString - длинная строка; • WideString - длинная строка в кодировке UNICODE; • String - универсальная строка; • PChar - строка, оканчивающаяся нулевым кодом (#0), введена для совместимости с другими языками программирования (такие строки используются в С++ и Windows). Переменная PChar — это ссылка на строку. Строки типа PChar размещаются в динамической памяти. Динамическая память - это свободная память, которая остается после загрузки программ и данных. Программный элемент 71


получает ее в процессе выполнения программы, а не на этапе компид (статическая память). Переменная типа PChar — это ячейка памяти, содер; щая адрес строки. По этому адресу записываются необходимые данньц Компилятор выделяет под переменную PChar всего 4 байта, а сама стра которая размещается по адресу, записанному в эти 4 байта, может быть бой длины. Если строки не существует, а переменная PChar объявлена, то | эту переменную необходимо записать nil (пустой адрес). Переменные типа AnsiString содержат динамические строки символов» ANSI-кодировке. Соответственно переменные типа WideString содержат динамические строки символов в кодировке UNICODE. Термин "динамиче.1 ские" означает, что переменные указанных типов, как и тип PChar, являюта I адресами, т.е. ссылками на память с соответствующим типом данных. ShortString - это фактически массив: array [0. .255] of char. Память в данном случае выделяется в 256 байт, а строка размещается динамически, т.е. через адрес. Символы строки содержатся в байтах с первого по 255. В байте с номером 0 содержится символ, порядковый номер которого представляет собой число фактически имеющихся в строке символов. Данный тип ShortString введен для совместимости с более ранними версиями языка Pascal. Строка String в зависимости от директивы компилятора {$Н} может совпадать с AnsiString ({$Н+} установлено по умолчанию) или с ShortString({$H-}). Для строки String можно задавать максимальное количество символов с помощью следующего объявления: var str: string [25];, т.е. строка sti может содержать не более 25 символов и в памяти она занимает 26 байт (не считая "накладных расходов"). Доступ к строке может осуществляться посимвольно, так как все символы в строке проиндексированы. В строке PChar символы нумеруются начиная с 0, в остальных строках - начиная с 1. Например, пусть объявлено: var strl: String; str2: PChar; И выполнены присваивание Strl:='Pascal' ; str2:= 'Pascal' ; тогда str[l] представляет символ

' P ' . a s ^ I l ] - символ va'. Co строками возможны операции конкатенации (сложения) и сравнения. Складываются строки, используя знак '+'. При сравнении строк действия выполняются слева направо в соответствии с ANSI-кодами отдельных символов. Например, код А меньше, чем а. Если строки разной длины, то короткая строка дополняется справа, причем код, который участвует в дополнен^ строки, меньше кода любого символа, существующего в ANSI-кодировке. Если в операциях участвуют строки разного типа, то очень часто необходимо явное преобразование типов. Для выполнения различных операций над строками существует множество подпрограмм (табл. 11). 72


Операция

"C^pOS,

|еп

)

j^pthl'S) Pos(substr, S) «ptl .enrihlS, newlen) "sSngOfChar(Ch, Count)

TSST

ilppeiCase(S)

Таблица11 Описание Возвращает подстроку длиной len символов из строки S начиная с символа номер pos Возвращает фактическую (динамическую) длину строки S Возвращает позицию первого вхождения подстроки substr в строку S Задает новую длину newlen строке S Возвращает строку, заполненную символом Ch в количестве Count Возвращает строку без начальных и конечных пробелов Возвращает строку с прописными буквами

ПРИМЕР ПРИЛОЖЕНИЯ 9

Провести исследование различных видов диалоговых окон вывода сообщений. Для отображения сообщений существует пять основных диалоговых окон. ShowMessage отображает статические сообщения. Это диалоговое окно отображает в центре экрана строку текста и кнопку "ОК" (рис. 24). Вызывается посредством использования процедуры ShowMessage(S), где S - строка типа String. ShowMessagePos (рис. 25) предназначено для тех же целей, что и ShowMessage. При вызове, кроме строки текста, процедуре необходимо передать экранные координаты X и У расположения этого окна. Р prim 9

Рис. 24

Рис. 25

В отличие от предыдущих диалоговых окон, MessageBox (рис. 26) позволяет задавать две строки: заголовок окна и поясняющий текст. Программно вызов оформлен в виде функции. Эта функция возвращает то значение, на какой кнопке щелкнул пользователь. Число отображаемых кнопок может быть одна или более и задается с помощью аргумента, представляющего собой набор предопределенных констант. Передаваемые в функцию строки заголовка и текста должны иметь тип PChar. Если эти строки типа String, то необходимо использовать преобразование типов, записывая Р = =PChar(S), где Р типа PChar, a S - String.

73


MessageDlg (рис. 27) отличается от MessageBox тем, что заголовок этого окна предопределен. Information

Рис. 26

Рис. 27

Функция MessageDlg имеет 4 аргумента: первый передает текст в виде строки типа String, второй — вариант Warning заголовка, третий — какие кнопки будут отображаться, четвертый позволяет организовать связь с файлом подсказки Help. Как и в случае предыдущего окна, fie'r/ функция возвращает, на какой кнопке щелкнул пользователь. Окно MesРис 28 sageDlg располагается в центре экрана. MessageDlgPos (рис. 28) является вариантом MessageDlg с той лишь разницей, что этому виду диалогового окна необходимо передавать координаты X и У его расположения на экране. Форма примера 9 представлена на рис. 29.

Рис. 2 9

Для реализации локального меню выбора варианта диалогового окна использовался компонент TRadioGroup - группа взаимосвязанных переключателей TRadioButton. С помощью свойства Items создается список взаимоисключающих альтернатив, в данном случае из пяти строк, для каждой из которых создается переключатель. При выборе мышью (или стрелками) того или иного переключателя в списке фиксируется строка и ее номер, который позволяет в 74


программе обеспечить выполнение того или иного кода. Выбранный номер записывается в свойстве Itemlndex. Нумерация переключателей задается от нуля. Переключатели можно располагать не только в один, но и в несколько столбцов. Число столбцов задается в свойстве Columns. С помощью свойства Caption можно задавать заголовок всей группе взаимозависимых переключателей. Строки меню заносятся в список. Вызывая в инспекторе объектов свойство Items, можно набрать пять строк, а значение Itemlndex установить, например, равным 3. Для данного компонента необходимо создать обрабочик OnClick. Текст программы приводится ниже. В данном примере имя формы (Name) выбрано MainForm, соответственно порождающий эту форму класс имеет имя TMainForm. unit P r i m 9 ; interface uses S y s U t i l s , Windows, M e s s a g e s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , E x t C t r l s , B u t t o n s ; type TMainForm = c l a s s ( T F o r m ) RadioGroupl: TRadioGroup; Bevell: TBevel; Label1: TLabel; BitBtnl: TBitBtn; procedure RadioGrouplClick(Sender: T O b j e c t ) ; end; var MainForm: TMainForm; implementation ($R *.DFM} procedure T M a i n F o r m . R a d i o G r o u p l C l i c k ( S e n d e r : T O b j e c t ) ; const sde="Демонстрация ' ; s b i = ' Б ы л о выбрано ' ; var T h e T e x t , T h e C a p t i o n : S t r i n g ; S: S t r i n g [ 6 ] ; W: Word; X, Y: I n t e g e r ; begin c a s e R a d i o G r o u p l . I t e m l n d e x of 0: S h o w M e s s a g e ( s d e + ' S h o w M e s s a g e 1 ) ; l:begin X := 5 0 ; Y := 60; ShowMessagePos(sde+'ShowMessagePos',X,Y); end; 2:begin T h e T e x t := ' Т е к с т - п о я с н е н и е к M e s s a g e B o x ' ; T h e C a p t i o n := sde+'MessageBox ' ; i f Application.MessageBox(PChar(TheText), PChar(TheCaption),MB_DEFBUTT0N1 +

75


MB_ICONEXCLAMATION + MB_OKCANCEL) = IDOK then ShowMessage(sbi+'OK1) else ShowMessage(sbi+'Cancel'); end; 3:begin W := M e s s a g e D l g ( s d e + ' M e s s a g e D l g 1 , m t l n f o r m a t i o n , [mbYes, mbNo, m b l g n o r e ] , 0 ) ; c a s e W of mrYes: S := ' Y e s ' ; mrNo: S := ' N o ' ; m r l g n o r e : S := ' I g n o r e ' ; end; {case} S h o w M e s s a g e ( s b l + S) ; end; 4 .-begin X := 50; Y := 7 5 ; W := M e s s a g e D l g P o s ( s d e + ' M e s s a g e D l g P o s 1 , m t W a r n i n g , m b A b o r t R e t r y I g n o r e , 0 , X, Y ) ; c a s e W of mrAbort: S := ' A b o r t ' ; mrRetry: S := ' R e t r y ' ; m r l g n o r e : S := ' I g n o r e ' ; end; (case} S h o w M e s s a g e ( s b i + S) ; end; end; {case} end; end.

В программе при объявлении строки S задана максимальная длина присваимого ей значения, равная 6 символам. Аргумент функции MessageDlg, заданный в квадратных скобках, означает, что с помощью конструктор! множества передается некоторое заданное множество.

ЗАПИСИ (ОБЪЕДИНЕНИЯ) С помощью зарезервированного слова "record" в единой структуре можно объединить данные разных типов. Общий синтаксис объявления такого объединения следующий: Туре <имя типа> = Record <Список полей>: <тип>; <Список полей>: <-гип>; End; .

76


Поля записи (или объединения) могут быть любого типа, число их не ограничено. Например, туре

co mplex

= record Re, Im: real;

(Тип комплексных ч и с е л }

end; = record Year:word; M o n t h : 1..12;

pata

Day:

(Тип - д а т а

рождения}

1..31;

end; person = record Name: string[40]; Age: word; Adres: string[80]; end;.

{Тип -

сведения о сотруднике}

Количество памяти, выделяемой под запись, может быть различным в зависимости от директивы {$А} (выравнивание). Если установлено {$А+}, то поля выравниваются по четным адресам - уменьшается время доступа к переменной в памяти. По умолчанию установлено {$А-}. Перед ключевым словом "record" можно добавить слово "Packed". В этом случае под запись выделяется минимальное количество байт, причем директива {$А} игнорируется. После объявления типа можно объявить переменные или типизированные константы, например, Var Z: Data; X: Complex; Const Birthday:Data=(Year:1974;Month:7;Day:12); Petrov:Person=(Name:'Иван' ;Age:22; Adres:'Новомосковск,Шфа,20,3') ; .

Доступ к полям записи в программе осуществляется следующим способом: Z. Year:=1969; {Z - с е л е к т о р з а п и с и ; Y e a r Z.Month:=7; Z.Day:=13; X.Re: 0.8; X.Im: 2.1; Z.Month:= Z.Day div 5+2;.

поле}.

Тип данных запись может иметь так называемую вариантную часть. Вариантная часть позволяет трактовать по-разному занимаемую полями одну и ту же память. Туре Figure®(Square,Triangle,Circle); G = Record X,Y: integer;

77


Case fig: Figure of Square: (Side: integer); Triangle: (Sidel,Side2,Angle: integer); Circle: (Radius: integer); end;.

В данном случае задан перечисляемый тип для обозначения трех геомет, рических фигур: квадрата, треугольника и окружности. Далее вводится объединение: х, Y - координаты привязки этих фигур на некоторой плоскости, Side (сторона) определяет квадрат, Sidel, Side2, Angle (угол) определяют треугольник, Radius — окружность. Начиная с ключевого слова Case, записывается вариантная часть. Память выделяется одна. Трактовать ее можно i данном случае так: в памяти находится или одна величина Side, или три величины Sidel, Side2, Angle, или одна величина Radius. Когда и как трактовать память, выбирает программист. Вариантная часть может быть только одна, и располагается она в конце записи. Синтаксически Fig есть параметр выбора (указывать его необязательно). Чаще всего вариантную часть записывают так: Case Figure Square: Triangle: Circle:

of (Side: integer); (Sidel,Side2,Angle: integer); (Radius: integer);.

Как замечено выше, память под вариантную часть выделяется одна и та же. В данном случае для Triangle требуется записать три величины типа integer, больше, чем для других фшур. Поэтому под вариантную часть будет выделено 12 байт памяти (по 4 байта на каждую величину). Далее выберем две переменные и запишем данные в эти переменные: Var MySquare: G;

MyCircle: G;

MySquare.Side:= 5;

MyCircle.Radius: = 10;.

Как можно заметить, отличий в записи информации в вариантные поля от обычных полей на этом уровне не существует. Если попытаться в примере выше прочитать значение MySquare. side, то оно будет равно 10, так как последняя запись в общую память была именно такой (MyCircle.Radius: = 10;). ОПЕРАТОР WITH Оператор With часто называется оператором присоединения. Для того чтобы не записывать каждый раз имя селектора, при обращении к полям записи используется этот оператор, например, With Data do begin

78


Year:= 1971;

Month:=5;

Day:=28;

End;.

П Р И М Е Р П Р И Л О Ж Е Н И Я 10

Пусть требуется рассчитать зарплату 3, премию П и итоговую сумму И двум сотрудникам в соответствии с табл. 12. Табельный номер 1

Т

ФИО Иванов И.И. Петров П.П.

Окл., Т.е. 6000 40

Час

Дни

176 184

22 23

Дни граф. 24 24

3

Таблица 12 П И

В данном случае заданы два варианта расчета зарплаты: по окладу (Окл. = 6000) и по часовой тарифной ставке (Т.е. = 40). В первом случае зарплата Дни (3) расчи тывается следующим образом: 3 = Окл. Дни граф. ^Окл. Во втором случае необходимо использовать такие соотношения: 3 = Т.с.-Час; Час < 8-Дни. Указанные ограничения в формулах означают, что, если сотрудник отработал больше, чем по графику, то это сверхурочные, которые оплачиваются отдельно (в примере сверхурочные не учитываются). Используемая во втором случае цифра 8 означает, что задан восьмичасовой рабочий день. Предусмотрена премия не более 150 % от зарплаты, которая выплачивается, если предприятие имеет прибыль. Форма с вариантом решения приводится на рис. 30. /

Пример

10

Иванов И.И. I 6000 Петров П.П. I 40

24 I 5500.00 23 i 24

7360.00

Л 50.00

12650.00 16928.00

Рис. 30

Задача решается в 2 этапа: вначале - ввод информации (можно изменить исходные данные), затем - расчет. Как и в предыдущих случаях, в данном примере использовались новые компоненты. Для указания, есть ли премия в данном месяце, необходим пе79


реключатель. При решении примера 10 использовался переключатель с «е. зависимой фиксацией TCheckBox. Этот стандартный элемент Windows по. зволяет выбрать или отменить определенную опцию. С помощью группц таких переключателей можно выбирать или отключать несколько опций од. новременно. Состояние переключателя (вкл/выкл) содержится в свойстве Checked. Для обработки переключений используется событие On Click. Кнопку можно перевести в третье состояние ("включенное серое") с помо. щью свойства AllowGrayed. Это свойство используется, если переключател» содержит еще ряд вложенных переключателей. Третье состояние переклю. чателя указывает, что вложенные опции не все включены или не все выключены. Узнать, включено ли свойство AllowGrayed, можно с помощью свойства State. Для полноты представления проблемы рассмотрим еще один вид пере, ключателей - переключатель с зависимой фиксацией - TradioButton. Этот вид переключателей в примере 9 уже использовался, но в составе группы. Если выбрано несколько таких переключателей, то они автоматически объединяются в группу и позволяют из множества опций выбрать одну, в отличие от CheckBox. TradioButton также имеет свойство Checked и событие Onclick, свойство AllowGrayed отсутствует. Для представления табличных данных использовалась графическая таблица TDrawGrid, которая, в отличие от TStringGrid, может отображать не только текстовую информацию, но и графическую. TDrawGrid имеет значительно больше возможностей, чем TStringGrid. Например, размеры строк в столбцов можно изменять индивидуально. Можно изменять и цвет ячеек (индивидуально). Внешний вид графической сетки настраивается с помощью следующих свойств: ColCount, RowCount — число столбцов и строк; DefaultColWidth, DefaultRowHeight — высота и ширина по умолчанию; Height, Width - ширина и высота таблицы; FixedColls, FixedRows - фиксированное число столбцов и строк. Индивидуальные размеры строк и столбцов содержатся в свойствах ColWidth и Row Height. Эти свойства являются массивами. Отметим несколько опций свойства Options: GoRowSizing разрешить или запретить изменение размеров строк, GoColSizing — разрешить или запретить изменение размеров столбцов, GoEditing - разрешить или не разрешить редактирование, GoAlwaysShowEditor - если эта опция отключена, то редактор загружается клавишей F2 или щелчком мыши, если эта опция включена, то редактор загружается автоматически. В отличие от TStringGrid, TDrawGrid не имеет свойства Cells (ячейки), поэтому ввод данных и их запись после редактирования нужно организовывать самостоятельно. Для отображения данных необходимо воспользоваться событием OnDrawCell — перерисовка ячеек. При перерисовке ячеек область рисования (в пикселах) задается в виде свойства Rect (прямоугольник). Свойство Red преобразует номера колонки и строки в прямоугольник, заданный в пиксе80


лах. Перерисовка ячейки зависит от свойства State (состояние), которое может иметь следующие значения: gdSelected - ячейка выбрана; gdFocused — н а ячейке находится фокус; gdFixed - зафиксированная ячейка (заголовок). Для рисования в DrawGrid имеется свойство TCanvas. Для задания прямоугольника существует метод CellRect, который в соответствии с аргументами: номером столбца и номером строки - задает координаты области рисования в пикселах. Событие OnSelectCell позволяет индивидуально запретить или разрешить передачу фокуса какой-либо ячейке внутри таблицы. Для организации ввода данных используется событие OnGetEditText. Для фиксирования изменений после ввода (после редактирования) используется событие OnSetEditText. На форме (см. рис. 30) выбраны такие значения для некоторых свойств DrawGrid: ColCount = 9, RowCount = 3, Options: goColSizing = true, goEditing = true, goAlwaysShowEditor = True, DefaultDrawing = False. Последнее свойство может отключать событие OnDrawCell, включая прорисовку по умолчанию. В данном примере необходимо реализовать несколько обработчиков событий: • OnClick -запуск рисования ячеек; • OnDrawCell - перерисовка ячеек; • OnGetEditText - вызов редактора ввода данных в ячейки; • OnSetEditText - запись информации после ввода; • OnKeyPress - ограничение (фильтр) ввода; • OnSelectCell - ограничение доступа к некоторым ячейкам. • При выполнении приложения наступает момент, когда форма рисуется на экране. Ответствен за этот процесс обработчик OnCreate, который вызывается автоматически. Можно дополнять код этого обработчика. В данном случае предусмотрим дополнительный код изменения ширины столбцов для DrawGrid. • С помощью Buttonl предусмотрим обработчик, задающий начало расчета (OnClick). • Для Editl необходим OnKeyPress. • Для Editl необходим OnExit, чтобы контролировать ввод числа (не более 150) по условию задачи. • Для CheckBoxl организуем обработчик при потере фокуса этим элементом (OnExit) и передаче фокуса или Editl или DrawGridl (обход Editl). Для записи результатов ввода и итоговых результатов предусмотрим двумерный массив Field, который должен объявляться глобально. Размеры массива [0...2], [0...8] - 3 строки и 9 столбцов. Для выполнения всех расчетов воспользуемся типом данных запись. Ниже приводится программа. 6 — 4758

81


unit p r i m l O ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , G r i d s , B u t t o n s , S t d C t r l s , E x t C t r l s type TMainForm = c l a s s ( T F o r m ) Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; D r a w G r i d l : TDrawGrid; C h e c k B o x l : TCheckBox; Editl: TEdit; Labell: TLabel; procedure DrawGridlDrawCell(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; R e c t : T R e c t ; S t a t e : T G r i d D r a w S t a t e ) ; procedure DrawGridlGetEditText(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; v a r V a l u e : S t r i n g ) ; procedure DrawGridlClick(Sender: T O b j e c t ) ; procedure FormCreate(Sender: T O b j e c t ) ; procedure DrawGridlSetEditText(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; c o n s t V a l u e : S t r i n g ) ; procedure DrawGridlSelectCell(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; v a r C a n S e l e c t : B o o l e a n ) ; procedure DrawGridlKeyPress ( S e n d e r : T O b j e c t ; v a r Key: C h a r ) ; procedure CheckBoxlExit(Sender: T O b j e c t ) ; procedure EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; end; var MainForm: TMainForm; const F i e l d : a r r a y [ 0 . . 2 , 0 . . 8 ] o f s t r i n g = ( ( ' Т а б Ы ' , •Ф.И.О.','Окл,^,'Час','Дней','Дн.г.','Зарпл', ' П р е м и я И т о г о ' ) , ( Ч ' , ' И в а н о в И.И. ' , ' 6 0 0 0 ' , • 1 7 6 ' , ' 2 2 ' , ' 2 4 ' , ' 0 ' , ' О ' , ' 0 ' ) , ( ' 2 ' , ' П е т р о в П.П.', ' 4 0 ' , 4 8 4 ' , ' 2 3 ' , ' 2 4 ' , '0', '0', '0') ) ; implementation {$R *.DFM) procedure TMainForm.DrawGridlDrawCell(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; R e c t : T R e c t ; S t a t e : T G r i d D r a w S t a t e ) ; var i , j : b y t e ; begin w i t h DrawGridl do f o r j : = 0 t o RowCount-1 do b e g i n i f j=0 then Canvas.Brush.Color:=clBtnFace e l s e Canvas.Brush.Color:=clWindow; f o r i : = 0 t o ColCount-1 do b e g i n Rect:=CellRect(i, j ) ; w i t h C a n v a s , R e c t do T e x t R e c t ( R e c t , L e f t + ( R i g h t -

82


Left-TextWidth(Field!j,i])) Top-TextHeight(Field[j,i])) end; end; end; procedure

div div

2,Top+(Bottom2,Field[j,i]);

TMainForm.DrawGridlGetEditText(Sender:TObject; A C o l , ARow: I n t e g e r ; v a r V a l u e : S t r i n g ) ;

begin Value:=Field[ARow,ACol]; end; procedure TMainForm.DrawGridlClick(Sender: TObject); begin w i t h DrawGridl do b e g i n DrawGridlDrawCell(Sender,0,0,CellRect(0,0), [gdFixed]); DrawGridlDrawCell(Sender,0,1,CellRect(0,1), [gdSelected,gdFocused]); DrawGridlDrawCell(Sender,0,2,CellRect(0,2), [gdSelected,gdFocused]); end; end; procedure TMainForm.FormCreate(Sender: TObj e c t ) ; var i : b y t e ; begin w i t h DrawGridl do for i : = 0 to ColCount-1 do

case i of

0,2: ColWidths[i]:=40; 1: C o l W i d t h s [ i ] : = 9 0 ; 3..5:ColWidths[i]:=36; else ColWidths[i]:=57; end; end; procedure TMainForm.DrawGridlSetEditText(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; c o n s t V a l u e : S t r i n g ) ; begin i f (ARow = 1) a n d (ACol <> 3) o r (ARow = 2) a n d (ACol <> 4) then Field[ARow,ACol]:=Value; i f (ARow = 1) a n d (ACol = 4) t h e n i f Length(Value) > 0 then Field[ARow,3]:= IntToStr(StrToInt(Value)*8); i f (ARow = 2) a n d (ACol = 3) t h e n i f Length(Value) > 0 then Field[ARow,4]:= IntToStr(round(StrToInt(Value)/8)); end; procedure TMainForm.DrawGridlSelectCell(Sender: T O b j e c t ; A C o l , ARow: I n t e g e r ; v a r C a n S e l e c t : B o o l e a n ) ; begin CanSelect:=true;

6*

83


w i t h DrawGridl do i f (ARow = 1 ) o r (ARow = 2) t h e n i f ACol i n [ 0 , 5 . . 8 ] t h e n C a n S e l e c t : = f a l s e ; end; procedure TMainForm.DrawGridlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin w i t h DrawGridl do i f Row > 0 t h e n b e g i n i f (Col = 2) a n d ( n o t ( k e y i n [ 1 0 • . . ' 9 ' , • . 1 , # 8 ] ) ) then key:=#0; i f ( ( C o l = 3) o r ( C o l = 4 ) ) a n d ( n o t (key i n [ ' 0 ' . . ' 9 ' , # 8 ] ) > t h e n k e y : = # 0 ; end; end; procedure TMainForm.CheckBoxlExit(Sender: T O b j e c t ) ; begin i f CheckBoxl.Checked then E d i t l . S e t F o c u s e l s e DrawGridl.SetFocus; end; procedure TMainForm.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ ' 0 ' . . ' 9 ' , ' . ' , # 8 ] ) t h e n k e y : = # 0 ; end; procedure TMainForm.EditlExit(Sender: T O b j e c t ) ; begin if Length(Editl.Text)=0 then Editl-Text:='0.01; i f (StrToFloat(Editl.Text)=0.0) or (StrToFloat(Editl.Text)>150.0) then i f Application.MessageBox('Введите % премии', •Число <= 150',MB_OK)=IDOK t h e n E d i t l . S e t F o c u s ; end; procedure TMainForm.ButtonlClick(Sender: T O b j e c t ) ; type rec=record t b n : word; fio: string[20); okl: Currency; c h , d n , d n g : word ; z p , p r , i t : Currency; end; v a r z : a r r a y [ 1 . . 2 ] of r e c ; i:byte; begin f o r i : = l t o 2 do b e g i n w i t h z [ i ] do b e g i n t b n : = S t r T o I n t ( F i e l d [ i , 0]) ; fio:=Field[i,1];

84


okl:=StrToFloat(Field[i,2]); ch:= S t r T o I n t ( F i e l d [ i , 3]) ; dn:= S t r T o I n t ( F i e l d [ i , 4 ] ) ; d n g : = S t r T o I n t ( F i e l d [ i , 5] ) ; if i = 1 then i f d n g <> 0 t h e n z p : = o k l * d n / d n g e l s e z p : = 0 e l s e i f ch>dng*8 t h e n z p : = o k l * d n g * 8 e l s e zp:=okl*ch; Field[i,6]:=FormatFloat('0.00',zp); i f CheckBoxl.Checked then pr:=StrToFloat(Editl.Text)*zp/100 else pr:=0.0; Field[i,7]:=FormatFloat('0.00• ,pr); it:=zp+pr; Field[i,8]:=FormatFloat(' 0.0 0 ' , i t ) ; w i t h DrawGridl do D r a w G r i d l D r a w C e l l ( S e n d e r , 0 , i , CellRect(0,x),[gdSelected,gdFocused]); end; end; BitBtnl.SetFocus; end; end.

Обработчик событий OnSetEditText содержит три оператора. Первый оператор не позволяет изменить количество часов для табельного номера 1 (оно связано с количеством отработанных дней) или количество дней для второго сотрудника (оно связано с количеством отработанных часов). Второй оператор изменяет количество часов, если количество дней изменено пользователем. Третий оператор служит тем же целям, что и второй, - изменяет количество дней для табельного номера 2. Обработчик OnSelectCell запрещает при вводе выбор ячеек, находящихся в столбцах 0,5,6, 7, 8.

СОВМЕСТИМОСТЬ И ПРЕОБРАЗОВАНИЕ ТИПОВ ДАННЫХ Когда в тех или иных операциях или операторах присутствуют данные различных типов, то возникают вопросы о соответствии типов друг другу. Стандартно выделяют три группы вопросов: •идентичность типов; •совместимость типов; •совместимость по присваиванию. 85


ИДЕНТИЧНОСТЬ т и п о в Если при объявлении использовать один и тот же идентификатор или несколько переменных объявляются с помощью одного описания, то такие типы идентичны. Например, Type lilt = Integer ; Var n : Int; k : Integer; vl, v2 : Array£1..10] of Real;.

В данном случае vl и v2 идентичны, п и к идентичны, но если объявлено: Var vl : Array[1..10] of Real;

v2 : Array[1..10] of Real;,

то vl и v2 не идентичны.

СОВМЕСТИМОСТЬ ТИПОВ При программировании необходимо помнить, что на операции с программными элементами компилятор налагает ограничения, разрешая выполнение только заложенных для указанного типа действий. Кроме того, совместимость типов требуется в выражениях, в том числе и в операциях отношения, а также при передаче аргументов функциям или процедурам. Рассмотрим некоторые наиболее распространенные варианты, когда типы считаются совместимыми: • типы идентичны; • один тип представляет тип-диапазон другого; • для объявления используется один и тот же базовый тип; • оба типа - целые типы; • оба типа - вещественные типы; • один тип - строка типа PChar, другой - массив символов с индексацией элементов, начинающейся от нуля.

СОВМЕСТИМОСТЬ ПО ПРИСВАИВАНИЮ Чтобы присваивание было корректным, необходимо выполнение следующих условий: • идентичность типов; • совместимость типов, причем значение типа слева от знака присваивания должно находиться в границах возможных значений типа справа от знака присваивания; • слева от знака присваивания - вещественный тип, а справа — целый тип; • слева - строка, кроме типа PChar, справа — любая строка; 86


• слева - строка, кроме типа PChar, справа — символ. ПРЕОБРАЗОВАНИЕ ТИПОВ В ряде случаев требуется преобразование одного типа в другой. Для этого используются или те же идентификаторы, с помощью которых типы объявлялись, или специальные функции или процедуры. Например, если объявлено: Type ByteRec — record Lo, Hi:byte; End;

WordRec = record

Lo, Hi:word; End; Var В:byte; W:word; L:LongInt;, то можно записать: L: =LongInt(W); В:"ByteRec(W).Lo; ByteRec (W).Hi:=0; W: =WordRec(L).Lo; В: =ByteRec(WordRec(L).Lo).Hi;.

К специальным функциям и процедурам преобразования типов относится множество подпрограмм, которые были использованы в программах выше (Ord, IntToStr, StrToInt, Chr, FloatToStr, и др.).

ОПЕРАТОРЫ ОБРАБОТКИ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ Когда возникает исключительная ситуация, то, независимо от ее источника, программа узнает о ней. Компилятор создает код, который в состоянии прервать выполнение программы. После прерывания из стека, содержащего локальные переменные, начинают "выталкиваться" данные. Процесс "выталкивания" данных продолжается до тех пор, пока не произойдет одно из двух событий: 1. Выполнение программы дойдет до операторов локальной обработки исключительной ситуации. 2. Управление будет передано встроенному глобальному обработчику исключительной ситуации. Приложение в большинстве случаев должно содержать программные фрагменты, предназначенные для обработки возникающих исключительных ситуаций, в том числе и для управления работой стека. Как было отмечено 87


выше, при возникновении исключительной ситуации создается объект обработки ошибки, остающийся в памяти до тех пор, пока он не будет оттуда удален. В любом приложении, основанном на VCL, имеется глобальная переменная Application, являющаяся экземпляром класса TApplication. Этот класс определен в модуле Forms и объединяет в себе все автоматически создающиеся формы программы. Объект Application выполняет множество функций. Одна из них - обеспечение по умолчанию глобального механизма обработки исключительных ситуаций. Как только произойдет исключительная ситуация, объект Application генерирует событие OnException. Событию TApplication.OnException можно назначить собственный обработчик. Этот процесс называется делегированием событий. Если никакая процедура не назначена, то событие OnException вызывает стандартный обработчик. Наряду с механизмом делегирования программную обработку ошибок можно осуществлять с помощью специальных операторов. Для этих целей определены две конструкции: Try...finally и Try...except. Они совершенно аналогичны по синтаксису, но различаются по назначению. Конструкция Tiy...finally имеет следующий синтаксис: Try <операторы> finally <операторы> End;.

Если в любом из операторов, размещенных между Try...finally, возникает исключительная ситуация, их выполнение прекращается и управление передается первому оператору, следующему за ключевым словом finally. После этого выполняются все операторы, стоящие между finally и End (операторы, стоящие между finally и end, выполняются и в том случае, если ошибки не произошло). Конструкция Try...finally применяется для довольно длительного перехвата исключительной ситуации, чтобы выполнить все полагающиеся перед завершением программы действия, прежде чем управление будет передано глобальному обработчику исключительных ситуаций или следующему уровню обработки ошибки. Эта конструкция не удаляет экземпляра объекта обработки исключительной ситуации, а только сообщает о ней. Конструкция Try...except применяется для перехвата исключительной ситуации с последующей возможной обработкой, предусматривающей освобождение экземпляра объекта обработки исключительной ситуации, после чего выполнение программы продолжается, что и отличает, главным образом, оператор Try...except от Try...finally. В этой конструкции есть необязательный дополнительный элемент, с помощью которого можно определить фактический тип возникшей исключительной ситуации, - элемент вида on...do: Try <операторы> except

88


o n E: Exception do <onepa®op> on E: Exception do <оператор>

else Соператор» End; • После ключевого слова else часто следует ключевое слово Raise, которое переводит обработку ошибки на новый уровень (разрешается создавать вложенные блоки Try), например, на уровень глобального обработчика. Raise прерывает программу и выталкивает данные из стека. Однако, если будет записано Raise <процедура>, где процедура позволяет создать собственное исключение, то выталкивания данных из стека не последует (если только внутри процедуры не возникнет ошибки). Процедура, записываемая после Raise, должна быть особого вида - она должна быть конструктором, так как она должна создать объект какого-либо класса исключений. В заключение отметим, что блок от Try до Finally (или до Except) называется блоком защиты ресурсов, а последующий блок до End - блоком очистки. ПРИМЕР ПРИЛОЖЕНИЯ 11 Построить график функции Г - \Ап(Х). Вариант решения задачи приводится на рис. 31.

Рис. 31

89


Методика построения графиков приводится при описании примера 4. Однако данная функция обладает рядом особенностей, которые удобно использовать для иллюстрации исключений. Во-первых, функция не определена при отрицательных значениях аргумента. Во-вторых, она не определена при Х= 1 (деление на нуль). Для всех особых точек, естественно, отображать график не будем. Для демонстрации рассматриваемых выше операторов воспользуемся методикой создания собственных исключений. Введем собственный тип класс ErangeError=class (EmathError) ;, — являющийся наследником встроенного класса EmathError. Используя ключевое слово Raise, создадим два исключения: одно - ErangeError — для фиксации случая отрицательного аргумента, а другое - переопределим стандартное EDivByZero. Если в программе встретятся другие исключения, то они будут обрабатываться стандартно - в программе эти ситуации определяются просто ключевым словом Raise. Программа для решения примера 11 приводится ниже. unit p r i m l l ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Editl: TEdit; Labell: TLabel; Edit2: TEdit; Label2: TLabel; Imagel: Tlmage; Label3: TLabel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; end; ERangeError = c l a s s ( E M a t h E r r o r ) ; var Forml: T F o r m l ; implementation {$R *.DFM} procedure TForml.EditlKeyPress(Sender: TObj e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ • 0 ' . . ' 9 ' , ' - ' , ' . ' , # 8 ] ) thenkey:=#0; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var a,b:real; xn,xk,yO,yk:integer;

90


mx,my:real; x,у,ymax:real; i:integer; IsDraw:boolean;

{Для п е р е к л ю ч е н и й при р и с о в а н и и } begin xn:=15; xk:=280; y0:=75; yk:=70; a:=StrToFloat(Editl.Text); b:=StrToFloat(Edit2.Text); ymax:=10; {Задано произвольно} IsDraw:=true; With Imagel.Canvas do begin Pen.Color:=clBlack; Pen.Width:=1; Brush.Color:=clWhite; Rectangle(0,0,Imagel.Width,Imagel.Height); MoveTo(xn,yO); LineTo(xk,yO); MoveTo(xn,yO+yk); LineTo(xn,yO-yk); End; i f b<=a t h e n b e g i n L a b e l 3 . C a p t i o n : = 'Графика н е т ' ; Exit; End; Imagel.Canvas.Pen.Color:=clRed; Image1.Canva s.Pen.Width:=2; mx:=(b-a)/(xk-xn); my:=yk/ymax; for i : = 0 to x k - x n do begin x:=a+i*mx; try i f x < 0 t h e n Raise E R a n g e E r r o r . C r e a t e F m t ('Шаг i = %d. О т р и ц а т е л ь н ы й д и а п а з о н ' , [ i ] ) ; i f (x > l - 0 . 5 * m x ) a n d (x < l + 0 . 5 * m x ) t h e n Raise E D i v B y Z e r o . C r e a t e F m t ('Шаг i = %d. Д е л е н и е на н у л ь ' , [ 1 ] ) ; y : = m y / l n (x) ; i f IsDraw t h e n b e g i n Imagel.Canvas.MoveTo(xn+i,yO-round(y) ) ; IsDraw:=false; end e l s e I m a g e l . C a n v a s . L i n e T o ( x n + i , y O - r o u n d ( y ) ) except on E : E R a n g e E r r o r d o b e g i n M e s s a g e D l g ( E . M e s s a g e , m t W a r n i n g , [ m b O K ] , 0) ; IsDraw:=true;


end; on E : E D i v B y Z e r o d o b e g i n Application.MessageBox(PChar(E.Message), 'Предупреждение',mb_OK); IsDraw:=true; e n d e l s e Raise; end; end; end; end.

В программе для создания объектов исключений используется встроенный конструктор CreateEmt. В качестве аргументов в этот конструктор передается строка, используемая для форматированного вывода (символ "%" представляет спецификатор формата, а последующий символ "d" - тип формата - вывод цифровых значений), и конструктор открытого массива, в котором в квадратных скобках указываются отдельные элементы вывода. Выполнять программу в данном случае необходимо не из среды Delphi, а вызывая соответствующий ЕХЕ-файл, иначе будут сгенерированы дополнительные исключительные ситуации, связанные с информацией отладчика.

МНОЖЕСТВА Тип множество задает неупорядоченную совокупность неповторяющихся объектов. Переменная типа множество - это совокупность объектов из исходного заданного множества. Может иметь значение "пусто" (пустое множество). Число элементов исходного множества ограничено - оно не может быть более 256. Для задания элементов множества может использоваться любой порядковый тип, однако порядковые номера элементов множества, т.е. значения функции ORD, должны находиться в пределах от 0 до 255. Для задания типа множества используется следующее объявление: Туре <Имя> = set of <тип элементов»;. При объявлении типа элементе» необходимо соблюдать ограничения, указанные выше. Например, Туре

А = set of Char; А1 = set of 'A' . ,'Z' ; Oper = set of (Plus, Minus, Mult, Divide); Number = set of Byte; D = set of 1..20;.

Переменные типа множество можно инициализировать с помощью типизированных констант. При этом значения задаются в виде конструктора множества Const K;D =

[5,9,11,17];

R:D =

[1. . 9,13,20] ;. Для

задания переменным текущих значений также можно использовать конст92


рукторы. Пусть объявлено Var АА:А;, тогда возможна запись (тип А объявлен выше) АА:= [Char (13) , C h a r ( 7 ) , ' 0 ' , ];. Если требуется присвоить этой переменной значение «пусто», то используется такой конструктор: АА : = [ ];, ОПЕРАЦИИ НАД МНОЖЕСТВАМИ Как и для других типов, имеется ряд встроенных операций для типа множество. Пусть заданы следующие типы и переменные: Type Mn = set of 1 . . 5 0 ; Var А, в , с : мп,- и пусть переменным присвоены значения: а := [ 3 , 5 , 9 , 1 0 ] ; в := [ 1 , 7 , 9 , 1 0 ] ; , тогда можно записать следующие операции (в фигурных скобках указан результат выполнения операции): • объединение множеств: С:=А+В,- - {1,3,5,7,9,10}; • разность (А-В о В-А): С:=А-В; - {3,5}, С:=В-А; - {1,7}; • пересечение (умножение): С: = А + в - {9,10};

• проверка эквивалентности (например, в операторе IF): <А=В) - {False}; • проверка неэквивалентности: (А о

в) - {True};

• проверка, является ли одно множество подмножеством другого: <А>=В) - {False}, (А<=В) - {False}; • проверка, входит ли заданный элемент в заданное множество: (3 in А) - {True}, (3 in в) - {False}; • стандартные подпрограммы для выполнения некоторых действий над множествами: Exclude (А, 3); — удалить из множества а элемент 3, Include (А, 3) ; — вставить элемент 3 во множество А. Рассмотрим следующую задачу. Пусть с помощью генератора случайных чисел требуется выбрать 30 элементов из диапазона 0...200 и вывести их на экран дисплея в порядке возрастания. Пусть вывод осуществляется с помощью компонента ТМето. Ниже запишем код, который будет использоваться далее в примере 12. Const к=30;

L=200;

Type Mn=set of 0..L; Var S: МП; Str:String; i,Z:byte; Begin Randomize; Str:=''; Memol.Lines.Clear; For i:=l to К do While true do begin

93


Z: = random(L+l); If Z in S then continue; Include(S,Z); Break; End; For i:=0 to L do begin If i in S then Str:=Str+IntToStr(i)+' If (i mod 20=0) and (length(trim(Str))>0) then begin Memol.Lines.Add(Str+' ' ); Str:=' End; End; End;.

В этой программе первый цикл For обеспечивает необходимое число итераций. Внутри этого цикла содержится цикл While, который позволяет "отсеять" повторяющиеся значения элементов множества. Второй цикл For необходим, чтобы сгенерированные элементы множества записать в объект Hemol и расположить их, во-первых, в порядке возрастания и, во-вторых, по строкам — в очередной строке располагаются элементы из соответствующей двадцатки. ПРИМЕР ПРИЛОЖЕНИЯ 12

Пусть требуется сформировать с помощью генератора случайных чисел три множества: первое должно обязательно содержать элемент 25, второе элемент 50, третье - элемент 100. Диапазон элементов - от 0 до 200 (исходное множество). Каждое множество должно состоять из 30 элементов. Вывести элементы этого множества в порядке возрастания, используя новый компонент TTabControl (на странице Win32). Компонент TTabControl представляет собой записную книжку (рис. 32), состоящую из вкладок и клиентской области. Вкладки, необходимые для листания страниц, располагаются вдоль верхней границы клиентской области. Встроенных страниц элемент не имеет, их нужно программировать самостоятельно с помощ��ю свойства Tabs - в Object Inspector нажать на "..." и в редакторе напечатать столько строк, сколько требуется вкладок. Нумерация начинается с нуля и номер активной на данный момент страницы содержится в свойстве Tablndex. Свойства TabWidth и TabHeigbt содержат размер вкладок. Управление данным компонентом осуществляется с помощью события OnCbange. Это событие происходит, когда мышью будет выбрана какаялибо вкладка. Дня того чтобы выводить какую-либо информацию в клиентскую область вкладки, необходимо использовать 2 свойства: Handle (дескриптор) и DisplayRect (задает прямоугольник клиентской области). 94


Клиентская область не имеет своей канвы, поэтому с помощью функции GetDC в динамической памяти необходимо создать канву и свойству Handle присвоить адрес ее расположения в памяти. Если происходит ошибка при выделении памяти, то генерируется исключительная ситуация. Поэтому в программе необходимо предусмотреть варианты контроля таких ситуаций с помощью рассмотренных выше конструкций Try. Вариант расчета приведен на рис. 32. Пример 12

7 @

х

Г

7 19 20 I 22 25 32 38 51 5? ;: 74 73 85 90 93 99 100 104 119 123 126 131 134 139 148 160 164 163 189 198 199

;

Рис. 32

Программа приведена ниже. unit priml2; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , F o r m s , Controls, Dialogs, S t d C t r l s , Buttons, ComCtrls, E x t C t r l s ; type TMainForm = c l a s s ( T F o r m ) TabControll: TTabControl; B e v e l l : TBevel; Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; Label2: TLabel; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure TabControllChange(Sender: T O b j e c t ) ; end; const k=30; L=200; type mn=set of 0 . . L ;

95


MainForm: TMainForm; s : a r r a y [ 0 . . 2 J o f mn; s s : a r r a y [ 0 . . 2 ] of m n = ( [ 2 5 ] , [ 5 0 ] , [ 1 0 0 ] ) ; implementation {$R *.DFM} procedure TMainForm.ButtonlClick(Sender: T O b j e c t ) ; var i,j,z:byte; begin randomize; f o r j : =0 t o 2 ,do repeat s[j] :=[]; f o r i : = l t o ะบ do w h i l e t r u e do b e g i n z:=random(L+l); i f z in s [ j ] then continue; i n c l u d e ( s [ j ] , z) ; break; end; until ss[j]<=s[j]; end; procedure TMainForm.TabControllChange(Sender: T O b j e c t ) ; var Canvas:TCanvas; str:string; i,j:byte; begin try Canvas:=TCanvas.Create; try Canvas.Handle:=GetDC((Sender as TTabControl).Handle); w i t h (Sender a s TTabControl) do b e g i n Canvas.Brush.Color:=clWhite; Canvas.FillRect(DisplayRect); Canva s . F o n t . C o l o r : = c l R e d ; str:=''; j:=0; f o r i : = 0 t o L do b e g i n i f i in s[TabIndex] then s t r : = s t r + I n t T o S t r ( i ) + ' '; i f ( i mod 20 = 0) a n d ( l e n g t h ( t r i m ( s t r ) ) > 0 ) t h e n b e g i n with DisplayRect do Canvas.TextOut(left+ Font.Size,top-j *font.Height+j,str); str:=''; inc (j) ; end; end; end; finally ReleaseDC((Sender as TTabCotrol).Handle, Canvas.Handle) var

96


end; finally Canvas.free; end; end; end.

Для создания канвы в клиентской области компонента TTabControl объявлена переменная Canvas типа TCanvas. Создание экземпляра данного типа осуществляется с помощью процедуры Create (эта процедура является конструктором). После выполнения определенных действий следует освободить занимаемую канвой память, когда канва не будет нужна, с помощью процедуры Free, называемой деструктором. Вывод на канву, в данном случае, текста осуществляется с помощью процедуры TextOut. Работа с динамической памятью требует контроля возникновения возможных исключительных ситуаций. В программе это осуществляется с помощью двух вложенных операторов try...finally...end. Как правило, объекты, которые автоматически создаются средой Delphi, автоматически и уничтожаются. В данном примере мы создали свой объект Canvas и поэтому должны сами его уничтожить. Первая конструкция try...finally отслеживает создание объекта Canvas, а вторая отслеживает подключение Canvas к TTabCantrol. Соответственно после ключевых слов Finally высвобождается занятая функцией GetDC и канвой память. Рассмотрим, почему использовалась операция as. Sender — это параметр, с помощью которого в обработчик событий передается конкретный экземпляр объекта. Операция as - это подсказка компилятору, какого типа передаваемый объект. В данном случае это объект типа TTabControl.

ВАРИАНТНЫЙ ТИП ДАННЫХ Этот тип данных объявляется с помощью ключевого слова Variant. Данные типа Variant представляют собой такие величины, тип которых либо неизвестен к началу работы программы, либо может изменяться в процессе ее выполнения. Они являются довольно гибким средством, но требуют дополнительной памяти и работа с ними происходит медленнее, чем с другими типами. Поэтому использовать их следует только там, где это необходимо, например, при работе с OLE-объектами, программировании серверов и т.д. Одна переменная вариантного типа может принимать значения различных типов, например объявлено: Var V: Variant; D: Double; S: 7 — 4758

97


String; W: Word;. М о ж н о записать: D:= 6.88; V:= D; W:= 50; V:= W; S:='НИ РХТУ' ; V:= S;.

Вариантный тип данных представляет собой запись и определен в моду, ле S y s t e m стандартным типом T V a r D a t a : TvarData = r e c o r d Vtype:word; Reserved!, Reserved2, Reserved3:word; Case I n t e g e r of VarSmallint: (VSmallint:Smallint); Varlnteger: (VInteger: Integer); VarSingle: (VSingle: S i n g l e ) ; VarDouble: (VDouble: D o u b l e ) : VarCurrency: (VCurrency: C u r r e n c y ) ; VarDate: (VDate:Double); VarOleStr: (V01eStr:PWideChar); VarDispath: (VDispath:Pointer); VarError: (VError:Integer); VarBoolean: (VBoolean:WordBool); VarUnknown: (VOnknown:Pointer); VarByte: (VByte:Byte); VarString: (VString:Pointer); VarAny: (VAny:Pointer); VarArray: (VArray:PVarArray); VarByRef: (VPointer:Pointer); End;.

Этот тип представляет собой запись с вариантной частью. Поле VType определяет конкретный текущий тип записываемого значения. Само значение записывается в вариантной части записи. Три поля Reserved-1-2-3 не используются. Сазе-константы, определяющие вариантную часть, описаны в модуле S y s t e m .

Имеется большое количество стандартных подпрограмм, которые позволяют во многих случаях избегать непосредственного обращения к внутренней структуре переменных вариантного типа. Какие-либо параметры вариантного типа могут быть определенного типа, неназначенные и неизвестные. Неназначенный параметр - это параметр, которому не назначено еще никакого значения. При своем создании параметр вариантного типа объявляется как неназначенный и получает значение VarEmpty, где varEmpty - константа, определенная в модуле System. Неизвестные параметры, как правило, являются ошибочными параметрами. В общем случае, переменные вариантного типа могут участвовать в выражениях, как и переменные других типов. Этот тип данных может представлять собой массив, в том числе массив, состоящий из элементов вариантного типа. Дня работы с такими массивами имеются стандартные подпрограммы. 98


ПРОЦЕДУРЫ И ФУНКЦИИ Обычно программа составляется так, что она состоит из блоков, которые строятся в какой-то степени произвольно. К крупным блокам относятся модули (Unit), библиотеки (DLL). К мелким блокам относятся подпрограммы, которые являются составной частью любых программных единиц. Подпрограмма выполняет некоторую логически завершенную последовательность действий. Существуют две разновидности подпрограмм, которые отличаются оформлением и способом передачи данйых. Это - процедуры и функции. Подпрограммы являются программными элементами, такими же, как и переменные, константы и т.д. Поэтому подпрограмма должна быть объявлена и определена до первого ее использования. Существуют предопределенные встроенные подпрограммы, которые содержатся в программных модулях и библиотеках. Подпрограммы может создавать и сам программист. При разработке подпрограммы важным является вопрос категорий используемых аргументов. Дело в том, что внутри подпрограммы все данные и вычисления локализованы, т.е. они недоступны (если не позаботиться об этом) для основной программы. Обмен результатами расчетов осуществляется с помощью параметров (аргументов). Существует два вида параметров: входные параметры, с помощью которых данные передаются внутрь подпрограммы, и выходные, с помощью которых передаются результаты расчета. Обмениваться данными с подпрограммой можно с помощью глобальных переменных (не рекомендуется). В этой связи различают: • локальные переменные, объявленные внутри подпрограммы и доступные только ей; • глобальные переменные, объявленные в блоках, к которым принадлежит данная подпрограмма, и доступные как охватывающему подрограмму блоку, так и подпрограмме; • формальные параметры, которые записываются в круглых скобках справа от имени подпрограммы при ее объявлении и доступные как использующей подпрограмму программной единице, так и, естественно, подпрограмме. Способ обмена данными с помощью глобальных переменных применяется только в простейших программах или каких-то блоках. Универсальным способом обмена данными является способ использования формальных параметров, который и разработан для этих целей. 7*

99


ПРОЦЕДУРА Заголовок процедуры имеет следующий синтаксис: Procedure <имя>(<список формальных парам.>) ; .Например, пусть заданы 2 маесива вещественных чисел, преобразовать их в массивы целых чисел. Program primer; Type Arlnt — Array[1..100] of Integer; ArFloat = Array[1..100] of Double; var Arlntl, Arlnt2: Arlnt; ArFloatl, ArFloat2: ArFloat; procedure Floattolnt (n:byte; const IntArray: Arlnt; var FloatArray: ArFloat); var i : Byte; Begin for i:=l to n do IntArray[x]:=round(FloatArray[x]); End; begin FloatToInt (20, Arlntl, ArFloatl); FloatToInt (30, Arlnt2, ArFloat2); End.

При разработке подпрограммы следует выделять входные и выходные параметры. В данном случае подпрограмма имеет два входных параметра: п и IntArray — и один выходной — FloatArray. В то же время эти параметры являются формальными параметрами. Фактические параметры — это "настоящие" переменные, с помощью которых заменяются формальные параметры при выполнении расчетов, т.е. при вызове процедуры (в примере имеются два вызова процедуры между b e g i n и end). ФУНКЦИЯ Функция по сравнению с процедурой имеет два отличия. Первое отличие состоит в ее заголовке и особой роли имени функции. Второе отличие связано с первьм - область используемых операторов должна обязательно содержать оператор присваивания какого-либо результата имени функции. Заголовок функции имеет следующий синтаксис: Function <имя> (<список та>;.

формальных

парам.>):

<тип

возвращаемого

результа-

Как следует из объявления, необходимо задавать тип возвращаемого результата. Это означает, что имя функции представляет собой ячейку (совокупность ячеек) памяти, с помощью которой результат расчета передается Б блок, который является владельцем данной функции. Таким образом, синтаксис функции не указывает на то, что передавать данные нужно с помо100


щью выходных формальных параметров, как для процедуры. Например, рассмотрим вычисление факториала Л'!=Л'(Л'-1).... Напишем функцию для вычисления N1: junction Factorial(N:Byte): Cardinal; Var Fact: Cardinal; i: Byte;

begin

If № 0 then Fact :=1 else Fact : = N; For i := N-l downto 2 do Fact := Fact * i; Factorial := Fact;

End;.

Здесь введена вспомогательная переменная Fact. Необходимость этого заключается в следующем. Дело в том, что, если справа от знака присваивания появляется имя функции, например, Factorial := i*Factorial;, то это означает, что справа при расчете функция вызывает саму себя (рекурсия)- Рекурсия - особая форма математических вычислений, когда функция строится посредством рекурентных соотношений, вызывая саму себя. В данном примере не используется рекурсивная форма расчета факториала, поэтому необходимо написать все операторы без вызова самой функции Factorial. Для того чтобы упростить программирование функций и не вводить вспомогательных переменных типа Fact, синтаксически встроена специальная переменная Result, которая имеет тот же тип, что и функция, и используется только внутри функции. Перепишем подпрограмму, приведенную выше: Function Factorial(N : Byte): Cardinal; Var I : Byte; begin If N=0 then Result:= 1 else Result:= N; For i := N-l downto 2 do Result := Result * i; End;.

В данном случае использование справа от знака присваивания переменной Result рекурсии не образует. Присваивание результата расчета имени функции из переменной Result осуществляется автоматически. Для расчета, например 12!, сначала объявим какую-либо переменную (пусть Q) Var Q : Cardinal;. Теперь можно записать оператор вычисления 12! Q := Factorial (12) ;.

Подпрограмму-функцию при необходимости можно вызвать так же, как процедуру, если программиста не интересует результат, возвращаемый функцией, а важны те действия, которые она выполняет. Такая возможность задается с помощью директивы {$Х+} (расширенный синтаксис, установлено по умолчанию). 101


РЕКУРСИЯ Допускается, что подпрограмма может вызывать саму себя. Эта возмо«. ность связана с тем, что при каждом новом обращении к подпрограмме, п&. раметры, которые она использует, заносятся в стек. Причем, параметры из стека удаляются при выходе из подпрограммы. Таким образом, в стеке сохраняются все параметры от предыдущих вызовов. В ряде случаев рекур. сивное оформление подпрограммы более компактное и эффективное. Но не следует забывать, что существует опасность переполнения стека. Классическим примером рекурсии является рекурсивная формула вычисления факториала М =N(N-1)1 (N-1)1 = (ЛЧ)(Л-2)! и т.д. Напишем подпрограмму вычисления факториала по рекурсивной формуле. Function Factorial(N: byte): Cardinal; Begin If N in [0,1] then Result:=l Else Factorial:=N*Factorial(N-l); End;.

При вычислении следует рассматривать прямой ход и обратный. В данном случае при прямом ходе в стек заносятся только данные. При обратном ходе происходят вычисления и удаляются параметры из стека. с_

ФОРМАЛЬНЫЕ И ФАКТИЧЕСКИЕ ПАРАМЕТРЫ Формальные параметры задаются при объявлении подпрограммы, фактические параметры указываются при выполнении расчетов, т.е. при вызове подпрограммы. Фактические параметры должны быть идентичных типов с формальными. Их число должно быть равно числу формальных параметров, должен совпадать и порядок следования фактических параметров с порядком записи формальных. Механизм передачи данных через формальные параметры реализуется с использованием специальной области памятистека. По умолчанию размер стека установлен 16384 байта. Можно изменить объем стека в настройках среды Delphi. ПАРАМЕТРЫ-ЗНАЧЕНИЯ

В этом случае для передаваемого фактического параметра создается копия в стеке. Синтаксис записи, например, таков: Function FF(a,b:Real; ...):...; (пример приведенной выше подпрограммы содержит этот тип параметров). На месте параметра-значения при вызове подпрограммы может стоять выражение, совместимое по присваиванию с формальным параметром. 102


Внутри подпрограммы значение такого параметра измениться не может. параметры можно задавать как параметры-значения. Передавать в подпрограмму массивы в виде параметров-значений нецелесообразно, так как расходуется лишняя память — в стеке создается дополнительная копия массива. Входные

ПАРАМЕТРЫ-ПЕРЕМЕННЫЕ

При использовании в подпрограммах параметров-переменных в стеке выделяются ячейки для размещения адресов фактических параметров. В отличие от параметров-значений, внутри подпрограммы значения параметровпеременных могут изменяться. Так как фактические параметры в этом случае передаются по адресу, любое изменение параметра-переменной внутри подпрограммы, таким образом, фиксируется в фактическом параметре. Параметр-переменную записывают, используя ключевое слово var, например procedure РР(var с:Integer; ...) ; (пример процедуры выше содержит этот тип параметра). Выходные параметры задаются как параметрыпеременные. ПАРАМЕТРЫ-КОНСТАНТЫ

Параметры-константы синтаксически введены для того, чтобы ключевое слово var сохранить за выходным параметром и в то же время чтобы входной параметр мог передаваться по адресу. Синтаксис записи этих параметр ров таков: Procedure РР (const a: real; ...) ; (пример процедуры, приведенной выше, содержит этот тип параметров). В данном случае простые переменные передаются как копии, а, например, массивы и строки - по адресу. Компилятор сам определяет, что передавать по адресу, а что как копию. Изменить параметры-константы внутри подпрограммы нельзя. Таким образом, входные параметры могут передаваться в подпрограмму как параметрыконстанты. Вместо параметров-констант можно подставлять выражения, как и для параметров-значений. ПАРАМЕТРЫ БЕЗ ТИПА

Существуют параметры-переменные и параметры-константы без указания их типа. Передаются такие параметры по адресу. При использовании этих параметров внутри подпрограммы необходимо определить их тип и воспользоваться, например, преобразованием типов. Рассмотрим следующий пример: пусть требуется найти максимальный элемент для заданного одномерного массива. Напишем функцию поиска максимального элемента. Function ММах(var a): Integer; Type Vect = array of integer;

103


Var x : byte;

Begin

Result := Vect(a)[0]; For x := 1 to High(Vect(a)) do If Result < Vect(a)[i] then Result := Vect(a)[x]; End;.

В качестве фактического параметра в данном случае можно использовать любой одномерный динамический массив целых чисел, размерность этого массива может быть любая. Пусть требуется найти с помощью этой функции максимальный элемент для вектора В, состоящего из 20 элементов. Сначала объявим этот вектор и переменную Y, в которую запишем результат: Var В:array of integer; Y: integer;. Далее надо определить массив, предварительно выделив для него память: SetLength <в,20);. Теперь запишем вызов функции Y: =Мтах <в);. М А С С И В Ы ОТКРЫТОГО ТИПА

В подпрограммах нельзя объявлять формальный параметр как статический массив в виде array [...] of ..., так как он не будет идентичен ни одному фактическому параметру. Если все же необходимо обмениваться с подпрограммой данными в виде статических массивов, то предварительно их надо типизировать, например, Type Vekt real;...procedure sum (const V:Vekt; ...).

=

array

[1..20]

of

Чаще всего при вычислениях с векторами или матрицами формальные параметры в подпрограммах описывают как открытые массивы, используя тот же синтаксис и все правила, которые применяются при работе с динамическими массивами. Перепишем предыдущую функцию, для того чтобы можно было передавать одномерный массив любой размерности и не использовать преобразование типов внутри подпрограммы. Function Mmax(const Mas: array of Integer): Integer; Var I : Byte; Begin Result := Mas[0); For i := 1 to High(Mas) do If Result < Mas[x] then Result := Mas[x]; End;.

Если формальный параметр представляет собой открытый массив, то в качестве фактического параметра можно использовать конструктор массива. Конструктор массива разрешается применять, если открытый массив задан в виде параметра-константы, как в предыдущей функции мюах. Конструктор открытого массива по синтаксису записи совпадает с конструктором множества. Пусть заданы три значения: а, 15, b + с, где а,Ь,с — переменные. 104


Найти среди них максимум с помощью подпрограммы Мтах и результат записать в У - Y := ММах([а, 15, Ь + с]);. Если необходим обмен данными с помощью массивов вариантного типа, т 0 используется формальный параметр типа Array of const (открытый массив вариантного типа). ПАРАМЕТРЫ ПО УМОЛЧАНИЮ Часто при вызове подпрограммы в нее нужно передавать параметры, имеющие некоторое заданное значение. Например, получить распределение случайных чисел, зависящее от многих параметров, но при одном заданном к = 5 или к = 9, где к — некоторая характеристика, остальные параметры изменяются одинаково. Заменить такие параметры, как к, на константы нельзя - все же они иногда меняются. Эта проблема решается введением параметров по умолчанию. Например, запишем функцию для вычисления распределения Гаусса: j (*-g>)2 у —— £ •JbcG Здесь х - случайная величина, заданная в диапазоне от а до b с шагом Л; т=(а+Ъ)!2; а - параметр, по умолчанию равный 1 (но иногда требуется задать значение, например 0,5 или 2). Function Gaussa(x, m: real; sigma: real=1.0): real; Begin Result:=exp(-sqr(x-m)/(2*sqr(sigma)))/ (sqrt(2*n)*sigma); End;. При выполнении расчетов можно записать так: Y:= Gaussa(x, (а+Ь)/2);, опуская параметр sigma, или так: У:= Gaussa(х,(а+Ь)/2,2.0);. Очень важно отметить следующее. Параметров по умолчанию может быть несколько. Но все они должны быть сосредоточены в конце списка параметров. Объяснение этому очень простое. Допустим, подпрограмма имеет 4 параметра. Из них первые два - параметры по умолчанию. В этом случае, если указаны при вызове подпрограммы 3 параметра, то как определить, что это первый, третий и четвертый или второй, третий и четвертый. Поэтому параметры можно опускать начиная с конца, например для функции Function MyFF(x:byte= 1; у:byte= 1; z:byte= 1) :byte; можно записать так: A:=MyFF(2,2,2) ;, A:=MyFF<2,2);, A:=MyFF(2) ;, A:=MyFF;, а так записывать нельзя: A: =MyFF{, 2, 2); A:=MyFF(2, , 2);. Параметром по умолчанию может быть только константа или константное выражение, т.е. то, что можно определить на этапе компиляции. 105


ПРОЦЕДУРА EXIT Эта процедура используется, если необходимо досрочно выйти из под. программы. Например, задан массив вещественных чисел, найти первый отрицательный элемент в этом массиве: Function Minus (const М : Array of Real) : Real; Var I: Byte; Begin Minus := 0.0; For X:—0 to High(M) do If M[i]<0 then begin Minus := M[i]; Exit; End; find; .

ДИРЕКТИВЫ ПОДПРОГРАММЫ Директивы подпрограммы дают компилятору дополнительную информацию по способу передачи данных, размещению данных в стеке и т.д. Директивы записываются в виде ключевых слов, идущих непосредственно за заголовком подпрограммы, отделяются они от заголовка знаком СОГЛАШЕНИЯ П О ПЕРЕДАЧЕ Д А Н Н Ы Х

Существуют четыре варианта передачи данных в подпрограммах: • Register - базовый режим, который задается по умолчанию и характеризуется тем, что может быть использовано до трех регистров процессора для передачи данных. Регистры — внутренняя память процессора, которая необходима непосредственно для проведения вычислений; • Pascal, отличающийся от предыдущего режима в основном тем, что регистры процессора для передачи параметров не используются; • Cdecl - режим, позволяющий использовать DLL, написанные на С++ или на других языках; • Stdcall - режим, позволяющий обращаться к подпрограммам Windows. ДИРЕКТИВА F O R W A R D

При объявлении подпрограмм существует правило, запрещающее использовать подпрограмму, если она не объявлена и не определена. Если подпрограмма Б вызывает А, а А вызывает Б (циклический вызов), то в этом случае, чтобы не нарушать правило, указанное выше, первую из подпро106


грамм объявляют с директивой Forward и после этого определяют сначала вторую, а потом первую подпрограммы. Например, procedure First(var х : Integer); Forward; procedure Second(var x,y : Single);

...First (x) ;... end; procedure First(Var x : Integer);

...Second(zl,z2) ;... end;.

ДИРЕКТИВА EXTERNAL

Эта директива используется в двух случаях: для вызова подпрограмм из DLL или для вызова подпрограмм, написанных на ассемблере и существующих в виде отдельного объектного файла. В первом случае записывается ключевое слово с дополнительной информацией, например, Procedure F i r s t ; external 'MyDll.dll';. Во втором случае имя DLL опускается. ДИРЕКТИВА ASSEMBLER

С помощью этой директивы можно подключать к программам подпрограммы, полностью написанные на языке Assembler, например, Function мах(х,у:integer):Integer; Assembler;

ASM Mov eax, x Cmp eax, у Jge 61 Mov eax, у gl: end;.

(Вместо begin} {сравнить} {перейти}

Эта подпрограмма ищет максимальное из двух чисел. ПЕРЕГРУЖЕННЫЕ ПОДПРОГРАММЫ Часто возникает потребность выполнять одни и те же действия над разными типами данных. Например, рассмотрим встроенную функцию Abs(X) вычисление модуля числа. Если аргумент X— число целое, то результат возвращается, как целое число, если аргумент — число вещественное, то и результат будет числом вещественным. При создании подпрограмм, которые в зависимости от типа аргумента возвращают разный тип результата, используется механизм перегрузки подпрограмм. Синтаксически перегруженные подпрограммы объявляются с директивой overload и, кроме того, подпрограмма определяется отдельно для каждого типа аргумента. Например, 107


Procedure ShowResult(I:integer); overload; Begin ShowMessage(IntloStr(I)); End; Procedure ShowResult(R:real); overload; Begin ShowMessage(FloatloStr(R)); End; Procedure ShowResult(S:string); overload; Begin ShowMessage(S); End; .

При

вызове

данной

подпрограммы

-

ShowResult (2);,

ShowResult(5.7);, ShowResult(1Перегруженная процедура') — скла-

дывается впечатление, что работает одна и та же процедура. Механизм перегрузки таков: компилятор определяет тип фактического аргумента и в зависимости от этого подставляет адрес той подпрограммы, которая с данным типом работает. ПРИМЕР ПРИЛОЖЕНИЯ 13

Пусть заданы два числа kl и к2. С помощью генератора случайных чисел, распределенных равномерно, создаются два множества такие, что первое содержит число kl, а второе — к2. Если к2 намного больше к!, то можно предположить, что в большинстве случаев сумма элементов второго множества больше суммы элементов первого множества. Проверить сформулированное утверждение на трех парах множеств. Количество элементов во множестве равно к. Диапазон чисел от 0 до L-100. В данном случае требуется сгенерировать 6 множеств. Обозначим их 50i, 5о2, 5ц, Sn, S21, S22- Для записи элементов этих множеств воспользуемся компонентом TPageControl, который находится на странице Win32 палитры компонентов. Компонент TpageControl используется для реализации многостраничных диалоговых панелей. Каждая страница представляет собой объект типа TTabSheet (лист с этикеткой). Название страницы задается свойств вом Caption. Текущая страница доступна через свойство ActivePage. Страницы нумеруются начиная с нуля. При переключении страниц возникает событие OnChange. На каждой странице могут быть расположены любые компоненты: TEdit, TMemo, TImage и т.д. Заголовки страниц могут отображаться или в одну строку со скроллингом, или в несколько строк. В последнем случае свойству MultiLine необходимо присвоить значение true. Для нашего примера были выбраны три страницы (с помощью правой клавиши мыши выбирается NewPage). На каждой странице расположено по два компонента типа TMemo: соответственно от Memol до Мешоб. 108


форма с вариантом решения представлена на рис. 33.

0467810121819 21 293031 34 4043454953 6061 626364 6568 637274 81 848591 9394 Сымма=1645

02361316 23293637 4041 42 44 4546 47 5253545859 606566697273 8283 8586959697 Сумма=1775

Рис. 33

Для ввода чисел к, kl, к2 использовался компонент TSpinEdit (страница Samples). Ввод с помощью этого элемента ограничивается только целыми числами. Их можно вводить или непосредственно, или с помощью встроенных стрелок, последовательно уменьшая или увеличивая число. Само число содержится в свойстве Value. В данном случае для решения примера понадобятся дополнительные подпрограммы. Создадим одну подпрограмму для генерации (пример 12) множеств (назовем ее CreateMii), другую (SumMn) — для нахождения суммы элементов множества. Расположим эти подпрограммы в секции Implementation модуля как необходимые для решения внутренних задач. Выберем тип Procedure для первой подпрограммы: она должна возвращать элементы множества. Вторая подпрограмма должна возвращать значение суммы (одно число), поэтому ее оформим в виде Function. Программа приведена ниже. unit p r i m l 3 ; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , C l a s s e s , Graphics,Controls, Forms, D i a l o g s , S t d C t r l s , B u t t o n s , C o m C t r l s , S p i n , E x t C t r l s ; type TForml = c l a s s ( T F o r m ) Panell: TPanel; Panel2: TPanel;

109


SpinEditl: TSpinEdit; SpinEdit2: TSpinEdit; Label2: TLabel; Label3: TLabel; S p i n E d i t 3 : TSpinEdit; Label4: TLabel; BitBtn2:TBitBtn; Button2:TButton; PageControll: TPageControl; TabSheetl: TTabSheet; TabSheet2: TTabSheet; TabSheet3: TTabSheet; Memol: TMemo; Memo2: TMemo; МетоЗ: TMemo; Memo4: TMemo; Memo 5 : TMemo; Memo6: TMemo; procedure PageControllChange(Sender: T O b j e c t ) procedure Button2Click(Sender: T O b j e c t ) ; end; const L = 1 0 0 ; •b^pe mn=set of 0 . . L ; var F o r n l : T F o r m l ; s : a r r a y [ 0 . . 2 , 1 . . 2 ] o f mn; ss:mn = []; k, k l , k 2 : b y t e ; implementation {$R *.DFM} procedure CreateMn (mbyte) ; var i , j , z : b y t e ; begin i f n=l then s s : = [ k l ] e l s e s s : = [ k 2 ] ; f o r j : = 0 t o 2 do repeat s [ j , n ] := []; f o r i : = l t o к do while t r u e do b e g i n z:=random(L+l); if z in s [ j , n ] then continue; include(s[j,n],z); break; end; until s[j,n]>=ss; end; function SumMn(const np,n:byte):word; var i:byte; begin

110


result:=0; f o r i : = l t o L do i f i in sEnp,n]

then

result:=result+i;

end; procedure TForml.Button2Click(Sender: T O b j e c t ) ; var k l 2 : i n t e g e r ; const s t r = ' H e выполняется условие ' ; begin randomize; kl2:=SpinEditl.Value ; i f (kl2>b) o r (kl2<0) t h e n b e g i n ShowMessage(str+'0<=kl<L'); SpinEditl.SetFocus; exit; end; kl:=kl2; kl2:=SpinEdit2.Value; i f (kl2>L) o r (kl2<0) t h e n b e g i n ShowMessage(str+'0<=k2<L'); SpinEdit2.SetFocus; exit ; end; k2:=kl2; kl2:=SpinEdit3.Value; i f kl2<=0 t h e n b e g i n ShowMessage(Str+'k>0') ; SpinEdit3.SetFocus; exit ; end; k:=kl2; CreateMn(1); CreateMn(2); end; procedure TForml.PageControllChange(Sender: TObj e c t ) ; var i : b y t e ; strl,str2:string; CurrentTab:TTabSheet; CurrentMemol:TMemo; CurrentMemo2:TMemo; begin strl:=''; str2:=''; CurrentTab:=PageControll.ActivePage; CurrentMemol:=nil; CurrentMemo2:=nil; c a s e C u r r e n t T a b . P a g e l n d e x of 0:begin CurrentMemol:=Memol;

111


CurrentMemo2:=Memo2; end; 1:begin CurrentMemol:=Memo3; CurrentMemo2:=Memo4; end; 2:begin CurrentMemol:=Memo5; CurrentMemo2:=Memo 6 ; end; end; i f ( C u r r e n t M e m o l o n i l ) and ( C u r r e n t M e m o 2 o n i l ) CurrentMemol.Lines.Clear; CurrentMemo2.Lines.Clear; f o r i : = 0 t o L do b e g i n if i in s[CurrentTab.PageIndex,1] then str1:=strl+inttostr(i)+' '; i f i in s[CurrentTab.PageIndex,2] then str2:=str2+inttostr(i)+' ' ; i f i mod 20 = 0 t h e n b e g i n i f l e n g t h ( t r i m ( s t r l ) ) > 0 then begin CurrentMemol.Lines.Add(str1); strl:=''; end; i f length(trim(str2))>0 then begin CurrentMemo2.Lines.Add(str2); str2:=''; end; end; end; end; CurrentMemol.Lines.Add('Сумма='+ inttostr(suimnn(CurrentTab.Pagelndex,1))); C u r r e n t M e m o 2 . L i n e s . A d d ( ' Сумма=' + inttostr(summn(CurrentTab.Pagelndex,2))); end; end.

then

begin

В данной программе введены три особые переменные CurrentTab: TTabSheet; CurrentMemol:TMemo; CurrentMemo2:TMemo;. Эти переменные являются объектами, т.е. порождены от типа класс. В программе показано, что с такими переменными можно работать, как с обычными переменными. Следует рассмотреть вопрос необходимости применения переменной ki2.

112


КЛАССЫ Язык программирования служит двум связанным между собой целям: он дает програм��исту аппарат для задания действий, которые должны быть выполнены, и формирует концепции, которыми пользуется программист, размышляя о том, что делать. Поэтому язык должен предоставлять программисту набор концептуальных инструментов (концепт - суть понятие). Таковыми, в первую очередь, являются классы, как встроенные, так и создаваемые программистом. Класс позволяет создавать собственные модели поведения объектов. При этом используется, как правило, иерархические совокупости понятий, с помощью которых можно представить решение задачи. Понятие класс позволяет программисту ввести новый тип, который специфицирует данные, необходимые для представления объектов этого типа, и множество операций для работы с этими объектами. Класс задает поведение объектов, т.е. как они создаются, как может осуществляться работа с ними и как они уничтожаются. ИНКАПСУЛЯЦИЯ Класс — основной элемент программирования в Delphi. Как тип, класс имеет ряд особенностей. Первая особенность типа класс заключается в том, что в одном типе объединены данные и код для обработки этих данных. Это свойство класса называется инкапсуляцией. Синтаксически класс подобен типу record, в котором можно выделить следующие элементы: • поля служат для записи данных; • методы — процедуры и функции для обработки полей; • свойства - специальные методы, к которым можно обращаться как к полям. Рассмотрим синтаксис объявления простейшего класса. Type TMyClass =class (TObject) Public AField: AType; Procedure AMethod(Param: AType); Function AFunc(Param: AType);AType; Property AProperty: Atype read AField m i t e AMethod; End;.

Объявленный выше класс включает одно поле AField какого-либо типа, два метода AMethod, AFunc и одно свойство AProperty. Объявление элементов класса делится на секции. В данном случае показана одна секция Public. Кроме Public могут присутствовать секции Рго8 — 4758

113


tected, Published, Private. Эти секции задают разную степень видимости при обращениях из других модулей. Внутри модуля, являющегося владельцем класса, доступны все элементы из различных секций. КЛАСС КАК ОБЪЕКТНЫЙ ТИП Учитывая, что класс служит для построения моделей поведения программных элементов, переменные типа класс называют объектами. Поэтому тип класс называется объектным типом. Объект представляет собой реализацию класса. Один и тот же класс может порождать множество объектов. Однако не существует двух объектов с одинаковыми значениями. Объявленные переменные объектных типов физически не представляют собой экземпляров класса. Они являются просто ссылками. Объекты создаются только в процессе выполнения программы. Под объекты необходимо не просто выделить память — их необходимо построить. Например, при построении формы используется файл ресурсов. Однако Delphi позволяет трактовать объект как обычную переменную. В отличие от других типов class можно объявлять только глобально. Запрещено объявлять классы внутри процедур и функций. НАСЛЕДОВАНИЕ Правила объявления класса таковы, что любой класс обязательно должен быть связан с каким-либо другим классом (существует одно исключение — TObject, являющийся корнем иерархического дерева классов). Соответственно любой класс может использовать какие-либо свойства, поля, методы уже существующих классов. В объявлении класса выше после ключевого слова class указан существующий класс TObject. Это означает, что тип TMyClass, кроме объявленных четырех элементов, содержит или наследует все элементы, содержащиеся в классе TObject. Класс TObject называется предком (родительским), а ТМуClass - потомком (дочерним). Наследование - вторая важная особенность объектных типов. Список наследников у данного класса может только возрастать. Каждый потомок может указать только одного предка. Однако неявно этот потомок содержит все поля, методы и свойства всех предков по линии наследования. Если предок не указан, то в Delphi считается, что предком является класс TObject. ОБЛАСТИ ВИДИМОСТИ Минимальную область видимости определяет секция Private. Вне модуля владельца данного класса элементы этой секции недоступны. В секцию 114


private следует помещать такие элементы, неосторожная модификация которых может привести к нежелательным последствиям. В секции Protected по сравнению с секцией Private защита элементов ослаблена. Элементы этой секции доступны только в классах потомках, в том числе и тогда, когда потомки создаются в другом модуле. Из секции Public элементы доступны в любом модуле, использующем данный класс. Если не указана область видимости, то по умолчанию принимается Published. Секция Published имеет область видимости такую же, что и секция Public. Отличие заключается в том, что эта секция имеет специальный интерфейс, благодаря которому информацию о членах этой секции может получить внешняя программа. Интерфейс Published используется инспектором объектов для визуализации компонентов. Если классы требуют интерфейса Published, то они относятся к типам времени выполнения RTTI (Ryntime Type Information). ОПЕРАЦИИ IS И A S Операции Is и As применяются к объектам. С помощью операции Is определяется, принадлежит ли данный объект указанному типу или одному из его потомков. Например, выражение AnObject is TMyClass возвращает true, если переменная AnObject совместима по присваиванию с переменной типа TMyClass. Для приведения типа какого-либо объекта применяется операция As. Например, with AnObject as TMyClass do ...

В операции As сначала проверяется совместимость типа с помощью операции Is. Если типы несовместимы, то запускается обработчик исключительной ситуации EInvalidCast. Поэтому использование следующей операции является менее надежным способом неявного приведения типа: with TMyClass (AnObject) do ...

МЕТОДЫ Методы служат для обработки информации, содержащейся в полях. Доступ к полям может осуществляться без дополнительных (формальных) параметров. Формальные параметры у методов обычно служат для обмена информацией с другими классами. В классе метод только объявляется. Описывается он в разделе реализации модуля (секция Implementation). При описании указывается имя классавладельца и через точку имя метода. Например, Procedure TMyClass.AMethod(Param:AType); Begin . . . . . . . . / end;.

115


Внутри begin ... end можно вызывать любые методы предков с указанием имени предка. Ближайший предок, имеющий такое же имя, как у данного метода, может вызываться с помощью зарезервированного слова inherited. Методы могут синтаксически оформляться следующим способом: • Procedure; • Function; • Constructor - вид Procedure, служащий для построения объекта, инициализации полей и правильного вызова так называемых виртуальных методов; • Destructor — вид Procedure, служащий для освобождения памяти, т.е. разрушения объекта. При построении объекта автоматически объявляется дополнительная переменная Self. Тип этой переменной совпадает с классом, породившим данный объект. Переменная Self является локальной и определена только внутри данного экземпляра класса. При необходимости внутри любого метода можно воспользоваться этой переменной. ВИДЫ МЕТОДОВ Методы бывают разных видов. По умолчанию методы являются статическими (Static) Методы в разных классах могут иметь одинаковые имена. При вызове методов обязательно указывается через точку имя объекта, вызвавшего данный метод. Например, пусть класс AClassType породил объект AnObject. Если вызывается метод AStaticMethod, то вызов записывается так: AnObject.AStaticMethod;.

Если в классе AClassType непосредственно такого метода нет, то вызывается ближайший по линии наследования метод с заданным именем. Недостаток статических методов заключается в следующем. Пусть метод AStaticMethod вызывает внутри себя другой метод, например SecondMethod. Пусть по линии наследования определено несколько методов с именем SecondMethod. В данном случае AStaticMethod вызовет ближайший по программному описанию SecondMethod, независимо от того объекта, который вызвал метод AStatiCMethod.TaKHM образом, при использовании статических методов нельзя конкретно указать, какой из имеющихся по линии наследования нескольких методов с одним именем требуется вызвать. М Е Т О Д Ы V IRTUAL И П О Л И М О Р Ф И З М

Особенности виртуальных (Virtual) методов рассмотрим на примере. Пусть объявлен класс AClass. Этот класс имеет потомка, который, в свою очередь, также имеет потомка, например AClass ->• BClass cciass. 116


Пусть AClass содержит метод Drag, который служит для рисования некоторой геометрической фигуры. При этом внутри метода Drag вызывается метод show для рисования заданной фигуры. Пусть метод show реализован в каждом из трех указанных классов для рисования точки (класс AClass), окружности (BClass) и квадрата (CClass). Метод Drag содержится только в AClass. Так как нужная фигура вызывается для рисования из метода show, нет необходимости дублировать его в других классах. Пусть заданы три объекта: Var

А:AClass;

В:Bclass;

С:Cclass;.

Пусть с помощью соответствующих конструкторов эти объекты созданы. Пусть вначале все три метода Show объявлены как статические. Запишем вызов на рисование геометрической фигуры: С.Drag;. Рассмотрим, какая фигура изобразится в данном случае. По линии наследования от CClass, вызвавшего Drag, в классе AClass найдется метод Drag. При вызове происходит обращение к show. В данном случае вызовется ближайший к Drag метод, находящийся в классе AClass, т.е. изобразится точка. Вызвать метод show.ro класса BClass для рисования окружност и или вызвать show из CClass и нарисовать квадрат с помощью статических методов нельзя. Итак, в данном случае ставится следующая задача. Вызов с.Drag; должен нарисовать квадрат, в.Drag; - окружность, соответственно A.Drag; — точку, т.е. метод Show для с .Drag должен вызываться из класса cclass, для в.Drag — из класса BClass, а для A.Drag — из класса AClass. Такая задача решается с помощью виртуальных методов show. Тогда вызов Show будет определяться конкретным фактическим объектом. Виртуальные методы определяются с помощью ключевого слова Virtual. По линии наследования все реализации одного и того же виртуального метода должны иметь одинаковые заголовки. С помощью зарезервированного слова Virtual определяется самая первая реализация виртуального метода, все последующие реализации переопределяются с помощью ключевого слова Override. Если заданный метод появляется далее по линии наследования без ключевого слова Override, то свойство виртуальности теряется для данного класса и всех его потомков. Статические методы определяются наэтапекомпиляции, а виртуальные — при выполнении программы. Для реализации вызовов виртуальных методов в зависимости от фактического объекта строится так называемая таблица виртуальных методов. Построение этой таблицы возлагается на соответствующий конструктор. Свойство одного и того же метода вести себя по-разному называется полиморфизмом (многоформным). Например, метод Drag, указанный в приведенном выше примере, может построить точку, окружность или квадрат. Для реализации полиморфизма используются виртуальные методы. 117


Итак, наряду с инкапсуляцией и наследованием, третьей особенностью типа класс по сравнению с другими типами является полиморфизм. М Е Т О Д Ы DYNAMIC

Механизм Dynamic доступа к другим методам подобен механизму Virtual. В обоих случаях адрес нужной процедуры или функции определяется фактическим объектом. Отличие заключается в том, что в данном случае строится таблица динамических методов. Обращение к этой таблице происходит медленнее, чем к таблице виртуальных методов, зато объем программы получается меньше. Методы Dynamic целесообразно использовать, когда класс имеет множество потомков, а число переопределяемых методов небольшое. М Е Т О Д Ы MESSAGE

Методы Message - обработки сообщений - представляют собой особую форму динамических методов. Обработчики сообщений всегда являются процедурами. Для ускорения поиска в таблице динамических методов после ключевого слова Message записывается константа целого типа, являющаяся индексом нужного метода. В обработчике сообщений имеется один параметр Var. Методы Message - это одно из звеньев взаимодействия компонентов Delphi с Windows. Объявление обработчика сообщений выглядит приблизительно так: Procedure Handle_WM_Paint (var Msglnfo) ; Message WM_Paint;.

Для поиска нужного адреса в таблице динамических методов в данном примере записана константа WM_Paint. Многие константы для Message зарезервированы за конкретными сообщениями Windows и выбирать их произвольно нельзя. Указанная константа поиска данного сообщения Windows может быть переопределена в каком-либо предке по линии наследования для данного объекта. Если константа Message не найдена, то вызывается метод обработки сообщений, заданный по умолчанию. М Е Т О Д Ы ABSTRACT

Обычно методы создаются для выполнения каких-то конкретных действий. Если по какой-либо причине это выполнить не удается, метод в классе может быть зарезервирован с обязательным переопределением его в классах потомков. Такой метод помечается ключевым словом Abstract. Если абстрактный метод не переопределен, то вызов такого метода приводит к вызову специальной процедуры Abstract, которая генерирует исключительную ситуацию. Абстрактным не может быть статический метод, так как статические методы нельзя переопределять. Например, 118


Type TmyParent=class Procedure AMethod;virtual;abstract; End; TmyClass=class(TmyParent) Procedure AMethod;override; End; М Е Т О Д Ы OVERRIDE

Как указано выше, зарезервированным словом Override помечаются переопределенные виртуальные или динамические методы (пример выше). М Е Т О Д Ы CLASS

Исходное назначение методов - определять поведение экземпляров объектов какого-либо класса. В некоторых случаях необходимо иметь ситуацию, когда поведение, задаваемое для метода, не должно зависеть от реально существующего объекта. Такая ситуация возникает с методом, который, например, должен возвращать имя класса. В этом случае метод помечается ключевым словом Class. В отличие от других команд, таких, как Dynamic, Virtual и т.д., слово Class ставится перед заголовком метода, например c l a s s Procedure AMethod;. ПРИМЕР П Р И Л О Ж Е Н И Я 14

Рассмотрим пример по использованию конструкторов и деструкторов в объектах. Кроме того, посмотрим, как можно использовать указатели на методы и методы class. Пусть на форме динамически строится совокупность объектов типа TButton. Далее пусть эти объекты последовательно разрушаются с выдачей соответствующих сообщений о числе объектов. Рассматривать этот пример будем последовательно, вводя все новые и новые конструкции и проверяя их непосредственно в среде Delphi. ДИНАМИЧЕСКОЕ СОЗДАНИЕ КОМПОНЕНТОВ

Пусть имеется форма, у которой есть только обработчик события ОпMouseDown, с помощью которого можно динамически строить кнопки, нажимая на левую клавишу мыши. Закрепляется каждая кнопка на форме в позиции координат точки, в которой была нажата клавиша мыши. Вот код данного обработчика: procedure TForml.FormMouseDown(Sender:TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); Var Btn:Tbutton;

119


Begin Btn:=TButton.Create(self) Btn.Parent:=self; Btn.Left:=x; Btn.Top:^y; Btn.Width:=Width+50; Btn.Caption:=Format<'Кнопка x,y= %d,%d',[x,y]); end;.

В Delphi ключевое слово self используется, например, когда нужно явно обратиться к текущей форме в одном из ее методов. Динамическое создание компонентов является тем случаем, когда требуется передать конструктору Create параметр Owner (указать владельца создаваемого объекта), а затем присвоить то же значение свойству Parent (указать родителя). В обоих случаях нужно передавать имя текущей формы (не всегда параметры Owner и Parent совпадают, как в данном случае). Таким образом, обработчик выше будет корректно работать, если параметр self заменить на Forml (пусть такое имя имеет переменная типа TForml). Следует отметить, что код, приведенный выше, обычно записывается с использованием оператора with: procedure TForml.FormMouseDown(Sender: TObject;Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin with TButton.Create(self) do begin Parent:=self; Left:—x; Top: =y ; Width:=Width+50; Caption:=Format('Кнопка x,y= %d,%d',[x,y]); end; end;.

Ниже приводится объявление модуля, используемого в данном случае: unit p r i m l 4 ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , E x t C t r l s , Menus; Type TForml = c l a s s ( T F o r m ) procedure FontiMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, Y: I n t e g e r ) ; end; var Forml: T F o r m l ; .

Вариант решения примера представлен на рис 34. 120


/"'

Пример 14

f Рис. 34 ИСПОЛЬЗОВАНИЕ КЛАССА СО СЧЕТЧИКОМ ОБЪЕКТОВ

Пусть в примере 14 будем строить кнопки некоторого собственного типа, унаследованного от типа TButton. Построим для этих целей класс, в котором предусмотрим счетчик создаваемых объектов. Для хранения числа создаваемых объектов понадобится переменная целого типа. Объявим ее в секции implementation: var CountBtns: i n t e g e r = 0 ; . Эта переменная содержит общую информацию о форме и представляет данные модуля, поэтому она не включена как поле в класс. Для доступа к такой информации понадобится метод класса. Теперь представим объявление нашего класса: type THyButton=class(TButton) public Constructor Create(AOwner:TComponent); override; Destructor Destroy; override; class function GetCount:integer; end;.

Увеличение счетчика объектов предусмотрим при вызове конструктора, уменьшение — при вызове деструктора. Для построения объектов и их разрушения, естественно, будем использовать стандартные методы. Ниже приводятся описания деструктора и конструктора: constructor TMyButton.Create (AOwner: TComponent); begin inherited; Inc(CountBtns); end; destructor TMyButton.Destroy; begin dec(CountBtns); inherited; end;.

121


Так как переменная CountBtns объявлена в секции implementation, обратиться к ней извне модуля нельзя. Только метод класса позволяет нам прочитать ее текущее значение: class function TMyButton.GetCount: integer; begin Result:=CountBtns; end;.

Теперь можно создавать объекты нашего нового типа TMyButton, немного изменив приведенный выше код обработчика FormMouseDown: procedure TForml.FormMouseDown{Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin with TMyButton.Create{self) do begin Parent:=self; Left:=x; Top:=y; Width:=Width+60; Caption:=Format{'%d кнопка x,y= %d,%d',[GetCount,x,y]); end;.

Класс-функция GetCount может вызываться, в отличие от обычных методов, как из объекта (в обработчике FormMouseDown), так и из класса. Например, добавим таймер (компонент TTimer) на форму и обеспечим при срабатывании таймера изменение заголовка формы с помощью следующего обработчика события OnTimerr procedure TForml.TimerITimer(Sender: TObject); begin Caption:=Format('Кнопок на форме %d*,[TMyButton.GetCount]); end;.

В результате решения получим, например, следующий вариант (рис. 35). Пример 14 Кнопок на форме 4

ш

ш

ш

.....

P ^ l g p .

1

I—»

I

Рис.35

122

|'„ jj'OjfXj


ОТСЛЕЖИВАНИЕ РАЗРУШЕНИЯ ОБЪЕКТОВ

Проследим за разрушением объектов на форме. Очевидно, это можно увидеть в секциях initialization и finalization, т.е. после того, как форма начнет разрушаться. initialization MessageBox(О,PChar(Format('На форме %d кнопок', [TMyButton.GetCount])),'Инициализация',mb_ok); finalization MessageBox(0,PChar(Format{'На форме %d кнопок', [TMyButton.GetCount])),'Финиш',mb_ok);.

В данном случае использовалась функция Windows API MessageBox, так как при разрушении формы доступ в секции finalization ко многим функциям Delphi невозможен. Запуская данную программу на выполнение, получим два сообщения до по��троения и после разрушения формы. СОБЫТИЯ В приложениях и компонентах событие возникает как результат послания операционной системой сообщения, что произошло некоторое действие, которое контролирует метод послания уведомления о событии. Этот метод проверяет, назначил ли пользователь соответствующий обработчик событий. Если событие произошло и обработчик назначен, то метод послания уведомления о событии вызывает соответствующий обработчик. Методы послания уведомления о событии можно переопределять, используя стандартные методы, или создавать свои собственные. Рассмотрим вариант переопределения методов послания уведомления о событии. Такие методы реализуются в разделе Protected. Поэтому потомки тех встроенных классов, в которых реализуются такие методы, могут делать доступными их для пользователя. Например, рассмотрим, как переопределяется метод послания уведомления о событии Click для кнопки, одновременно изменяя ее заголовок на строку, в которую записано текущее время. TtimeButton=class(Tbutton) Protected Procedure Click; override; End; Procedure TtimeButton.Click; Inherited Click; Caption:=DateTimeToStr(Now); End;.

123


Методы послания уведомления о событии являются указателями на метод, которые вызываются в случае, когда компонент получает сообщение от Windows о том, что произошло некоторое событие, воздействующее на данный компонент. Обработчики событий представляют собой свойства, которые выполняют чтение и запись в указатели на метод. Например, рассмотрим фрагмент, взятый из класса TControl, содержащегося в исходном коде VCL. Имена обработчиков событий по соглашению содержат префикс On, Вначале объявляется событие: TNotifyEvent = procedure (Sender: TObject) of object;. Далее объявляется класс, содержащий поля данного типа, и обработчики событий, работающие с этими полями, Tcontrol=class(TComponent) Private FOnClick: TNotifyEvent; Ptotected Property Clxck;

OnClick:

TnotifyEvent

read

FOnClick

write

FOn-

end;.

При создании собственных обработчиков необходимо научиться строить методы посылки уведомления о событии. Эти методы должны уметь получать и обрабатывать сообщения Windows, что возможно сделать с помощью методов вида Message. УКАЗАТЕЛИ НА МЕТОДЫ Этот тип данных представляет собой ссылку (указатель) на метод. Указатель на метод содержит два адреса: одна ссылка на адрес кода метода, другая — на адрес экземпляра объекта - представляет собой скрытый параметр self. Адрес self представляет собой в данном случае адрес расположения данных, принадлежащих конкретному объекту. Указатели на методы реализуют один из принципов компонентной технологии — делегирование. Если кто-то написал класс, у которого есть поля в виде указателей на методы, например, Туре TNotifyEvent=procedure{Sender:Tobject) of object; TMyButton=clas s OnClick: TNotifyEvent; End;,

то можно менять поведение построенных (даже скомпилированных) объектов этого типа, просто присваивая этим указателям новые методы. Например, пусть объявлено: 124


Type TForml=class(TForm) Procedure OnButtonlClick(Sender:Tobject); Buttonl:MyButton; End;. Теперь при построении компонента на форме можно делегировать обработчик OnButtonlClick из TForml в MyButton путем следующего присваивания: MyButton.OnClick:=Forml.OnButtonlClick;. ПРИМЕР ПРИЛОЖЕНИЯ 15 Продолжим рассмотрение примера 14. Попытаемся не только динамически создавать новые объекты, но и разрушать их также динамически. Выберем, что уничтожение очередного объекта будет наступать, как только пользователь нажмет на клавишу Backspace (#8). Для реализации этой идеи понадобятся указатели на методы. В данном случае динамически созданный объект для своего уничтожения должен отслеживать нажатие клавиши #8. Для этих целей может служить событие OnKeyPress. Этому событию надо делегировать собственное событие. Прежде всего необходимо убедиться, что оно объявлено как указатель на метод, иначе делегирование невозможно. В справочной системе Delphi можно найти следующее объявление: TKeyPressEvent=procedure(Sender:Tobject;var key:char) of object; Property OnKeyPress: TKeyPressEvent;. Таким образом, для делегирования необходимо создать свою процедуру. В классе TForml добавим собственный обработчик: Procedure MyButtonKeyPress(Sender:Tobject; var key:Char);. Кроме того, в класс TForml необходимо добавить переменную для записи того динамически построенного метода, который подлежит удалению, так как может удаляться не только текущий объект, но и предыдущий (если новые объекты не создаются). Добавим такое объявление в секцию Private ToDestroy: TMyButton;. Теперь определим новую процедуру обработки события OnKeyPress: procedure TForml.MyButtonKeyPress(Sender: TObject; var Key: Char); begin if key=#8 then ToDestroy:=Sender as TButton; end;. 125


Эта процедура не уничтожает, а запоминает подлежащий уничтожению объект. Чтобы она работала, необходимо при создании кнопки делегировать наш обработчик, записывая OnKeyPress: =MyButtonKeyPress ; . Допишем еще одну строку для установки фокуса на текущий объект SetFocus;, т.е. после удаления очередного объекта необходимо переместить фокус ввода на предыдущий объект. Удаление кнопки реализуем в обработчике OnTimer. Полный текст программы приводится ниже. unit p r i m l 5 ; Interface uses Windows, Messages, SysUtils,Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TMyButton=class(TButton) public Constructor Create(AOwner:TComponent); override; Destructor Destroy; override; class function GetCount:integer; end; TForml = class(TForm) Timerl: TTimer; procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure TimerITimer(Sender: TObject); private ToDestroy:TMyButton; Procedure MyButtonKeyPress(Sender:Tobject; var key:Char); end; var Forml: TForml; implementation var CountBtns: integer = 0; {$R *.dfm} Constructor TMyButton.Create(AOwner: TComponent); begin inherited; Inc(CountBtns); end; Destructor TMyButton.Destroy; begin .dec(CountBtns); inherited; end; class function TMyButton.GetCount: integer; begin Result:=CountBtns; end; 126


procedure TForml.MyButtonKeyPress(Sender: TObject; var Key: Char); begin if key=#8 then ToDestroy:=Sender as TMyButton; end; procedure TForml.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin with TMyButton.Create(self) do begin Parent:=self; Left:=x; Top:=y; Width:=Width+60; Caption:=Format('%d кнопка x,y= %d,%d', [GetCount,x,y]); OnKeyPress:=MyButtonKeyPress; SetFocus; end; end; procedure TForml.TimerlTimer(Sender: TObject); begin if Assigned(ToDestroy) then begin SelectNext(ToDestroy,false,true); ToDestroy.Free; ToDestroy:=nil; end ; Caption:=Format('Кнопок %d',[TMyButton.GetCount]) ; end; end.

Вариант решения получим таким же, как на рис. 35. Однако в данном варианте, нажимая неоднократно на клавишу Backspace, можно удалить все кнопки, построенные на форме. Все переменные типа класс (например, ToDestroy) по сути являются указателями, поэтому для проверки, существует ли тот или, иной объект, применяется функция Assigned (обработчик TimerlTimer), которая прове-

ряет, равна переменная значению "пустой указатель" (т.е. nil) или нет. ТИПЫ ССЫЛКИ НА КЛАСС

Такой программный элемент, как тип обычной переменной, представляющий собой правила обращения с теми или иными переменными, существует только во время компиляции программы. Эти правила не могут измениться во время выполнения программы. Типы ссылки на класс, которые синтаксически объявляются как class of TmyClass, позволяют нарушить

указанные правила. С помощью этих типов можно обращаться к объектным типам во время выполнения программы. Для некоторого заданного типа

127


объектный тип — это значение, которое можно записать в переменную типа ссылки на класс. Во время выполнения программы в разных ее участках переменной данного типа можно присваивать в качестве значения различные объектные типы и, таким образом, строить нужные объекты, вызывая тот или иной виртуальный конструктор. СВОЙСТВА Свойства представляют интерфейс с внутренними полями данных того или иного объекта. Внутренние поля обычно объявляются в разделе Private. Их имена, как правило, начинаются с буквы F, например поле FColor. Соответственно свойство, с помощью которого осуществляется доступ к заданному полю FColor, имеет имя Color. Свойства могут объявляться с различной степенью доступа. Если требуется, чтобы они отображались в окне инспектора объектов, их объявляют в разделе Published. Свойства могут создаваться с помощью различных типов данных, а именно: • Simple - простые свойства; • Enumerated — перечисляемые; • Set - множества; • Object - объектного типа; • Array - индексированные свойства. Разные типы свойств по-разному отображаются в инспекторе объектов и имеют свои собственные редакторы для изменения значений свойств. Далее рассмотрим синтаксис записи различных видов свойств. СВОЙСТВА SIMPLE

Простые свойства включают числовые, символьные и строковые типы данных. Наиболее часто используются свойства типа integer и string. Например, свойства Width, Height имеют тип integer, свойство Caption имеет тип string. Объявим для демонстрации синтаксиса записи простых свойств следующий класс: Туре TSimple=class(TCustomControl) Private FString: string; Published Property StringPrqp:string read FString write FString; end;.

Как следует из этого примера, свойства имеют тип поля, в котором хранится значение данного свойства. Кроме того, для свойства необходимо ука128


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

СВОЙСТВА ENUMERATED

Свойства enumerated определяются типом перечень и логическим типом Boolean. Обычно все возможные значения данного типа свойств некоторым способом помечаются, например, как в объявлениях ниже: Туре TEnumProp=(epZero, epOne, epTwo, epThrae); TEnum=class(TCustomControl) Private FEnumProp: TEnumProp; Published Property EnumProp:TEnumProp read FEnumProp write FEnumProp; end;. СВОЙСТВА SET

Элементы свойства Set (множество) в инспекторе объектов заключаются в квадратные скобки. Для развертывания в инспекторе объектов элементов базового множества, включающего в себя все возможные варианты, имеется знак "+", расположенный слева от наименования свойства. Включить или не в��лючить тот или иной элемент из базового множества в рабочее множество выбирают путем указания true или false. Рабочее множество определяет значение данного свойства. Рассмотрим пример объявления свойства Set. При этом используется тип TEnumProp, объявленный выше. Туре TSetProp=set of TEnumProp TSetClass=class(TCustomControl) Private FSetProp:TSetProp; Published Property SetProp:TsetProp read FSetProp write FSetProp; End; . СВОЙСТВА OBJECT

Свойства Object в инспекторе объектов помечаются или знаком "+" или кнопкой с многоточием (•••)• Например свойство Font имеет объектный тип. Для того чтобы привести пример объявления свойства объектного типа, необходимо предварительно сформировать объект какого-либо класса и вы9 — 4758

129


брать предка для этого класса. На практике в большинстве случаев в качестве предка выбирается встроенный класс TPersistent. Объявим класс, который будет определять далее в примере тип свойства. Туре TObjectDop=class (TPersistent:) Private FMylnt:integer; Public Property MyProp:integer read FMylnt write FMylnt; End; .

В данном случае нужно решить одну проблему. Объявленный выше класс (TObjectDop) должен войти как составная часть в класс, который должен будет содержать свойство заданного объектного типа TObjectDop. Соответственно потребуется задавать этому свойству значение, а это означает, что потребуется объект типа TObjectDop, для которого необходимо обеспечить выделение памяти и освобождение памяти. Стандартных методов выделения и освобождения памяти в данном случае недостаточно. Для выделения памяти создадим конструктор create, а для высвобождения памяти — деструктор Destroy. Туре TObjectProp=class(TCustomControl) Private FObj ectProp: TObjectDop; Public Constructor Create(AOwner:TComponent);override; Destructor Destroy;override; Published Property ObjectProp: TObjectDop read FObjectProp write FObjectProp; End; .

Конструктор и деструктор объявлены с командой Override. Это означает, что в данном классе переопределяются имеющиеся по линии наследования стандартные виртуальные конструктор Create и деструктор Destroy.

Определим эти методы. Constructor TObjectProp.Create(AOwner:TComponent); begin Inherited Create(AOwner); FObjectProp:= TObjectProp.Create; end; Destructor TObjectProp.Destroy; Begin FobjectProp.Free;

130


inherited Destroy; End;.

Необходимо обратить внимание на то, в какой последовательности вызываются стандартные методы с помощью процедуры inherited. СВОЙСТВА ARRAY

Свойства Array позволяют создать очень похожие на массивы индексированные свойства, которые отличаются от обычных массивов по двум основным аспектам: • свойства типа Array могут индексироваться строковым значением; • свойства типа Array могут получать доступ только к одному элементу за одно чтение. В окне Object Inspector эти свойства помечаются кнопкой с многоточием (—). Для изменения значений свойств данного вида вызывается специальный редактор. Ниже приводится пример объявления индексированного свойства. Туре Str7=string[7]; TMas=array [1..3] of str7; TMasClass=class(TCustomControl) Private FMasProp: TMas; Function GetMasInt(pIndex:integer):string; Function GetMasStr(plndex:string):integer; Public Constructor Create(AOwner:TComponent);override; Property MasPropInt[Index:integer]:string read GetMasInt; Property MasPropStr[Index:string]:4integer read GetMasStr; end;.

В данном случае переопределяется конструктор Create, который необходим не для выделения памяти, а для задания значений элементам индексированного свойства. Поэтому необходимо будет исключить вызов стандартного конструктора с помощью команды inherited. Далее определим конструктор и две функции для чтения значений элементов индексированного свойства, которое в данном случае имеет доступ к соответствующему полю только по чтению. Constructor TMasClass.Create(AOwner:TComponent); begin FMasProp[1]:='one'; FMasProp[2]:='two'; FMasProp[3]:='three'; End; Function TMasClass.GetMasInt(plndex:integer):string; 9»

131


begin result:='unknown'; if plndex in [1..3] then result:= EMasProp[plndex]; end; Function TMasClass.GetMasStr(plndex:string):integer; var x:integer; begin result:=-l; for x:=l to 3 do if Uppercase(FMasProp[x])=UpperCase(plndex) then begin result:=x; exit; end; end;.

Здесь первая функция может читать значение свойства при заданном индексе, а вторая может использоваться для поиска индекса по заданному значению. ЗАДАНИЕ НАЧАЛЬНЫХ ЗНАЧЕНИЙ СВОЙСТВАМ

Когда создаются свойства, они первоначально получают нулевые или неопределенные (в зависимости от типа свойства) значения. Если необходимо задать свойствам какие-либо значения по умолчанию, то используются следующие шесть команд: Default, NoDefault, Default для свойств типа массив, Stored, Index, Dispid. Например, Property NumberProp:integer read Fnumber write FNumber default 5; Property EnumProp: TEnumProp Read FenumProp write FEnumProp default epOne;.

Для свойства типа массив команда Default указывается после точки с запятой без использования какого-либо значения. Массив, если он задан при объявлении класса, представляет собой начальные значения элементов индексированного свойства. Property ArrayPropInt[Index:integer]: string read GetArrayPropInt; Default;.

Команда NoDefault применяется обычно для унаследованных свойств при указании, что родительское значение не действует. Команда s t o r e d используется для того, чтобы указать, сохранять ли текущее значение, например, Property NumProl:integer read Fnuml write FNuml stored true; Property NumPro2:integer read FNum2 write FNum2 Stored falserProperty NumPro3:integer read Fnum3 write Fnum3 Stored Fun3;.

132


В первом случае указано, что необходимо сохранять текущее значение свойства, во втором — не сохранять, в третьем — сохранять, если функция Fun3 возвращает -true.

Команда index позволяет обеспечить гибкость при объявлении методов доступа Read и Write к полям. Рассмотрим это на примере. Пусть два свойства IntPropl и intProp2 для доступа к полям FintPropi и FintProp2 используют одни и те же подпрограммы: property IntPropl:integer Index 1 read GetlntProp write SetlntProp; property IntProp2:integer Index 2 read GetlntProp write SetlntProp;.

Для указания того, что свойство intPropl работает с полем FintPropi, a intProp2 - с полем FlntProp2, используется команда Index. В данном случае число после слова index указывает, какое поле следует использовать, например, подпрограммы GetlntProp и SetlntProp можно определить так: Function <имя класса>.GetlntProp begin

(plndex:integer):integer;

case plndex of

1:result:=FIntPropl; 2:result:=FIntProp2; end; end;

Procedure <имя класса>.SetlntProp

(plndex:integer; pValue:integer);

begin case plndex of 1: FintPropi:= pValue; 2: FIntProp2:= pValue; end; end;.

Команда Dispid применяется при работе с объектами OLE. ПРИМЕР П Р И Л О Ж Е Н И Я 16

Пусть требуется построить на форме три геометрические фигуры: круг, квадрат, эллипс. Необходимо обеспечить возможность изменения цвета каждой фигуры. Пусть будет по два цветовых варианта каждой фигуры (рис. 36). Для решения задачи построим четыре класса. Первый класс назовем TDrawArea. Включим в него следующие действия: инициализация всех по133


лей, рисование фигуры. Остальные три класса определяют конкретную реализацию рисования круга, квадрата и эллипса.

Рис 36

Программа приводится ниже. unit PRIM16; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForml = class(TForm) Panell: TPanel; btnRedCircle: TButton; btnBlueCircle: TButton; btnBlackSquare: TButton; btnWhiteSquare: TButton; btnYellowEllipse: TButton; btnGreenEllipse: TButton; Buttonl: TButton; procedure AllBtnClick(Sender: TObject); procedure ButtonlClick(Sender: TObject); end; ArrayColor=array[1..6] of TColor; TDrawArea — class(TPersistent) private xl,yl,x2,y2:integer; FArrayBrushColor: ArrayColor; FPenColor: TColor; function GetBrushColor(plndex:integer): TColor; public constructor InitColor; constructor InitXY(NewXl,NewYl,NewX2,NewY2:integer); property PPenColor: TColor read FpenColor write EpenColor Default clBlack; property PBrushColor[plndex:integer]: TColor

134


read GetBrushColor; procedure DrawFxg;virtual;abstract; procedure Drawlt; end; TSquare = class(TDrawArea) procedure DrawFig;override; end; TEllipse = class(TDrawArea) procedure DrawFig;override; end; TCircle = class(TDrawArea) procedure DrawFxg;override; end; var Forml: TForml; MySquare:TSquare; MyEllipse:TEllipse; MyCircle:TCircle; implementation {$R *.DFM} Constructor TDrawArea.InitColor; begin FArrayBrushColor[1]:=clRed; FArrayBrushColor[2]:=clBlue; FArrayBrushColor[3]:=clBlack; FArrayBrushColor[4]:=clWhite; FArrayBrushColor[5]:=clYellow; FArrayBrushColor[6]:=clGreen; End; Constructor TDrawArea.InitXY(NewXl,NewYl,NewX2,NewY2: integer); begin xl:=NewXl; yl:=NewYl; x2:=NewX2; y2:=NewY2; end; Function TDrawArea.GetBrushColor(plndex:integer):TColor; begin result:=-l; if plndex in [1..6] then result:=FArrayBrushColor[plndex]; end; procedure TDrawArea.Drawlt; begin DrawFig; end; procedure TSquare.DrawFig; begin Forml.Canvas.Rectangle(xl,yl,x2,y2);

135


end; procedure TCircle.DrawFig; begin Forml.Canvas.Ellipse(xl-x2,yl-x2,xl+x2,yl+x2); end; procedure TEllipse.DrawFig; begin Forml.Canvas.Ellipse(xl,Ń&#x192;1,x2,y2); end; procedure TForml. AllBtnClick{Sender: TObject); begin if (Sender as Tbutton=btnKedCircle) or (Sender as Tbutton=btnBlueCircle) then begin i f not assigned(MyCircle) then begin MyCircle:=TCircle.Create; MyCircle.InitColor; MyCircle.InitXY(50,50,40,0); Forml.Canvas.Pen.Color:=MyCircle.PPenColor; end; i f Sender as Tbutton = btnRedCircle then begin Forml.Canvas.Brush.Color:=MyCircle.PBrushColor[1]; MyCircle.Drawlt; end; i f Sender as Tbutton = b t n B l u e C i r c l e then begin Forml.Canvas.Brush.Color:=MyCircle.PBrushColor[2]; MyCircle.Drawlt; end; end; if (Sender as Tbutton=btnBlackSquare) or (Sender as Tbutton=btnWhiteSquare) then begin i f not assigned(MySquare) then begin MySquare:=TSquare.Create; MySquare.InitColor; MySquare.InitXY(110,10,190,90); Forml.Canvas.Pen.Color:=MySquare.PPenColor; end; i f Sender as Tbutton = btnBlackSquare then begin Forml.Canvas.Brush.Color:=MySquare.PBrushColor[3]; MySquare.Drawlt; end; i f Sender as Tbutton = btnWhiteSquare t h e n begin Forml.Canvas.Brush.Color:=MySquare.PBrushColor[4]; MySquare.Drawlt; end; end; if (Sender as Tbutton=btnYellowEllipse) or (Sender as Tbutton=btnGreenEllipse) then begin i f not assigned(MyEllipse) then begin

136


MyEllipse:=TEllipse.Create; MyEllipse.InitColor; MyEllipse.InitXY(220,10,280,90); Forml.Canvas.Pen.Color:=MyEllipse.PPenColor; end; i f Sender as Tbutton = b t n Y e l l o w E l l i p s e then begin Forml.Canvas.Brush.Color:=MyEllipse.PBrushColor[5]; MyEllipse.Drawlt; end; i f Sender as Tbutton = b t n G r e e n E l l i p s e then begin Forml.Canvas.Brush.Color:=MyEllipse.PBrushColor[6]; MyEllipse.Drawlt; end; end; end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; begin if assigned(MyCircle) then MyCircle.Free; i f assigned(MySquare) then MySquare.Free; if assigned(MyEllipse) then MyEllipse.Free; close; end; end.

В данном примере показано, как работать со свойствами типа Array. Следует обратить внимание, что каждый из трех используемых объектов должен строиться один раз. Для проверки существования объекта используется функция Assigned. При выходе из программы (ButtonlClick) все объекты разрушаются. В данной программе показано, как использовать один обработчик событий (AllBtnciick) для всех шести кнопок построения геометрических фшур. Используемые конструкторы InitColor и InitXY созданы не для построения объектов, а для инициализации данных, поэтому в их описании отсутствует вызов inherited.

ФАЙЛОВЫЕ ТИПЫ Тип файл представляет собой последовательность элементов одного типа, обычно расположенных на внешнем устройстве, например на жестком диске. Можно выделить три основных типа файлов на физическом устройстве: 1. Данные в файле располагаются в виде строк разной длины. Каждая строка заканчивается двумя символами #13 #10 (#13 - возврат каретки, #10 137


перевод строки). Эти символы отделяют строки друг от друга. Максимальный размер строки ограничен, чаще всего он равен 128 символам, можно увеличить этот размер до максимального, равного 255 символам. При поиске нужной информации в таком файле необходимо найти #13 #10, прочитать найденную строку и проверить, есть ли заданная информация в этой строке, затем опять следует искать #13 и #10 и т.д. Таким образом, поиск может осуществляться, последовательно читая этот файл только сначала, т.е. файл рассматривается как один массив строк, разделенных символами #13 #10. Если нужно вставить информацию в середину такого файла, то это можно сделать путем переписывания старого файла в новый со вставкой новых строк и последующим удалением старого файла. Операционная система допускает дописывание информации в конец файла. В соответствии со свойствами такой файл называют файлом последовательного доступа, или текстовым. 2. Данные представляются в виде записей одинаковой длины. Никаких разделителей между записями нет. Операционная система может расположить головку считывания-записи информации на любой байт (установить файловый указатель). Так как все записи одинаковой длины, файловый указатель операционной системой может быть установлен на начало любой записи, таким образом получается, что все записи будто бы пронумерованы. Первая запись нумеруется числом 0. Если длина записи равна 80 байт, то установку файлового указателя на начало второй записи (запись с номером 1) операционная система выполняет на отметке 80 байт. Эту запись можно прочитать или перезаписать. Такие файлы являются типизированными файлами прямого доступа (в отличие от текстовых файлов). 3. Файлы рассматриваются как последовательность байт. Эти файлы являются файлами прямого доступа с длиной записи, чаще всего, равной 1 байту. С помощью такого подхода можно прочитать любой файл, но трудно интерпретировать информацию. При работе с файлами автоматически проверяются ошибки ввода или вывода, если включена (по умолчанию) директива {$1+}. В этом случае при возникновении ошибки программа завершается. Если директива проверки файловых ошибок отключена {$1-}, то прог рамма продолжает выполняться и можно проверить результат работы с файлом с помощью стандартной функции IOResuit.

ТЕКСТОВЫЕ ФАЙЛЫ Этот тип файлов объявляется с помощью файловой переменной, типа TextFile: Var Т : TextFile;. Перед тем как начинать работу с файлом, необходимо файловую переменную связать с конкретным файлом. Это осуществляется с помощью сле138


дующей процедуры: AssxgnFile(<ф.п.>, Наше) ;, где <ф.п.> —файловая переменная, Name - строка типа String, содержащая имя файла, например AssxgnFxle(Т, ' a . t x t ' ) ; .Строка Кате может содержать не только имя файла, но и путь к файлу. По окончании работы с файлом его нужно закрыть процедурой c l o s e F i l e (<ф.п.>). Это требование обязательно должно соблюдаться для файлов, в которые записывалась информация. С помощью файлов можно вводить информацию в память, хранить информацию, передавать ее от одних программных единиц к другим. Для чтения и записи информации используются встроенные стандартные процедуры. Перед чтением информации необходимо явно указать, что файл будет использоваться только для чтения с помощью процедуры Reset (<ф. п. >). Эта процедура устанавливает файловый указатель на начало файла. Чтение информации осуществляется с помощью следующих процедур: ReadLn(<ф.п.>, <список>); Read(<ф.п.>, <список>);. Первая процедура позволяет полностью читать строку вместе с символами #13 #10. Вторая процедура читает элемент или несколько элементов строки, но в пределах одной строки. Например, Read(T, а , Ь , с ) ; ReadLn(Т, а , Ь , с ) ; . Если а, Ь, с - строковые переменные, то первый оператор заполнит данными только одну переменную а (Ь и с останутся пустыми). Текстовые файлы имеют одну особенность: несмотря на то, что файл состоит из строк, он может содержать, наряду со строковыми данными, и другие типы данных, например числовые. Прочитать из текстового файла можно данные следующих типов: целого, вещественного, символьного, строкового. Дополнительно к перечисленным типам записывать можно еще логический тип данных. При чтении чисел автоматически происходит перекодирование информации в двоичный код. Если элементы не строковые, то в переделах одной строки файла может находиться несколько элементов, обязательно отделенных друг от друга разделителями. В качестве разделителей используются следующие символы: пробел, символ табуляции, возврат каретки (#13). При подготовке информации необходимо соблюдать эти требования - записывать в явном виде разделители. Процедуры Read и ReadLn читают информацию последовательно по одному символу (в том числе читаются #13, #10 и #26 - конец файла), причем, если чтение происходит не в строковые переменные, то значимыми для Read являются все виды разделителей, а для ReadLn - только #13 и #10. Например, при чтении информации в строковые переменные String или PChar заносятся очередные символы до #13 и #10.

139


Для подготовки текстовых файлов можно использовать не только программную среду Delphi, но и любые простые редакторы. При записи информации необходимо для файла задать явно режим записи. Для текстовых файлов режим записи задается одновременно с режимом создания нового файла. При этом используется процедура Rewrite (<ф. п. >). Если файл, указанный в соответствующей процедуре AssignFile, существует, то он уничтожается и создается новый. Запись информации в файл осуществляется процедурами Write(<ф.п.>,<список>) или WriteLn (<ф. п. > ,<список>). Write записывает очередной элемент строки, WriteLn записывает строку вместе с #13 и #10. В отличие от процедур чтения, в списке можно указывать не только переменные, но и выражения. Если элементы числовые, то в списке необходимо явно предусматривать разделители между этими элементами. Например, WriteLn (Т, 'число а=' ,а,' ' , 'число Ъ=' ,Ь) ;.

ТИПИЗИРОВАННЫЕ Ф А Й Л Ы Файловая переменная для этого типа объявляется следующим образом: Var F : file of «тип элементов»;. Тип элементов может быть любым, за исключением файлового типа и указательного. Например, Var F : file of Double;.

Как и в случае текстовых файлов, для работы с типизированными файлами необходимо файловую переменную связать с конкретным файлом, затем указать режим работы с этим файлом и по окончании закрыть его, например, AssignFile(F, *c:\a.dat'); Reset(F); {или Rewrite(F)} CloseFile(F);.

Процедура Rewrite, как и в случае текстовых файлов, открывает новый типизированный файл для записи информации. Однако процедура Reset работает не так, как с текстовыми файлами, - она устанавливает доступ и на чтение, и на запись информации. Чтение и запись осуществляются следующими процедурами: Read(<$.n.>, <список>); Write(<ф.п.>, <список>);.

Так как данный файл является файлом прямого доступа, можно устанавливать файловый указатель на любую запись файла. Для этой цели используется процедура Seek(<ф.п.>,<номер ааписи>); {отсчет от 0}.

140


Найденную запись можно прочитать или снова перезаписать. В отличие от текстовых файлов, при записи информации в типизированный файл нельзя использовать выражения в процедуре Write. Ф А Й Л Ы БЕЗ ТИПА В данном случае файловая переменная указывается так: Var F : file;. Связывание файловой переменной с конкретным файлом и закрытие файла осуществляют так же, как и для типизированных файлов. Однако задание режима работы с файлом отличается от аналогичной операции, применяемой к типизированному файлу: Beset(<$.n.>, Size); Rewrite(<ф.п.>. Size);.

Добавляется дополнительный параметр Size — размер элементов в файле. Величину Size можно устанавливать равной 1. Можно не указывать Size, тогда система по умолчанию примет величину 128. Чтение и запись производятся процедурами: BlockRead(<ф.п.>, Buffer, N, Result); BlockWrite(<ф. п. >, Buffer, N, Result);.

Здесь Buffer обычно задается описанием Array ... of Char или Array ... of Byte, N — число элементов, которое требуется прочитать или записать; Result — конкретное число элементов, которое было прочитано или записано. Если пользователь знает, какая информация записана в файле, то можно задавать конкретный размер записи. Например, если необходимо работать со структурами данных, то, используя функцию sizeOf, можно определить размер записи, кратный полученному с помощью SizeOf числу. Использование процедур BlockRead и BlockWrite в этом случае значительно ускорят работу с файлами. ДОПОЛНИТЕЛЬНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ Существует достаточно большое число процедур для работы с файлами, например текстовый файл можно дописывать с помощью задания специального режима: Append(<ф.п.» ;. Эта процедура устанавливает указатель в конец файла. Очень полезной является функция Eof (<ф.п.>) (End of file). Она возвращает true, если достигнут конец файла. С помощью этой функции можно контролировать навигацию по файлу: While not Eof(F) do begin

141


Read(F, a); end;.

. . . .

Процедура Read (как и процедура write) сдвигает файловый указатель на единицу. Таким образом, с помощью приведенного выше цикла можно прочитать файл до конца. ПРИМЕР П Р И Л О Ж Е Н И Я 17

Пусть имеется некоторый текст, его необходимо занести в текстовый файл, зашифровать, переписывая в другой файл, и таким образом подготовить для отправки, например, по электронной почте. Пусть шифрование осуществляется методом простой одноалфав��тной подстановки. Все символы исходного файла заменяются кодами, отличающимися от исходных символов на определенную величину, которая меняется от символа к символу. Для кодирования с помощью генератора случайных чисел создается кодировочная таблица, например, из 23 чисел (адресат знает о числе 23). Эта таблица представляет собой алфавит кодирования. Текст разбивается на группы символов, включая #13 #10, и каждый символ изменяется в соответствии с кодировочной таблицей, т.е. код первого символа увеличивается на первое число из таблицы, код второго — на второе число из таблицы и т.д. Для осуществления дешифровки кодировочную таблицу также необходимо записать в итоговый файл. К О М П О Н Е Н Т TMAINMENU

При решении данной задачи построим меню с помощью компонента TMainMenu. Это меню можно представить в виде табл. 13. Список объектов меню (строк меню в табл. 13) TMainMenu содержится в свойстве Items. Строки меню имеют тип TMenuItems. Tmenultems в свою очередь содержит свойство Items, с помощью которого можно создавать подменю. Текст строки меню записывается в свойстве Caption свойства Items. Исходный файл

Кодирование

Новый файл Добавить Прочитать

Таблица кодировки Кодирование файла

Проверка

Таблица 13 Выход

С активизацией меню связано событие OnClick. Данное событие выполняется, когда пользователь выбирает какой-либо пункт меню. К каждому пункту меню необходимо привязать свой обработчик. Доступ к пунктам меню осуществляется по свойству Name (имя). В табл. 14 приводятся варианты имен пунктов меню, которые используются далее в программе. 142


Таблица 14 MainFile

CodeFile

NewFile AddFile ReadFile

CodeTabl InfoCode

InfoCompare

ExitPoint

Для придания меню более привлекательного вида можно добавлять разделительные полосы (достаточно задать одним символом "минус" ("-")). При проектировании меню с помощью редактора меню (рис. 37) необходимо выбрать имена (свойство Name) пунктам меню (см. табл. 14).

"У; MdniF orni MariMeu i li | l^fi , X | ЩКодирсеание Проверка Выход 1 j Новый файл Г * ( Добавить юс Прочитать \ _ i

1

Рис. 37

В пункте меню можно назначить быструю клавишу, для этого перед каким-либо символом нужно поставить символ &, например &Close, и можно вызывать пункт меню в данном случае с помощью Alt + С (первая буква слова Close). Пункту меню можно назначить горячую клавишу, тогда в любое время можно выполнить данный пункт меню. Для этого нужно воспользоваться свойством ShortCut. Пункт меню можно сделать недоступным, если в процессе выполнения свойству Enabled задать false. С помощью Visible можно скрыть любой пункт меню или подменю. Свойство Checked позволяет отмечать флажком последний выбранный пункт меню. Форма примера 17 приводится на рис. 38.

Рис. 38

143


Справа на компоненте Memol формы примера 17 находится изображение компонента MainMenul, которое будет невидимым при выполнении программы. Ниже приводится программный код примера. unit P r i m l 7 ; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , E x t C t r l s , Menus, B u t t o n s ; type TMainForm = c l a s s ( T F o r m ) MainMenul: TMainMenu; MainFile: TMenuItem; CodeFile: TMenuItem; I n f o C o m p a r e : TMenuItem; TMenuItem; ExitPoint: NewFile: TMenuItem; AddFile: TMenuItem; ReadFile: TMenuItem; CodeTabl: TMenuItem; TMenuItem; InfoCode: Panell: TPanel; Memol: TMemo; BitBtnl: TBitBtn; BitBtn2: TBitBtn; procedure NewFileClick(Sender: T O b j e c t ) ; procedure ExitPointClick(Sender: T O b j e c t ) ; procedure AddFileClick(Sender: T O b j e c t ) ; procedure ReadFileClick(Sender: T O b j e c t ) ; procedure CodeTablClick(Sender: T O b j e c t ) ; procedure InfoCodeClick(Sender: T O b j e c t ) ; procedure BitBtnlClick(Sender: T O b j e c t ) ; procedure BitBtn2Click(Sender: T O b j e c t ) ; procedure InfoCompareClick(Sender: T O b j e c t ) ; end; Const n = 2 5 ; stext='Ошибка открытия ф а й л а ' ; Type T T a b l e C o d e = a r r a y [ l . . n ] o f c h a r ; var M a i n F o r m : TMainForm; TableCode:TTableCode; T: TextFile; F: File of TTableCode; FNoType: File; implementation {$R *.DFM) procedure TMainForm.NewFileClick(Sender: T O b j e c t ) ; begin Panell.Visible:=true; BitBtn2.Visible:=true;

144


memol.SetFocus ; end; procedure TMainForm.ExitPointClick(Sender: T O b j e c t ) begin close; end; procedure TMainForm.AddFileClick(Sender: T O b j e c t ) ; begin Panell-Visible:=true; BitBtn2.Visible:=true; Memol.Lines.LoadFromFile('Inpfil.txt'); memol.SetFocus; end; procedure TMainForm.ReadFileClick(Sender: T O b j e c t ) ; var s t r : s t r i n g ; begin

{$1-}

AssignFile(T,'Inpfil.txt'); reset(T); {$!+} if IOResultoO then begin M e s s a g e D l g ( s T e x t , m t E r r o r , [ m b O k ] , 0) ; exit; end; Panell.Visible:=true; BitBtn2.Visible:=true; w h i l e n o t e o f ( T ) do b e g i n readln(T,str); Memol.Lines.Add(str); end; CloseFile(T); memol.SetFocus; end; procedure TMainForm.CodeTablClick(Sender: T O b j e c t ) ; var i : byte; str:string; begin Randomize; f o r i : = l t o n do TableCode[i]:=char(Random(256)); Panell.Visible:=true; BitBtn2.Visible:=false; str:=''; f o r i : = 1 t o n do s t r : = s t r + T a b l e C o d e [ i ] + ' '; Memol.Lines.Add(str); end; procedure TMainForm.InfoCodeClick(Sender: T O b j e c t ) ; var i,res: integer; 10 â&#x20AC;&#x201D; 4758


Buffer,Buf:TTableCode; str: string; k: b y t e ; begin

{$1-}

AssignFile(F,'Outfil.dat1); rewrite(F); if IOResultoO then begin MessageDlg(sText,mtError,[mbOk],0); exit; end; AssignFile(FNoType,'Inpfil.txt'); reset(FNoType,1); {$I+> if IOResultoO then begin MessageDlg(sText,mtError,[mbOk],0); CloseFile(F); exit; end; Write(F,TableCode); Panell.Visible:=true; B i 1 ; B t n 2 . V i s i b l e : =f a l s e ; w h i l e n o t eof(FNoType) do b e g i n BlockRead(FNoType,Buffer, n, r e s ) ; str:='1; f o r i : = l t o n do b e g i n if i<=res then begin k:=ord(Buffer[i])+ord(TableCode(i]); Buf[i]:=chr(k); end e l s e B u f [ i ] : = T a b l e C o d e [ i ] ; str:=str+Buf[i]+' '; end; Memol.Lines.Add(str); Write(F,Buf); end; CloseFile(FNoType); CloseFile(F); end; procedure TMainForm.BitBtnlClick(Sender: T O b j e c t ) begin Memol.Lines.Clear; Panell.Visible:=false;

end;

procedure TMainForm.BitBtn2Click(Sender: begin Memol.Lines.SaveToFile('Inpfil.txt'); BitBtnlClick(Sender); end;

146

TObject)


procedure TMainForm.InfoCompareClick(Sender: var i : integer; Buf:TTableCode; str:string; к: byte; begin

TObj e c t ) ;

{$1-}

AssignFile(F,'Outfil.dat'); reset(F);

{$1+}

if

IOResultoO then begin MessageDlg(sText,mtError,[mbOk],0); exit; end; Panell.Visible:=true; BitBtn2.Vis ible:=false ; str:=''; seek(F,1); w h i l e n o t e o f ( F ) do b e g i n read(F,Buf); f o r i : = l t o n do b e g i n k:=ord(Buf[i])-ord(TableCode[i]); i f k=10 t h e n b e g i n Memol.Lines.Add(str) ; str:=''; end e l s e s t r : = s t r + c h r ( k ) ; end; end; CloseFile(F); end; end.

Следует отметить, что в данной программе происходит управление доступом К Panell и кнопке BitBtn2 (отдельно) с помощью свойств Visible. В примере 17 используются два файла: текстовый — inpFil. txt и типизированный - OutFil. dat. Для работы с этими файлами введены три файловые переменные. Текстовый файл открывается как стандартный текстовый для корректировки и просмотра и как файл без типа, чтобы посимвольно прочитать информацию и закодировать ее. Для кодирования введена переменная к типа Byte. Обратить внимание, что при сложении, возможно, может получиться значение, выходящее за пределы диапазона этого типа, т.е. более 255. Однако перекодирование происходит корректно, так как при вычитании также происходит выход за пределы диапазона и прежнее число восстанавливается (полезно поэксперементировать с подобными способами сложения и вычитания, используя отдельную программу). При добавлении

10'

147


или занесении новой информации через компонент Memo необходимо в конце текста обязательно набирать Enter.

УКАЗАТЕЛИ Как было отмечено вьппе, переменные могут располагаться или в статической или в динамической памяти. В первом случае под них выделяется вполне определенный объем памяти и связи между программными элементами устанавливаются на этапе компиляции и компоновки программы. Во втором случае память выделяется на этапе выполнения программы. Причем она может освобождаться и повторно выделяться. Это позволяет эффективно ее использовать. При этом используется динамическая память — специально выделенная область оперативной памяти - Heap (куча). К динамическим переменным относятся рассмотренные выше строки и динамические массивы, а также классы. Переменные данного типа называются ссылками. Работа со ссылками, в общем случае, отличается от работы с обычными переменными тем, что необходимо выделя ть память на какомто этапе выполнения программы, а затем освобождать ее. Для строк память выделяется автоматически, автоматически строки (почти все типы) и уничтожаются. Это достигается довольно сложной структурой строки (кроме памяти под значение строки выделяется несколько байт для служебных целей). При работе с динамическими массивами приходится иногда (например, при присваивании) учитывать, что это ссылки. Для создания в программе собственных динамических переменных введен в язык Object Pascal тип указатель. Указателем иногда называют любую динамическую переменную. Существуют стандартные указатели Pointer и типизированные указатели. Переменная-указатель - это переменная, которая хранит не сами данные, а адрес размещения этих данных. Указатели Pointer - это просто адреса без указания, что по этим адресам записано. Типизированные указатели содержат в себе еще информацию о типе хранимых данных. При объявлении типизированного указателя задается базовый тип данных. В этом случае компилятор легко определяет необходимый объем динамической памяти, который требуется выделить данному указателю. Объявление типизированного указателя записывается, например, следующим образом: Type Var

148

Mas=array [1..10] of real; PtrMas="Mas; P:PtrMas;.


Это же можно записать по-другому: Var Р:лаггау Ц . . 10] of real;. Здесь объявлен указатель Р, который является физическим носителем адреса расположения одномерного массива из 10 вещественных чисел. В соответствии с объявлением указателя выделяется всего 4 байта статической памяти для записи адреса. Для записи данных вначале необходимо выделить динамическую память, ее адрес записать в переменную-указатель и после этого размещать данные. Рассмотрим, как синтаксически это может быть записано. Объявим две переменные: Var PI: integer; D: integer;. Теперь запишем: New (PI) ; Р1Л:=5; 0:=2+Р1Л; Dispose (PI) ;. Здесь первый оператор выделяет память, второй записывает число 5. Третий выполняет сложение с содержимым выделенной динамической памяти. Четвертый освобождает занятую память. Указателю можно присваивать значение n i l - пустой адрес, например Pl:=nil;. Для задания значения указателю можно воспользоваться операцией взятия адреса, например Pl:=@D;. В данном случае показано, что указателю можно присваивать адрес в статической памяти. Стандартный указатель объявляется, например, так: Var РР:Pointer;. Память под этот указатель выделяется с помощью следующей процедуры: GetMem (РР, 20) ;. Здесь выделено 20 байт динамической памяти. Эту процедуру можно использовать и для выделения памяти под типизированные указатели, например, для объявленного выше указателя Р: GetMem (Р, SizeOf (Mas)) ;. Функция SizeOf (Т) используется для указания размера базового типа для переменной-указателя, здесь т — базовый тип данных. Если память выделена процедурой GetMem(<укааатель>, size), то для освобождения памяти используется процедура FreeMem (<указатель>, Size). Например, FreeMem (Р, SizeOf (Mas)) ; .

К указателям применимы две операции сравнения: "равно" и "не равно". Возможно присваивание их друг другу, например, если объявлено: Var PI, Р2: "double; РЗ: Pointer;, то возможны присваивания: Р2:=Р1; или

РЗ :=Р2;. Нельзя присваивать Р2 :=РЗ, так как переменная Р2 требует задания базового типа, а РЗ его не имеет. Присваивание РЗ:=Р2 происходит с потерей информации о базовом типе. Для проверки значения указателя на неравенство nil можно использовать функцию Assigned (<указаФель>). Эта функция возвращает true, если значение указателя не равно nil. В модулях System и SysUtils размещены различные стандартные подпрограммы работы с указателями и динамической памятью. Рассмотрим, каков синтаксис присваивания значений элементам массива Р, объявленного выше: New(Р) ; Р л [1] :=6.7; Р А f2] : =-3.5; и т.д.

По окончании работы с массивом занятую память нужно освободить: Dispose(Р);.

149


П Р И М Е Р П Р И Л О Ж Е Н И Я 18

Прочитать целые числа из текстового файла в память и вывести их в обратной последовательности в три столбца: в первом столбце числа со значениями не больше 100, во втором - значения от 100 до 200 включительно, в третьем - значения больше 300. Файл можно подготовить с помощью примера 17. Пусть подготовлен файл InpFil.txt. Количество чисел в файле неизвестно. Пусть числа типа Byte. При решении задачи вначале определим возможное количество чисел, чтобы задать объем выделяемой динамической памяти, а затем, предварительно разместив числа в памяти, выведем их на экран дисплея в обратной последовательности. Примем для простоты (и с запасом), что каждое число занимает два байта вместе с разделителем. Форма и вариант расчета приводятся на рис. 39.

Рис. 39

В данном примере использовался компонент TListView (страница Win32). Этот элемент управления отображает список в удобном для пользователя виде. В этом списке, кроме текста, могут отображаться пиктограммы. Компоненты списка представляют собой коллекцию элементов типа TListltem. Для добавления их в список используется свойство Items и метод Add. В зависимости от значения свойства ViewStyle список может отображаться различными способами. В примере выбран стиль отображения vsReport. TListltem имеет свойство Caption, с помощью которого формируется текст самого левого столбца, и Subltems, с помощью которого формируется текст остальных столбцов. Но прежде чем создавать текст из нескольких столбцов, необходимо последние создать. Столбцы именуются обычно ListColumn, их тип - TListColumn. Столбцам можно задать заголовок посредством свойства Caption и ширину, используя свойство Width. Кроме того, свойству TListltem.Selected необходимо задать значение true. Порядок работы со всеми этими свойствами приводится в программе. unit p r i m l 8 ; interface

150


uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , ComCtrls, E x t C t r l s ; type TForml = c l a s s ( T F o r m ) ListViewl: TListView; Panell: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; procedure ButtonlClick(Sender: T O b j e c t ) ; end; var Forml: T F o r m l ; implementation {$R *.DFM) procedure T F o r m l . B u t t o n l C l i c k ( S e n d e r : T O b j e c t ) ; type I n t A r r a y = a r r a y [ 0 . . 1 0 0 0 ] of b y t e ; const s t e x t = ' О ш и б к а о т к р ы т и я ф а й л а ' ; var n:integer; p:pointer; a,I,k:integer; s : a r r a y [ 1 . . 3 ] of s t r i n g ; t:textfile; f : f i l e of c h a r ; Listltem:TListltem; ListColumn:TListColumn; begin (SI-) AssignFile(f,'Inpfil.txt') ; reset(f); {$1+} i f IOResultoO then begin MessageDlg(sText,mtError,[mbOk],0); exit; end; n : = ( F i l e S i z e ( f ) + 1 ) div 2; CloseFile(f) ; p:=nil; try GetMem(p,n); {$1-} AssignFile(t,'Inpfil.txt') ; reset(t); {$1+} i f IOResultoO then begin MessageDlg(sText,mtError,[mbOk],0); exit; end; a:=-l; w h i l e n o t e o f ( t ) do b e g i n

151


inc (a); read(t,intArray(рл)[a]); end; CloseFile(t); f o r i : = l t o 3 do b e g i n L i s t C o l u m n : = L i s t V i e w l . C o l u m n s .Add; c a s e i of 1: L i s t C o l u m n . C a p t i o n : = ' Числа<=100'; 2: ListColumn.Caption:='100<Числа<=200'; 3: ListColumn.Caption:=' Числа>200'; end; ListColumn.Width:=14*Font.Size; s[i] : = " ; end; f o r i : = a - l downto 0 do b e g i n k:= I n t A r r a y ( р л ) [ ± ] i f k<=100 t h e n s [ l ] : = s [ l ] + ' '+IntToStr(k) e l s e i f k<=200 t h e n s [ 2 ] : = s [ 2 ] + I '+IntToStr(k) else s[3]:=s[3]+' '+IntToStr(k); if (length(s[1])>14) or (length(s[2])>14) or ( l e n g t h ( s [ 3 ] ) > 1 4 ) o r (i=0) t h e n b e g i n ListItem:=ListViewl.Items.Add; ListItem.Caption:=s[1]; Listltem.Selected:=true; ListViewl.Selected.Subltems.Add(s[2]); ListViewl.Selected.Subltems.Add(s[3]); s [ 1 ] ' ; s [ 2 ] : =1 ' ; s[3]:=''; end; end; finally FreeMem(p,n) ; end; end; end.

В элементе ListViewl ширина столбцов связана с размером используемого шрифта. Значение 14 для расчета ширины столбца выбрано произвольно (с помощью оператора IF length (s[l]) >14... выводится не более 14 символов в строке). Оператор Try контролирует выделение памяти для указателя р. Обратная последовательность вывода чисел обеспечивается оператором For ... downto...

152


ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ Рассмотрим построение программы, которая имеет дело с людьми, служащими в некоторой фирме. Структура данных для служащего может включать, например, имя, возраст, подразделение, оклад. Определим структуру данных для менеджера. Во-первых, менеджер является служащим, поэтому к нему относится рассматриваемая структура данных. Во-вторых, к менеджеру относятся некоторые добавочные характеристики, например список служащих, которые входят в его группу. В данном случае в памяти необходимо определить служащих, в том числе и менеджера. Но память с данными для менеджера должна содержать еще список подчиненных ему служащих. Очевидно, этот список должен просто ссылаться в памяти на адреса расположения информации о служащих, чтобы не повторять еще раз упомянутые данные. Таким образом, в данном случае необходимо использовать указатели дтя построения структур данных (динамических структур данных). Среди динамических структур данных можно выделить линейные структуры (списки) и структуры-деревья, например, такие: • однонаправленные списки; • двунаправленные списки; • стеки; • очереди; • бинарные деревья. Динамические структуры обычно состоят из двух частей. Первая часть включает содержательную информацию, а вторая необходима для построения связей с другими элементами. Эти связи строят с помощью указателей. ОДНОНАПРАВЛЕННЫЕ СПИСКИ

Структура однонаправленного связанного списка приводится на рис. 40, где принято, что содержательная часть состоит из текстовой информации (Info) в виде строки. Звездочка представляет собой указатель, с помощью которого первый элемент связывается со вторым, второй - с третьим и т.д. Конец списка указывается с помощью пустого указателя nil. Построить такую структуру можно с помощью следующего объявления: Туре PStroka = '>Stroka; Stroka = record Info:string; Sled: PStroka; End;.

Здесь вначале объявлен типизированный указатель (PStroka) с базовым типом запись (stroka), а затем сам базовый тип. Звездочка на рис. 40 пред153


ставляет указатель Sled, a info — содержательную информацию. Следует обратить внимание, что синтаксически разрешается вначале объявлять указатель на несуществующий базовый тип, который обязательно далее должен быть объявлен.

1

f

*

•к

Info

я> я

*

я

— • nil

Info

Info Рис.40

Построим теперь список, например, из трех элементов. Пусть переменные SI, S2, S3 содержат некоторую полезную информацию в виде строк, и пусть объявлены переменные: var Spisok, P:PStroka;. Переменная Spisok будет использована для хранения всего списка, а р — вспомогательный указатель. Напишем такой код для решения задачи: (Начало списка} New(Spisok); (Запись строки SI в список} Spisok".Info:=S1; (Новый э л е м е н т с п и с к а } New (P) ; ( З а п и с ь с т р о к и S2 в э л е м е н т Р} P A .Info:=S2; (Подключение э л е м е н т а д в а в с п и с о к } Spisok*.Sled:=P; (Третий элемент списка} New(P) ; ( З а п и с ь с т р о к и S3 в э л е м е н т Р} P A .Info:=S3; (Подключение э л е м е н т а 3 в с п и с о к } Spisok A .Sled-.Sled:=P; (Конец с п и с к а } P".sled:=nil; (Теперь у к а з а т е л ь Р не нужен}. P:=nil;

В соответствии с этим кодом процесс формирования списка можно представить схематично так, как изображено на рис. 41. Spisok

p

Spisok

*

*

*

S1

S2

SI

1 —

P

Spisok

*

*

*

S2

S3

SI

1 —

*

S2

1 —

*

•nil

S3

Рис. 41

Связанный список дает варианты альтернативного представления массивов. Отличие заключается в том, что число элементов в списке заранее неизвестно. Однако на этапе выполнения программы памяти под список выделяется ровно столько, сколько требуется для записи его элементов. 154


Основными операциями, которые выполняются при работе с динамическими структурами данных, являются следующие: добавление элемента, исключение элемента, поиск заданного элемента. ДВУНАПРАВЛЕННЫЕ СПИСКИ

Структура двунаправленного связанного списка приводится на рис. 42. Данная структура соответствует следующему объявлению: Type PStroka = А Stroka; Stroka • record Info:string; Pred,Sled: PStroka; End;.

*

nil

•к

nil Info

Info

Info Рис. 42

Для построения связей между отдельными элементами списка используются два указателя: Pred - связь с предыдущим элементом и Sled - связь с последующим элементом. Соответственно имеет место ограничение связей элементов списка и слева, и справа с помощью пустого указателя n i l . Построим список из трех элементов, как в Предыдущем случае, и при условиях предыдущей задачи. New(Spisok); Spisok A .Info:=S1; Spisok A .Pred:=nil; New (P) ; P A .Info:=S2; P A .Pred:=Spisok; Spisok A .Sled:=P; New(P); P A .Info:=S3; P A .Pred:=Spisok A .Sled; Spisok A .Sled A .Sled:=P; P A .sled:=nil; P:=nil;.

{Начало с п и с к а } { З а п и с ь с т р о к и SI в с п и с о к } {Ограничение с в я з е й списка с л е в а } {Новый э л е м е н т с п и с к а } { З а п и с ь с т р о к и S2 в э л е м е н т Р} { С в я з ь с предыдущим э л е м е н т о м } {Подключение э л е м е н т а д в а в с п и с о к } {Третий э л е м е н т с п и с к а } { З а п и с ь с т р о к и S3 в э л е м е н т Р} { С в я з ь с предыдущим э л е м е н т о м } {Подключение э л е м е н т а 3 в с п и с о к } {Конец с п и с к а }

На основе двунаправленного списка возможно формирование кольцевых связанных списков, когда самый первый элемент списка ссылается на последний элемент, а последний - на первый элемент. 155


СТЕКИ, ОЧЕРЕДИ

Предполагается, что элементы в списках могут добавляться и извлекаться в произвольном порядке. Существуют списки с заранее заданными процедурами добавления и извлечения элементов. Это стеки и очереди. Очередью называется однонаправленный или двунаправленный связанный список с процедурой работы FIFO — First In, First Out (первым пришел, первым ушел). В списках типа очередь элемент добавляется в конец списка, а извлекается из начала списка. Стек — связанный список с процедурой работы LIFO — Last In, First Out (последним пришел, первым ушел). Элементы стека добавляются в конец списка и извлекаются из конца списка. Идентифицируется очередь с помощью двух указателей: запоминается начало очереди и конец списка. Стек можно идентифицировать одним указателем, помечая дно стека с помощью nil. БИНАРНЫЕ ДЕРЕВЬЯ

Содержательную информацию часто требуется как-то ранжировать. С целью ранжирования информации вводится понятие ключа. Например, в шахматных программах очередные ходы помечают с помощью числовых критериев оценки текущей позиции. Одним из способов представления ранжированной информации является бинчрное дерево. Бинарное дерево, по определению, - это иерархическая структура, схематично представляемая, например, так, как показано на рис. 43.

(44j Рис.43

Бинарное дерево имеет узлы, в которые может входить одна ветвь, а выходить не более двух (бинарное). Верхняя вершина не имеет входа и называется корнем. Построить бинарное дерево можно следующим образом. Пусть имеется набор числовых оценок для некоторого ключа, например, такой: 35, 86, 49, 27, 31, 56, 62, 44, 29, 26, 33, 88. Примем, что вправо по ходу дерева, начиная от корня, значение ключа увеличивается, а влево — уменьшается. 156


Тогда получим структуру дерева, представленную на рис. 43. Структуру бинарного дерева синтаксически можно объявить следующим образом-. Туре

PDerevo= A Derevo; Derevo=record Key:word; Info: string; L e f t , E i g h t : Pderevo; End; .

В заключение рассмотрим такую задачу. Пусть требуется построить очередь для данных, содержащихся в текстовом файле A. t x t . Можно записать: Program Prim;

Type

Var

Begin

PStroka = "Stroka; Stroka = record Info:string; Sled: PStroka; End; T:TextFile; P,Pbegin,Pend:PStroka; S:string;

New(Pbegin); Pend:=Pbegin;

p:=pbegin;

AssignFile(T,'A.txt' ); Reset (T); While not eof(T) do begin Readln(T,S); P A .Info:=S; Pend'4. Sled:=P; Pend:=P; New (P) ; End; Pend".s1ed:=ni1 ; Dispose(P); CloseFile(T);

End.

Здесь указатель Pbegin является идентификатором начала очереди, поэтому ему еще до начала цикла выделяется память (New (Pbegin);). Указатель Pend необходим для запоминания последнего элемента очереди. Вспомогательный указатель Р позволяет обеспечить циклические вычисления. Цикл построен так, что указатель Pend постоянно продвигается по списку, обеспечивая добавление очередного элемента р конец очереди. 157


ПРИМЕР ПРИЛОЖЕНИЯ 19

Среда Delphi содержит встроенный класс TList, с помощью которого можно создавать однонаправленные списки. Этот класс предназначен для организации списка указателей на размещенные в адресном пространстве какие-либо структуры данных и обеспечивает эффективную работу с элементами списка. Доступ к отдельному элементу списка осуществляется посредством индексированного свойства Items[Index], Нумерация элементов начинается с нуля. Элементы списка можно добавлять в его конец с помощью метода Add или в середину, используя метод Insert. Можно реализовать сортировку элементов с помощью метода Sort. Однако, так как заранее состав структуры, на которую указывают указатели списка, неизвестен, необходимо методу Sort передавать адрес разрабатываемой программистом процедуры попарного сравнения каких-либо полей, входящих в состав структуры отдельного элемента списка. Список создается посредством стандартного конструктора Create. Соответственно необходимо обеспечить его удаление в приложении с помощью деструктора Free. Интерфейс и возможности приложения можно увидеть, изучая вариант решения данного примера (рис. 44). Окружности рисуют, используя обработчик ImagelMouseDown.

Рис.44

158


В примере 19 реализуется запись в список TList некоторой структуры данных, представляющей собой следующий тип (Туре), используемый для рисования окружностей: PMyList = A AList; AList = record пгшР: Integer; xP,yP:integer; r:integer; Col: TColor; end; .

Здесь numP — номер окружности; xP, yP — координаты центра окружности; г — радиус; Col - цвет. Данное приложение состоит из трех модулей: основная форма, вспомогательная форма для ввода цвета рисования и третья форма, служащая для поиска и рисования заданного элемента. Ниже приводится текст программы основного модуля. unit OnitList; interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , E x t C t r l s , Buttons,UnitSearch; type TForml = c l a s s ( T F o r m ) E d i t l : TEdit; L a b e l l : TLabel; Label2: TLabel; Imagel: Tlmage; B u t t o n l : TButton; B e v e l l : TBevel; Button2: TButton; Button3: TButton; BitBtnl: TBitBtn; Bevel2: TBevel; Button4: TButton; procedure F o r m C r e a t e ( S e n d e r : T O b j e c t ) ; procedure F o r m C l o s e ( S e n d e r : TObj e c t ; var Action: TCloseAction); procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) ; procedure ImagelMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, Y: I n t e g e r ) ; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure Button2Click(Sender: T O b j e c t ) ; procedure Button3Click(Sender: T O b j e c t ) ; procedure Button4Click(Sender: T O b j e c t ) ; public

159


MyList: TList; end; PMyList = *AList; AList = record numP: Integer; xP,yP:integer; r:integer; Col: TColor; end; var F o r m l : T F o r m l ; ARecord: PMyList; kZap:integer=0; i mplementation uses UnitColor; {$R * . d f m } procedure TForml.FormCreate{Sender: TObj ect); begin MyList := TList.Create; I m a g e l . C a n v a s . P e n . W i d t h := 2 ; end; procedure TForml.FormClose(Sender: T O b j e c t ; var Action: TCloseAction); begin Dispose(ARecord); MyList.Free; end; procedure TForml.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin if n o t (key i n [ ' 0 ' . . ' 9 • , # 8 ] ) then key:=#0; end; procedure TForml.ImagelMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, У: I n t e g e r ) ; begin New(ARecord) ; ARecordA.xP := x ; ARecord".yP := y ; if length(Editl.Text)=0 then Editl.Text:='0'; ARecord".R := S t r T o I n t ( E d i t l . T e x t ) ; i n c (kZap) ; ARecord".numP:=kZap; Form2. ShotAlodal; ARecord".Col:=Form2.NewColor; MyList. Add (ARecord) ; w i t h Imagel.Canvas, ARecord" do b e g i n P e n . C o l o r := A R e c o r d " . C o l ; E l l i p s e (xP - R, y P - R, xP + R, y P + R) ; T e x t O u t ( x P - F o n t . S i z e d i v 2,

160


yP+Font.Height d i v 2, end;

IntToStr(numP));

end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; begin Imagel.Canvas.Brush.С olor:=clWhite; Imagel.Canvas.Rectangle(0,0, Imagel.Width,Imagel-Height); end; function C o m p a r e s ( I t e m l , I t e m 2 : P o i n t e r ) : I n t e g e r ; begin Result:=PMyList(Iteml) A .xP-PMyList(Item2) A .xP; end; procedure TForml.Button2Clxck(Sender: TObject); var i:integer; begin MyList.Sort(@CompareX); f o r i : = 0 t o ( M y L i s t . C o u n t - 1) d o b e g i n ARecord:=MyList.Items[i]; ARecord*.numP:=i+l; end; end; procedure TForml.Button3Clxck(Sender: TObject); var i:integer; begin f o r i : = 0 t o ( M y L i s t . C o u n t - 1) d o b e g i n ARecord := MyList. Items[i] ; w i t h I m a g e l . C a n v a s , ARecord'1 do b e g i n P e n . C o l o r := C o l ; E l l i p s e (xP - R, yP - R, x P + R, y P + R ) ; T e x t O u t ( x P - F o n t . S i z e d i v 2, yP+Font.Height d i v 2, I n t T o S t r ( n u m P ) ) ; end; end; end; procedure TForml.Button4Clxck(Sender: TObject); begin Form3.Show; end; end.

В основном модуле строится список, который может быть отсортирован по координате хР. Все элементы отсортированного списка вновь нумеруются в соответствии с увеличением значений координаты хР. Далее приводится программный код второго модуля. В этом модуле используется новый компонент TColorBox, который обеспечивает ввод требуемого цвета. unit UnitColor; 11—4758

161


interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForm2 = c l a s s ( T F o r m ) ColorBoxl: TColorBox; L a b e l l : TLabel; procedure ColorBoxlChange(Sender: TObject); public NewColor:TColor; end; var Form2: TForm2; implementation {$R * . d f m ) procedure TForm2.ColorBoxlChange(Sender: T O b j e c t ) ; begin NewColor:=ColorBoxl.Selected; close; end; end.

Интерфейс второй формы представлен на рис. 45. После выбора цвета осуществляется автоматический возврат в основную форму.

r— n /

Выбор цвета рисования

с fc . !]• clBlack ... 5

в

1?* h 'ёт

® i

:!

ЮНОСТИ-

»

N

ш

'

* ' '

^Ё! т : : • -

-

'•Ш п Ш £2. . . . . . .

*

%

щ Щ

Рис. 45

Программный код третьего модуля приводится ниже. unit UnitSearch; interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s ; type TForm3 = c l a s s ( T F o r m ) Labell: TLabel; E d i t l : TEdit;

162


BitBtnl: TBitBtn; B u t t o n l : TButton; B e v e l l : TBevel; Bevel2: TBevel; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : procedure ButtonlClick(Sender: T O b j e c t ) ; end; var Form3: T F o r m 3 ; implementation uses UnitList, UnitColor; {$R * . d f m } procedure TForm3.EditlKeyPress(Sender: T O b j e c t ; v a r Key: C h a r ) ; begin if length(Editl.Text)=0 then E d i t l . T e x t : = ' 0 ' ; i f n o t (key i n [ • 0 • . . • 9 ' , # 8 ] ) t h e n key:=#0; end; procedure TForm3.ButtonlClick(Sender: T O b j e c t ) ; var k , i : i n t e g e r ; begin if length(Editl.Text)=0 then E d i t l . T e x t : = ' 0 ' ; к:=StrToInt(Editl.Text); i f (k = 0) o r (k > kZap) t h e n b e g i n ShowMessage( 'Неверно з а д а н номер т о ч к и ' ) ; exit; end; f o r i : = 0 t o ( F o r m l . M y L i s t . C o u n t - 1) d o b e g i n ARecord : = Forml .MyList. Items [i] ; i f ARecord*.numP=k t h e n b e g i n w i t h C a n v a s , ARecord* do b e g i n P e n . C o l o r := C o l ; E l l i p s e (xP - R, y P - R, x P + R, yP + R ) ; TextOut(xP-Font.Size d i v 2, yP+Font.Height d i v 2, I n t T o S t r ( n u m P ) ) ; end; break; end; end; end; end.

Char);

В данном модуле предусмотрено рисование окружности не на элементе Image, а непосредственно на канве формы, которая представлена на рис. 46. Модуль u n i t s e a r c h позволяет организовать поиск заданного элемента в сформированном связанном списке. В данном проекте необходимо обратить внимание, как происходит работа, если приложение состоит из нескольких форм. Обычно имеются главная п*

163


и вспомогательные фо��мы. Вызов других форм организуется с помощью кнопок или меню посредством метода Show (активная форма) или ShowModal (неактивная форма).

Рис. 46

В программе выше используются следующие вызовы других форм: Form2. ShowModal, Form3. Show.

ПРОЦЕДУРНЫЙ ТИП Выше рассматривались указатели, которые позволяли работать с переменными. Однако есть указатели, обеспечивающие работу с процедурами и функциями. Такие указатели представляют собой ссылки на адреса расположения подпрограмм и являются переменными процедурного типа. Объявляется процедурный тип следующим образом: Type Var

PI:Procedure; Р2:Procedure(var X,У:real); РЗ:Function(X:integer):double; Q2:P2; Q3:P3;.

Далее в программе может быть объявлено несколько функций с одним параметром, как указано в РЗ, например: Function Sum(X: integer): double;, Funcnion Mult(X:integer) :double;. Далее можно записать

следующие операторы: Q3:= Sum; Y:= Q3(8) ; Q3:= Mult; У:= Q3(Z); Q2:= nil;.

В качестве значения переменной процедурного типа не могут использоваться стандартные подпрограммы из модуля System. 164


Процедурный тип используется очень широко при описании событий в классах, в которых он представляет указатели на методы.

ПРОГРАММНЫЕ ЕДИНИЦЫ DLL DLL означает Dynamic Link Library (динамически компонуемая библиотека). DLL - это библиотеки, подключаемые к другим программным единицам во время их выполнения. Удобство их состоит в том, что ресурсы библиотек могут использоваться одновременно несколькими программами. Применение DLL не зависит от языка, на котором она написана. DLL составляют основу архитектуры Windows. Модель программирования на основе DLL позволяет добиться модульности всех элементов системы, а модульность операционной системы - предоставить программисту более динамичный и открытый интерфейс. DLL формируются и компилируются независимо от использующих их программ, в которых будут только обращения к подпрограммам библиотек. В отличие от модуля Unit, DLL может передавать другим программным единицам только подпрограммы, хотя сама может иметь практически те же элементы, что и, например, программа-проект. Заголовок DLL состоит из зарезервированного слова Library и имени библиотеки. Имя библиотеки совпадает с именем файла, в который записывается ее программный код. Раздел экспортирования начинается с зарезервированного слова Exports. Секция инициализации начинается с ключевого слова begin, а заканчивается end с точкой. Назначение ее такое же, как у аналогичной секции модуля Unit. Эта секция может отсутствовать. Остальные разделы могут присутствовать в любом количестве, как и в других программных единицах. При создании DLL необходимо в репозитории (см. рис. 6) выбрать соответствующий пункт. Правила написания этой программной единицы такие же, что и у программы-проекта. Отдельные части библиотеки можно поместить в модули. Основное отличие заключается в разделе экспортирования. Только подпрограммы, объявленные в этом разделе, могут быть использованы другими программными единицами. Экспортируемые подпрограммы должны иметь внутреннее (в DLL) имя и могут иметь необязательное внешнее имя, которое используется как альтернатива при импортировании, например Exports Subroutinel name AddC, Subroutine2;.

165


ПРИМЕР ПРИЛОЖЕНИЯ 20

Пусть требуется построить библиотеку подпрограмм работы с комплексными числами и выполнить, например, сложение двух комплексных чисел, используя эту библиотеку. Пусть в библиотеке будет содержаться пока одна функция сложения двух чисел. Так как необходимо ввести новый тип, декларация которого требуется в двух программных единицах (основной модуль, библиотека), создадим дополнительный модуль DecIComplex, содержащий только одно объявление типа. Данный пример дополним еще одной формой, в которой представим сведения о программе (рис. 47). Основная форма для этого примера представлена на рнс. 48.

РМЯНЯКМЩ.. J65.00 Г ' 1 "Г - Г - ' Г Т

1!

--W -

им-' n Tf -

%р^4000 I

Рис. 48

Г

;

Т Hi f

j 1

f - '"'-г

'•

При разработке данного приложения вначале необходимо построить библиотеку. Затем создать проект с формой рис. 48. Далее последовательно добавить в этот проект модуль с формой, представленной на рис. 47, и мо166


дуль с объявлением типа Complex. Ниже приводятся тексты кодов используемых программных единиц. unit prim20; {Модуль с основной формой.} interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , E x t C t r l s , M e n u s , DeclComplex, About; type TMainForm = c l a s s ( T F o r m ) TBevel; Bevell: Bevel2: TBevel; TEdit; Editl: TLabel; Labell: Edit2: TEdit; Label2: TLabel; Label4: TLabel; Edit5: TEdit; TEdit; Edit6: Edit3: TEdit; Edit4: TEdit; Label3: TLabel; Label5: TLabel; Label6: TLabel; Label7: TLabel; Label8: TLabel; Label9: TLabel; LabellO: TLabel; Label11: TLabel; MainMenul: TMainMenu; TMenuItem; Calc: TMenuItem; About: E x i t P o i n t : TMenuItem; Input: TMenuItem; procedure EditlKeyPress(Sender: T O b j e c t ; v a r K e y : C h a r ) ; procedure C a l c C l i c k ( S e n d e r : T O b j e c t ) ; procedure ExitPointClick(Sender: T O b j e c t ) ; procedure I n p u t C l i c k ( S e n d e r : T O b j e c t ) ; procedure A b o u t C l i c k ( S e n d e r : T O b j e c t ) ; end; var MainForm: T M a i n F o r m ; Function AddC(X,У:Complex):Complex; external 'MyDLL.dll'; implementation {$R *.DFM} procedure T M a i n F o r m . E d i t l K e y P r e s s ( S e n d e r : T O b j e c t ; v a r Key: C h a r ) ; begin i f n o t (key i n [ ' 0 • . . ' 9 ' , ' - ' , # 8 ] ) t h e n k e y : = # 0 ;

167


end; procedure T M a i n F o r m . C a l c C l i c k ( S e n d e r : T O b j e c t ) ; var X,Y,Z:Complex; begin X.Re:=StrToInt(Editl.Text); X.Im:=StrToInt(Edit2.Text); Y.Re:=StrToInt(Edit3.Text); Y.Im:=StrToInt(Edit4.Text); Z:=AddC(X,Y); E d i t 5 . T e x t := F o r m a t F l o a t ( ' 0 . 0 0 Z . R e ) ; E d i t 6 . T e x t := F o r m a t F l o a t ( ' 0 . 0 0 ' , Z . I m ) ; end; procedure T M a i n F o r m . E x i t P o i n t C l i c k ( S e n d e r : T O b j e c t ) ; begin Close; end; procedure T M a i n F o r m . I n p u t C l i c k ( S e n d e r : T O b j e c t ) ; begin Editl.SetFocus; end; procedure T M a i n F o r m . A b o u t C l i c k ( S e n d e r : T O b j e c t ) ; begin AboutForm.ShowModal; end; end. unit About; (Модуль с дополнительной формой.) interface uses Windows, C l a s s e s , G r a p h i c s , F o r m s , Controls, StdCtrls, Buttons, ExtCtrls; type TAboutForm = c l a s s ( T F o r m ) Panell: TPanel; OKButton: TBitBtn; P r o g r a m l c o n : TImage; ProductName: TLabel; Department: TLabel; Copyright: TLabel; Comments: TLabel; end; var AboutForm: T A b o u t F o r m ; Implementation {$R *.DFM} end. unit DeclComplex; {Модуль с объявлением типа Complex.} interface Type Complex=record

168


Re,Im:Real; end; implementation end.

Library MyDLL;

{Программа DLL.)

uses S y s U t i l s , C l a s s e s , DeclComplex; function AddC(X,У:complex):complex; begin with result do begin Re:=X.Re+Y.Re; Im:=X.Im+Y.Intend; end;

Exports AddC; begin end.

Импортирование подпрограмм другими программными единицами возможно в статическом (неявном) или динамическом (явном) режимах. Если используется статическое импортирование подпрограммы, то задается заголовок такой подпрограммы с командой External. Если DLL является Windows-библиотекой или, например, написанной на С++, то необходимо указать соглашения о вызовах. Например, Procedure Procl(var х: Variant) ; Cdecl; External 'CDLL.dll';. В этом примере из библиотеки CDLL.dll, написанной на С++, указано соглашение этого языка о передаче данных Cdecl. При статическом импортировании сама библиотека вызывается операционной системой при первом к ней обращении и выгружается, когда потребность в ней отпадает. При динамическом импортировании библиотека грузится непосредственно внутри той программной единицы, которой она необходима. После того как надобность в библиотеке исчезнет, ее нужно выгрузить. Для загрузки используется стандартная функция LoadLibrary (API-функция) с единственным параметром типа PChar, задающим имя библиотеки, например var Handle:HModule;

.. . Handle: =LoadLibrary ('MyDll.dll') ;. Для вы-

грузки DLL используется подпрограмма FreeTii hrary (Handle);. Д ля работы с библиотекой необходимо получить адрес требуемой функции (или процедуры). При этом используется API-функция GetProcAdress (Handle:

HModule,

lpProcName:

LPCSTR) : FARPROC, которая

ищет подпрограмму, указанную именем lpProcName. В качестве результата возвращается указатель на функцию (или процедуру) или nil. Полученный указатель должен быть далее приведен к нужному типу. Например, возможен такой вариант: Unit uCallDll; Interface

169


-type TAddC = Function(X,У:Complex):Complex; TForml=clas s(TForm) private {Добавим в описание формы) DLLHandle: HModule; ExternalMyFunctionPointer:TFarProc; ExternalMyFunction: TAddC; implementation procedure TForml.ButtonlClick(Sender:TObject); DLLHandle:= LoadLibrary(lMyDll.dll'); ExternalMyFunctionPointer:= GetProcAdress(DLLHandle, PChar('AddC') ); ExternalMyFunction:= TAddC(ExternalMyFunctionPointer);

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

ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ Ниже будут рассмотрены некоторые стандартные для Windows технологии программирования. Вообще технология представляет собой набор методов и способов, позволяющих получить требуемые результаты. Поэтому знание различных подходов в программировании позволит разрабатывать профессионально законченные и надежно работающие приложения. Выше были рассмотрены технологии, например, построения циклических вычислительных процессов, построения динамически компонуемых библиотек и т.д. Далее рассмотрим более сложные технологии. ПОТОКИ ДАННЫХ Потоки — достаточно мощное средство при реализации операций ввода или вывода данных, располагающихся на различных носителях. Потоки представляют собой специальные объекты, которые строятся с помощью наследников абстрактного класса TStream. Этот класс содержит методы, в которых заложены операции чтения, записи, изменения текущего положения указателя данных, открытия и закрытия потока. Поскольку для разных носи170


телей реализация указанных выше операций зависит от конкретной реализации устройства, построено несколько специальных классов. Наиболее часто используются потоки TFileStream, THandleStream для работы с файлами на диске и TMemoryStream для работы с памятью. TFileStream используется, когда необходимо работать с именем файла, THandleStream используется, например, с файлами Windows, когда определяется дескриптор файла. Любая работа с файловым потоком начинается с его построения. При этом используется стандартный конструктор Create(const Filenamerstring; Mode:Word). Параметр Filename задает имя файла, а параметр Mode - режимы создания (fmCreate), чтения (fmOpenRead), записи (fmOpenWrite) или одновременного чтения и записи (fmOpenReadWrite) файла. С помощью операции логического или (or) к указанному параметру можно добавить режим совместного использования, например fmShareExclusive — файл недоступен для открытия другими приложениями. При этом поток работает с файлом без учета типа хранящихся в нем данных. Для чтения и записи данных при работе с потоком используются методы Read и Write, содержащие по два параметра: переменная, куда считываются байты и количество байтов. ПРИМЕР ПРИЛОЖЕНИЯ 21

Пусть заданы некоторые совокупности целых чисел. Требуется построить, используя эти данные, диаграммы (или графики). Данные берутся из файлов. С помощью, например, StringGrid можно вводить новые данные или корректировать значения точек и записывать в файлы. Работу с файлами необходимо реализовать с помощью потоков. В данном примере нам понадобятся три новых компонента. Для визуализации данных воспользуемся диаграммами, которые можно построить с помощью компонента TChart со страницы Additional палитры компонентов. Данный компонент имеет множество настроек и позволяет представлять одновременно несколько диаграмм. В данном случае из множества настроек выберем минимум. Представляемые данные в виде серий задаются с помощью рядов целых чисел (Series). Для создания таких рядов необходимо после размещения компонента на форме щелкнуть на нем правой кнопкой мыши. В появившемся выпадающем меню выбрать пункт Edit Chart - откроется редактор с двумя вкладками. Щелкнуть мышкой на вкладке Chart. С помощью кнопки Add построим, например, три ряда (Series) точек и для каждого ряда выберем вариант графика, цвет и подпись. На форме появятся объекты типа TBarSeries. Далее на вкладке Series выбрать страницу Data Source и установить Random Values (для построения графиков с помощью случайных точек). Все свойства можно редактировать в инспекторе объектов. 171


Для демонстрации возможностей файловых потоков нам понадобятся два диалоговых стандартных компонента со страницы Dialogs: OpenDialog выбор открываемого файла и SaveDialog - выбор сохраняемого файла. Основная настройка этих элементов заключается в задании расширения выбираемых файлов (DefaultExt - задать CHR — тип файлов, с которыми работает компонент TChart) и фильтра (Filter). Чтобы задать фильтр в файловых диалогах, из инспектора объектов необходимо выйти в FilterEditor и набрать в каждом компоненте по две строчки (табл. 15). Таблица 15 Filter Name Chart files (*.chr) Any file (*.*)

Filter *.chr * *

Для открытия диалога вызывается метод Execute, а при выходе возвращается имя файла в свойстве FileName. Вариант решения примера 21 приводится на рис. 49. Т ' П Р И М Р Р 21 (Данные файла £:Welphie\delphnew\metpro\p2...

I ^ Q ®

В ы ш и т ы (рафиков

Поверхность ]Столбиковый •ОяаиячН 30

ifcva

11

72

Запись

i

ез

Рис. 49

В примере используется ряд обработчиков событий. Обработчик FormCreate задает первоначальные значения для исходных данных, которые при необходимости можно записать в файл (кнопка "Запись", обработчик SaveButtonClick) или проигнорировать, загрузив данные из файла (кнопка "Файл", обработчик OpenButtonClick). Кроме того, запоминаются в массиве 172


Se (объявлен в секции формы private) настройки цвета диаграмм и вызывается обработчик (UpdateButtonClick, кнопка "Изменить"), который строит эти диаграммы. С помощью свернутых списков Combos запоминаются варианты графиков (или диаграмм). Использование обработчиков ComboChange и ChBoxMarksClick позволяет выбрать соответственно желаемый вид диаграммы или промаркировать их (при необходимости можно записать в файл свой выбор). Из всего многообразия графиков в данном случае выбраны четыре: линейный, столбиковый, поверхность, точки (занесены в списки используемых трех элементов TComboBox). Для удобства коррекции данных в строковой таблице подключается маска ввода в виде обработчика StringGridlGetEditMask. Ниже приводится текст программы. unit Prim21; interface uses S y s U t i l s , Windows, M e s s a g e s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , G r i d s , S t d C t r l s , TeEngine, S e r i e s , E x t C t r l s , T e d P r o c s , C h a r t , Menus, B u t t o n s ; type TForml = c l a s s ( T F o r m ) C h a r t l : TChart; Seriesl: TBarSeries; Series2: TBarSeries; Series3: TBarSeries; Panell: TPanel; ChBoxMarks: TCheckBox; UpdateButton: TButton; StringGridl: TStringGrid; C o m b o B o x l : TComboBox; ComboBox2: TComboBox; СошЬоВохЗ: TComboBox; OpenDialogl: TOpenDialog; SaveDialogl: TSaveDialog; OpenButton: TButton; SaveButton: TButton; BitBtnl: TBitBtn; procedure F o r m C r e a t e ( S e n d e r : T O b j e c t ) ; procedure UpdateButtonClick(Sender: T O b j e c t ) ; procedure StringGridlGetEditMask(Sender: T O b j e c t ; A C o l , ARow: L o n g i n t ; v a r V a l u e : s t r i n g ) ; procedure ChBoxMarksClick(Sender: T O b j e c t ) ; procedure C o m b o C h a n g e ( S e n d e r : T O b j e c t ) ; procedure OpenButtonClick(Sender: T O b j e c t ) ; procedure SaveButtonClick(Sender: T O b j e c t ) ; private Combos: array [0..2] of TComboBox; CurrentFile: atring; Se:array [1..3] of TColor;

173


end; var Forml: T F o r m l ; implementation ($R *.DFM} procedure TForml.FormCreate(Sender: TObj e c t ) ; var I, J: Integer; begin w i t h S t r i n g G r i d l do b e g i n f o r I : = l t o 5 do C e l l s [ 1 , 0 ] : = F o r m a t ( ' Г р у п п а %d', [ I ] ) f o r J : = l t o 3 d o C e l l s [ 0 , J ] : = F o r m a t ( 1 С е р и я %d", [ J ] ) ; Randomize; f o r I := 1 t o 5 do f o r J : = ,. 1 t o 3 d o C e l l s [ I , J ] := I n t T o S t r ( R a n d o m ( 1 0 0 ) ) ; end; Combos [0] : = ComboBoxl; Combos [ 1 ] : = ComboBox2; Combos [2] : = СотЬоВохЗ; f o r I := 0 t o 2 do b e g i n C o m b o s [ I ] . I t e m l n d e x := 1; Se[I+l] : = C h a r t l . S e r i e s [I] . S e n e s C o l o r ; end; UpdateButtonClick(self) ; end; procedure TForml.UpdateButtonClick(S e n d e r : TObj e c t ) ; var I, J: Integer; begin f o r I := 1 t o 3 do b e g i n Chartl.Series[I-l].Clear; f o r J := 1 t o 5 do C h a r t l . S e r i e s [ I - l ] . A d d ( StrToInt(StringGridl.Cells[J, I]), ' ' , Se[I]); end; end; procedure TForml.StringGridlGetEditMask(Sender: T O b j e c t ; A C o l , ARow: L o n g i n t ; v a r V a l u e : s t r i n g ) ; begin Value := 'О^-О1; end; procedure TForml.ChBoxMarksClick(Sender: T O b j e c t ) ; var I: Integer; begin f o r I := 1 t o 3 do C h a r t l . S e r i e s [ 1 - 1 ] . M a r k s . V i s i b l e := ChBoxMarks.Checked end; procedure TForml.ComboChange(Sender: TObj e c t ) ; var I: Integer; SeriesClass: TChartSeriesClass; NewSeries: TChartSeries;

174


begin f o r I := 2 downto 0 do C h a r t l . S e r i e s [ I ] . F r e e ; f o r I := 0 t o 2 do b e g i n c a s e Combos [ I ] . I t e m l n d e x o f 0: S e r i e s C l a s s := T L i n e S e r i e s ; 1 : S e r i e s C l a s s := T B a r S e r i e s ; 2 : S e r i e s C l a s s := T A r e a S e r i e s ; e l s e S e r i e s C l a s s := T P o i n t S e r i e s ; end; NewSeries := S e r i e s C l a s s . C r e a t e ( s e l f ) ; N e w S e r i e s . P a r e n t C h a r t := C h a r t l ; N e w S e r i e s . T i t l e : = F o r m a t ( ' С е р и я %d', [ 1 + 1 ] ) ; end; ChBoxMarksClick(self); UpdateButtonClick(self); end; procedure TForml.OpenButtonClick(Sender: T O b j e c t ) ; v a r Readstrearn: TFileStream; I , J, Value: I n t e g e r ; begin if OpenDialogl.Execute then begin C u r r e n t F i l e := O p e n D i a l o g l . F i l e n a m e ; C a p t i o n : = ' ПРИМЕР 20 (Данные ф а й л а ' + C u r r e n t F i l e + ' ) ' ; try ReadStream := TFileStream.Create(CurrentFile, fmOpenRead); f o r I := 1 t o 5 do f o r J := 1 t o 3 do b e g i n ReadStream.Read(Value, SizeOf(Integer)); S t r i n g G r i d l . C e l l s [ I , J ] := I n t T o S t r ( V a l u e ) ; end; ReadStream.Read(Value, SizeOf(Integer)); ChBoxMarks.Checked := B o o l e a n ( V a l u e ) ; f o r I := 0 t o 2 do b e g i n ReadStream.Read(Value, SizeOf(Integer)); C o m b o s [ I ] . I t e m l n d e x := V a l u e ; ReadStream.Read(Value, SizeOf(Integer)); Se[i+1] : = V a l u e ; end; finally ReadS tream.Free; end; ChBoxMarksClick(self); ComboChange(self); UpdateButtonClick(self); end; end; procedure TForml.SaveButtonClick(Sender: T O b j e c t ) ; var SaveStream: TFileStream;

175


I , J, Value: Integer; begin if SaveDialogl.Execute then begin C u r r e n t F i l e := S a v e D i a l o g l . F i l e n a m e ; C a p t i o n : = ' ПРИМЕР 20 (Данные ф а й л а ' + C u r r e n t F i l e + ' ) 1 ; try SaveStream := TFileStream.Create(CurrentFile, fmOpenWrit or fmCreate); f o r I := 1 t o 5 do f o r J := 1 t o 3 do b e g i n Value := S t r T o I n t D e f ( Trim(StringGridl-Cells[I,J]), 0); SaveStream.Write(Value,SizeOf(Integer)); end; V a l u e := I n t e g e r ( C h B o x M a r k s . C h e c k e d ) ; SaveStream.Write (Value, SizeOf (Integer) ) ; f o r I := 0 t o 2 do b e g i n V a l u e : = Combos [ I ] - I t e m l n d e x ; SaveStream.Write(Value,SizeOf(Integer)); SaveStream.Write(Se[i+l],SizeOf(Integer)); end; finally SaveStream.Frее; end; end; end; end.

Работа с потоками реализована в обработчиках OpenButtonClick и saveButtondick, в которых соответственно выполняется чтение файла в поток Readstream и запись потока SaveStream в файл. Компонент TChart работает с собственным типом бинарных файлов. Потоки Readstream и SaveStream корректно записывают и воспроизводят информацию. В данном случае информация складывается из целых чисел в следующей последовательности: точки графиков, признак меток (преобразуется в булевый тип из числа), виды графиков и их цвета. П Р И М Е Р П Р И Л О Ж Е Н И Я 22

В данном примере рассмотрим работу с потоками в памяти (TmemoryStream). Пусть вводится информация о некоторых спортивных соревнованиях. Примем для упрощения, что необходимо ввести порядковый номер участника и время, которое он показал. Эту информацию далее необходимо отсортировать в порядке возрастания по времени и записать в файл. Пусть показанное участниками соревнования время колеблется от 20 до 35 минут и 176


пусть оно генерируется в программе с помощью генератора случайных чисел. Ниже приводится программа решения данного примера. unit p r i m 2 2 ; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , Controls,Forms,Dialogs,ExtCtrls,StdCtrls,StrUtils,Buttons; type TForml = c l a s s ( T F o r m ) Panell: TPanel; OpenDialogl: TOpenDialog; SaveDialogl: TSaveDialog; Memol: TMemo; Memo2: TMemo; М е т о З : TMemo; Labell: TLabel; Label2: TLabel; Label3: TLabel; BitBtnl: TBitBtn; Buttonl: TButton; GroupBox1: TGroupBox; C h e c k B o x l : TCheckBox; CheckBox2: TCheckBox; procedure ButtonlClick(Sender: T O b j e c t ) ; end; Res=record n:word; t:TDateTime; end; const k = 5 0 ; var Forml: T F o r m l ; i mplementation {$R * . d f m } function ResToStr(r:Res):string; var s:string; begin s:=lntToStr(r.n); Result:=StringOfChar(' •,4-length(s))+ s+1 '+FormatDateTime('nn.ss',r.t); end; procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; var i,m:Integer; TRes,VRes:Res; CurrentFile,s:string; MyMemoryS t r earn: TMemoryS t r earn; begin Randomize; memol.Lines.Clear; 12 — 4758

177


memo2.Lines.Clear; MyMemoryStream:=nil; try MyMemoryStream:=TMemoryStream.Create; MyMemoryStream.SetSize(k*SizeOf(R< )); f o r i := 1 t o ะบ do b e g i n Tres.n:=i; TRes.t:=0.014+0.0l*Random; MyMemoryStream. Write(TRes,SizeOf(Res)); s:=ResToStr(TRes) ; memol.Lines.Add(s); end; f o r m : = k - l downto 1 do b e g i n MyMemoryStream.Seek(m*SizeOf(Res),0); MyMemoryStream.Read(VRes,SizeOf(Res)); f o r i : = l t o m do b e g i n MyMemoryStream.Seek((i-1)*SizeOf(Res),0); MyMemoryStream.Read(TRes,SizeOf(Res)); if T r e s . t > Vres.t then begin MyMemoryStream.Seek((i-1)*SizeOf(Res),0) MyMemoryStream.Write(VRes,SizeOf(Res)); VRes:=TRes; end; end; M^demoryStream.Seek(m*SizeOf(Res),0); MyMemoryStream.Write(VRes,SizeOf(Res)); end; MyMemoryS trearn.Seek(0,0); f o r i := 1 t o ะบ do b e g i n MyMemoryStream.Read(TRes,SizeOf(Res)); S:=ResToStr(TRes); memo2.Lines.Add(s); end; i f CheckBoxl.Checked then if SaveDialogl.Execute then begin C u r r e n t F i l e := S a v e D i a l o g l . F i l e n a m e ; MyMemoryStream.SaveToFile(CurrentFile); end; i f CheckBox2.Checked t h e n i f OpenDialogl.Execute then begin C u r r e n t F i l e := O p e n D i a l o g l . F i l e n a m e ; MyMemoryStream.Clear; MyMemoryStream.LoadFromFile(CurrentFile); memo3.Clear; MyMemoryS trearn.Seek(0,0); f o r i := 1 t o ะบ do b e g i n MyMemoryStream.Read(TRes,SizeOf(Res)); s:=ResToStr(TRes) ;

178


тетоЗ.Line s.Add(s); end; end; finally MyMemoryS tream.Free; end; end; end.

Вариант решения примера приведен на рис. 50, где показано, что отсортированные данные (второй столбец) из потока в памяти корректно записываются в файл (третий столбец). 7 '

ПРИМЕР

28.15 26.03 21-59 29.03 24.46 33.48 24.57 21.38 26.04 31.10 27.04 24.24 25.59 28.47 25.58 32.01 26.07

28.12

22

Jk

23 49 42 48 8 3 46 19 45 35 29 22 37 20 25 12 5 7

20.47 20.51 21.26 21.32 21.38 21.59 22.05 22.22 22.29 22.40 23.26 23.35 23.44 24.01 24.22 24.24 24.46 24.57

# Я •

23 49 42 48 8 3 46 19 45 35 29 22 37 20 25 12 5 7

|„

Ц

ь*.

Ь.

т

20.47 20.51 21.26 21.32 21.38 21.59 22.05 22.22 22.29 22.40 23.26 23.35 23.44 24.01 24.22 24.24 24.46 24.57

J7. Ч т ш м ё :

Ж

Рис. 50

Для настройки диалоговых компонентов можно воспользоваться табл. 15, задав какое-либо расширение файлов, например, *.dat. Это расширение необходимо также записать в свойство DefaultExt (задать dat). В примере показана работа потока при чтении и записи, а также его взаимодействие с файлами.

ИНТЕРФЕЙС drag and drop Интерфейс Drag and Drop обеспечивает один из механизмов взаимодействия двух элементов управления во время выполнения приложения. При 12»

179


этом могут выполняться различные операции переноса и приема данных или перемещения элемента, например, на форме. Для того чтобы этот механизм заработал, требуется настроить соответствующим образом с помощью обработчиков событий и инспектора объектов некоторые заданные элементы управления. В заданной паре один должен быть источником (Source), другой приемником (Target). Пользователь помещает указатель мыши на нужный элемент, нажимает левую кнопку мыши и, не отпуская ее, начинает перемещение курсора ко второму элементу. При достижении этого элемента пользователь отпускает кнопку мыши. В этот момент выполняются предусмотренные разработчиком действия. Среди этих действий могут быть передача текста, значений свойств, шрифта, простое перемещение с места на место и т.д. Таким образом, Drag and Drop — средство связывания двух компонентов при помощи указателя мыши. Любой элемент управления является изначально источником в механизме Drag and Drop. Его поведение на начальном этапе настраивается с помощью свойства Property DragMode: TDragMode, где Type TDragMode = (dmManual, dmAutomatic);.

Значение dmAutomatic обеспечивает автоматическую реакцию компонента на нажатие левой кнопки мыши — механизм перетаскивания включается самостоятельно. Значение dmManual (установлено по умолчанию) требует от разработчика обеспечить включение специфических действий при перетаскивании. Для инициализации переноса в источнике используется метод BeginDrag (если не включено dmAutomatic), который можно включить, например, в обработчике OnMouseDown. Приемником может стать любой компонент, для которого создан обработчик события OnDragOver. Этот обработчик вызывается при достижении курсором мыши компонента-приемника. Окончание переноса фиксируется работой обработчика OnDragDrop, в котором можно выполнить некоторые действия в приемнике. Определенные действия предусмотрены и в источнике с помощью обработчика OnEndDrag, после того как он получит сообщение об окончании операции переноса. Следует отметить, что не любые действия возможно реализовать. П Р И М Е Р П Р И Л О Ж Е Н И Я 23

Рассмотрим пример по использованию Drag and Drop. В программе, приведенной ниже, представлены почти все вышеуказанные виды обработчиков событий по реализации данного интерфейса. unit P r i m 2 3 ; interface uses Windows, M e s s a g e s ,

180

SysUtils,

Variants,

Classes,Graphics,


Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Buttons; type TForml = class(TForm) Editl: TEdit; Edit2: TEdit; Panell: TPanel; Panel2: TPanel; Buttonl: TButton; BitBtnl: TBitBtn; procedure EditlMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Edit2DragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure Edit2DragDrop(Sender, Source:TObject; X,Y:Integer); procedure EditlEndDrag(Sender, Target: TObject; X, Y: Integer); procedure FormDragOver(Sender, Source: TObject; X, Y: Integer;State: TDragState; var Accept: Boolean); procedure FormDragDrop(Sender, Source: TObject; X, Y: Integer); procedure PanellDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); procedure PanellDragDrop(Sender, Source: TObject; X, Y: Integer); end; var Forml: TForml; implementation ($R *.dfm} procedure TForml.EditlMouseDown(Sender: TObject; Button: TMouseButton;Shift: TShiftState; X, Y: Integer); begin if Button=mbLeft then TEdit(Sender).BeginDrag(true); end; procedure TForml.Edit2DragOver(Sender,Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); begin if Source is TEdit then Accept:=true else Accept:=false; end; procedure TForml.Edit2DragDrcp(Sender,Source: TObject; X, Y: Integer); begin TEdit(Sender).Text:=TEdit(Source).Text; TEdit(Sender).SetFocus; TEdit(Sender).SelectAll; end; procedure TForml.EditlEndDrag(Sender, Target: TObject; X, Y: Integer);

181


begin i f Assigned(Target) then TEdit(Sender).Text:= 'Текст перенесен в 1+TEdit(Target).Name; end; procedure TForml.FormDragOver(Sender, S o u r c e : T O b j e c t ; X, Y: I n t e g e r ; S t a t e : T D r a g S t a t e ; v a r A c c e p t : B o o l e a n ) ; begin i f (Source.ClassName='TPanel') or (Source.ClassName= 'TButton') then Accept:=true else Accept:=false; end; procedure TForml.FormDragDrop(Sender, S o u r c e : T O b j e c t ; X, Y: I n t e g e r ) ; begin TControl(Source).Left:=x; TControl(Source).Top:=y; end; procedure TForml.PanellDragOver(Sender,Source:TObj e c t ; X, Y: I n t e g e r ; S t a t e : T D r a g S t a t e ; v a r A c c e p t : B o o l e a n ) ; begin i f Source i s TButton then A c c e p t : = t r u e e l s e A c c e p t : = f a l s e ; end; procedure TForml.PanellDragDrop(Sender, S o u r c e : T O b j e c t ; X, Y: I n t e g e r ) ; begin i f Source i s TButton then begin TButton(Source).Left:=(Sender as TPanel).Left+x; TButton(Source).Top:=(Sender as TPanel).Top+y; i f Source i s TBitBtn then begin TBitBtn(Source).Width:=147; T B i t B t n ( S o u r c e ) . C a p t i o n : = ' Н а форму н е л ь з я ' ; if Application.MessageBox(PChar('Выход'), P C h a r ( ' В ы б о р варианта'),MB_DEFBUTT0N1 + MB_ICONEXCLAMATION + MB_0KCANCEL) = IDOK t h e n c l o s e ; end; end; end; end.

В обработчике PanellDragDrop как и в PanellDragOver учитывается, что TBitBtn является вариантом TButton. Если рассматривать форму, то

все представленные на ней элементы являются наследниками класса TControl, поэтому в обработчике FormDragDrop не конкретизируются разре-

шенные к перемещению компоненты. При преобразовании типов объектов использовались две конструкции, например, TButton (Source) или Source

as Tbutton, что по результату означает одно и то же. Для элементов Editl, Edit2 и Panell ("приемник") установлено значение DragMode = dmManual,

182


для остальных элементов DragMode = dmAutomatic. На рис. 51 представлена форма данного примера.

7 ПРИМЕР 23 р[

Исгочшк •Щ^-.

- .

. . . .

ssa^ii..«ггт-ч»' - = 1 Приемник

ftwj

•Приемник

Паав/it.

JFC*ASJ ••• •

. .. 11 ilIч г, i~*' •• •••-•• Рис. 51

I.^

Вариант выполнения переноса Drag and Drop представлен на рис 52. 7 ' ПРИМЕР 23

0 @ ®

.,rJSs*JK - •» ЙТекст перенесен в Edit2

Яг stem

*

j "нот Д

ш

юза Рис. 52

г f г — ч г г i» ми»

Можно выделить изменение "поведения" кнопки BitBtnl. Она потеряла заданную функцию Close. Для выхода из программы (если не пользоваться 183


стандартным способом закрытия окна) предусмотрен специальный диалог, вызываемый при перемещении данной кнопки. ТЕХНОЛОГИЯ DRAG AND DOCK Данная технология реализует динамическое перетаскивание мышью и прицепление одного объекта к другому. В данном механизме участвуют два элемента: один - док (docking site) - может принимать объекты, другой — клиент (dockable control) - присоединяемый компонент. Delphi наделяет данной технологией потомков классов TWinControl и TControl. Класс объекта, играющий роль дока, должен быть производным от класса TWinControl, а класс стыкуемого объекта - от Tcontrol (или TWinControl). Если рассматривать свойства компонентов, то доком может быть любой объект, обладающий свойством DockSite типа Boolean. Объекты-доки должны быть способны выступать по отношению к другим объектам в качестве контейнеров. Что касается стыкуемых элементов, то подходящие для них компоненты должны иметь два свойства DragKind и DragMode. Как и в случае с технологией перетаскивания Drag and Drop возможны два варианта реализации механизма Drag and Dock, задаваемые в свойстве DragMode: dmManual и dmAutomatic. В свойстве DragKind необходимо задать dkDock. Иногда в данной технологии бывает полезным свойство AutoSize. Когда оба свойства дока DockSite и AutoSize имеют значение true, док (если это не форма) во время выполнения программы не виден до тех пор, пока к нему не будет пристыкован хотя бы один клиент. Таким поведением часто наделяются компоненты TPanel, которые в этом случае имеют нулевое значение для одного из измерений (высоты или ширины). Программист для управления данной технологией может воспользоваться рядом обработчиков событий. Реакцию клиента на события, возникающие в моменты начала и конца переноса, можно задавать в обработчиках ОпStartDock и OnEndDock. Во время переноса можно управлять процессом с помощью следующего ряда подключаемых к доку обработчиков: OnGetSitelnfo, OnDockOver, OnDockDrop, OnUnDock. Событие OnGetSitelnfo используется для некоторых компонентов, например для TPanel. Данное событие в самом начале процесса перетаскивания рассылает сообщения и параметры клиента всем потенциальным докам (у которых свойство DockSite установлено в true) В ответ док должен сообщить решение о приеме клиента и предоставить прямоугольник приема в случае положительного варианта. Два события OnDockOver и OnDockDrop в точности соответствуют своим аналогам из технологии Drag and Drop. В обработчике OnUnDock можно запрограммировать некоторые действия в момент покидания дока и "приземления" клиента в другом месте. 184


Следует отметить, что реализация технологии Drag and Dock намного сложнее, чем реализация Drag and Drop. В частности, перед стыковкой необходимо вычислять возможный прямоугольник приема. В модуле uDockForm приводится такая функция (ComputeDockingRect). Кроме того, в некоторых сложных вариантах при установке у дока свойства UseDockManager в true возможно использование менеджера контроля докинга (свойство DockManager), с помощью которого определяется прямоугольник BoundsRect как быстрый способ получения контроля клиента на доке. Данный менеджер реализует интерфейс IDockManager, имеющий множество возможностей настройки поведения дока. Пристыкованный элемент может быть перемещен в другую позицию при помощи методов ManualDock, ManualFIoat, Dock или можно воспользоваться (для некоторых типов клиентов) свойством FloatingDockSiteClass, устанавливая его значение в CustomDockForm. ПРИМЕР П Р И Л О Ж Е Н И Я 24

Данный пример демонстрирует некоторые возможности технологии перетаскивания элементов или форм на друтие формы или элементы. На рис. 53 представлен общий интерфейс примера и основная форма с пристыкованными к ней двумя формами-клиентами. Эти формы пристыкованы не непосредственно на основную форму, а при помощи двух компонентов TPanel. 7-* ПРИ.УП

uL»*.kM..«. окно-док

[Йиеяь111Йнель21 Вьиод Yellow Blue Green Lime Puipie Red

Рис. 53

185


Программа состоит из четырех модулей. Основной модуль (uMain) содержит форму основного дока, которая строится в процессе запуска приложения. Модуль (uDockForm) содержит объявление формы-клиента, на которой расположен один компонент ТМето. При желании можно ввести некоторый текст в этом редакторе. Формы-клиенты строятся при создании основной формы. Предусмотрено построение сразу семи форм, отличающихся цветом. Первоначально все эти семь форм невидимы. Остальные два модуля, как и основная форма, содержат объявления форм-доков: В модуле uConjoinHost объявляется простая форма, а в uTabHost - форма, содержащая компонент TpageControI, т.е. форма в виде записной книжки, состоящей первоначально из одной страницы. Все формы имеют соответствующие заголовки для простоты их распознавания. Вначале рассмотрим простой вариант данного примера без применения модулей uConjoinHost и uTabHost и соответствующих дополнительных форм. Ниже приводится программный код основного модуля. unit uMain; interface uses Windows, M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , Menus, S t d C t r l s , C o m C t r l s , A c t n L i s t , T o o l W i n , E x t C t r l s , uDockForm; type TMainForm = class(TForm) CoolBarl: TCoolBar; ToolBarl: TToolBar; ToolBar2: TToolBar; ToolButtonl: TToolButton; ToolButton2: TToolButton; ToolButton3: TToolButton; ToolButton4: TToolButton; ToolButton5: TToolButton; ToolButton6: TToolButton; ToolButton7: TToolButton; btnToolBarl: TToolButton; btnToolBar2: TToolButton; A c t i o n H s t l : TActionList; ViewToolBarl: TAction; ViewToolBar2: TAction; ExitAction: TAction; ViewYellowWindow: T A c t i o n ; ViewBlueWindow: T A c t i o n ; ViewGreenWindow: T A c t i o n ; ViewRedWindow: T A c t i o n ; ViewTealWindow: T A c t i o n ; ViewPurpleWindow: TAction; ViewLimeWindow: T A c t i o n ; LeftDockPanel: TPanel;

186


BottomDockPanel: TPanel; VSplitter: TSplitter; HSplitter: TSplitter; M a i n M e n u l : TMainMenu; F i l e 2 : TMenuItem; E x i t 2 : TMenuItem; View2: TMenuItem; T o o l B a r 2 1 : TMenuItem; T o o l B a r l l : TMenuItem; Y e l l o w l : TMenuItem; B l u e l : TMenuItem; G r e e n l : TMenuItem; L i m e l : TMenuItem; P u r p l e l : TMenuItem; R e d l : TMenuItem; T e a l l : TMenuItem; procedure F o r m C r e a t e ( S e n d e r : TObj e c t ) ; procedure ViewToolBarlExecute(Sender: T O b j e c t ) ; procedure ViewToolBar2Execute(Sender: TObj e c t ) ; procedure ExitActionExecute(Sender: T O b j e c t ) ; procedure ViewClientWindowExecute(Sender: T O b j e c t ) ; procedure CoolBariDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; S t a t e : TDragState; var Accept: Boolean); procedure Lef tDockPanelDockOver(Sender:TObj e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y; I n t e g e r ; State: TDragState; var Accept: Boolean); procedure LeftDockPanelDockDrop(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ) ; procedure LeftDockPanelUnDock(Sender: T O b j e c t ; C l i e n t : TControl; NewTarget: TWinControl; v a r Allow: Boolean) procedure LeftDockPanelGetSitelnfo(Sender: T O b j e c t ; DockClient: TControl; var InfluenceRect: T R e c t ; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; procedure BottomDockPanelDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); public procedure ShowDockPanel(APanel: T P a n e l ; MakeVisible: Boolean; C l i e n t : TControl); end; var MainForm: T M a i n F o r m ; implementation {uses uTabBost, uConjoinHost;) {$R * . d f m } const Colors: array [0..6] of TColor = ( c l Y e l l o w , c l B l u e , clGreen, clRed, clTeal, clPurple, clLime);


ColStr: array[0..6] of string = ( ' Y e l l o w ' , 'Blue', 'Green', 'Red', 'Teal', 'Purple', 'Lime1); var DockWindows: array[0..6] of TDockableForm; procedure TMainForm.FormCreate(Sender: T O b j e c t ) ; var I: Integer; begin f o r I := 0 t o High(DockWindows) do b e g i n DockWindows[I] := TDockableForm.Create(Application); DockWindows[I].Caption := ColStr[I]; DockWindows[I].Memol.Color := Colors[I]; DockWindows[I].Memol.Font.Color := Colors[I] xor $00FFFFFF; DockWindows[I].Memol.Text:=ColStr[I] + ' window'; end; end; procedure TMainForm.ShowDockPanel(APanel: T P a n e l ; MakeVisible: Boolean; C l i e n t : TControl); begin i f not MakeVisible and ( A P a n e l . V i s i b l e D o c k C l i e n t C o u n t > 1) t h e n E x i t ; i f APanel = LeftDockPanel t h e n V S p l i t t e r . V i s i b l e := M a k e V i s i b l e e l s e H S p l i t t e r . V i s i b l e := M a k e V i s i b l e ; i f MakeVisible then i f Apanel = LeftDockPanel then begin A P a n e l . W i d t h := C l i e n t W i d t h d i v 3 ; V S p l i t t e r . L e f t := A P a n e l . W i d t h + V S p l i t t e r . W i d t h ; end e l s e b e g i n A P a n e l . H e i g h t := C l i e n t H e i g h t d i v 3 ; H S p l i t t e r . T o p := C l i e n t H e i g h t - A P a n e l . H e i g h t HSplitter.Width; end e l s e i f APanel = LeftDockPanel then APanel.Width : e l s e A P a n e l . H e i g h t := 0; i f M a k e V i s i b l e a n d ( C l i e n t <> n i l ) t h e n C l i e n t . S h o w ; end; procedure TMainForm.ViewToolBarlExecute(Sender: T O b j e c t ) begin T o o l B a r 1 1 . C h e c k e d := n o t T o o l B a r l l . C h e c k e d ; b t n T o o l B a r l . D o w n := T o o l B a r l l . C h e c k e d ; i f ToolBarl.Floating then T o o l B a r l . H o s t D o c k S i t e . V i s i b l e := T o o l B a r 1 1 . C h e c k e d e l s e T o o l B a r l . V i s i b l e := T o o l B a r l 1 . C h e c k e d ; end; procedure TMainForm.ViewToolBar2Execute(Sender: T O b j e c t ) begin T o o l B a r 2 1 . C h e c k e d := n o t T o o l B a r 2 1 . C h e c k e d ; b t n T o o l B a r 2 . D o w n := T o o l B a r 2 1 . C h e c k e d ; i f ToolBar2.Floating then

188


T T o o l D o c k F o r m ( T o o l B a r 2 . H o s t D o c k S i t e ) . V i s i b l e := ToolBar21.Checked e l s e T o o l B a r 2 . V i s i b l e := T o o l B a r 2 1 . C h e c k e d ; end; procedure TMainForm.ExitActionExecute(Sender: T O b j e c t ) ; begin Close; end; procedure TMainForm.ViewClientWindowExecute(Sender: T O b j e c t ) ; var DockWindow: TDockableForm; begin DockWindow : = DockWindows[(Sender a s TComponent).Tag]; w i t h DockWindow d o i f HostDockSite i s TPageControl then T T a b D o c k H o s t ( H o s t D o c k S i t e . O w n e r ) .Show e l s e i f (HostDockSite i s TConjoinDockHost) and not HostDockSite.Visible then begin HostDockSite.Show; DockWindow.Show; end e l s e i f (HostDockSite i s TPanel) and ( ( H o s t D o c k S i t e . H e i g h t = 0) o r (HostDockSite.Width = 0)) t h e n MainForm.ShowDockPanel( H o s t D o c k S i t e a s T P a n e l , T r u e , DockWindow) e l s e DockWindow.Show; end; procedure TMainForm.CoolBarlDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); v a r ARect: TRect; begin A c c e p t := ( S o u r c e . C o n t r o l i s T T o o l B a r ) ; i f Accept then begin A R e c t . T o p L e f t := C o o l B a r l . C l i e n t T o S c r e e n ( COolBarl.ClientRect.TopLeft); A R e c t . B o t t o m R i g h t := C o o l B a r l . C l i e n t T o S c r e e n ( CoolBarl.ClientRect.BottomRight); S o u r c e . D o c k R e c t := A R e c t ; end; end; procedure TMainForm.LeftDockPanelDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); var ARect: TRect; begin A c c e p t := S o u r c e . C o n t r o l i s TDockableForm; i f Accept then begin

189


A R e c t . T o p L e f t := LeftDockPanel.ClientToScreen(Point(0, 0)); A R e c t . B o t t o m R i g h t := L e f t D o c k P a n e l . C l i e n t T o S c r e e n ( P o i n t ( S e l f . C l i e n t W i d t h d i v 3, L e f t D o c k P a n e l . H e i g h t ) ) ; S o u r c e . D o c k R e c t := A R e c t ; end; end; procedure TMainForm.LeftDockPanelDockDrop(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ) ; begin i f (Sender a s TPanel).DockClientCount = 1 then ShowDockPanel(Sender as TPanel, True, n i l ) ; (Sender as TPanel).DockManager.ResetBounds(True); end; procedure TMainForm.LeftDockPanelUnDock(Sender: TObject; C l i e n t : TControl; NewTarget: TWinControl; var Allow: Boolean); begin i f (Sender as TPanel).DockClientCount = 1 then ShowDockPanel(Sender a s TPanel, F a l s e , n i l ) ; end; procedure TMainForm.LeftDockPanelGetSitelnfo(Sender: TObject; DockClient: TControl; var InfluenceRect: T R e c t ; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; begin CanDock : = D o c k C l i e n t i s T D o c k a b l e F o r m ; end; procedure TMainForm.BottomDockPanelDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); var ARect: TRect; begin Accept := S o u r c e . C o n t r o l i s TDockableForm; i f Accept then begin A R e c t . T o p L e f t := B o t t o m D o c k P a n e l . C l i e n t T o S c r e e n ( Point(0, -Self.ClientHeight div 3)); ARect.BottomRight:=BottomDockPanel.ClientToScreen( Point(BottomDockPanel.Width, BottomDockPanel.Height)); S o u r c e . D o c k R e c t :== A R e c t ; end; end; end.

Для основной формы установлено свойство DockSite=false. Для двух расположенных на ней панелей (TPanel) и CoolBarl установлено DockSite=true. Таким образом, "причаливание" клиентов TDockableForm

разрешено к двум панелям. К компоненту TCoolBar также разрешено "причаливание". Используются при этом стандартные методы. Тип форм190


клиентов, которые могут быть пристыкованы к основной форме, описан во втором модуле uDockForm. unit uDockForm; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , Menus, E x t C t r l s , S t d C t r l s ; type TDockableForm = class(TForm) Memol: TMemo; procedure FonBDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); procedure F o r m C l o s e ( S e n d e r : T O b j e c t ; var Action: TCloseAction); private function ComputeDockingRect(var D o c k R e c t : T R e c t ; MousePos: T P o i n t ) : TAlign; end; implementation {$R * . d f m } uses ComCtrls, uMain; procedure TDockableForm.FormDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); var ARect: TRect; begin Accept := (Source.Control is TDockableForm); if Accept and (ComputeDockingRect(ARect,Point(X, Y)) <> alNone) then Source.DockRect := ARect; end; function TDockableForm.ComputeDockingRect(var D o c k R e c t : TRect; MousePos: T P o i n t ) : TAlign; v a r DockTopRect, DockLeftRect, DockBottomRect, DockRightRect, DockCenterRect: T R e c t ; begin R e s u l t := alNone; DockLeftRect.TopLeft : = P o i n t ( 0 , 0 ) ; DockLeftRect.BottomRight : = P o i n t ( C l i e n t W i d t h d i v 5 , ClientHeight); DockTopRect.TopLeft : = P o i n t ( C l i e n t W i d t h d i v 5 , 0 ) ; DockTopRect.BottomRight : = P o i n t ( ClientWidth div 5 * 4 , ClientHeight div 5); DockRightRect.TopLeft : = P o i n t ( C l i e n t W i d t h d i v 5 * 4 , 0); DockRightRect.BottomRight:=Point(ClientWidth, C l i e n t H e i g h t ) ; DockBottomRect.TopLeft : = P o i n t ( C l i e n t W i d t h d i v 5 , ClientHeight div 5 * 4); DockBottomRect.BottomRight : = P o i n t ( C l i e n t W i d t h d i v 5 * 4 ,

191


ClientHeight); DockCenterRect.TopLeft : = P o i n t ( C l i e n t W i d t h d i v 5 , C l i e n t H e i g h t d i v 5) ; DockCenterRect.BottomRight : = P o i n t ( C l i e n t W i d t h d i v 5 * 4 , ClientHeight div 5 * 4 ) ; i f PtlnRect(DockLeftRect, MousePos) t h e n b e g i n R e s u l t := a l L e f t ; DockRect := D o c k L e f t R e c t ; DockRect.Right ClientWidth div 2; end e l s e i f PtlnRect(DockTopRect, MousePos) t h e n b e g i n R e s u l t := a l T o p ; DockRect := DockTopRect; D o c k R e c t . L e f t := 0 ; D o c k R e c t . R i g h t := C l i e n t W i d t h ; DockRect.Bottom := C l i e n t H e i g h t d i v 2 ; end e l s e i f PtlnRect(DockRightRect, MousePos) t h e n b e g i n R e s u l t := a l R i g h t ; DockRect := D o c k R i g h t R e c t ; DockRect.Left : = ClientWidth div 2; end e l s e i f PtlnRect(DockBottomRect, MousePos) t h e n b e g i n R e s u l t := a l B o t t o m ; DockRect := D o c k B o t t o m R e c t ; DockRect . L e f t := 0 ; D o c k R e c t . R i g h t := C l i e n t W i d t h ; D o c k R e c t . T o p := C l i e n t H e i g h t d i v 2 ; end e l s e i f PtlnRect(DockCenterRect, MousePos) t h e n b e g i n R e s u l t := a l C l i e n t ; DockRect := D o c k C e n t e r R e c t ; end; i f R e s u l t = alNone then E x i t ; DockRect.TopLeft := ClientToScreen(DockRect.TopLeft); DockRect.BottomRight:=ClientToScreen(DockRect.BottomRight); end; procedure TDockableForm.FormClose(Sender: T O b j e c t ; var Action: TCloseAction); begin A c t i o n s= c a H i d e ; end; end. Модуль uDockForm определяет форму, для которой установлено: DockSite=true, DragKind=DkDock, DragMode=dm Automatic, т.е. данная форма может быть как клиентом, так и доком. Важную функцию выполняет подпрограмма ComputeDockingRect, которая позволяет вычислить возможный 192


вариант (по позиции курсора мыши) и прямоугольник пристыковки клиента. Можно попытаться изменить используемые в этой функции константы при расчете прямоугольника пристыковки. Приводятся 5 возможных вариантов, которые применяются для форм типа TDockableForm. Функция PtinRect проверяет предоставляемый доком прямоугольник и проводит вычисления, необходимые для пристыковывания клиента. Второй вариант выполнения примера приводится на рис. 54, на котором показано, что используются плавающие панели инструментов.

Рис. 54

В примере использовалось несколько новых компонентов. Как можно заметить, в частности (см. рис. 53), одновременно было применено меню и две инструментальные панели с кнопками. Практика показала, что для построения панелей инструментов удобно использовать специальные компоненты TToolBar и TCoolBar, находящиеся на странице Win32 палитры компонентов. Эти компоненты имеют большое разнообразие возможностей, свойств и методов. В данном случае TCoolBar используется как контейнер, на котором расположены две инструментальные панели со специальными кнопками TToolButton. Кроме кнопок, на панель TToolBar можно помещать комбинированные списки, быстрые кнопки SpeedButton, редакторы Edit и другие элементы. Для добавления новой 13 — 4758

193


кнопки нужно щелкнуть на панели правой кнопкой мыши и выбрать пункт New Button. Так как инструментальные кнопки и пункты меню дублируют друг друга в работе, для синхронизации управляющих элементов был использован компонент TActionList. Нажатием правой кнопки мыши на компоненте, расположенном на форме, вызывается специальный редактор Action List Editor, который позволяет набрать нужное количество объектов действий Action типа Taction (рис. 55, категории ViewWindows (а) и ViewToolBars (б)). r- — 1

Iи -

**T

I Categories:

Actions.

_

- —

•.1

[«litingM^inrjim.4rtfcnLi-t1

Editing MainForm ActionListl У-

£3 -

Ж1

Categories:

(No Category) ViewToolBars

[ ViewToolBatl ViewToolBar2 ViewWindows j [At! Actions) jI

ViewYellowWindow ViewBlueWindow ViewGieenWindow (All Actions) ViewRedv/indow j ViewTeaWindow ViewPurpteWmdow : ViewLimeWindow

КШИ

а)

б) Рис. 55

Все объекты Action группируются в категории, число которых можно выбирать произвольно или равным числу пунктов в линейке меню. На рис. 55 не показано, что пункт "Выход" входит в состав категории (No Category). Очевидно, с каждым совпадающим по своему действию пунктом меню и инструментальной кнопкой должен работать один и тот же обработчик событий. Дальнейшая настройка действий Action выполняется с помощью инспектора объектов (рис. 56, 57). Obieet Inspector |ToolButton1 TTooButon LftqpetSes^Evejej;; •p-w i ViewVeUowWmdow L JTOUiwa. |§3l •-Л fcYeHow j - -j^: VieW&indow« ^ й

. .«iked -«Ly*)

(false Tn«?

, « :0 nagCc ей 0

.{«)

1

Object Inspector ToolBu«on1

BActkx ViewYelovA^iridow ' ''•Гмя e OnM»* /V., * Otop • wntoem,> OnCwteSPopu

a)

6) Рис. 56

194

TT«s£iita


Образцы настройки свойств и событий для всех кнопок показаны на рис. 56 (показан выбор свойств (а) и событий (б) для кнопки Yellow). Естественно, вначале необходимо с помощью свойства Name присвоить всем кнопкам имена (на рис. 56 это не показано). Следует обратить внимание на то, что вместо события OnClick для пунктов меню настраивается событие ОпExecute, используемое для действий Acton. Среда Delphi автоматически подставляет для события OnClick пункта меню (см. рис. 56, б) то же самое событие, что устанавлено программистом для события OnExecute. Последняя операция заключается в согласовании действий элемента АсtionListl и меню MainMenul. Данное согласование также проводят, используя инспектор объектов. На рис. 57 показан выбор свойств (о) и событий (б) для пункта меню Yellow]. Имена всех пунктов меню и инструментальных кнопок можно найти в тексте программы примера 24. InwiKCtur Yellowl

:К' jy

Properties j E<даЛ: Eltaio, CapйогС^ядгиу сьгечо

"1ЕЭ feYeBow ViewWiniiows Fate jTiue

4

itYsllcwl

ТМевиИаш

Ш

d]

PKperties Everts SAtiir.

Vie*»Y«HoWWir)dow te

?

j'-u S ' ic • ОиПИ.

-i:

I

Afstann а)

6} Рис. 57

В примере использовался еще один новый элемент TSplitter. Этот компонент служит для формирования окна, разделенного плавающими границами на несколько зон. Сами компоненты TSplitter при этом остаются невидимыми. В данном случае было применено два таких элемента на нижней границе и слева от основной формы (там, где на рис. 53 пристыкованы клиенты) для того, чтобы пристыкованные клиенты не уходили за пределы границ окна-дока. Теперь, рассмотрим более сложные варианты технологии Drag and Drop с использованием нестандартных подходов, в частности, такого инструмента, как DockManager. Для этого нам понадобятся упоминавшиеся выше два модуля. Кроме того, необходимо немного изменить второй модуль uDockForm.

Необходимо обратить внимание на то, что построение форм C o n j o i n DockKost и TabDockHost осуществляется в процессе выполнения приложения. Поэтому в программе-проекте нужно убрать строки автоматического построения этих форм, которые появятся после подключения к проекту дополнительных модулей. Далее приводится текст программы второго модуля, 13»

195


в котором опущено описание метода ComputeDockingRect (приводится выше). u n i t uDockForm; interface u s e s Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Menus, ExtCtrls, StdCtrls; type TDockableForm = class(TForm) Memol: TMemo; p r o c e d u r e FonBDockOver(Sender: TObject; Source: TDragDockObject; X, Y: Integer; State: TDragState; var Accept: Boolean); p r o c e d u r e FormClose(Sender: TObject; var Action: TCloseAction); private f u n c t i o n ComputeDockingRect(var DockRect: TRect; MousePos: TPoint): TAlign; p r o c e d u r e CMDockClient(var Message: TCMDockClient); message CM_DOCKCLIENT; end; implementation {$R *.dfm) u s e s ComCtrls, uTabHost, uConjoinHost, uMain; p r o c e d u r e TDockableForm.FormDockOver(Sender: TObject; Source: TDragDockObject; X, Y: Integer; State: TDragState; var Accept: Boolean); var ARect: TRect; begin Accept := (Source.Control is TDockableForm); if Accept and (ComputeDockingRect(ARect, Point(X, Y)) <> alNone) then Source.DockRect := ARect; end; f u n c t i o n TDockableForm.ComputeDockingRect end; p r o c e d u r e TDockableForm.FormClose(Sender: TObject; var Action: TCloseAction); begin if (HostDockSite is TConjoinDockHost) then if HostDockSite.VisibleDockClientCount <= 1 then HostDockSite.Hide; if (HostDockSite is TPanel) then MainForm.ShowDockPanel( HostDockSite as TPanel, False, nil); Action := caHide; end; p r o c e d u r e TDockableForm.CMDockClient (var Message: TCMDockClient); 196


var

ARect: TRect; DockType: TAlign; Host: TForm; Pt: TPoint;

begin if Message.DockSource.Control is TDockableForm then begin Pt.x := Message.MousePos.x; Pt.y := Message.MousePos.y; DockType := ComputeDockingRect(ARect, Pt); if (HostDockSite is TPanel) then begin Message.DockSource.Control.ManualDock( HostDockSite, nil, DockType); Exit; end; if DockType = alClient then begin Host := TTabDockHost.Create(Application); Host.BoundsRect := Self.BoundsRect; Self.ManualDock(TTabDockHost(Host).PageControll, nil, alClient); Message.DockSource.Control.ManualDock( TTabDockHost(Host).PageControll, nil, alClient); Host.Visible := True; end else begin Host := TConjoxnDockHost.Create(Application); Host.BoundsRect := Self.BoundsRect; Self.ManualDock(Host, nil, alNone); Message.DockSource.Control.ManualDock( Host, nil, DockType); Host.Visible := True; end; end; end; end. Процедура CMDockClient позволяет подключить к приложению новые доки из двух подключенных модулей. Если сообщение клиента о возможности присоединения было проигнорировано (в данном случае основной формой), то процедура CMDockClient строит с помощью объявленной вспомогательной переменной Host один из двух объявленных в модулях uConjoinHost и uTabHost новых доков и после дополнительных проверок пытается пристыковать клиентов с помощью нестандартных методов к этим докам. Может быть построено несколько таких доков. Один вариант формыдока (форма-контейнер) объявлен в модуле, который приводится ниже. unit uConjoinHost; interface uses Windows, Messages, SysUtils, Classes, Graphics, 197


C o n t r o l s , F o r m s , D i a l o g s , uDockForm; type TConjoinDockHost = class(TForm) procedure F o r m C l o s e ( S e n d e r : T O b j e c t ; var Action: TCloseAction); procedure FormDockDrop(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ) ; procedure FormUnDock(Sender: T O b j e c t ; C l i e n t : T C o n t r o l ; NewTarget: TWinControl; v a r Allow: B o o l e a n ) ; procedure FormDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; S t a t e : TDragState; var Accept: Boolean); procedure FormSetSitelnfо(Sender: T O b j e c t ; DockClient: TControl; var I n f l u e n c e R e c t : TRect; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; private procedure DoFloat(AControl: TControl); end; var ConjoinDockHost: TConjoinDockHost; implementation {$R * . d f m ) procedure TConjoinDockHost.DoFloat(AControl: T C o n t r o l ) ; var ARect: TRect; begin ARect.TopLeft AControl.ClientToScreen(Point(0, 0)); A R e c t . B o t t o m R i g h t := A C o n t r o l . C l i e n t T o S c r e e n ( P o i n t (AControl.UndockWidth, AControl.UndockHeight)); AControl.ManualFloat(ARect); end; procedure TConjoinDockHost.FormClose(Sender: TObj e c t ; var Action: TCloseAction); begin i f DockClientCount = 1 then begin DoFloat(DockClients[0]); A c t i o n := c a F r e e ; e n d e l s e A c t i o n := c a H i d e ; end; procedure TConjoinDockHost.FormDockDrop(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ) ; begin DockManager.ResetBounds(True); end; procedure TConjoinDockEost.FormUnDock(S e n d e r : T O b j e с t ; C l i e n t : TControl; NewTarget:TWinControl; v a r Allow: Boolean); begin i f C l i e n t i s TDockableForm t h e n T D o c k a b l e F o r m ( C l i e n t ) . D o c k S i t e := T r u e ;

198


i f ( D o c k C l i e n t C o u n t = 2) a n d ( N e w T a r g e t <> S e l f ) t h e n P o s t M e s s a g e ( S e l f . H a n d l e , WM_CLOSE, 0 , 0) ; end; procedure TConjoinDockHost.FormDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; S t a t e : TDragState; var Accept: Boolean); begin A c c e p t := S o u r c e . C o n t r o l i s TDockableForm; end; procedure TConjoinDockHost.FormGetSitelnfo(Sender: TObject; DockClient: TControl; var InfluenceRect: T R e c t ; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; begin CanDock : = D o c k C l i e n t i s T D o c k a b l e F o r m ; end; end.

Для формы-контейнера установлено: DockSite=true и UseDockManager=true (обязательно установить, иначе сгенерируется исключительная ситуация). На рис. 58 показан вариант пристыковки к форме-контейнеру трех клиентов.

Рис. 58

Последняя форма (TabDockHost) не может принимать клиентов - эту функцию выполняет расположенный на ней компонент PageControll, у которого установлено свойство DockSite=true. Модуль, в котором эта форма объявлена, приводится ниже. unit uTabHost; interface uses W i n d o w s , M e s s a g e s , S y s U t i l s , C l a s s e s , C o n t r o l s , Forms, D i a l o g s , ComCtrls; type TTabDockHost = class(TForm) PageControll: TPageControl;

* Graphics,

199


procedure F o r m C l o s e ( S e n d e r : T O b j e c t ; var Action: TCloseAction); procedure PageControllUnDock(Sender: T O b j e c t ; C l i e n t : TControl; NewTarget: TWinControl; v a r Allow: Boolean); procedure PageControllGetSitelnfĐž(Sender: T O b j e c t ; DockClient: TControl; var I n f l u e n c e R e c t : TRect; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; procedure PageControllDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; S t a t e : TDragState; var Accept: Boolean); end; var TabDockHost: TTabDockHost; implementation {$R * . d f m } uses uDockForm; procedure TTabDockHost.FormClose(Sender: T O b j e c t ; var Action: TCloseAction); var ARect: TRect; begin i f PageControll.DockClientCount = 1 then begin w i t h P a g e C o n t r o l l . D o c k C l i e n t s [ 0 ] do b e g i n A R e c t . T o p L e f t := C l i e n t T o S c r e e n ( P o i n t ( 0 , 0)); A R e c t . B o t t o m R i g h t := C l i e n t T o S c r e e n ( Point(UndockWidth, UndockHeight)); ManualFloat(ARect); end; A c t i o n := c a F r e e ; e n d e l s e A c t i o n := c a H i d e ; end; procedure TTabDockHost.PageControllUnDock(Sender: T O b j e c t ; C l i e n t : T C o n t r o l ; NewTarget:TWinControl; v a r Allow:Boolean); begin i f ( P a g e C o n t r o l l . D o c k C l i e n t C o u n t = 2) a n d ( N e w T a r g e t <> S e l f ) t h e n P o s t M e s s a g e ( S e l f . H a n d l e , WM_CLOSE, 0 , Obend; procedure TTabDockHost.PageControllGetSitelnfo(Sender: TObject; DockClient: TControl; var InfluenceRect: T R e c t ; MousePos: T P o i n t ; v a r CanDock: B o o l e a n ) ; begin CanDock : = D o c k C l i e n t i s T D o c k a b l e F o r m ; end; procedure TTabDockHost.PageControllDockOver(Sender: T O b j e c t ; S o u r c e : T D r a g D o c k O b j e c t ; X, Y: I n t e g e r ; State: TDragState; var Accept: Boolean); begin Accept := S o u r c e . C o n t r o l i s TDockableForm; end; end.

200


На рис. 59 отображена возможность применения формы "записная книжка" с пристыкованными к ней тремя формами (основная форма не показана). Естественно, страницы на форме можно переключать и, кроме того, делать на страницах какие-то записи. /

Форм* док "Записная книлка"

Рис.59

В заключение отметим использование функции ManualDock, которая может организовать технологию Drag and Dock в случаях отсутствия необходимых установок для двух стыкующихся элементов. В частности, в модуле uDockForm выполняется операция стыковки между основной формой и клиентом при условии, что клиент запрашивает и получает прямоугольник "причаливания" в центре основной формы (задано программно: alClient в процедуре CMDockClient). Результат такой операции представлен на рис. 60. JcHoewoe окно док ? ' ПРИМЕР 24 4.*"- ВДВДВДВДНЭТЧЯЙ

IlffceHiWW»mbZ 11 Yetew

glue

X

Выход

£reen

Lime

Purple

Red

Jed

в •—1

Рис. 60 14 — 4758

201


Окно в данном случае потеряло свои стандартные кнопки и стало неуправляемым с помощью мыши. Чтобы вернуть этому окну управление, как предусмотрено в приложении, необходимо обеспечить стыковку с ним еще одного клиента. В зависимости от варианта второй стыковки может быть получен один из двух случаев, представленных на рис. 58 и 59, которые получаются при обработке полученной операции с помощью похожих обработчиков, находящихся в модуле TConjoinDockHost (FormUnDock) и модуле TTabDockHost (PageControllUnDock).

ИСПОЛЬЗОВАНИЕ Ф У Н К Ц И Й W I N D O W S API ПРИ РАБОТЕ С ФАЙЛАМИ Иногда требуются стандартные функции работы с файлами Windows, тем более что возможности в них расширены. Каждый файл в Windows описывается не переменной, а дескриптором (THandle, можно описывать Cardinal), который представляет собой 32-разрядную величину, идентифицирующую файл в операционной системе. Открывается файл при помощи следующей функции: function CreateFileA(lpFileName; PAnsiChar; dwDesiredAccess, dwShareMode: DWORD; IpSecurityAttributes: PSecurityAttributes; dwCreationDisposition, dwFlagsAndAttributes: DWORD; hTemplateFile: THandle): THandle; stdcall;.

Такое большое количество параметров функции связано с тем, что СгеateFileA используется для открытия файлов на диске, устройств, каналов, портов и, вообще, любых источников ввода и вывода. Рассмотрим назначение параметров. Здесь lpFileName - имя открываемого объекта; dwDesiredAccess — способ доступа к объекту (может иметь значение GENERic_READ — для чтения, GENERIC_WRITE - для записи или комбинация этих значений). Если dwDesiredAccess =0, то можно получить атрибуты файла без фактического его открытия. Параметр dwShareMode открывает режим совместного с другими программами доступа (0 - совместный доступ запрещен; FILE_SHARE_READ - для чтения; FILE_SHARE_WRITE - для записи; комбинация этих значений - полный доступ). В рассматриваемой функции можно задавать атрибуты защиты объекта - IpSecurityAttributes (если равно nil, то устанавливаются атрибуты по умолчанию). Следующий параметр dwCreationDisposition отвечает за способ открытия объекта (CREATE_NEW — создается новый объект, если таковой существует, иначе возвращается О Ш И б к а ERROR_ALREADY_EXISTS; CREATE_ALWAYS — создается новый объект, если таковой существует, или, если возможно, перезаписыва202


ется, иначе выдается ошибка; OPEN_EXISTING — открывает существующий объект или, если таковой не найден, возвращается ошибка; OPEN_ALWAYS — открывает существующий объект, если таковой не найден, он создается). Набор атрибутов (скрытый, системный, сжатый) и флагов для открытия объекта задается параметром dwFlagsAndAttributes. Последний параметр hTemplateFile задает файл-шаблон, атрибуты которого используются для открытия объекта. Функция возвращает дескриптор открытого объекта (некоторое число). Если открыть объект невозможно, то возвращается код ошибки INVALID_HANDLE_VALOE. Более полные сведения об ошибке можно получить, вызвав функцию GetLastError. Закрывается объект функцией f u n c t i o n CloseHandle(hObject: THandle): BOOL; s t d c a l l ; . Для чтения и записи данных используются следующие функции: f u n c t i o n R e a d F i l e ( h F i l e : THandle; var B u f f e r ; nNumberOfBytesToRead: DWORD; var lpNumberOfBytesRead: DWORD; lpOverlapped: POverlapped): BOOL; s t d c a l l ; , f u n c t i o n W r i t e F i l e ( h F i l e : THandle; c o n s t B u f f e r ; nNumberOfBytesToWrite: DWORD; var lpNumberOfBytesWritten: DWORD; lpOverlapped: POverlapped): BOOL; s t d c a l l ; . Здесь nNumberOfBytesToRead и nNumberOfBytesToWrite — количество байт, которое нужно прочитать или записать; lpNumberOfBytesRead и lpNumberOfBytesWritten - количество байт, которое фактически прочитано или записано. Параметр lpoverlapped - указатель на некоторый дескриптор структуры (типа TOverlapped) события отложенного ввода или вывода, например, для каких-то относительно "медленных" устройств. Создается событие следующей функцией: f u n c t i o n CreateEventA ( lpEvent A t t r i b u t e s : P S e c u r i t y A t t r i b u t e s ; bManualReset, b l n i t i a l S t a t e :BOOL; lpName: PAnsiChar): THandle; s t d c a l l ; . Эта функция как раз и возвращает указанный выше дескриптор, на который ссылается параметр lpOverlapped. С помощью данного события автоматически включается отложенный ввод или вывод. Время в миллисекундах (параметр dwMilliseconds), которое разрешается ожидать при отложенном вводе или выводе, указывается в функции f u n c t i o n WaitForSingleObject (hHandle : THandle; dwMilliseconds: DWORD): DWORD; s t d c a l l ; , которая прерывает отложенную операцию чтения или записи. Можно задать бесконечный интервал INFINITE. Функция c r e a t e F i l e A полезна при работе с портами, например, следующая программа позволяет успешно открыть порт СОМ1 и выдать минимальные сведения о подключенном к этому порту модеме. procedure TForml.ButtonlClick(Sender: 14*

TObject); 203


var

CoramPort : s t r i n g ; hDev : THandle; M o d e m S t a t : DWord;

begin CommPort := 'COM11; hDev := CreateFile(PChar(CommPort), GENERIC_READ,0,nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); i f hDev = INVALID_HANDLE_VALUE t h e n b e g i n S h o w M e s s a g e ( 1 Ошибка о т к р ы т и я п о р т а ' + C o m m P o r t ) ; exit; end; i f GetCtommModemStatus(hDev,ModemStat)<>false t h e n b e g i n i f ModemStat and MS_CTS_ON <> 0 t h e n ShowMessage( ' У п р а в л е н и е CTS ( c l e a r - t o - s e n d ) в п о р я д к е . ' ) ; i f ModemStat and MS_DSR_ON <>0 t h e n S h o w M e s s a g e ( ' У п р а в л е н и е DSR ( d a t a - s e t - r e a d y ) в п о р я д к е . ' ) ; i f ModemStat and MS_RING_ON <> O t h e n S h o w M e s s a g e ( 'Управление r i n g i n d i c a t o r в п о р я д к е . ' ) ; i f ModemStat and MS_RLSD_ON <> 0 t h e n S h o w M e s s a g e ( ' У п р а в л е н и е RLSD ( r e c e i v e - l i n e - s i g n a l - d e t e c t ) в п о р я д к е . ' ) ; end; CloseHandle(hDev); end; .

Еще один вариант использования рассматриваемой функции Windows API можно привести, когда требуется посекторное чтение, например, в дисководе А:. Const SectorSize=5X2; Var Buffer:Pointer; FUNCTION TForml.ReadSector(Head,Track,Sector:integer; Buffer:pointer):boolean; var hDev:THandle; DevName:string; nb:cardinal ; begin DevName:='\\.\A:'; hDev:=CreateFile(pChar(DevName),GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, FILE_ATTRIBOTEJHORMAl,0); i f hDev=INVALID_HANDLE_VALDE t h e n b e g i n Result:=false; Exit; end; SetFilePointer(hDev,(Sector-1)*SectorSize,nil,FILE_BEGIN); Result:=ReadFile(hDev,Buffer,SectorSize,nb,nil) and (nb=SectorSize);

204


CloseHandle(hDev); end; .

В данном примере для позиционирования файлового указателя применяется еще одна функция Windows API SetFilePointer, объявленная следующим образом: function SetFilePointer(hFile: THandle; lDistanceToMove: Longint; lpDistanceToMoveHigh: Pointer; dwMoveMethod: DWORD): DWORD; stdcall;.

Как следует из примера функции ReadSector, позиционирование файлового указателя не учитывает значений параметров Head и Track (опущено для упрощения). При необходимости можно добавить взаимосвязь всех трех параметров. Далее рассматривается небольшой пример, чтобы продемонстрировать указанные выше функции в работе. П Р И М Е Р П Р И Л О Ж Е Н И Я 25

Пусть имеется текстовый файл, созданный по правилам Delphi. Требуется открыть этот файл указанной выше функцией Windows API и прочитать его содержимое. При выполнении этого примера встретятся некоторые трудности из-за того, что придется читать файл как совокупность байтов, а потом эти байты пытаться интерпретировать. Вариант решения примера приводится на рис. 61.

Рис. 61

Ниже приводится текст программы. unit U M S f i l e ; interface

205


uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s type TForml = c l a s s ( T F o r m ) Buttonl: TButton; Memol: TMemo; BitBtnl: TBitBtn; Memo2: TMemo; Label1: TLabel; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure FormDeactivate(Sender: T O b j e c t ) ; public hDev:THandle; end; var F o r m l : T F o r m l ; Buffer:pointer; FilSize:cardinal; implementation {$R * . d f m } procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; type buf=array of byte; var FilName:string; nb:cardinal; i,r:byte; F:File; sNumber:string[3]; begin FilName:='InpFil.txt'; B u f f e r := N i l ; FilSize:=0? {$1-} A s s i g n F i l e ( F , FilName); System.Reset(F, 1); ($1+) if IOResultoO then begin memol.Lines.Add('Ошибка открытия ф а й л а ' ) ; Exit; End; F i l S i z e := F i l e S i z e ( F ) ; closeFILE(F); memol.Lines.Add(1FilSize= '+inttostr(FilSize)); memo2.Lines.LoadFromFile(FilName); hDev:=CreateFileA(pChar(FilName) ,GENERIC_READ, FILE_SHARE_READ or FILE_SHARE_WRITE,nil, OPEN_EXISTING, FILE_ATTRIBDTE_NORMAL,0); i f hDev=INVALID_HftNDLE_VALDE t h e n b e g i n m e m o l . L i n e s . A d d ( 1 Ошибка о т к р ы т и я ф а й л а W i n d o w s ' ) ; Exit;

206


end; try Getmem(Buffer, FilSize) ;

ReadFile(hDev,Buffer,FilSize,nb,nil);

memol.Lines.Add('Прочитано б а й т о в ' + i n t t o s t r ( n b ) ) ; m e m o l . L i n e s . A d d ( 1 Прочитаны ч и с л а ' ) ; sNumber:=''; FOR I : = 0 TO F i l S i z e DO BEGIN r:=buf(Qbuffer)[I]; if chr(r) in [ , 0 , . . , 9 4 then sNumber:=sNumber+chr(r) else begin i f length(sNumber)>0 then memol.Lines.Add(sNumber); sNumber:='•; end; END; finally

CloseHandle(hDev);

end; end;

procedure TForml.FormDeactivate(Sender:

TObject);

begin FreeMem(Buffer, FilSize) ; end; end.

В данной программе вначале определяется количество байт в исходном файле, которое включает в себя все символы, содержащиеся в файле, в том числе и разделители, т.е. отдельные цифры чисел, пробелы, #13, #10. Далее этот файл открывается как файл Windows. Байты считываются в переменную B u f f e r и с помощью преобразования типа P o i n t e r в тип Array of b y t e восстанавливаются в исходные числа. В примере показан один из вариантов работы с типом Pointer. ИСПОЛЬЗОВАНИЕ О Т О Б Р А Ж А Е М Ы Х Ф А Й Л О В В Windows под памятью подразумевается не только оперативная память (ОЗУ), но также и память, резервируемая операционной системой на жестком диске. Этот вид памяти называется виртуальной памятью. Посредством страничной с��стемы (paging system) подкачки в такую память на жестком диске отображаются код и данные. По мере необходимости требуемый фрагмент виртуальной памяти переносится из страничного файла в ОЗУ и, таким образом, становится доступным для выполнения. Данный механизм отображения можно применить и для любого другого файла, сделав его содержимое частью адресного пространства. 207


Для выделения фрагмента виртуальной памяти должен быть создан специальный системный объект , называемый отображаемым файлом. Отображаемый в адресное пространство дескриптор объекта создается операционной системой при вызове следующей функции Windows API: function CreateFileMappingA(hFile: THandle; lpFileMappingAttributes: PSecurityAttributes; flProtect, dwMaxi mnmSizeHigh, dwMaximumSizeLow:DWORD; lpName: PAnsiChar): THandle; stdcall;.

Первый параметр соответствует дескриптору открытого при помощи функции createFile файла. Второй параметр является указателем на структуру с данными по защите файла. Посредством параметра flProtect указывается тип доступа к файлу, например, PAGE_READWRITE. С помощью параметров dwMaximumSizeHigh, dwMaximumSizeLow задается вьщеляемое пространство виртуальной памяти. Если отображается весь файл, то их значения можно задать равными нулю (при условии, что файл намного меньше 4 Гбайт). Последний параметр представляет собой имя объекта файлового отображения (применяется при вызовах из разных, но одновременно использующих созданный объект, программ). Следующий этап построения отображения - спроецировать данные файла в адресное пространство. Этой цели служит следующая функция: function MapViewOfFile(hFileMappingObject: THandle; dwDesiredAccess: DWORD; dwFileOffsetHigh, dwFileOffsetLow, dwNumberOfBytesToMap: DWORD): Pointer; stdcall;.

Первый параметр является дескриптором созданного объекта файлового отображения. С помощью второго параметра определяется режим доступа к объекту (FILE_MAP_WRITE, FILE_MAP_READ, FILE_MAP_ALL_ACCESS). Посредством третьего и четвертого параметров задается смещение отображаемого участка относительно начала файла (чаще всего оба параметра равны нулю). Пятый параметр определяет количество байт данных файлового отображения - если равен нулю, то отображаются все данные, выделенные при этом функцией CreateFileMappingA. Функция MapViewOfFile вернет начальный адрес (тип возвращаемого результата - Pointer) данных в памяти. Далее можно работать с исходным файлом как с обычной памятью. По окончании работы с отображаемым в памяти файлом необходимо вернуть занятое адресное пространство, используя функцию function UnmapViewOfFile(lpBaseAddress:

Pointer):

BOOL;

stdcall;, где

lpBaseAddress - используемый выше указатель. Последняя операция, которую надо выполнить, - закрыть полученный выше дескриптор. Эта операция выполняется путем вызова еще одной функции Windows API: function CloseHandle(hObject: THandle): BOOL; stdcall;.

208


П Р И М Е Р П Р И Л О Ж Е Н И Я 26

Анализ качества продукции на некотором производстве проводится путем измерения при некоторых условиях х некоторого признака V- Пусть определенная совокупность х,у, My, Dy, состоящая из п = 8 элементов, записана в типизированный файл DatPro.dat, причем первоначально My, Dy равны нулю и их предстоит вычислить. Если производство работает нормально, то совокупность точек х, у должна ложиться на прямую линию. Методом наименьших квадратов необходимо постоянно контролировать на производстве этот факт, т.е. если ошибка отклонения функции y=f(х) от прямой линии меньше, чем некоторая величина £ то качество выпускаемой продукции в пределах нормы, иначе необходимо принимать меры по изменению, например, технологического режима. Пусть 4=0,08. В данном случае применяются следующие формулы расчета: My = a+b-y ; Dy = My-y ; а =

Щх21-[х-уЩ Н*2]-М2

.

ь=п-[х-у\-Ы-Ы 2

'

В этих формулах обозначено, например,

.

и-[* ]-М2 ' п = . Расчет ошибки вы<=1

полняется по следующей формуле: т = л г ^ ^ ^ . Результаты V

п-1

решения

примера представлены на рис. 62.

0.92 0 9243 0.0043 1.94 1.9334 -0.0066 3.88 3.9517 0.0717 5.02 4.9608 -0.0592 6.04 5.9699 -0.0701 7.89 7.9881 0.0981 10.08 10.0064 -0.0736 10.98 11.0155 0.0355

Рис. 62

209


Исходные данные необходимо записать с помощью отдельной программы в типизированный файл. В с е необходимые сведения по работе с отображаемыми файлами приводятся в следующем тексте программы: unit U n i t 2 5 ; interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , B u t t o n s , S t d C t r l s ; type TForml = c l a s s ( T F o r m ) Memol: TMemo; Buttonl: TButton; BitBtnl: TBitBtn; Memo2: TMemo; Labell: TLabel; Label2: TLabel; procedure ButtonlClick(Sender: T O b j e c t ) ; public hFile:THandle; hFileMapObject: THandle; PFileMem: Pointer ; end; datP=record x,y,My,Dy:real; end; const n=8; var Forml; T F o r m l ; implementation {$R * . d f m ) procedure TForml.ButtonlClick(Sender: TObj e c t ) ; type buf=array of datP; var FilName:string; r:datP; s:string; i:byte; f f : f i l e of d a t P ; a,b,Sx,Sy,Sx2,Sxy:real; begin FilName:= 1 DatPro.dat'; {$1-} AssignFile(ff,FilName); Reset(ff); {$1+} i f IOResult<>0 t h e n begin memol.Lines.Add('Ошибка открытия ф а й л а ' ) ; Exit; end; f o r i : = l t o n do b e g i n ; 210


Read(ff,r); _ i i .i w i t h r do b e g i n s:=FormatFloat('0.00',x)+FormatFloat(' 0.00',y)+ FormatFloat(' 0.00',My)+ FormatFloat(' 0.00',Dy); memol.Lines.Add(s); end; end; CloseFile(ff); PFileMem:=nil; hFile:=CreateFileA(pChar(FilName),GENERIC_READ or GENERICJWRITE, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL , 0) ; i f hFi1e=INVALID_HANDLE_VALUE t h e n b e g i n memo2.Lines.Add('Ошибка открытия файла W i n d o w s ' ) ; Exit; end; hFileMapObj ect:=CreateFileMapping(hFile, nil, PAGE_READWRITE, 0,0,'ShareMem'); i f h F i l e M a p O b j e c t = INVALID_HANDLE_VALUE t h e n b e g i n m e m o 2 . L i n e s . A d d ( ' О ш и б к а о т к р ы т и я файла о т о б р а ж е н и я 1 ) ; CloseHandle(hFile); Exit; end; PFileMem := MapViewOfFile(hFileMapObject, FILE_MAP_ALL_ACCESS,0, 0,n*Sizeof(datP)); i f PFileMem = n i l t h e n b e g i n memo2.Lines.Add('Ошибка открытия отображения в п а м я т и ' ) ; CloseHandle(hFileMapObject) ; CloseHandle(hFile) ; Exit; end; a. . = >/I . Ь Sx:=0.0; Sy:=0.0; Sx2:=0.0; Sxy:=0.0; FOR I : = 0 TO n - 1 DO with buf(PFileMem)[I] do begin Sx:=Sx+x; Sx2:=Sx2+sqr(x) ; Sy:=Sy+y; Sxy:=Sxy+y*x; end; a:=(Sy*Sx2-Sxy*Sx)/(n*Sx2-sqr(Sx)) ; b:=(n* Sxy-Sy* Sx)/(n*Sx2-sqr(Sx)); Sy:=0.0; FOR I : = 0 TO n - 1 DO S.•

211


with b u f ( P F i l e M e m ) [ I ] do begin My:=a+b*x; Dy:=My-y; Sy:=Sy+sqr(Dy) ; end; Sxy:=sqrt(Sy/(n-l)) ; memo2.Lines.Add( 'Ошибка='+FormatFloat(' 0.00000', Sxy)); i f Sxy>0.08 t h e n memo2.Lines.Add( 'В системе производства с б о и . . . ' ) else memo2.Lines.Add('В п р о и з в о д с т в е в с е в п о р я д к е ! ' ) ; if PFileMemOnil then DnMapViewOf File (PFileMem) ; if hFileMapObjectOO then CloseHandle (hFileMapObject) ; if hFile=0 then CloseHandle(hFile); AssignFile(ff,FilName) ; Reset(ff); о .—I I . Ь. , memo2.Lines.Add(s); f o r i : = l t o n do b e g i n ; Read(ff,r); w i t h r do b e g i n s:=FormatFloat('0.00',x)+FormatFloat(' 0 . 0 0 ' , y)+ FormatFloat(' 0.0000',My)+ FormatFloat(' 0.0000',Dy); memo2.Lines.Add(s); end; end; CloseFile(ff); end; end.

Следует обратить внимание на то, какое преобразование типов было использовано для работы с выделенной под файл памятью. Самым замечательным является то, что любые изменения в памяти мгновенно отражаются в отображенном файле. Так, если второй раз запустить программу на выполнение, то исходные данные (см. рис. 62) будут отражать уже не нулевые значения величин My и Dy. ПРОГРАММНЫЕ ПОТОКИ Программные потоки (подзадачи, нити, Threads) - мощный инструмент, когда приложению необходимо выполнять сразу несколько действий (по крайней мере, они создают иллюзию одновременного выполнения нескольких операций). Если задачу приложения можно разделить на различные блоки: ввод или вывод, связь, обработка некоторых особых событий и т.д., то потоки могут быть органично встроены в программные решения. Сделав приложение многопоточным, можно получить дополнительные воз212


можности управления с помощью изменения приоритетов потоков или, например, снять большую нагрузку на приложение, распределив ее между потоками. Потоки в полной мере эксплуатируют операционную систему. Например, летящие листы бумаги, которые мы видим, когда копируется файл, отображаются на экране, благодаря активной подзадаче, в то время как основной процесс занят копированием данных. На самом деле операционная система (если не используется многопроцессорные компьютеры) "героически разрывается" между различными процессами и подзадачами, периодически приостанавливая одну выполняемую задачу и переходя к следующей в вечно крутящейся карусели. Таким образом, поток - это объект, созданный внутри процесса и получающий время процессора, которое выделяется квантами. Когда Windows загружает программу, строится процесс, для которого создается хотя бы один поток, чтобы запустить программный код. Процессы определяют данные, ресурсы, адресное пространство и пространство виртуальных адресов. Потоки, работающие в данном процессе, используют все ресурсы, предоставленные процессу операционной системой, и отвечают за выполнение кода. Потоки подразделяются на симметричные (они имеют одинаковое предназначение, исполняют один и тот же код и могут разделять одни и те же ресурсы) и асимметричные (решающие различные задачи и, как правило, не разделяющие совместные ресурсы). ПРИОРИТЕТЫ

потоков

Итак, каждый поток выполняется во всем выделенном пространстве памяти процесса. Таким образом, все подзадачи приложения могут видеть и изменять все глобальные данные. Поэтому при наличии нескольких потоков необходимо позаботиться о синхронизации их работы, так как операционная система выполняет переключения между подзадачами и основным процессом, выделяя, чаще всего, для них различное количество процессорного времени. Количеством выделяемого процессорного времени можно управлять с помощью назначаемых приоритетов. Операционная система может назначать приоритеты в соответствии со своими установками, или можно задать нужный приоритет, используя специальные системные подпрограммы (например, SetPriorityClass). Система при выполнении приложения будет расставлять подзадачи в соответствии с их приоритетом и в том случае, когда нет доступных для выполнения подзадач с высоким приоритетом, выполняются подзадачи с приоритетом более низкого уровня. Если несколько подзадач имеют один и тот же уровень приоритета, то они расставляются системой и выполняются одна за другой по кругу. Приоритеты присваиваются отдельно процессам и каждому включенному в процесс потоку. Приоритет, устанавливаемый для процесса, известен 213


как базовый (base priority). Базовый приоритет может изменяться от 0 до 31. Для потоков можно использовать приоритеты от 0 до 15, если процесс не имеет приоритета REALTIME_PRIORIТY_CLASS. В табл. 16 приводятся основные сведения по приоритетам процессов. Таблица 16 Наименование

Класс приоритета IDLE PRIORITY C L A S S NORMALJPRIORITYCLASS

Пассивный (фоновый) Нормальный

H I G H PRIORITY C L A S S REALTIME PRIORITY CLASS

Высокий Реального времени

Основной приоритет 4

7 (фоновый), 9 (переднего плана) 13 24

Существуют свои приоритеты и для потоков, но не абсолютные, а относительные - зависящие от приоритета порождающего их процесса. Таким образом, при установке приоритета для потока необходимо сообщить системе, более высокий или низкий приоритет по сравнению с базовым следует установить, и система справится с этим сама. Естественно, чтобы выбрать для потока приоритет TIMEJCRITICAL, нужно иметь очень веские основания, иначе другие процессы в системе могут не получить достаточного процессорного времени. КЛАСС TTHREAD

Для реализации потоков Delphi предоставляет класс TThread. Наиболее важными методами являются Execute и Synchronize. Первый метод осуществляет выполнение потока. Этот метод абстрактный. Поэтому необходимо создать класс, который является потомком TThread, и в этом классе переопределить метод Execute. Второй метод Synchronize, чтобы избежать одновременного использования компонентов приложения, сообщает основной подзадаче, что требуется обращение к ресурсам, которые, возможно, заняты. При этом передается адрес метода, который основная подзадача может вызвать, чтобы выполнить требуемую потоком работу. Процесс извещения является последовательным и основная подзадача должна полностью завершить выполняемую работу, прежде чем получить следующее извещение. Извещающее сообщение отсылается при помощи подпрограммы Windows SendMessage. Результатом является то, что подзадача будет заморожена вплоть до полной отработки задания внутри основной подзадачи. В программе перед выполнением потока, являющегося классом, естественно, должен быть построен соответствующий объект. Д ля этого вызывается метод Create, имеющий один аргумент CreateSuspended типа Boolean. Если поток создан со значением аргумента CreateSuspended = false, то он немедленно (как только получит время процессора в соответствии с его собственным приоритетом и приоритетом процесса) начинает выполняться (запускается метод Execute), в противном случае Execute запускается вы214


зовом метода Resume. Внутри метода Execute могут выполняться некоторые циклические действия, для завершения которых можно воспользоваться свойством Terminated. Устанавливая значение этого свойства равным true внутри метода Execute или извне, поток получает команду о завершении его работы. С помощью метода Suspend можно приостановить работу потока и затем продолжить, вызывая метод Resume. Переустанавливая значение свойства Priority, можно управлять приоритетом потока. ПРИМЕР П Р И Л О Ж Е Н И Я 27

Рассмотрим простой пример по использованию одного дополнительного потока. Пусть некоторый поток с помощью собственного метода, например, Paint применяется для закраски некоторой области прямоугольной формы. Основной поток приложения создается для рисования окружностей внутри той же области с помощью обработчика OnMouseDown. Для построения приложения необходимо создать проект с двумя модулями. Один модуль — основная программа С объявленной переменной типа поток в секции Public внутри создавемого класса формы, другой модуль — модуль потока. Поместим на форму две кнопки TButton. Создадим для них два обработчика OnClick. Для формы, как уже отмечено, создается обработчик OnMouseDown. Далее создадим модуль для реализации вспомогательного потока (основной поток автоматически создается для процесса (приложения)). Необходимо открыть меню File - New и выбрать пункт Other. Откроется диалоговое окно, в котором нужно выбрать (дважды щелкнуть мышкой) значок Thread Object. Когда появится диалоговое окно, необходимо ввести имя класса TThread (Class Name = TPaintThread) и нажать на кнопку ОК. Ниже приводится весь программный код данного примера, который демонстрирует использование метода Synchronize. unit MyThreadl; {Модуль для определения потока} interface uses Classes; type TPaintThread = class(TThread) private x,y: integer; protected procedure Execute; override; procedure Paint; end; implemen tation uses Onit27_l, Graphics; procedure TPaintThread.Execute; begin

215


randomize; Repeat x:=random(250); y:=random(Forml.ClientHeight); Synchronize(Paint); u n t i l Terminated; end; procedure TPaintThread.Paint; begin Forml.Canvas.Pixels[x, y]:=clPurple; end; end. unit Unit27_l; (Основной модуль) interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , E x t C t r l s , B u t t o n s , MyThreadl; type TForml = c l a s s ( T F o r m ) Buttonl: TButton; Button2: TButton; BitBtnl: TBitBtn; procedure ButtonlClick(Sender: T O b j e c t ) ; procedure Button2Click(Sender: T O b j e c t ) ; procedure FormMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, Y: I n t e g e r ) ; public MyPaintThread:TPaintThread; end; var Forml: T F o r m l ; implementation {$R * . d f m ) procedure TForml.ButtonlClick(Sender: T O b j e c t ) ; begin Buttonl.Enabled:=false; Button2.Enabled:=true; MyPaintThread:=TPaintThread.Create(false); end; procedure TForml.Button2Click(Sender: T O b j e c t ) ; begin MyPaintThread.Free; B u t t o n l . E n a b l e d := True; B u t t o n 2 . E n a b l e d := F a l s e ; end; procedure TForml.FormMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, Y: I n t e g e r ) ; begin C a n v a s . P e n . C o l o r := C o l o r ;

216


C a n v a s . B r u s h . C o l o r := C o l o r ; C a n v a s . E l l i p s e (x - 3 0 , у - 3 0 , x + 3 0 , end; end.

у + 30);

Следует обратить внимание на строку MyPaintThread:TPaintThread; и использование модуля Graphics. Среда Delphi может не добавить этот модуль в список uses. На рис. 63 приводится вариант решения данного примера.

Рис. 63 ИСПОЛЬЗОВАНИЕ БЛОКИРОВКИ В П Р И М Е Р Е 27

Можно написать другой вариант решения данного примера, воспользовавшись особенностью компонента TCanvas. Дело в том, что этот компонент содержит два метода Lock и UnLock, которые позволяют блокировать вывод на канву с последующей разблокировкой. Естественно, при использовании этих методов канвы отпадает необходимость синхронизации работы потоков с помощью метода Synchronize. Код программы приводится далее. Из текста кода основного модуля приводится только один обработчик TForml. FormMouseDown, в который внесены изменения. Отметим, что код потока немного упростился. u n i t MyTread2; interface uses

type 15

4758

Classes;

217


TPaintThread = class(TThread) protected procedure Execute; override; end; implementation uses Unit27_2, Graphics; procedure TPaintThread.Execute; var x , y : i n t e g e r ; begin randomize; Repeat x:=random(250); у:=random(Forml.ClientHeight); with Forml.Canvas d o b e g i n Lock; try Pixels[x,y]:=clPurple; finally UnLock; end; end; until Terminated; end; end. unit Unit27_2; procedure TForml.FormMouseDown(Sender: T O b j e c t ; B u t t o n : T M o u s e B u t t o n ; S h i f t : T S h i f t S t a t e ; X, Y: I n t e g e r ) ; begin Canvas.Lock; try C a n v a s . P e n . C o l o r := C o l o r ; C a n v a s . B r u s h . C o l o r := C o l o r ; C a n v a s . E l l i p s e (x - 3 0 , у - 3 0 , x + 3 0 , у + 3 0 ) ; finally Canvas.Unlock; end; end; end.

Нельзя не заметить, что второй вариант примера работает значительно быстрее. Но, к сожалению, не для любых потоков можно применить подобные приемы блокировки. М Н О Г О П О Т О Ч Н О Е П Р И Л О Ж Е Н И Е В ПРИМЕРЕ 28

В данном примере создаются три потока, которые закрашивают разными цветами один и тот же прямоугольник, как и в примере 27. Однако способ 218


закраски в примере 28 отличается тем, что используется сканирование прямоугольной области. В варианте, который приводится ниже, используется встроенная в класс TThread система безопасности с помощью метода синхронизации. Следует отметить, что данный подход не спасает приложение и компьютер от зависания, поэтому этот вариант примера лучше запускать из среды Delphi, чтобы можно было воспользоваться клавишами Ctrl+F2. Правда, вероятность зависания зависит от установленных приоритетов для каждого потока. В примере используются новые компоненты: TTrackBar - для изменения приоритета потоков и TProgressBar — для визуального контроля скорости работы того или иного потока. Компонент TTrackBar применяется, когда требуется изменять значения в заданном диапазоне. Текущее значение определяется свойством Position, диапазон значений задается свойствами Min и Мах. В примере задано Min=0, Мах=3. Второй компонент (TProgressBar на странице Win32 — индикатор хода работ) показывает графическую полосу хода выполнения какой-либо операции. Текущая позиция индикатора определяется свойством Position, свойства Min и Мах задают диапазон индикатора. В примере задано Min=0, Мах определяется свойством формы ClientHeight, т.е. в данном случае числом дисплейных строк. Число участков, на которое делится весь диапазон, задано равным 10. Это число определяет величину шага сканирования диапазона, которое записывается в свойстве Step. Ниже приводится программный код примера. Отметим, что в обработчике TrackBarlChange используются свойства Tag компонентов TtrackBar, которые соответственно необходимо с помощью инспектора объектов установить равными 1,2, 3. unit: MyThread3; interface uses C l a s s e s , G r a p h i c s , C o m C t r l s ; type TPaintThread = class(TThread) private x,у:integer; FColor:TColor; FProgressBar:TProgressBar; public constructor InitColor(nColor:TColor); property ProgressBar:TProgressBar write FProgressBar; procedure DisplayProgress; protected procedure Execute; override; procedure Paint; end; implementation uses Unit28; 15*

219


constructor TPaintThread.InitColor(nColor:TColor); begin Fcolor:=nColor ; end; procedure TPaintThread.DisplayProgress; begin FprogressBar.Position:=y; end; procedure TPaintThread.Paint; var j: Integer; begin f o r j : = 0 t o 250 d o b e g i n x:=j; Forml.Canvas.Pixels[x,у]:=FColor; end; end; procedure TPaintThread.Execute; var i: Integer; begin repeat f o r i : = 0 t o F o r m l . C l i e n t H e i g h t do b e g i n y:=i; Synchronize(DisplayProgress); Synchronize(Paint); end; u n t i l Terminated; end; end. unit Onit28; interface uses Windows, M e s s a g e s , S y s U t i l s , V a r i a n t s , C l a s s e s , G r a p h i c s , C o n t r o l s , Forms, D i a l o g s , S t d C t r l s , B u t t o n s , C o m C t r l s , MyThread3; type TForml = c l a s s ( T F o r m ) TrackBarl: TTrackBar; TrackBar2: TTrackBar; ТгаскВагЗ: TTrackBar; BitBtnl: TBitBtn; C h e c k B o x l : TCheckBox; C h e c k B o x 2 : TCheckBox; CheckBox3: TCheckBox; ProgressBarl: TProgressBar; ProgressBar2: TProgressBar; ProgressBar3: TProgressBar; procedure CheckBoxlClick(Sender: T O b j e c t ) ; procedure TrackBarlChange(Sender: T O b j e c t ) ;

220


p r o c e d u r e F o r m C r e a t e ( S e n d e r : TObject); p r o c e d u r e FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer) public PT: array [1..3] of TPaintThread; end; v a r F o r m l : TForml; implementation {$R *.dfm} p r o c e d u r e T F o r m l . C h e c k B o x l C l i c k ( S e n d e r : TObject); begin Forml.Canvas.Lock; if (Sender as TCheckbox).Checked then PT [(Sender as TCheckbox).Tag].Resume else PT [(Sender as TCheckbox).Tag].Suspend; Forml.Canvas.UnLock; end; p r o c e d u r e TForml.TrackBarlChange(Sender: TObj ect); begin PT [(Sender as TTrackBar).Tag].Priority := TThreadPriority ((Sender as TTrackBar).Position) end; p r o c e d u r e TForml.FormCreate(Sender: TObject); begin PT [1] := TPaintThread.Create (true); PT [2] := TPaintThread.Create (true); PT [3] := TPaintThread.Create (true); PT[1].InitColor(clRed); PT[2].InitColor(clBlue); PT[3].InitColor(clGreen); ProgressBarl.Max:=Forml.ClientHeight; ProgressBar2.Max:=Forml.ClientHeight; ProgressBar3.Max:=Forml.ClientHeight; P r o g r e s s B a r l . S t e p : = F o r m l . C l i e n t H e i g h t d i v 10; P r o g r e s s B a r 2 . S t e p : = F o r m l . C l i e n t H e i g h t d i v 10; P r o g r e s s B a r 3 . S t e p : = F o r m l . C l i e n t H e i g h t d i v 10; PT[1].ProgressBar:=ProgressBarl; PT[2].ProgressBar:=ProgressBar2; PT [ 3 ] . P r o g r e s s B a r : = P r o g r e s s B a r 3 ; end; p r o c e d u r e TForml.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Canvas.Lock; try Canvas.Pen.Color := Color; Canvas.Brush.Color := Color;


Canvas.Ellipse finally Canvas. Unloosen d; end; end.

(x - 3 0 , у - 3 0 , x + 3 0 ,

у + 30);

Результат решения примера приводится на рис. 64 (показаны установки приоритетов, обеспечивающие достаточно надежную работу приложения).

Рис. 64

В примере создается массив РТ из трех потоков. Цвет закраски прямоугольной области задается с помошью вызова конструктора InitColor. Разделение общих ресурсов, используемых потоками, обеспечивается методом Synchronize. Для ускорения работы синхронизируется работа не каждого доступа к канве по изменению цвета пиксела (pixels [х,у]), а только очередной дисплейной строки. Можно попытаться синхронизировать доступ к каждому пикселу, возможно, это обеспечит более надежную работу (но замедлится работа приложения). Надежной работы данной программы можно добиться, используя методы блокировки канвы, как это было применено в примере 27. Далее для этого случая приводится код модуля, в котором объявлен поток (код основного модуля не изменился по сравнению с предыдущим примером). unit MyThread3_l; interface uses C l a s s e s , G r a p h i c s ,

222

ComCtrls;


type TPaintThread = class(TThread) private FColor:TColor; FProgressBar:TProgressBar; public constructor InitColor(nColor:TColor); property ProgressBar:TProgressBar write FProgressBar; protected procedure Execute; override; end; implementation uses Unit28_l ; constructor TPaintThread.InitColor(nColor:TColor) ; begin Fcolor:=nColor; end; procedure TPaintThread.Execute; var x,y: Integer; begin repeat f o r y : = 0 t o F o r m l . C l i e n t H e i g h t do b e g i n FprogressBar.Position:=y; Forml.Canvas.Lock; try f o r x : = 0 t o 250 do Forml.Canvas.Pixels[x,y]:=FColor; finally Forml.Canvas.UnLock; end; end; u n t i l Terminated; end; end.

Единственное, что можно отметить для данного варианта, это то, что наблюдается некоторое притормаживание, когда включается в работу основной поток — рисование круга (или происходит нажатие на кнопку выхода из приложения). Это связано с тем, что блокируется не одна операция pixels [х,у], а весь цикл закраски пикселов в данной строке. П Р О Б Л Е М Ы СИНХРОНИЗАЦИИ ПОТОКОВ

В классе TTread не зря введен метод синхронизации потоков. Он необходим, чтобы как-то блокировать неприятные ситуации, которые могут случиться. Следует отметить, что в примерах, приведенных ранее, имеются дополнительные трудности, связанные с использованием бесконечно рабо223


тающих потоков (бесконечные циклы). Поэтому даже в случае двух потоков стандартные методы синхронизации иногда не справляются. Если в процессе работает несколько потоков, то можно ожидать две основные неприятности: тупики и гонки. Тупики возникают, когда два или более потоков ожидают один и тот же ресурс и блокируют друг друга, так как не указано, какой поток должен в такой ситуации получить нужный ресурс. Гонки возникают, когда два или более потоков используют один и тот же ресурс и изменяют его в непредусмотренном порядке, поскольку операционная система может поменять очередность работы потоков. Для разрешения указанных и некоторых других ситуаций разрабатываются дополнительные механизмы синхронизации: функции ожидания и объекты синхронизации. Среди функций чаще всего используются WaitForSingleObject и WaitForMultipleObjects (функции Windows API). Среди объектов выделяются Critical Section (критическая секция), Event (событие), Mutex (взаимное исключение), Semaphore (семафор), Timer (таймер). В различных случаях выбирается свой вариант.


ПЕРЕЧЕНЬ КОМПОНЕНТОВ, ИСПОЛЬЗОВАННЫХ В ПРИМЕРАХ В табл. 17 приводится перечень используемых компонентов, которые понадобились при решении примеров. Номер примера 1 2 3 4 5 6 7 8 9 10 12 13 14 17 18 19 20 21 22 24 27 28

Компоненты Tbutton, TLabel, TEdit TPanel, TBitBtn TBevel TImage TListBox TMemo TComboBox TStringGrid TRadioGroup, TRadioButton TCheckBox, TDrawGrid TTabControl TPageControl, TSpinEdit TTimer TMainMenu TListView TColorBox, TList Проект с двумя формами TChart, TFileStream, TOpenDialog, TSaveDialog TMemoryStream TActionList, TCoolBar, TToolBar, TSplitter TThread TProgressBar, TTrackBar

225


СПИСОК ЛИТЕРАТУРЫ 1 Гофман В., Хомоненко A. Delphi 6. — Дюссельдорф, М., Киев, С-П6: БХВ - Петербург, 2001. 2Дарахвелидзе П., Марков Е. Программирование в Delphi 7. - С-П6: БХВ-Петербург, 2003. 3.Использование Delphi 3/ Т. Миллер, Д. Пауэл и др.. - Киев, М.: Диалектика, 1997. Л.Кэнту М. Delphi 4. - М „ Харьков, Минск, С-Пб: Питер, 1999. 5.Федоров A. Delphi 2.0 для всех. - М . : КомпьютерПресс, 1997.

226


ОГЛАВЛЕНИЕ ВВЕДЕНИЕ

3

ОСНОВЫ DELPHI 5 ОБЩАЯ ТЕХНОЛОГИЯ ПРОГРАММИРОВАНИЯ 5 ЯЗЫК ПРОГРАММИРОВАНИЯ 6 ОБЪЕКТНО-ОРИЕНТИРОВАННОЕ ПРОГРАММИРОВАНИЕ 7 ВИЗУАЛЬНОЕ ПРОГРАММИРОВАНИЕ 8 СОБЫТИЙНО УПРАВЛЯЕМОЕ ПРОГРАММИРОВАНИЕ 8 WINDOWS-ПРИЛОЖЕНИЕ 10 СРЕДА ПРОГРАММИРОВАНИЯ 10 ПЕРВОНАЧАЛЬНЫЕ СВЕДЕНИЯ О ПРОЕКТЕ ПРИЛОЖЕНИЯ 12 ВСТРОЕННЫЙ ОТЛАДЧИК 14 ИСПОЛЬЗОВАНИЕ ВСТРОЕННЫХ КЛАССОВ 14 ИЕРАРХИЯ КЛАССОВ 14 ИСПОЛЬЗОВАНИЕ ПАЛИТРЫ КОМПОНЕНТОВ И ИНСПЕКТОРА ОБЪЕКТОВ 16 ИСПОЛЬЗОВАНИЕ ГРАФИКИ 17 ОСНОВНЫЕ ИНСТРУМЕНТЫ „..17 ГРАФИЧЕСКИЕ ДАННЫЕ И ПАЛИТРА 19 НЕКОТОРЫЕ ОБЩИЕ СВОЙСТВА КОМПОНЕНТОВ 20 СОХРАНЕНИЕ ПРОЕКТА 21 ПОСТРОЕНИЕ ПРОСТЕЙШЕГО ПРОЕКТА 22 ПОНЯТИЕ ИСКЛЮЧИТЕЛЬНОЙ СИТУАЦИИ 23 ВВЕДЕНИЕ В OBJECT PASCAL 24 СТРУКТУРА ПРИЛОЖЕНИЯ 24 СТРУКТУРА ПРОГРАММЫ-ПРОЕКТА 25 СТРУКТУРА МОДУЛЯ. 26 ПРИМЕР ПРИЛОЖЕНИЯ 1 28 ОПИСАНИЕ ПРОГРАММНЫХ ЭЛЕМЕНТОВ 31 ПРОГРАММНЫЕ ЭЛЕМЕНТЫ И АДРЕСА ПАМЯТИ. 31 ОБЛАСТИ ВИДИМОСТИ. 31 ПРАВИЛА ЗАПИСИ ИМЕН. 32 ВРЕМЯ ЖИЗНИ ИДЕНТИФИКАТОРОВ 32 ИСПОЛЬЗОВАНИЕ ЛОКАЛЬНЫХ ПЕРЕМЕННЫХ В ПРИМЕРЕ 7.... 32 ИСПОЛЬЗОВАНИЕ ГЛОБАЛЬНЫХ ПЕРЕМЕННЫХ В ПРИМЕРЕ 1...33 ТИПЫ 33 ПРОСТЫЕ ТИПЫ 34 ЦЕЛЫЕ ТИПЫ. 35 227


СИМВОЛЬНЫЕ ТИПЫ ЛОГИЧЕСКИЕ ТИПЫ. ТИППЕРЕЧЕНЬ ИНТЕРВАЛЬНЫЙ ТИП ВЕЩЕСТВЕННЫЙ ТИП ТИП ДАТА-ВРЕМЯ ВЫРАЖЕНИЯ КОНСТАНТЫ. ТИПИЗИРОВАННЫЕ КОНСТАНТЫ ПЕРЕМЕННЫЕ ОПЕРАЦИИ. ФУНКЦИИ ПОРЯДОК ВЫЧИСЛЕНИЯ ВЫРАЖЕНИЙ. ВИДЫ ОПЕРАТОРОВ ПРОСТЫЕ ОПЕРАТОРЫ СОСТАВНОЙ ОПЕРАТОР

36 37 37 38 38 39 39 40 40 40 41 42 42 43 43 44

ОПЕРАТОРЫ УСЛОВНОГО ПЕРЕХОДА ОПЕРАТОР IF ПРИМЕР ПРИЛОЖЕНИЯ 2 ОПЕРАТОР CASE ПРИМЕР ПРИЛОЖЕНИЯ 3 ИСПОЛЬЗОВАНИЕ ENTER В ПРИМЕРЕ 3

44 45 45 47 48 52

ОПЕРАТОРЫ ЦИКЛА ОПЕРАТОР ЦИКЛА FOR ПРИМЕР ПРИЛОЖЕНИЯ 4 . ОПЕРАТОР ЦИКЛА WHILE ПРИМЕР ПРИЛОЖЕНИЯ 5 ОПЕРАТОР ЦИКЛА REPEAT ПРИМЕР ПРИЛОЖЕНИЯ б ИСПОЛЬЗОВАНИЕ ПРОЦЕДУР BREAK И CONTINUE ПРИМЕР ПРИЛОЖЕНИЯ 7

53 53 54 58 58 61 61 64 64

МАССИВЫ СТАТИЧЕСКИЕ МАССИВЫ ДИНАМИЧЕСКИЕ МАССИВЫ ПРИМЕР ПРИЛОЖЕНИЯ 8

67 67 68 70

СТРОКИ ПРИМЕР ПРИЛОЖЕНИЯ 9 ЗАПИСИ (ОБЪЕДИНЕНИЯ) ОПЕРАТОР WITH

71 73 76 78

228


ПРИМЕР ПРИЛОЖЕНИЯ 10 СОВМЕСТИМОСТЬ И ПРЕОБРАЗОВАНИЕ ТИПОВ ДАННЫХ ИДЕНТИЧНОСТЬ ТИПОВ СОВМЕСТИМОСТЬ ТИПОВ СОВМЕСТИМОСТЬ ПО ПРИСВАИВАНИЮ ПРЕОБРАЗОВАНИЕ ТИПОВ

79 85 86 86 86 87

ОПЕРАТОРЫ ОБРАБОТКИ ИСКЛЮЧИТЕЛЬНЫХ СИТУАЦИЙ 87 ПРИМЕР ПРИЛОЖЕНИЯ 11 89 МНОЖЕСТВА ОПЕРАЦИИ НАД МНОЖЕСТВАМИ ПРИМЕР ПРИЛОЖЕНИЯ 12

92 93 94

ВАРИАНТНЫЙ ТИП ДАННЫХ

97

ПРОЦЕДУРЫ И ФУНКЦИИ ПРОЦЕДУРА ФУН1ЩИЯ РЕКУРСИЯ ФОРМАЛЬНЫЕ И ФАКТИЧЕСКИЕ ПАРАМЕТРЫ ПАРАМЕТРЫ ЗНАЧЕНИЯ. ПАРАМЕТРЫ-ПЕРЕМЕННЫЕ ПАРАМЕТРЫ-КОНСТАНТЫ ПАРАМЕТРЫ БЕЗ ТИПА МАССИВЫ ОТКРЫТОГО ТИПА ПАРАМЕТРЫ ПО УМОЛЧАНИЮ ПРОЦЕДУРА EXIT ДИРЕКТИВЫ ПОДПРОГРАММЫ СОГЛАШЕНИЯ ПО ПЕРЕДА ЧЕ ДАННЫХ. ДИРЕКТИВА FORARD ДИРЕКТИВА EXTERNAL ДИРЕКТИВА ASSEMBLER ПЕРЕГРУЖЕННЫЕ ПОДПРОГРАММЫ ПРИМЕР ПРИЛОЖЕНИЯ 13

99 100 100 102 102 102 103 103 103 104 105 106 106 106 106 107 107 107 108

КЛАССЫ ИНКАПСУЛЯЦИЯ КЛАСС КАК ОБЪЕКТНЫЙ ТИП НАСЛЕДОВАНИЕ ОБЛАСТИ ВИДИМОСТИ ОПЕРАЦИИ IS И AS

113 113 114 114 114 115 229


МЕТОДЫ ВИДЫ МЕТОДОВ МЕТОДЫ VIRTUAL И ПОЛИМОРФИЗМ МЕТОДЫ DINAMIC МЕТОДЫ MESSAGE МЕТОДЫ ABSTRACT. МЕТОДЫ OVERRIDE МЕТОДЫ CLASS ПРИМЕР ПРИЛОЖЕНИЯ 14 ДИНАМИЧЕСКОЕ СОЗДАНИЕ КОМПОНЕНТОВ ИСПОЛЬЗОВАНИЕ КЛАССА СО СЧЕТЧИКОМ ОБЪЕКТОВ ОТСЛЕЖИВАНИЕ РАЗРУШЕНИЯ ОБЪЕКТОВ СОБЫТИЯ УКАЗАТЕЛИ НА МЕТОДЫ ПРИМЕР ПРИЛОЖЕНИЯ 15 ТИПЫ ССЫЛКИ НА КЛАСС СВОЙСТВА СВОЙСТВА SIMPLE СВОЙСТВА ENUMERATED СВОЙСТВА SET. СВОЙСТВА OBJECT. СВОЙСТВА ARRAY. ЗАДАНИЕ НА ЧАЛЬНЫХ ЗНА ЧЕНИЙ СВОЙСТВАМ. ПРИМЕР ПРИЛОЖЕНИЯ 16

П5 116 116 118 118 118 119 119 119 119 121 123 123 124 125 127 128 128 129 129 129 131 132 133

ФАЙЛОВЫЕ ТИПЫ ТЕКСТОВЫЕ ФАЙЛЫ ТИПИЗИРОВАННЫЕ ФАЙЛЫ ФАЙЛЫ БЕЗ ТИПА ДОПОЛНИТЕЛЬНЫЕ ПРОЦЕДУРЫ И ФУНКЦИИ ПРИМЕР ПРИЛОЖЕНИЯ 17 КОМПОНЕНТ TMAINMENU

137 138 140 141 141 142 142

УКАЗАТЕЛИ ПРИМЕР ПРИЛОЖЕНИЯ 18 ДИНАМИЧЕСКИЕ СТРУКТУРЫ ДАННЫХ ОДНОНАПРАВЛЕННЫЕ СПИСКИ ДВУНАПРАВЛЕННЫЕ СПИСКИ СТЕКИ, ОЧЕРЕДИ. БИНАРНЫЕ ДЕРЕВЬЯ. ПРИМЕР ПРИЛОЖЕНИЯ 19 ПРОЦЕДУРНЫЙ ТИП

148 150 153 153 155 156 156 158 164

230


ПРОГРАММНЫЕ ЕДИНИЦЫ DLL ПРИМЕР ПРИЛОЖЕНИЯ 20

165 166

ТЕХНОЛОГИИ ПРОГРАММИРОВАНИЯ ПОТОКИ ДАННЫХ ПРИМЕР ПРИЛОЖЕНИЯ 21 ПРИМЕР ПРИЛОЖЕНИЯ 22 ИНТЕРФЕЙС DRAG AND DROP ПРИМЕР ПРИЛОЖЕНИЯ 23 ТЕХНОЛОГИЯ DRAG AND DROP ПРИМЕР ПРИЛОЖЕНИЯ 24 ИСПОЛЬЗОВАНИЕ ФУНКЦИЙ WINDOWS API ПРИ РАБОТЕ С ФАЙЛАМИ ПРИМЕР ПРИЛОЖЕНИЯ 25 ИСПОЛЬЗОВАНИЕ ОТОБРАЖАЕМЫХ ФАЙЛОВ ПРИМЕР ПРИЛОЖЕНИЯ 26 ПРОГРАММНЫЕ ПОТОКИ ПРИОРИТЕТЫ ПОТОКОВ КЛАСС TTHREAD ПРИМЕР ПРИЛОЖЕНИЯ 27 ИСПОЛЬЗОВАНИЕ БЛОКИРОВКИ В ПРИМЕРЕ 27 МНОГОПОТОЧНОЕ ПРИЛОЖЕНИЕ В ПРИМЕРЕ 28 ПРОБЛЕМЫ СИНХРОНИЗАЦИИ ПОТОКОВ

170 170 171 176 179 180 184 185 202 205 207 209 212 213 214 215 217 218 223

ПЕРЕЧЕНЬ КОМПОНЕНТОВ, В ПРИМЕРАХ СПИСОК ЛИТЕРАТУРЫ

225 226

ИСПОЛЬЗОВАННЫХ


Basicsofprogrammingindelphiemelyanov