    Разработка форм 

    11 ГЛАВА 

    В этой главе мы займемся разработкой остальных форм ввода данных системы 
RENTMAN. С помощью этих форм будет выполняться редактирование таблиц TENANT, 
PROPERTY, CALL, LEASE и WORDER. Ознакомившись с особенностями создания форм 
для такой достаточно сложной системы, как приложение RENTMAN, вы получите 
хорошие знания основ разработки форм баз данных. 
    Формы TENANT и PROPERTY, в отличие от ранее созданных форм, используют 
компонент DBCtrlGrid. Управляющая сетка данных, представляемая компонентом 
DBCtrlGrid, позволяет одновременно просматривать несколько записей одной и той 
же таблицы, как и обычная сетка. Различие же между управляющей и обычной 
сетками данных состоит в том, что компонент DBCtrlGrid представляет каждую 
запись таблицы в нескольких строках, тогда как компонент DBGrid отводит для 
каждой записи только одну строку. Компонент DBGrid будет использоваться в 
формах CALL и WORDER, а компонент DBCtrlGrid — в формах TENANT и PROPERTY. 

    Форма TENANT 

    Форма TENANT является потомком класса fmControlGridForm. Из меню Delphi 
выберите команду File=>New и в открывшемся диалоговом окне New Itmes щелкните 
на вкладке Forms. Выберите на этой вкладке элемент fmControlGridForm, щелкните 
на переключателе Inherit, а затем — на кнопке ОК. В окне конструктора форм 
Delphi должна появится форма, озаглавленная fmControlGridForml. 
    Измените значение свойства Name формы на fmRTENCGDO, а свойства Caption — 
на Tenant Edit Form. Выберите команду File=>Use Unit и дважды щелкните на 
пункте RENTDATA списка. Это сделает форму модуля данных dmRENTMAN системы 
RENTMAN доступной в программном модуле новой форме. 

    На заметку 
    Если вы заблокировали элементы управления формы fmControlGridForm или 
любого ее потомка, то элементы управления новой формы также будут 
заблокированными. Прежде чем продолжать работу по созданию формы, нужно 
разблокировать их, так как блокировка усложняет настройку. Для этого, 
убедившись что текущим выбранным объектом является форма, выберите команду 
Edit=>Lock Controls, чтобы снять символ пометки, находящийся рядом с этой 
командой. Разблокировать элементы управления нужно только в том случае, если 
вы сами заблокировали их на этой форме или на форме класса-предка. По 
умолчанию элементы управления не заблокированы. 

    Первое, что можно сделать с этой формой, — это изменить ее размер. 
Установите значение свойства Height формы равным 400, а свойства Width — 
равным 565. Такие размеры облегчат размещение многочисленных полей таблицы TENANT. 

    Глава 11. Разработка форм 217 


    Удаление унаследованных компонентов 

    Обратите внимание на три кнопки новой формы, которые вы когда-то добавляли 
на форму класса fmEditForm. В нашем случае эти кнопки не нужны, так как только 
что созданная форма не относится к формам быстрого ввода данных. Однако вы не 
сможете просто так удалить их, поскольку Delphi не позволяет удалять 
компоненты, унаследованные от одного из классов-предков. Тем не менее 
существуют методы, с помощью которых в тех или иных случаях можно избавиться 
от ненужных компонентов и тем самым освободить на форме дополнительное пространство. 
    Щелкните на панели paMiddle и установите ее свойства Enabled и Visible 
равными False. Поскольку эта панель является контейнером, содержащим три 
вышеупомянутые кнопки, то такая операция приведет к сокрытию и отключению 
принадлежащих ей кнопок, чего вы, собственно, и добивались. Отключение панели 
само по себе для вас не имеет значения — целью является лишь отключение 
зависящих от нее компонентов. Доведите дело до конца — присвойте свойству 
Align этой же панели значение aINone, а свойству Align панели раТор — значение 
alClient. Это приведет к тому, что средняя панель "уступит место" верхней 
панели, которая, в свою очередь, займет всю клиентскую область окна формы. 
Наконец щелкните правой кнопкой мыши на компоненте paMiddle и из появившегося 
всплывающего меню выберите команду Send to Back. Панель paMiddle и ее кнопки 
должны скрыться за другими компонентами формы. 
    Может быть, вам непонятно, зачем нужно было отключать кнопки. В конце 
концов, что плохого в том, чтобы пользоваться назначенными им клавишами 
мнемонического доступа, даже если сами кнопки невидимы? Действительно, 
независимо от того, видим компонент или нет, использование назначенных ему 
клавиш мнемонического доступа и клавиатурных эквивалентов приводит к 
срабатыванию данного компонента. Однако кнопки не были удалены — они лишь 
отключены и сделаны невидимыми. Если вам нравиться, можете не отключать 
кнопки, а только убрать их с экрана — это дело вкуса. Лично я предпочитаю не 
связываться с комбинациями клавиш, по которым срабатывают скрытые компоненты. 
На мой взгляд, если та или иная комбинация облегчает работу, то о ее 
использовании и назначении пользователь должен получать информацию с экрана, а 
не руководствуясь интуицией. 

    Настройка управляющей сетки данных 

    Для того чтобы настроить компонент DBCtrlGrid новой формы, выполните 
следующие операции. 
    1. Если сервер InterBase еще не работает, запустите его. 
    2. Нажмите <Shift+F12> для того, чтобы открыть диалоговое окно View Form и 
дважды щелкните на модуле данных dmRENTMAN в списке форм, представленных этим 
окном. Если появится окно регистрации на сервере, введите имя пользователя и пароль. 
    3. Перейдите в новую форму, еще раз нажав <Shift+F12> и выбрав из списка 
новую форму. 
    4. Выберите компоненты DBCtrlGrid и DBNavigator и измените их свойство 
DataSource на dmRENTMAN.dsTENANT, а затем отмените выбор компонента DBNavigator. 
    5. В таблице TENANT имеется 11 полей. Десять из них — редактируемые 
текстовые поля, а одиннадцатое — текстовое поле, предназначенное только для 
чтения. Поместите на первую из трех панелей компонента DBCtrlGrid один 
компонент DBText и 10 компонентов DBEdit. Не пытайтесь размещать компоненты на 
двух нижних панелях, заполненных диагональной сеткой, — эти панели недоступны. 
    6. Установите значения свойств Height каждого из компонентов, помещенного 
на управляющую сетку данных, равными 19. 
    7. Поместите на компонент DBCtrlGrid 11 компонентов Label и разместите их 
над соответствующими компонентами DBText и DBEdit. Завершите настройку 
компонентов в соответствии с табл. 11.1 и рис. 11.1. 

    Совет 
    Для того чтобы поместить на форму несколько одинаковых компонентов, не 
возвращаясь всякий раз в палитру компонентов, а выполните следующие операции. 
    1. Нажмите и удерживайте клавишу <Shift>. 
    2. Щелкните в палитре на нужном компоненте, после чего можете отпустить <Shift>. 
    3. Щелкайте на форме для размещения каждого очередного компонента. 
    4. Щелкните на пиктограмме указателя мыши в палитре компонентов, чтобы 
перейти в обычный режим использования палитры. 
    
    218 Часть II. Учебный курс

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

    Таблица 11.1. Компоненты управляющей сетки данных и их атрибуты 

    Компонент	Name				Width	DataField		Метка 

    DBText		dteTENANT_NUMBER		30 	TENANT NUMBER 	No. 
    DBEdit 		dedNAME 			121 	NAME 			Name 
    DBEdit 		dedHOMEPHONE 		90 	HOME PHONE 		HomePhone 
    DBEdit 		dedWORKPHONE 		90 	WORKPHONE 		WorkPhone 
    DBEdit 		dedICEPHONE 		90 	ICEPHONE 		Emergency Phone 
    DBEdit 		dedEMPLOYER 		121 	EMPLOYER 		Employer 
    DBEdit 		dedEMPLOYERADDRESS	121 	EMPLOYERADDRESS 	Employer Address 
    DBEdit 		dedEMPLOYERCITY		121 	EMPLOYERCITY 	City 
    DBEdit 		dedEMPLOYERSTATE 		25 	EMPLOYERSTATE 	State 
    DBEdit 		dedEMPLOYERZIP 		90 	EMPLOYERZIP 	Zip 
    DBEdit 		dedCOMMENTS 		530 	COMMENTS 		Comments 

    На рис. 11.1 показан вид формы после выполнения всех вышеуказанных операций. 

    Рис. 11.1. Новая форма с компонентами для работы с данными, размещенными 
на компоненте DBCtrlGrid 

    На заметку 
    Когда компонент DBEdit не связан с определенным набором данных, в его 
строке ввода отображается имя компонента. Другими словами, в отличие от 
обычного компонента Edit, для компонента DBEdit не существует способа очистки 
содержимого строки ввода несвязанного компонента во время разработки формы. 
Однако это не представляет особой проблемы, так как при выполнении программы 
содержимое будет очищаться автоматически. У компонентов Edit содержимое строки 
ввода можно очистить и во время разработки формы, удалив значение свойства Text. 

    Глава 11. Разработка форм 219 


    Совет 
    Для того чтобы сразу изменить значения одного и того же свойства у разных 
компонентов, выполните следующие операции. 
    1. Щелкните на первом компоненте. 
    2. Нажмите и удерживайте клавишу <Shift>. 
    3. Щелкните на остальных компонентах. 
    4. Нажмите <F11> для перехода в окно Object Inspector. В списке будут 
отображаться только те свойства, которые являются общими для всех компонентов. 
Если вы измените значение такого свойства, то это изменение распространится на 
все остальные аналогичные свойства выбранных компонентов. 
    5. Кроме того, можно щелкнуть правой кнопкой мыши на любом из выбранных 
компонентов и получить перечень операций, выполнение которых будет 
распространяться сразу на все выбранные компоненты. 


    Выравнивание компонентов 

    Обязательно воспользуйтесь командами Align и Size всплывающего меню Delphi 
для того, чтобы быстро и точно выровнять элементы управления новой формы. С 
помощью этих команд можно выровнять элементы управления как по вертикали, так 
и по горизонтали, а также привести их размеры в соответствие. 

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


    Установка порядка обхода 

    Разместив компоненты, нужно правильно настроить порядок их обхода при 
нажатии <ТаЬ>. Для этого используется свойство TabOrder компонентов. Можно 
настроить порядок обхода сразу всех компонентов, используя редактор Tab Order 
Delphi. Откройте окно редактора порядка обхода, выбрав команду меню Edit=>Tab 
Order, а затем щелкайте на кнопках перемещения элементов списка, изменяя тем 
самым порядок обхода соответствующих компонентов. Кроме того, для перемещения 
компонентов в списке можно воспользоваться операцией перетаскивания. 
    Настроив порядок обхода, сохраните новую форму. В качестве имени файла 
программного модуля укажите RTENCGDO.PAS (R — от имени системы RENTMAN, TEN 
означает название таблицы TENANT, аббревиатура CGD — главный компонент формы 
dbCtrlGriD, а 0 — номер версии формы). 

    Совет 
    В Delphi практически все операции, где используется мышь, можно выполнить 
и с помощью клавиатуры. В табл. 11.2 перечислены некоторые часто используемые 
клавиши и комбинации клавиш, знание которых может облегчить и ускорить вашу 
работу. Обратите внимание, что некоторые из них зависят от того, используете 
ли вы стандартное назначение клавиш Delphi. 

    Таблица 11.2. Некоторые часто используемые комбинации клавиш 
интегрированной среды разработки Delphi 

    Клавиша или комбинация клавиш  		Функция 

    <F11> 					Переход в окно Object Inspector 
    <F12> 					Переключение между окном редактирования исходного текста и конструктором форм 


    220 Часть II. Учебный курс 


    Окончание табл. 11.2 

    Клавиша или комбинация клавиш  		Функция 

    <Ctrl+Enter>				To же, что и двойной щелчок в окне Object Inspector 
    <Alt+стр-вн>      			Раскрытие списка объектов в окне Object Inspector 
    <Tab>, <<->, <стр-вв>, <->>,    Перемещение между компонентами или свойствами 
    <стр-вн> 
    <Ctrl+<->, <Ctrl+стр-вв>,  	Перемещение текущего компонента в окне конструктора форм 
    <Ctrl+->>, <Ctrl+стр-вн> 
    <Shift+<->, <Sruft+стр-вв>,     Изменение размера текущего компонента в окне конструктора форм 
    <Shift+->>, <Shift+стр-вн> 
    <Shift+F12>              		Выбор формы 
    <Alt+0>                 		Выбор окна 
    <F9>                   		Запуск приложения на выполнение 
    <F1>                   		Получение справки по текущему компоненту в окне конструктора форм 
    <F5>                    		Включение и выключение точки прерывания в текущей строке в окне редактора исходного текста 
    <Ctrl+Tab>               		Переход на следующую вкладку в окне редактора исходного текста 
    <Ctrl+Shift+Tab>           	Переход на предыдущую вкладку в окне редактора исходного текста 


    Приложение RENTMAN и поля с автоинкрементом 

    Итак, форма почти завершена, за исключением одной детали. Вы, должно быть, 
обратили внимание, что новая форма не позволяет редактировать столбец 
TENANT_NUMBER. Это сделано умышленно, так как компания Allodium пожелала, 
чтобы система автоматически генерировала новый номер арендатора при добавлении 
записи в таблицу арендаторов. Поэтому вся работа по увеличению значения 
столбца TENANT NUMBER возлагается на приложение. Это позволит гарантировать, 
что в таблице TENANT все строки будут иметь правильные номера. Как будет 
показано в главе 20, "Бизнес-правила и сервер базы данных", лучшее место для 
применения бизнес-правил, включая те, которые определяют поля с 
автоинкрементом, — это сервер базы данных. В этом случае бизнес-правила 
доступны всем приложениям, а не только тем, которые написаны с помощью Delphi 
или используют BDE. 
    Поскольку CASE-средства, которые мы применяли для создания базы данных 
RENTMAN, не создали объектов InterBase, необходимых для получения столбцов с 
автоинкрементом, вам придется создать их самостоятельно. Для этого потребуется 
создать два новых объекта базы данных RENTMAN — генератор и триггер. Генератор 
— это объект СУБД InterBase, предназначенный, как ясно из его названия, для 
генерации последовательных номеров, помещаемых в определенные столбцы таблицы. 
Сама операция вставки выполняется с помощью триггера или хранимой процедуры. В 
нашем случае для размещения очередного номера в столбце TENANT NUMBER мы 
воспользуемся триггером вставки. Триггер вставки срабатывает при каждой 
операции вставки новой строки в таблицу (в данном случае — в таблицу TENANT). 
При срабатывании, триггер будет вызывать функцию GEN_ID (), которая обращается 
к генератору за очередным номером. Каждый вызов данной функции приводит к 
увеличению на единицу внутреннего счетчика генератора, поэтому при каждом 
вызове функция GEN_ID () возвращает новое значение. 
    Компания Allodium пожелала, чтобы автоматическое присвоение номеров 
выполнялось и в других таблицах базы данных RENTMAN, а не только в таблице 
TENANT. Это означает, что каждой из этой таблиц нужны объекты генератора и 
триггера вставки. Единственным исключением является таблица WODETAIL — к ней 
предъявляются специальные требования, на которых я остановлюсь несколько позже. 
    Чтобы не создавать все эти объекты базы данных по одному, можно 
воспользоваться сценарием SQL, который создаст сразу все нужные нам объекты. 
Такой сценарий удобно редактировать с помощью редактора исходного текста 
Delphi, так как он поддерживает синтаксическое выделение ключевых слов и 
конструкций SQL, что облегчает работу с текстом сценария. Создав сценарий, 
запустите его на выполнение с помощью утилиты InterBase Windows ISQL. После 
выполнения сценария все нужные объекты будут созданы и помещены в базу данных 
RENTMAN.

    Глава 11. Разработка форм 221 

    Для того чтобы создать и настроить генераторы и триггеры системы RENTMAN, 
выполните следуюшие операции. 
    1. Щелкните на пункте меню File=>New, в открывшемся диалоговом окне New 
Items выберите элемент Text, а затем щелкните на кнопке ОК. Это приведет к 
открытию нового файла в окне редактирования исходного текста Delphi. 
    2. Сохраните новый пустой файл под именем RSYSTRGO.SQL. Сохранение файла с 
расширением SQL приводит к включению соответствующего режима синтаксического выделения. 
    3. Введите исходный текст файла, руководствуясь листингом 11.1, а затем 
сохраните работу. Не забудьте изменить оператор CONNECT в соответствии с 
именем каталога, используемым на вашем компьютере для хранения базы данных RENTMAN. 
    4. Запустите утилиту InterBase windows ISQL и выберите команду File=>Run an 
ISQL Script. Укажете имя файла только что созданного сценария, когда получите 
запрос на ввод имени сценария. 
    5. Получив запрос системы, нужно ли направлять выходную информацию в 
отдельный файл, щелкните на кнопке No. Это позволит вам просматривать всю 
выходную информацию в окне утилиты Windows ISQL. 
    6. После небольшой задержки должно появиться сообщение, информирующее вас 
о том, что сценарий выполнился без ошибок. Это означает, что генераторы и 
триггеры успешно созданы. 
    7. Закройте окно утилиты Windows ISQL и вернитесь в Delphi. 
    8. Нажмите <Shift+F12> чтобы открыть диалоговое окно View Form и щелкните 
дважды на имени модуля данных dmRENTMAN. 
    9. Щелкните дважды на компоненте taTENANT, чтобы открыть окно редактора 
полей, а затем щелкнете на столбце TENANT_NUMBER. 
    10. В окне Object Inspector Delphi измените значение свойства Required 
столбца TENANT_NUMBER на False, а затем закройте окно редактора полей. Это 
необходимо для того, чтобы приложение не пыталось получить значение из столбца 
TENANT_NUMBER, так как система InterBase будет его устанавливать с помощью 
только что созданных вами генератора и триггера. 
    11. Повторите эти действия для всех остальных таблиц, присваивая свойствам 
Required всех первичных ключей значение False. He забывайте, что первичный 
ключ таблицы WODETAIL состоит из двух столбцов — WORDER_NUMBER И 
WODETAIL_LINE_NUMBER. Установите свойства Required обоих столбцов равными False. 
    12. Щелкните на вкладке Events окна Object Inspector, а затем дважды 
щелкните на значении обработчика события AfterPost объекта taTENANT. В 
исходном тексте обработчика события введите следующий оператор: 

    taTENANT.Refresh; 

    В этом операторе вызывается метод Refresh для сообщения приложению RENTMAN 
того, что значения столбца TENANT_NUMBER автоматически сгенерированы сервером. 
    13. Используя тот же подход, настройте обработчики события AfterPost всех 
остальных таблиц модуля данных. При написании операторов с вызовом метода 
Refresh не забывайте правильно указывать имя объекта таблицы. 

    Листинг 11.1. Сценарий SQL для создания генераторов и триггеров базы 
данных RENTMAN 

    CONNECT 'С:\DATA\RENTMAN\RENTMAN.GDB' USER 'SYSDBA' PASSWORD 'masterkey'; 
    CREATE GENERATOR TENANT_NUMBER_GEN; 
    CREATE GENERATOR PROPERTY_NUMBER_GEN; 
    CREATE GENERATOR LEASE_NUMBER_GEN; 
    CREATE GENERATOR CALL_NUMBER_GEN; 
    CREATE GENERATOR WORDER_NUMBER_GEN; 
    CREATE GENERATOR EMPLOYEE_NUMBER_GEN; 
    CREATE GENERATOR WORK_TYPE_CODE_GEN; 
    COMMIT WORK; 

    SET TERM !! ; 
    CREATE TRIGGER TENANTInsert FOR TENANT 
          BEFORE INSERT 
              POSITION 0 


    222 Часть II. Учебный курс 


       AS BEGIN 
              NEW.TENANT_NUMBER = GEN_ID(TENANT_NUMBER_GEN, 1); 
       END !! 

    CREATE TRIGGER PROPERTYInsert FOR PROPERTY 
           BEFORE INSERT 
                POSITION 0 
           AS BEGIN 
                   NEW.PROPERTY_NUMBER = GEN_ID(PROPERTY_NUMBER_GEN, 1) 
           END !!

    CREATE TRIGGER LEASEInsert FOR LEASE 
           BEFORE INSERT 
                 POSITION 0 
           AS BEGIN 
                  NEW.LEASE_NUMBER = GEN_ID(LEASE_NUMBER_GEN, 1); 
           END !! 

    CREATE TRIGGER CALLInsert FOR CALL 
         BEFORE INSERT 
             POSITION 0 
       AS BEGIN 
           NEW.CALL_NUMBER = GEN_ID(CALL_NUMBER_GEN, 1); 
       END !! 

    CREATE TRIGGER WORDERInsert FOR WORDER 
          BEFORE INSERT 
              POSITION 0 
          AS BEGIN 
                NEW.WORDER_NUMBER = GEN_ID(WORDER_NUMBER_GEN, 1); 
          END !! 

    CREATE TRIGGER EMPLOYEEInsert FOR EMPLOYEE 
          BEFORE INSERT 
                 POSITION 0 
         AS BEGIN 
                NEW.EMPLOYEE_NUMBER = GEN_ID(EMPLOYEE_NUMBER_GEN, 1) 
         END !!
 
    CREATE TRIGGER WORKTYPEInsert FOR WORKTYPE 
          BEFORE INSERT 
                POSITION 0 
         AS BEGIN 
              NEW.WORK_TYPE_CODE = GEN_ID(WORK_TYPE_CODE_GEN, 1); 
        END !! 

    CREATE TRIGGER WODETAILInsert FOR WODETAIL 
          BEFORE INSERT 
              POSITION 0 
         AS 
             DECLARE VARIABLE NumLines INTEGER; 
             BEGIN 
                  SELECT MAX(WODETAIL_LINE_NUMBER)+1 
                  FROM WODETAIL 
                  WHERE WODETAIL.WORDER_NUMBER = NEW.WORDER_NUMBER 
                  INTO :NumLines; 
                      IF (NumLines IS NULL) THEN 
                         NEW.WODETAIL_LINE_NUMBER = 1; 
                     ELSE 
                         NEW.WODETAIL_LINE_NUMBER = NumLines; 
                    END !! 

    SET TERM ; !! 
    COMMIT WORK; 


    Глава 11. Разработка форм 223


    Совет 
    Система Delphi умеет импортировать большинство типов ограничений и 
бизнес-правил прямо из сервера SQL. Это означает, что если вы определили 
ограничения или используемые по умолчанию значения столбцов на сервере, то 
можете занести эту информацию в словарь данных Delphi, а оттуда — в 
разрабатываемое приложение. К сожалению, это распространяется на все объекты 
такого рода, за исключением столбцов с автоинкрементом. Дело в том, что сами 
столбцы импортируются в словарь данных корректно, но приложение никак не 
узнает, о том, что их значение автоматически устанавливается сервером. Когда 
вы сталкиваетесь с ситуациями, в которых Delphi неправильно обрабатывает 
значения, подготавливаемые сервером, можете воспользоваться вызовом метода 
Refresh соответствующего набора данных, для того чтобы получить обновленные 
значения текущей строки с сервера. Обработчик события AfterPost — это самое 
удачное место для такого вызова, так как данное событие возникает после 
сохранения изменений текущей строки. Вызов метода Refresh обеспечивает 
осведомленность приложения обо всех изменениях, внесенных в поля текущей 
строки сервером. 

    Совет 
    Начальное значение, возвращаемое генератором InterBase, можно установить с 
помощью команды SET GENERATOR. Например 

    SET GENERATOR TENANT_NUMBER_GEN TO 1000; 

    Этот оператор начнет присваивать номера, начиная с номера 1001. 

    SET GENERATOR TENANT_NOMBER_GEN TO 0; 

    Этот оператор сбросит счетчик генератора в исходное состояние. Обратите 
внимание на операторы, создающие генератор и триггер для таблицы WODETAIL. 
Почему они отличаются от таких же операторов для остальных таблиц? Потому что 
элементы этой таблицы нельзя пронумеровать простой последовательностью чисел. 
Как вы, должно быть, помните, таблица WODETAIL содержит описание работ, 
указанных в нарядах. Каждая работа, указанная в наряде, имеет собственный 
номер. Естественно, вы вряд ли захотите, чтобы первая работа второго наряда 
имела номер 8, а не 1 из-за того, что в первом наряде было 7 работ. Более 
привычным является подход, при котором работы внутри каждого наряда 
нумеруются, начиная с 1. Именно для этого и предназначены операторы, создающие 
триггер вставки для таблицы WODETAIL: при срабатывании триггера он находит 
номер последней строки текущего наряда и присваивает новой строке этого же 
наряда следующий номер, помещая значение в столбец  WODETAIL_LINE_NUMBER. 
Обратите также внимание на завершающий оператор COMMIT WORK, который сохраняет 
все внесенные в базу данных изменения. Использование этого оператора в 
сценариях SQL является хорошим стилем, так как при его выполнении закрываются 
все неявные транзакции, инициированные утилитой ISQL, а изменения записываются 
на диск. Поэтому, используя данный оператор, вы существенно снизите риск 
потери результатов работы. Все служебные транзакции, закрытые ISQL при 
использовании оператора COMMIT WORK, после сохранения данных на диске 
запускаются снова, поэтому можете применять данный оператор столько раз, 
сколько это необходимо. 

    Подключение формы к приложению 

    Для того чтобы протестировать новую форму, нужно привязать ее к 
соответствующей команде главного меню приложения. Откройте главную форму 
приложения, fmRSYSMANO, перейдите в режим конструктора меню и щелкните дважды 
на пункте Tenants подменю Tables. В открывшемся окне введите следующий оператор: 

    fmRTENCGDO.Show; 

    Нажмите <F12> и вернитесь в окно конструктора формы. 

    224 Часть II. Учебный курс 


    Тестирование формы TENANT 


    Внимание! 
    Перед каждым запуском приложения не забывайте сохранять проект на диске. 
Это обеспечит сохранение результатов вашей работы, если приложение по каким-то 
причинам нарушит работу операционной системы. Для того чтобы такое сохранение 
выполнялось автоматически, используйте команду Tools=>Environment 
Options=>Preferences и на вкладке Autosave options установите флажок Editor 
files. Кроме того, можно сохранить проект, щелкнув на кнопке Save Project 
панели инструментов или выбрав команду File=>Save All. 

    Убедившись, что выбранным объектом является форма fmRSYSMANO, выберите 
команду File=>Use Unit и из полученного списка выберите пункт RTENCGDO. Это 
свяжет программный модуль формы fmRTENCGDO с главной формой приложения 
RENTMAN, что позволит обращаться к форме таблицы TENANT из главного меню 
приложения. Теперь сохраните проект и запустите приложение на выполнение. 
    Когда приложение RENTMAN загрузится, нажмите <F5> — на экране должна 
появиться новая форма. Щелкните на кнопке навигатора базы данных, 
предназначенной для вставки записи (кнопка с изображением знака +), и добавьте 
к таблице новую запись. Заполните поля соответствующими значениями, учитывая 
имеющиеся ограничения (например, поле State может содержать только значения ОК 
или TX). Добавьте в таблицу как минимум три строки. 
    На рис. 11.2 приведен возможный вид новой формы. 

    Рис. 11.2. Вид формы таблицы TENANT во время работы приложения 


    Настройка панели инструментов 

    После подсоединения формы TENANT к системе меню приложения RENTMAN 
осталось связать ее лишь с соответствующей кнопкой панели инструментов. Если 
приложение все еще работает, закройте его. В окне конструктора форм откройте 
главную форму приложения, fmRSYSMANO, и щелкните на компоненте SpeedButton, 
который вы должны связать с формой TENANT, — sbTenants. Нажмите <F11>, чтобы 
перейти в окно Object Inspector, а затем щелкните на вкладке Events. Щелкните 
на кнопке раскрытия списка значений события OnClick и выберите из него 
значение TenantslClick. Это значение представляет собой имя обработчика 
события, который вызывается при щелчке пользователя на пункте Tables=>Tenants 
во время работы приложения RENTMAN. Присвоив в качестве значения обработчика 
события OnClick кнопки sbTenants ссылку на обработчик события TenantslClick, 
вы тем самым обеспечили единообразие работы соответствующих кнопки панели 
инструментов и пункта меню. 
    Теперь пользователь, щелкнув на кнопке панели инструментов, представленной 
в приложении объектом sbTenants, увидит на экране ту же форму, что и при 
выборе соответствующего пункта меню. Для 

    Глава 11. Разработка форм 225 
    8   Delphi 3 и системы клиент/сервер 

того чтобы назначение кнопки панели инструментов было понятнее, для нее 
можно создать всплывающую подсказку. Подробнее мы поговорим об этом в главе 
13, "Последние штрихи". 
    Итак, разработка формы TENANT закончена, и мы можем перейти к форме PROPERTY. 

    Форма PROPERTY 

    Щелкните на пункте меню File=>New и снова выберите на вкладке Forms 
открывшегося окна New Items элемент fmControlGridForm, затем щелкните на 
переключателе Inherit и на кнопке ОК. В окне конструктора форм должна появится 
новая форма класса fmControlGridForm. На основе этой формы мы сделаем форму 
редактирования таблицы PROPERTY. 
    Начните с переименования объекта формы в fmPROCGDO и присвоения ей 
заголовка наподобие Property Edit Form. Сохраните программный модуль формы в 
каталоге системы RENTMAN в файле RPROCGDO.PAS. 

    Настройка управляющей сетки данных 

    Из 22 столбцов таблицы PROPERTY семь имеют булев тип, а остальные — 
текстовый или числовой. Для того чтобы настроить верхнюю панель компонента 
DBCtrlGrid формы, выполните следующие операции. 
    1. Поместите на верхней панели управляющей сетки данных компонент DBText. 
    2. Правее и ниже компонента DBText разместите 11 компонентов DBEdit и 3 
компонента DBComboBox. 
    3. В качестве значения свойства Items компонента DBComboBox введите 
следующий список. 

    Oklahoma City 
    Norman 
    Edmond 
    Dallas 
    Richardson 
    Piano 

    Данный компонент будет обслуживать поле CITY таблицы PROPERTY. 
    4. В качестве значения свойства items второго компонента DBComboBox 
введите следующий список. 

    Deerfield 
    Firewheel 
    Legacy Hills 
    Switzerland Estates 
    Sherwood 
    Rocknoll 

    Данный компонент будет обслуживать поле ADDITION таблицы PROPERTY. 
    5. В качестве значения свойства items третьего компонента DBComboBox 
введите следующий список. 

    Putnam City 
    Oklahoma City 
    Richardson 
    Edmond 
    Garland 
    Dallas 
    Piano 

    Данный компонент будет обслуживать поле SCHOOLDISTRICT таблицы PROPERTY. 
    6. Выберите все три компонента DBComboBox и установите их свойства Style 
равными csDropDownList. Это сделает невозможным ввод пользователями значений, 
которых нет в списках, определенных с помощью свойств Items этих компонентов. 
    7. Поместите на форму семь компонентов DBCheckBox, расположив их в нижней 
части верхней панели. 
    8. Выберите все семь компонентов DBCheckBox и присвойте в качестве 
значений их свойств ValueChecked и ValueUnchecked, соответственно, Т и F. 

    226 Часть II. Учебный курс 

9. Разместите на форме метки, соответствующие компонентам DBText, DBEdit и DBComboBox.

10. Разместите компонент DBText таким образом, чтобы он находился в левом верхнем углу управляющей
сетки данных. Остальные компоненты разместите в соответствии с рис. 11.3.

Внимание!
Помещайте компоненты только на самую верхнюю панель компонента DBCtrlGrid— остальные панели
использовать нельзя.

Компонент DBText будет отображать значение столбца PROPERTY_NUMBER без возможности его изме-
нения. Компоненты DBEdit и DBComboBox обеспечивают доступ к остальным текстовым столбцам табли-
цы PROPERTY, а компоненты DBCheckBox отображают булевы значения из соответствующих столбцов.

Связывание компонентов с таблицей PROPERTY

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

1. С помощью команды меню File=>Use Unit откройте список модулей приложения и дважды щелкните на
элементе RENTDATA, чтобы связать модуль данных dmRENTMAN с новой формой.

2. Присвойте свойству DataSource компонента DBCtrlGrid в качестве значения ссылку на источник
данных dmRENTMAN.dsPROPERTY. Сделайте то же самое для свойства DataSource компонента
DBNavigator, указав это же значение.

3. Щелкая на каждом компоненте, помещенном на управляющую сетку данных, настройте значения его
свойств в соответствии с табл. 11.3.

Совет
Можно воспользоваться окном редактора полей и перетащить на форму компоненты TField, сразу же свя-
зав их с нужными полями таблицы. При этом автоматически создается связанная с полем данных метка.
Этот метод является более быстрым и простым, чем ручная настройка обычных компонентов для работы с
данными.

Таблица 11.3. Компоненты управляющей сетки данных и их атрибуты

Комлонент	Name				Width	DataField		Метка

DBText	dteProperty_Number	40	PROPERTYNUMBER	No
DBEdit	dedAddress			121	ADDRESS		Address
DBComboBox	dcbCity			100	CITY			City
DBEdit	dedState			40	STATE			State
DBEdit	dedZip			70	ZIP			Zip
DBComboBox	dcbAddition			115	ADDITION		Addition
DBComboBox	dcbSchoolDistrict		142	SCHOOLDISTRICT	SchoolDistrict
DBEdit	dedRent			50	RENT			Rent
DBEdit	dedDeposit			50	DEPOSIT		Deposit
DBEdit	dedLivingAreas		30	LIVINGAREAS		Living
DBEdit	dedBedRooms			30	BEDROOMS		Bed
DBEdit	dedBathRooms		30	BATHROOMS		Bath


Глава 11. Разработка форм 227


    Окончание табл. 11.3 

Компонент	Name			Width	DataField		Метка 

DBEdit	dedGarageType	30	GARAGETYPE		Garage 
DBEdit	dedLastLawnDate	60	LASTLAWNDATE	LastLawn 
DBEdit	dedLastSprayDate	60	LASTSPRAYDATE	LastSpray 
DBCheckBox	dckCentralAir	65	CENTRALAIR		CentralAir 
DBCheckBox	dckCentralHeat	75	CENTRALHEAT		CentralHeat 
DBCheckBox	dckGasHeat		65	GAS HEAT		GasHeat 
DBCheckBox	dckRefrigerator	73	REFRIGERATOR	Refrigerator 
DBCheckBox	dckRange		53	RANGE			Range 
DBCheckBox	dckDishWasher	73	DISHWASHER		Dishwasher 
DBCheckBox	dckPrivacyFence	84	PRIVACYFENCE	PrivacyFence 
    
    
	Доступ к данным во время разработки 
    

    После того как все компоненты будут связаны с соответствующими полями, вы 
должны увидеть данные первой строки таблицы, представленные компонентом 
DBCtrlGrid. Это возможно благодаря тому, что таблица PROPERTY уже открыта 
(если вы помните, при разработке формы модуля данных dmRENTMAN мы обеспечили 
автоматическое открытие всех таблиц), а также благодаря способности 
конструктора форм Delphi отображать данные открытой таблицы даже на этапе 
разработки формы. Поскольку поля связаны с открытым набором данных, вы должны 
увидеть, что соответствующие компоненты больше не отображают имен своих 
программных объектов, как это обычно происходит во время разработки формы. 
Отсутствие имен в компонентах, предназначенных для работы с данными, говорит о 
том, что они, во-первых, связаны с таблицей PROPERTY, а во-вторых, что эта 
таблица пуста. 

    Изменение порядка обхода 

    Поскольку вы помешали компоненты на управляющую сетку данных в 
произвольном порядке, то вам, возможно, понадобиться изменить порядок их 
обхода при нажатии <ТаЬ>. Для этого нужно из меню выбрать команду Edit=>Tab 
Order, а затем в открывшемся диалоговом окне с помощью кнопок перемещения 
элементов установить нужный порядок обхода (то же самое можно сделать, 
перетаскивая элементы в соответствии со списком). Порядок обхода компонентов 
должен соответствовать их физическому расположению на форме, чтобы при нажатии 
<ТаЬ> и <Shift+Tab> на экране отображалось то, что ожидает увидеть пользователь. 

    Клавиши мнемонического доступа 

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

    Тестирование формы PROPERTY 

    После размещения всех компонентов и настройки их свойств нужно 
протестировать форму PROPERTY. Давайте начнем с подключения ее к главной форме 
приложения, fmRSYSMANO. 

    Подключение формы PROPERTY к приложению 

    Откройте форму fmRSYSMANO в окне конструктора форм Delphi, а затем 
выберите команду File=>Use Unit. Щелкните дважды на элементе RPROCGDO 
открывшегося списка. Данная операция обеспечила наличие связи между формой 
PROPERTY и главной формой приложения, что позволяет открыть форму PROPERTY с 
помощью системы меню приложения. 

    228 Часть II. Учебный курс 

    Открыв конструктор меню, дважды щелкните на пункте Property подменю 
Tables. В открывшемся окне редактирования исходного текста введите следующую строку: 

    fmPROCGDO.Show; 

    При двойном щелчке на пункте Property подменю Tables система Delphi 
автоматически создает метод класса формы с именем Propertylclick. Этот метод 
вызывается щелчком на пункте Property во время работы приложения. Таким 
образом, после добавления вышеуказанной строки метод Propertylclick будет 
всегда открывать форму fmPROCGDO, используя для этого ее метод show. Теперь 
давайте присвоим ссылку на данный метод обработчику события OnClick кнопки 
панели инструментов sbProperty. 

    Настройка панели инструментов 

    Если вы не закрыли окно редактирования исходного текста, нажмите <F12> для 
того, чтобы вернуться в окно конструктора форм, а затем щелкните на компоненте 
sbProperty, находящемся в верхней части главной формы приложения. Нажмите 
<F11> для перехода в окно Object Inspector, а затем откройте вкладку Events. 
Щелкните на кнопке раскрытия списка значений события OnClick и выберите из 
него значение Propertylclick. Это приведет к тому, что обработчик события 
щелчка на кнопке sbProperty будет выполнять те же операции, что и обработчик 
события щелчка на пункте Tables=>Property меню приложения. Назначение одному 
обработчику события ссылки на другой обработчик является более эффективным 
методом, чем вызов ранее созданного обработчика из метода другого обработчика. 
    Теперь сохраните проект и запустите приложение. После того, как система 
RENTMAN загрузиться, нажмите <F4> для того, чтобы проверить работу формы 
PROPERTY. На рис. 11.3 показан пример этого окна во время работы приложения. 
    
    Рис. 11.3. Окончательный вид формы PROPERTY во время работы приложения 

    Можете ввести какие-нибудь данные, чтобы проверить, как работает форма. На 
рис. 11.3 показаны три строки, которые я ввел с этой целью. 

    Форма LEASE 

    Выберите из меню команду File=>New, а затем на вкладке Forms открывшегося 
окна New Items снова выберите элемент fmControlGridForm, после чего щелкните 
на переключателе Inherit и на кнопке ОК. В окне конструктора форм должна 
появится новая форма класса fmControlGridForm. На основе этой формы мы 
создадим форму редактирования таблицы LEASE. 
    Начнем настройку формы с изменения имени ее объекта на fmRLEACGDO и 
заголовка на Lease Edit Form. Сохраните программный модуль формы в каталоге 
системы RENTMAN в файле под именем RLEACGDO.PAS. 

    Глава 11. Разработка форм 229 


    Настройка управляющей сетки 

    Из двенадцати столбцов таблицы LEASE девять являются текстовыми или 
числовыми полями, одно — полем булевого типа, а два других — внешними ключами 
других таблиц. Эти внешние ключи будут обрабатываться с помощью полей выборки 
табличного компонента taLEASE. Для того чтобы настроить управляющую сетку, 
выполните следующие операции. 
    1. Поместите на верхнюю панель компонента DBCtrlGrid один компонент DBText 
и два компонента DBLookupComboBox. 
    2. Немного ниже этих компонентов расположите восемь компонентов DBEdit. 
    3. Поместите на ту же верхнюю панель компонента DBCtrlGrid один компонент DBCheckBox. 
    4. Установите свойства ValueChecked и ValueUnchecked компонента DBCheckBox 
равными, соответственно, T и F. 
    5. Поместите рядом со всеми компонентами DBText, DBEdit и DBLookupComboBox 
по одному компоненту Label. 
    6. Разместите компонент DBText таким образом, чтобы он находился в левом 
верхнем углу верхней панели управляющей сетки данных. Остальные компоненты 
разместите в соответствии с рис. 11.4, который приведен несколько ниже. 
    Компонент DBText предназначен для отображения значения столбца 
LEASE_NUMBER. Остальные текстовые столбцы таблицы LEASE будут представлять 
компоненты DBEdit и DBLookupComboBox, а столбец, содержащий булевы значения, — 
компонент DBCheckBox. 

    Совет 
    Вам уже, должно быть, порядком надоело каждый раз вводить имя пользователя 
и пароль СУБД InterBase. Для того чтобы отключить на время разработки запрос 
пароля, откройте форму dmRentMan и дважды щелкните на компоненте TDatabase. 
Это приведет к открытию окна редактора базы данных, с помощью которого можно 
назначить используемый по умолчанию пароль для этого экземпляра компонента. Не 
забывайте, что это лишь временная мера, небрежное использование которой может 
повлечь нарушение правил обеспечения безопасности базы данных и управления 
доступом. Если пароль указан в самом приложении, то человек, который 
просмотрит файлы приложения, сможет без труда узнать этот пароль. 

    Связывание компонентов с таблицей LEASE 

    Прежде чем связывать компоненты, предназначенные для работы с данными, с 
табличным объектом taLEASE, нужно добавить к последнему еще пару полей 
выборки. Эти поля позволят выбирать данные из столбцов TENANT_NUMBER и 
PROPERTY_NUMBER таблицы LEASE, используя имена арендаторов и адреса объектов 
недвижимости, а не ничего не говорящие номера. Для того чтобы настроить поля 
выборки таблицы taLEASE, выполните следующие операции. 
    1. Нажмите <Shift+F12> для открытия списка форм и дважды щелкните на 
элементе этого списка — dmRENTMAN, представляющем форму модуля данных. 
    2. Щелкните дважды на компоненте taLEASE, чтобы открыть окно редактора полей. 
    3. Щелкните правой кнопкой мыши на списке полей и из появившегося меню 
выберите пункт New field (или нажмите <Ctrl+N>). 
    4. В качестве имени нового поля введите TENANT_NAME, укажите, что оно 
имеет тип String, и установите его размер равным 30. 
    5. Щелкните на переключателе Lookup, а затем настройте элемент Key Fields 
на TENANT_NUMBER. 
    6. Выберите объект taTENANT в качестве набора данных поля выборки, а 
свойству Lookup Keys присвойте значение TENANT_NUMBER. 
    7. В качестве значения свойства Result Field присвойте ссылку на поле NAME 
таблицы TENANT, затем щелкните на кнопке ОК. 

    230 Часть II. Учебный курс 

    Все перечисленные операции нужны для установления связи между компонентами 
taLEASE и taTENANT. Теперь проделаем то же самое, но для компонентов 
taPROPERTY и taLEASE. 
    1. Нажмите <Ctrl+N>, чтобы добавить еще одно поле в таблицу taLEASE. 
    2. В качестве имени нового поля введите PROPERTY_ADDRESS, указав, что оно 
имеет тип String и размер 30. 
    3. Щелкните на переключателе Lookup, а затем настройте элемент Key Fields 
на PROPERTY_NUMBER. 
    4. Выберите объект taPROPERTY в качестве набора данных поля выборки, а 
свойству Lookup Keys присвойте значение PROPERTY_NUMBER. 
    5. В качестве значения свойства Result Field присвойте ссылку на поле 
ADDRESS таблицы PROPERTY, затем щелкните на кнопке ОК. 
    Итак, связь между компонентом taLEASE и компонентами taTENANT и taPROPERTY 
установлена. Это позволяет рассматривать столбцы TENANT_NAME и 
PROPERTY_ADDRESS в качестве составных частей набора данных taLEASE. 

    На заметку 
    Вы, возможно, не видите причины для добавления двух новых полей выборки в 
объект taLEASE. На первый взгляд кажется, что можно было бы настроить 
соответствующие свойства компонентов DBLookupComboBox. В конце концов, именно 
для этого и предназначены свойства ListSource и ListField компонента 
DBLookupComboBox. Это правильно, однако такой подход не срабатывает, когда эти 
компоненты помещены на управляющую сетку данных. Поэтому не остается ничего, 
кроме как обойти "неумолимый" компонент DBCtrlGrid, запрещающий такие операции 
находящимся на нем компонентам DBLookupComboBox. Так как последние хорошо 
справляются с задачей обработки предопределенного списка, представленного 
полями выборки, то мы можем применить выборку нужных данных с помощью таких 
полей, добавив их в табличный объект taLEASE. 


    Поля выборки 

    Как я уже упоминал, в таблице PROPERTY на самом деле нет поля TENANT_NAME 
— это так называемое поле выборки (lookup field). Поле выборки — это столбец 
специального типа, который используется для представления на логическом уровне 
данных, физически находящихся в другой таблице. В нашем примере для выборки 
данных столбца NAME таблицы TENANT используется столбец TENANT_NUMBER таблицы 
PROPERTY. 
    Используя поля выборки, можно очень просто ссылаться на данные из других 
таблиц без какого-либо программирования. Компоненты DBGrid, DBCtrlGrid, 
DBListBox и DBLookupComboBox "умеют" работать с полями выборки и автоматически 
обновлять их в случае необходимости. Фактически, компоненты DBGrid и 
DBCtrlGrid сами предоставляют раскрывающийся список для полей, которые они 
распознали как поля выборки. Это означает, что элемент управления будет не 
только отображать правильное значение поля из таблицы выборки, но и позволит 
вам увидеть остальные значения этого столбца с помощью раскрывающегося списка. 
Обратите внимание на то, что остальные компоненты для работы с данными 
(например, DBEdit) не могут автоматически обновлять поля выборки. Поэтому, 
если вы захотите по каким-то причинам, чтобы эти компоненты работали не с 
реальными полями, а с полями выборки, то обновление вам придется выполнять с 
помощью соответствующих операторов программы. 

    Завершение настройки формы 

    Настроив поля выборки, вернемся к форме fmRLEACGDO. Для того чтобы связать 
компоненты, предназначенные для работы с данными, с соответствующими столбцами 
таблицы LEASE, выполните следующие операции. 
    1. С помощью команды File=>Use Unit откройте окно со списком программных 
модулей приложения и дважды щелкните на элементе RENTDATA в этом списке для 
того, чтобы связать модуль данных dmRENTDATA с модулем новой формы. 
    2. Установите свойства DataSource компонентов DBCtrlGrid и DBNavigator 
равными dmRENTMAN.dsLEASE. 

    Глава 11. Разработка форм 231 

    3. Щелкая на каждом компоненте, помещенном на управляющую сетку данных, 
присваивайте их свойствам DataField ссылки на соответствующие поля таблицы 
LEASE, как показано в табл. 11.4. Настройте в соответствии с табл. 11.4 
остальные поля всех компонентов. 

    Таблица 11.4. Компоненты управляющей сетки данных и их атрибуты 

    Компонент		Name				Width	DataField		Метка 

    DBText			dteLEASENUMBER		40	LEASENUMBER		No. 
    DBLookupComboBox	dIcbTenant Name		145	TENANTNAME		Tenant 
    DBLookupComboBox	dIcbProperty Address	145	PROPERTYADDRESS	Property 
    DBEdit			dedBeginDate		64	BEGINDATE		Begin 
    DBEdit			dedEndDate			64	ENDDATE		End 
    DBEdit			dedMovedInDate		64	MOVEDINDATE		Movedin 
    DBEdit			dedMovedOutDate		64	MOVEDOUTDATE	MovedOut 
    DBEdit			dedRent			50	RENT			Rent 
    DBEdit			dedDeposit			50	DEPOSIT		Deposit 
    DBEdit			dedPetDeposit		50	PETDEPOSIT		PetDeposit 
    DBEdit			dedRentDue Day		40	RENTDUEDAY		DueDay 
    DBCheckBox		dckLawnService		85	LAWNSERVICE		LawnService 
    DBEdit			dedComments			355	COMMENTS		Comments 
    

    Тестирование формы LEASE 

    Настроив компоненты и их свойства, можно перейти к тестированию формы. Для 
этого сначала нужно подключить новый программный модуль fmRLEACGDO к 
программному модулю fmRSYSMANO главной формы Приложения RENTMAN. 

    Подключение формы LEASE к приложению 

    Откройте форму fmRSYSMANO в окне конструктора форм Delphi, а затем 
выберите команду File=>Use Unit. Щелкните дважды на элементе RLEACGDO 
открывшегося списка. Данная операция обеспечила наличие связи между формой 
LEASE и главной формой приложения, что позволяет открыть форму PROPERTY с 
помощью системы меню приложения. 
    Открыв конструктор меню, дважды щелкните на пункте Leases подменю Tables. 
В открывшемся в этот момент окне редактирования исходного текста введите 
следующую строку: 

    fmRLEACGDO.Show; 

    При двойном щелчке на пункте Leases подменю Tables, система Delphi 
автоматически создает метод класса формы с именем LeaselClick. Этот метод 
вызывается щелчком на пункте Leases во время работы приложения. Таким образом, 
после добавления вышеуказанной строки, метод LeaselClick будет всегда открывать 
форму fmPLEACGDO, используя для этого ее метод Show. Теперь, как и в других 
формах, давайте присвоим ссылку на данный метод обработчику события OnClick 
кнопки панели инструментов sbLease. 

    Настройка панели инструментов 

    Вернитесь в окно конструктора форм, а затем щелкните на компоненте 
sbLease, находящемся в верхней части главной формы приложения. Нажмите <F11> 
для перехода в окно Object Inspector, а затем откройте вкладку Events. 
Щелкните на кнопке раскрытия списка значений события OnClick и выберите из 
него значение LeaselClick. Как и для форм TENANT и PROPERTY, это приведет к 
тому, что обработчик события щелчка на кнопке sbLease будет выполнять те же 
операции, что и обработчик события щелчка на пункте Tables=>Leases меню 
приложения. 
    
    232 Часть II. Учебный курс 

    Теперь сохраните проект и запустите приложение. После того, как система 
RENTMAN загрузиться, нажмите <F6>, чтобы проверить работу формы LEASE. На рис. 
11.4 показан пример этого окна во время работы приложения. 

    Рис. 11.4. Окончательный вид формы LEASE во время работы приложения 

    Можете ввести какие-нибудь данные, чтобы проверить, как работает форма. На 
рис. 11.4 показаны три строки, которые я ввел с этой целью. 
    Конечно же, настройку форм TENANT, PROPERTY и LEASE можно продолжить, 
однако вы уже, наверное, поняли, что процесс создания таких форм, особенно с 
использованием компонентов DBCtrlGrid, достаточно прост и несложен. В 
следующих разделах мы рассмотрим разработку форм WORDER и CALL. Для каждой из 
соответствующих таблиц нужно создать по две формы, в которых интенсивно 
используется компонент DBGrid. 

    Форма WORDER 

    Форма таблицы WORDER отличается от других форм данного приложения тем, что 
она относится к формам вида "главная—подчиненная", так как призвана отображать 
данные из таблиц WORDER и WODETAIL. Эти две таблицы связываются на уровне базы 
данных через ограничения ссылочной целостности. В форме WORDER вы обеспечите 
их визуальную связь через соответствующие табличные компоненты, находящиеся в 
модуле данных dmRENTMAN. 
    Если приложение RENTMAN все еще работает, закройте его. Выберите из меню 
команду File=>New, а затем на вкладке Forms открывшегося окна New Items 
выберите элемент fmGridForm, после чего щелкните на переключателе Inherit и на 
кнопке ОК. В окне конструктора форм должна появится новая форма класса fmGridForm. 
    Данная форма будет появляться на экране при выборе пункта Work Orders из 
главного меню системы RENTMAN. С помощью этой формы пользователь сможет 
вставлять, редактировать или удалять строки таблицы WORDER. При вставке и 
редактировании строк будет появляться вторая форма, fmRWORMDEO, вида 
"главная—подчиненная", с помощью которой можно будет редактировать 
одновременно и таблицу WORDER, И таблицу WODETAIL. 

    Сеточная форма 

    Сеточная форма предназначена для предоставления полной информации о 
таблице WORDER. Она дает пользователю возможность быстро просмотреть данные об 
имеющихся нарядах, что является одной из самых типичных ситуаций при работе с 
такими системами, как RENTMAN. С другой стороны, для одновременного 
редактирования строки таблицы WORDER вместе с соответствующими строками 
таблицы WODETAIL "будет применяться более удобная форма вида 
"главная—подчиненная". Для нас необычность этой формы заключается в том, что 
ее элементы управления используют не один источник данных, как это было во 

    Глава 11. Разработка форм 233

всех созданных нами раньше формах, а разные — элементы управления, 
находящиеся в верхней части формы, будут использовать таблицу WORDER, а 
находящиеся в ее нижней части — таблицу WODETAIL. 
    Заметьте, что в форме вида "главная—подчиненная" можно обойтись и без 
сетки данных. Вместо нее можно поместить на форме навигатор базы данных и 
связать его с таблицей WORDER. Однако в этом случае пользователь не сможет 
быстро просматривать все записи таблицы WORDER. При использовании навигатора 
строки будут обрабатываться последовательно, что нежелательно в таких 
ориентированных на транзакции таблицах, как WORDER. Кроме того, присутствие 
двух компонентов DBNavigator на одной форме может запутать пользователя. 
Поэтому лучше использовать не второй навигатор, а сетку данных. 
    Измените имя объекта новой формы на fmRWORGRDO, а ее заголовок — на Work 
Order Selection Form, а затем сохраните программный модуль формы в файле 
RWORGRDO.РАS. 
    Для создания и настройки формы потребуется лишь немного усилий и времени. 
Сначала мы создадим два поля выборки для получения информации из других 
таблиц, а затем настроим свойства DataSource компонентов формы, 
предназначенных для работы с объектами баз данных. 

    Создание полей выборки 

    Прежде всего нужно создать в объекте таблицы WORDER два поля выборки. Поле 
PROPERTY_NUMBER таблицы WORDER будет использоваться для получения значения 
поля ADDRESS таблицы PROPERTY. Для того чтобы создать и настроить поля выборки 
объекта таблицы WORDER, выполните следующие операции. 
    1. Откройте форму модуля данных dmRENTMAN в окне конструктора форм и 
дважды щелкните на компоненте taWORDER для того, чтобы открыть окно редактора полей. 
    2. Нажмите <Ctrl+N> для того, чтобы создать новое поле, а затем в качестве 
имени нового поля введите PROPERTY_ADDRESS. 
    3. Укажите, что новое поле имеет тип String, и установите его размер 
равным 30. 
    4. Щелкните на переключателе Lookup, а затем настройте элемент Key Fields 
на PROPERTY NUMBER. 
    5. Выберите объект taPROPERTY в качестве набора данных поля выборки, а 
свойству Lookup Keys присвойте значение PROPERTY_NUMBER. 
    6. В качестве значения свойства Result Field присвойте ссылку на поле 
ADDRESS, а затем щелкните на кнопке ОК для закрытия диалогового окна. 
    7. Все описанные выше операции приведут к тому, что поле ADDRESS будет 
выбираться из таблицы PROPERTY и сохраняться в объекте таблицы WORDER в виде 
поля выборки PROPERTY_ADDRESS. Новое поле можно увидеть в нижней части списка 
полей таблицы WORDER. 
    8. Снова нажмите <Ctrl+N> для того, чтобы создать второе поле. В этот раз 
нужно определить поле выборки, которое представляет значения, хранящиеся в 
столбце NAME таблицы EMPLOYEE. Это позволит вам отображать имена рабочих, 
которым будет передаваться на исполнение наряд. 
    9. Назовите новое поле выборки EMPLOYEE_NAME, установив в качестве его 
типа тип String, а размер — равным 30. 
    10. Теперь нужно настроить свойства поля выборки, как вы это делали для 
поля PROPERTY_ADDRESS. Щелкните на переключателе Lookup, а затем настройте 
элемент Key Fields на EMPLOYEE_NUMBER. 
    11. Выберите объект taEMPLOYEE в качестве набора данных поля выборки, а 
свойству Lookup Keys присвойте значение EMPLOYEE_NUMBER. 
    12. В качестве значения свойства Result Field присвойте ссылку на поле 
NAME, а затем щелкните на кнопке ОК для закрытия диалогового окна. Все 
описанные выше операции приведут к тому, что поле NAME будет выбираться из 
таблицы EMPLOYEE с помощью столбца EMPLOYEE_NUMBER, связывающего две таблицы, 
и сохраняться в объекте таблицы WORDER в виде поля выборки EMPLOYEE_NAME. 

    Установление связи вида "главная-подчиненная" 

    Поскольку вы находитесь в форме модуля данных dmRENTMAN, давайте сразу же 
установим связь "главная—подчиненная" между таблицами WORDER и WODETAIL. 
Наличие такой связи позволит создать форму, на которой будут отображаться обе 
синхронизированные таблицы. Для того чтобы связать таблицы, выполните 
следующие операции. 
    1. Щелкните на объекте таблицы WODETAIL, а затем нажмите <F11>, чтобы 
перейти в окно Object Inspector. 

    234 Часть II. Учебный курс 

    2. В качестве значения свойству MasterSource присвойте ссылку на объект 
источника данных dsWORDER. 
    3. Щелкните на кнопке подстановки, которая находится рядом со свойством 
MasterFields, чтобы открыть окно конструктора связей полей Delphi. 
    4. С помощью окна Field Link Designer свяжите таблицы WORDER и WODETAIL. 
На левой панели этого окна показаны поля таблицы WODETAIL, а на правой — поля 
таблицы WORDER. Щелкните на поле WORDER_NUMBER, которое должно присутствовать 
в обеих таблицах, а затем — на кнопке Add. Между таблицами установится связь, 
как показано на рис. 11.5. Щелкните на кнопке ОК, чтобы закрыть окно 
конструктора связей. 
    Щелчок на столбце WORDER_NUMBER приводит к установке связей между двумя 
таблицами по данному столбцу. Подчиненная таблица будет отображать только те 
строки, у которых значение поля WORDER_NUMBER соответствует значению этого 
поля в текущей строке главной таблицы. При этом создается эффект визуальной 
связи двух таблиц. Обратите внимание, что использовать конструктор полей не 
обязательно. Того же эффекта можно добиться, если указать в качестве значения 
свойств MasterFields и IndexFieldNames имя столбца WORDER_NUMBER. 
    Поскольку подчиненная таблица должна правильно отображать строки при 
добавлении их в главную таблицу, то для этого нужен механизм, который бы 
обеспечивал передачу подчиненной таблице номера строки, вставляемой в главную 
таблицу. Это можно осуществить, например, с помощью следующего оператора, 
который нужно набрать в исходном тексте обработчика события OnNewRecord 
объекта taWODETAIL. 

    taWODETAILWORDER_NUMBER.Value:=taWORDERWORDER_NUMBER.Value; 


    Назначение столбцу используемого по умолчанию значения 

    В форме dmRENTMAN можно сделать еще некоторые улучшения. Для этого нужно 
создать обработчик события OnNewRecord таблицы WORDER. Так как при разработке 
базы данных RENTMAN мы не назначили используемого по умолчанию значения для 
столбца StartDate, можно сделать это сейчас, используя для этих целей функцию, 
возвращающую текущую дату, что в большинстве случаев будет вполне оправдано. 
Щелкните на компоненте taWORDER, затем дважды щелкните на значении обработчика 
события OnNewRecord в окне Object Inspector. Введите следующий оператор в окне 
редактирования исходного текста. 

    taWORDERStartDate.Value:=Date; 

    Это приведет к тому, что при добавлении новой строки в таблицу текущая 
дата будет присвоена в качестве используемого по умолчанию значения поля 
StartDate этой строки. Как видно из этого примера, реализация бизнес-правил на 
уровне приложения имеет свои преимущества. Для начала можно отметить, что 
такие операции в приложении выполняются гораздо быстрее, чем при использовании 
других методов. Это может объяснятся как тем, что код приложения уже находится 
в памяти, так и тем, что разработчик лучше знает используемый им язык 
программирования, а не SQL, поэтому находит более эффективные решения, чем при 
использовании SQL. 
    С другой стороны, присвоение используемого по умолчанию значения полю 
StartDate в обработчике события OnNewRecord является прекрасным примером того, 
как на первый взгляд хорошая идея при близком рассмотрении оказывается не 
такой уж хорошей. (Откусив яблоко, мы подчас находим в нем червя.) Обратитесь 
к главе 14, "Тестирование", в которой я показываю лучший способ присвоения 
используемого по умолчанию значения полю StartDate. Пока же оставьте оператор 
в том виде, в котором он приведен в данном разделе. 

    Назначение источника данных 

    Теперь, закончив настройку модуля данных, можно перейти к назначению 
источника данных компонентам формы DBNavigator и DBGrid. Перейдите на форму 
WORDER (объект fmRWORGRDO в конструкторе форм), щелкните на пункте меню 
File=>Use Unit. В открывшемся окне выберите из списка элемент RENTDATA. Это 
обеспечит связь новой формы с модулем данных. Выберите компоненты DBGrid и 
DBNavigator, перейдите в окно Object Inspector и измените их свойства 
DataSource, присвоив им в качестве значения ссылку на объект dmRENTMAN. 
dsWORDRER. Таким образом, новая форма будет использоваться для редактирования 
таблицы WORDER базы данных RENTMAN. 
    
    Рис. 11.5. Окно конструктора связей полей Delphi 
    

    Глава 11. Разработка форм 235


    Настройка столбцов сетки данных 

    Нам осталось выполнить последние приготовления, прежде чем мы увидим нашу 
форму в работе. Нужно настроить столбцы сетки данных таким образом, чтобы 
сделать отображаемую ею информацию более осмысленной. Некоторые из столбцов 
таблицы WODETAIL отображать не нужно, так как их значения уже будут показаны в 
других местах формы. Кроме того, нам не нужны одновременно и ключевые поля 
выборки, и поля результата выборки — вполне достаточно только полей 
результата. Наконец, поля результата выборки не должны находится в конце 
списка столбцов, где их трудно увидеть. Скорей всего, вы захотите передвинуть 
их как можно ближе к началу таблицы. 
    Для настройки сетки данных выполните следующие операции. 
    1. Щелкните дважды на компоненте DBGrid, чтобы открыть окно редактора 
столбцов. Щелкните на кнопке Add All Fields этого окна, чтобы сделать все поля 
таблицы WORDER столбцами сетки данных. 
    2. Обратите внимание, что поля выборки находятся в конце списка. Так как в 
таблице WORDER накапливается информация об эксплуатационных работах, 
выполнявшихся на тех или иных объектах недвижимости, имеет смысл передвинуть 
поле, описывающее адрес объекта недвижимости, как можно ближе к началу таблицы. 
    Щелкните на поле выборки PROPERTY_ADDRESS и перетащите его в списке, 
опустив сразу после столбца WORDER_NUMBER. 
    Точно так же перетащите столбец EMPLOYEE NAME и опустите его сразу после 
столбца PROPERTY_ADDRESS. 

    Рис. 11.6. С помощью редактора столбцов можно настроить сетку данных 

    Выберите поля PROPERTY_NUMBER и EMPLOYEE_NUMBER и щелкните на кнопке 
Delete, чтобы удалить их. Столбцы удаляются только из компонента DBGrid, 
по-прежнему оставаясь в наборе данных. На рис. 11.6 показано окно редактора 
столбцов компонента DBGrid. Удалив вышеназванные столбцы, закройте окно. 

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

    Подключение сеточной формы к приложению 

    Итак, настало время подключить сеточную форму WORDER к приложению. 
Откройте главную форму приложения, fmRSYSMANO, в окне конструктора форм 
Delphi, а затем дважды щелкните на пункте Work Orders подменю Tables. В 
открывшемся окне редактирования исходного текста введите следующую строку: 

    fmRWORGRDO.Show; 

    Теперь надо настроить панель инструментов. Нажмите <F12> для того, чтобы 
вернуться в окно конструктора форм, а затем щелкните на компоненте 
sbWorkOrders, находящемся в верхней части главной формы приложения. Нажмите 
<F11> для перехода в окно Object Inspector, а затем откройте вкладку Events. 
Щелкните на кнопке раскрытия списка значений события OnClick и выберите из 
него значение WorkOrderslClick. Это приведет к тому, что обработчик события 
щелчка на кнопке sbWorkOrders будет выполнять те же операции, что и обработчик 
события щелчка на пункте Tables=>Work Orders меню приложения. Настроив меню и 
панель инструментов, добавьте программный модуль формы RWORGRDO к оператору 
uses модуля главной формы RSYSMANO с помощью команды меню File=>Use Unit. 
    
    236 Часть II. Учебный курс 


    Тестирование формы 

    Теперь все готово к тестированию формы. Прежде чем запустить приложение на 
выполнение, убедитесь в том, что свойства Active компонентов Table модуля 
данных dmRENTMAN имеют значение True. Затем сохраните проект и нажмите <F9> 
для запуска приложения. Когда система RENTMAN загрузится, нажмите <F7> для 
того, чтобы открыть форму WORDER. 

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


    Улучшение формы 

    При первом же взгляде на форму сразу становятся видны недостатки в ее 
внешнем виде. Хотя основное назначение этой формы — возможность выбора 
информации о нужных нарядах, почему-то оставлено очень мало места для 
отображения данных таблицы WORDER. Если форма предназначается для выбора 
какой-то информации, она должна отображать как можно больше строк. Кроме того, 
в ней должно быть видно как можно больше столбцов, чтобы легче было находить 
нужные строки. Если вы попробуете максимизировать находящуюся в данный момент 
на экране форму, то увидите, что средняя панель занимает на ней слишком много 
места, а сетка данных отображает столько же информации, как и прежде. Чтобы 
устранить этот недостаток, нужно увеличить размеры формы в окне конструктора 
форм и соответствующим образом исправить расположение ее компонентов. 
    Если приложение все еще работает, закройте его и вернитесь в Delphi. Для 
того, чтобы улучшить внешний вид формы WORDER, выполните следующие операции. 
    1. Выберите объект формы в окне конструктора форм (можно воспользоваться 
раскрывающимся списком окна Object Inspector, так как занятую компонентами 
форму трудно выбрать с помощью мыши). Измените значение свойства WindowState 
формы на wsMaximized. 
    2. Вернитесь в окно конструктора форм и максимизируйте форму. 
    3. Выберите компонент paMiddle и установите его свойство Align равным 
alBottom. Обратите внимание, как послушно он "лег" на компонент paBottom, у 
которого свойство Align имеет такое же значение. С помощью данной возможности 
Delphi можно размещать панели одна над другой. 
    4. Уменьшите высоту панели paMiddle, перетаскивая ее верхнюю границу до 
тех пор, пока ее высота не станет как раз такой, чтобы разместить находящиеся 
на ней кнопки. 
    5. Выберите компонент раТор и установите его свойство Align равным 
alClient. При этом верхняя панель, на которой размещена сетка данных, должна 
заполнить все свободное пространство формы. Так как у компонента DBGrid 
свойство Align также имеет значение alClient, то сетка данных заполнит все 
пространство панели раТор. 

    На рис. 11.7 показана форма с внесенными в нее только что описанными изменениями. 


    Обеспечение доступности сетки данных только для чтения 

    Осталось еще сделать еще одну доработку в работе формы. Так как данная 
форма нужна только для выбора той или иной строки из таблицы WORDER, то не 
должно допускаться редактирование этой таблицы. Одной из причин такого 
ограничения является то, что при редактировании таблицы WORDER нужно 
редактировать и таблицу WODETAIL, так как они связаны между собой. Однако для 
этого будет создана отдельная форма fmRWORMDEO, а сеточная форма 
использоваться лишь для просмотра таблицы нарядов и выбора нужной строки. 
    Исходя из этого, необходимо немного доработать форму fmRWORGRDO. 
Во-первых, нужно в окне Object Inspector установить свойство Readonly 
компонента DBGrid равным True. Это позволит запретить редактирование данных, 
отображаемых сеткой. Во-вторых, необходимо изменить работу навигатора, чтобы с 
его помощью пользователь не мог внести изменения в таблицу WORDER. 

    Глава 11. Разработка форм 237 


    Рис. 11.7. Сеточная форма HORDER во время работы приложения 

    Доработка компонента DBNavigator 

    Щелкните дважды на компоненте DBNavigator формы. Откроется окно 
редактирования исходного текста, в котором нужно ввести следующие операторы. 

    With DBNavigatorl do 
       case Button of 
          nblnsert : 
             begin 
                DataSource.DataSet.Insert; 
                fmRWORMDEO.ShowModal ; 
             end; 
         nbEdit : 
             begin 
               DataSource.DataSet.Edit; 
               fmRWORMDEO.ShowModal; 
             end; 
       end; 

    Эти операторы перекрывают обработчик события OnClick компонента 
DBNavigator, изменяя поведение навигатора при щелчке на кнопках Insert и Edit. 
Таким образом, при вставке новой строки или перевода набора данных в режим 
редактирования вызывается форма WORDER вида "главная-подчиненная", fmRWORMDEO. 
Обратите внимание на то, что данный фрагмент невозможно скомпилировать до тех 
пор, пока не будет создана форма fmRWORMDEO, о которой мы поговорим в 
следующем разделе. 

    На заметку 
    Вы, должно быть, помните, что мы поместили кнопки Add, Edit и Delete на 
форму класса fmEditForm, чтобы предоставить пользователю возможность 
модифицировать набор данных, используя не только мышь и кнопки навигатора, но 
и клавиши мнемонического доступа, назначенные вышеупомянутым кнопкам. Как вы 
помните, каждая из этих кнопок не делает ничего, за исключением вызова 
обработчика события OnClick навигатора. Таким образом, когда пользователь 
нажимает комбинацию клавиш, с помощью которых обеспечивается мнемонический 
доступ к той или иной кнопке, выполняется операция вставки, удаления или 
редактирования, как при щелчке на соответствующей кнопке компонента 
DBNavigator. Благодаря этому нехитрому приему, вам не нужно вносить каких-либо 
изменений в исходный текст обработчиков событий этих кнопок. Даже если вам 
понадобится кардинально изменить логику их работы, достаточно будет перекрыть 
метод OnClick навигатора, как и показано выше. 


    238 Часть II. Учебный курс 


    Форма WORDER вида "главная-подчиненная" 

    Итак, теперь нам нужно создать форму, с помощью которой можно было бы 
одновременно редактировать таблицы WORDER и WODETAIL. Так как между этими 
таблицами установлена связь типа "главная— подчиненная", то с помощью такой 
формы пользователь сможет просматривать все строки таблицы WODETAIL, связанные 
с текущей строкой таблицы нарядов, выбранной с помощью формы fmRWORGRDO. 
Текущая строка таблицы WORDER будет отображаться в верхней части формы, а 
соответствующие ей строки таблицы WODETAIL — в нижней части. 
    Для того чтобы приступить к созданию новой формы, выполните следующие операции. 
    1. Выберите из меню команду File=>New, а затем перейдите на вкладку Forms 
открывшегося окна New Items. 
    2. Выберите элемент fmMasterDetailForm, затем щелкните на переключателе 
Inherit и на кнопке ОК. 
    3. Измените имя объекта новой формы на fmRWORMDEO, а ее заголовок — на 
Work Order Edit Form, затем сохраните программный модуль формы в файле 
RWORMDEO.РАЗ. 

    Поле выборки 

    Прежде чем приступать к настройке формы, давайте определим поле выборки 
набора данных табличного объекта taWODETAlL, с помощью которого из таблицы 
WORKTYPE будет выбираться значение поля DESCRIPTION. Для этого выполните 
следующие операции. 
    1. Откройте форму модуля данных dmRENTMAN в окне конструктора форм Delphi, 
дважды щелкните на наборе данных taWODETAlL, чтобы открыть окно редактора 
полей, а затем нажмите <Cltr+N> для создания нового поля. 
    2. В строке Name введите имя WORKTYPEDESCRIPTION, выберите переключатель 
типа поля String и установите размер поля равным 30. 
    3. Щелкните на переключателе Lookup, а затем настройте элемент Key Fields 
на WORK_TYPE_CODE. 
    4. Выберите объект taWORKTYPE в качестве набора данных поля выборки, а 
свойству Lookup Keys присвойте значение WORK_TYPE_CODE. 
    5. В качестве значения свойства Result Field присвойте ссылку на поле 
DESCRIPTION, затем щелкните на кнопке ОК для закрытия диалогового окна. Все 
описанные выше операции приведут к тому, что описание работы будет выбираться 
из таблицы WORKTYPE с помощью столбца WORK_TYPE_CODE таблицы WODETAIL. Таким 
образом, поле WORK_TYPE_CODE таблицы WODETAIL будет преобразовываться в 
значение поля выборки DESCRIPTION формы. 
    6. Закончив создание нового поля, закройте окно редактора полей и снова 
откройте форму в окне конструктора форм. 

    Настройка источников данных 

    Теперь нужно настроить источники данных формы, чтобы подготовить ее к 
размещению компонентов. 
    1. Выберите команду File=>Use Unit и из полученного списка выберите элемент 
RENTDATA, чтобы открыть доступ к форме модуля данных dmRENTMAN. 
    2. Выберите одновременно компоненты DBGrid и DBNavigator и измените их 
свойства DataSource, указав в качестве значения ссылку на источник данных 
dmRENTMAN. dsWODETAlL. По окончании процесса разработки формы в верхней ее 
части будет отображаться строка таблицы WORDER, а в нижней — соответствующие 
строки таблицы WODETAIL. 

    Обеспечение записи текущей строки главной таблицы 

    Поскольку новая форма работает одновременно с двумя таблицами, то нам 
понадобится специальный механизм, обеспечивающий сохранение внесенных 
изменений в строку главной таблицы до того, как будут добавлены строки 
подчиненной таблицы (posting). Необходимость такого механизма обусловлена 
наличием ограничений на соблюдение ссылочной целостности, примененных к 
таблице WODETAIL — новое значение столбца WORDER_NUMBER должно сначала 
создаваться в таблице WORDER, а только потом в таблице WODETAIL. Понятно, что 
до тех пор, пока новая запись не сохранена в таблице WORDER, для сервера она не 

    Глава 11. Разработка форм 239 

существует. Поэтому отсутствие такого механизма приведет к тому, что при 
добавлении новых записей выборка информации из подчиненной таблицы будет 
происходить неправильно. Для того чтобы создать такой механизм, выполните 
следующие операции. 
    1. Щелкните дважды на компоненте DBNavigator формы. Должно открыться окно 
редактирования исходного текста с помещением курсора в первую строку 
обработчика события DBNavigatorlClick. 
    2. Введите следующие операторы: 

    with dmRENTMAN.taWORDER do 
       if (State in [dsEdit, dslnsert]) and 
          (Modified) then begin 
	       Post; 
             Edit; 
       end; 
    with DBNavigatorl.DataSource.DataSet do 
       case Button of 
         nbEdit : Edit; 
         nblnsert : Insert; 
        end; 


    На заметку 
    Для того чтобы откомпилировать модифицированный модуль формы, понадобится 
добавить модуль DB в операторе uses этого программного модуля. Хотя на форме 
fmRWORMDEO находится немало компонентов, предназначенных для работы с данными, 
среди них нет ни одного, с помощью которых модуль DB автоматически добавляется 
в оператор uses, поэтому вам придется выполнить это вручную. 

    Эти операторы проверяют состояние таблицы WORDER и в тех случаях, когда 
она редактировалась или когда в нее добавлялись новые строки, сохраняют в базе 
данных все внесенные изменения (вызов метода Post). Сразу же после этого 
таблица снова переводится в режим редактирования (метод Edit), чтобы 
пользователь мог продолжить внесение изменений в информацию текущей строки. 
Так как между таблицами taWORDER и taWODETAlL установлена связь типа 
"главная—подчиненная", сохранение данных на диске приведет к тому, что 
компонент taWODETAlL снова переключится в режим просмотра со сбросом текущего 
состояния. Поэтому в конце данного фрагмента использован оператор case, с 
помощью которого проверяется, какая кнопка была нажата, чтобы повторить эту операцию. 

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


    Настройка столбцов сетки 

    Покончив с навигатором, перейдем к настройке столбцов сетки данных. 
    1. Щелкните дважды на компоненте DBGrid, чтобы открыть окно редактора столбцов. 
    2. Щелкните на кнопке Add All Fields, а затем удалите из списка поля 
WORDER_MUMBER и WORK_TYPE_CODE. Поле WORDER_NUMBER не нужно отображать в сетке 
данных, поскольку оно и так будет показано в верхней части формы, а поле 
WORK_TYPE_CODE — поскольку для отображения и редактирования соответствующей 
ему информации мы воспользуемся полем выборки. 
    3. Переместите поле WORKTYPEDESCRIPTION в верхнюю часть списка, расположив 
его сразу под полем WODETAIL_LINE_NUMBER. 
    4. Установите свойство Read-only поля WODETAIL_LINE_NUMBER равным True, а 
затем закройте редактор полей. 

    240 Часть II. Учебный курс 


    Помещение компонентов на форму 

    В верхней части формы fmRWORMDEO будет отображаться информация из шести 
столбцов таблицы taWORDER. Поместите на форму один компонент DBText, три 
компонента DBEdit и два компонента DBLookupComboBox. Теперь надо связать эти 
компоненты с соответствующими столбцами табличного объекта taWORDER. 

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

    Разместив на форме все компоненты, установите их свойства DataSource 
равными ссылке на источник данных dmRENTMAN.dsWORDER. В табл. 11.5 
перечислены все компоненты с соответствующими значениями свойств. 

    Таблица 11.5. Компоненты верхней части формы WORDER и их атрибуты 

    Компонент		Name				Data Field		Метка 

    DBText			dteWORDERNUMBER		WORDERNUMBER	No. 
    DBLookupComboBox	dIcbPROPERTY_ADDRESS	PROPERTY_ADDRESS	Address 
    DBLookupComboBox	dlcbEMPLOYEE_NAME		EMPLOYEE_NAME	Employee 
    DBEdit			dedStartDate		STARTDATE		StartDate 
    DBEdit			dedEndDate			ENDDATE		EndDate 
    DBEdit			dedComments			COMMENTS		Comments 

    Так как ни один из помещенных на форму компонентов не имеет встроенной 
метки, то для создания таких меток рядом с каждым из них нужно разместить 
компоненты TLabel. 

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

    На рис. 11.8 показан вид новой формы на данном этапе. 


    Рис. 11.8. Новая форма WORDER на стадии разработки 


    Глава 11. Разработка форм 241 


    Тестирование формы WORDER вида "главная-подчиненная"                | 

    Для того чтобы протестировать новую форму, нужно сначала подключить ее к 
приложению RENTMAN. В данном случае мы будем подключать форму не к главному 
меню приложения, а к сеточной форме WORDER, fmRWORGRDO. 

    Подключение формы вида "главная-подчиненная" к сеточной форме 

    Для того чтобы связать друг с другом сеточную форму и форму вида 
"главная—подчиненная", выполните следующие операции. 
    1. Откройте созданную на предыдущем этапе форму fmRWORGRDO в окне 
конструктора форм. 
    2. Выберите команду File=>Use Unit и в открывшемся окне дважды щелкните на 
элементе RWORMDEO, представляющем программный модуль формы вида 
"главная—подчиненная". Теперь программный фрагмент, который вы добавили ранее 
в обработчик события OnClick навигатора, будет нормально компилироваться. 
    Связав две формы, настройте компонент DBGrid таким образом, чтобы при 
двойном щелчке на одной из его строк или при щелчке на кнопке Edit открывалась 
форма WORDER вида "главная—подчиненная". 
    1. Щелкните на компоненте DBGrid, затем откройте вкладку Events окна 
Object Inspector. 
    2. Щелкните на кнопке раскрытия списка значений обработчика события 
OnDblclick и выберите из него ссылку на обработчик события btEditdick. Теперь 
при двойном щелчке на элементе сетки данных будет происходить то же, что и при 
щелчке на кнопке Edit. 
    3. Сохраните проект и запустите приложение на выполнение. На рис. 11.9 
показано, как ваша новая форма может выглядеть во время работы приложения. 

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

    Рис. 11.9. Вид формы WORDER вида "главная—подчиненная " во время работы приложения 


    Улучшение формы 

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

    242 Часть II. Учебный курс 


    Использование запросов для получения дополнительной информации 

    Бесспорным недостатком реляционной модели баз данных, на который постоянно 
ссылаются критики реляционных СУБД, является тот факт, что полная 
нормализация, как правило, влечет за собой необходимость использования 
операции объединения нескольких таблиц для получения ответа даже на самые 
простые запросы. В реляционных базах данные распределены в такой степени, что 
для самых простых запросов нужно писать огромные процедуры SQL. 
    Большинство экспертов склоняются к мысли, что простота администрирования, 
надежность самих структур данных, а также множество других факторов делают 
соблюдение технологии РСУБД самым главным требованием разработки хороших 
приложений баз данных. Однако сейчас вы на собственном опыте убедитесь в 
правоте критиков РСУБД, утверждающих, что реляционная модель влечет за собой 
необходимость разработки больших запросов для получения сравнительно простых 
структур данных. Всю необходимую вспомогательную информацию, о которой мы 
говорили в предыдущем разделе, вы получите из базы данных RENTMAN с помощью запросов. 
    Итак, для того чтобы получить строку из таблицы TENANT по данным таблицы 
WORDER, вам понадобиться получить текущее значение столбца PROPERTY_NUMBER из 
таблицы WORDER, а затем выбрать соответствующую строку из таблицы PROPERTY. 
После этого, с помощью объединения таблицы PROPERTY с таблицей LEASE, можно 
будет получить сведения об арендаторе данного объекта недвижимости. В терминах 
баз данных это называется многоуровневым объединением (multi-level join). 
Необходимость в использовании такого сложного механизма получения информации 
вызвана нормализацией базы данных. 
    Для создания многоуровневых объединений можно воспользоваться двумя 
способами. Во-первых, можно обратиться в тексте программы к методам FindKey 
каждого табличного объекта и "вручную", т.е. с использованием обычных приемов 
программирования на языке Object Pascal, выбрать всю нужную информацию. 
Во-вторых, можно воспользоваться оператором SQL, который создаст 
многоуровневое объединение и вернет всю нужную нам информацию. Мы 
воспользуемся вторым способом. 

    Запрос quTenantInfo 

    Для того чтобы создать запрос quTenantInfo, выполните следующие операции. 
    1. Откройте форму модуля данных dmRENTMAN в окне конструктора форм. 
    2. Поместите на форму компонент Query. Измените значение его свойства Name 
на quTenantInfo, а свойства Database — на dbRENTMAN. Щелкните дважды на 
свойстве SQL и введите следующие операторы запроса. 

    select t.NAME, t.HOMEPHONE, p.CITY, p.STATE, p.ZIP, p.ADDITION 
    from PROPERTY p, 
    LEASE l, 
    TENANT t, 
    where p.PROPERTY_NUMBER=:PROPERTY_NUMBER 
    and p.PROPERTY_NUMBER=l.PROPERTY_NUMBER 
    and 1.ENDDATE = (select MAX(ENDDATE) from LEASE 
    where PROPERTY_NUMBER=p.PROPERTY_NUMBER) 
    and ((l.MOVEOUTDATE > "TODAY") or (MOVEOUTDATE IS NULL)) 
    and 1.TENANT_NUMBER=t.TENANT_NUMBER 

    Запрос использует переданное ему значение PROPERTY_NUMBER для поиска 
самого последнего договора аренды по данному объекту недвижимости, откуда 
выбирается имя последнего арендатора. Полученные результаты возвращаются 
только в том случае, если последний арендатор еще арендует данный объект. 
Обратите внимание на использование константы TODAY. Система InterBase будет 
транслировать ее в текущую дату, поэтому в запросе отбираются только те 
договоры аренды, дата окончания которых больше текущей (> "TODAY") или не 
указана (is NULL). 
    Набрав текст запроса, щелкните на кнопке ОК, чтобы сохранить его, а затем 
выполните следующие операции. 
    1. Щелкните дважды на свойстве Params компонента Query и в открывшемся 
диалоговом окне присвойте параметру PROPERTY_NUMBER тип Integer, затем 
щелкните на кнопке ОК. 
    2. Щелкните дважды на компоненте quTenantInfo, чтобы открыть окно 
редактора полей. 

    Глава 11. Разработка форм 243 

    3. Нажмите <Ctrl+A> для добавления всех полей. 
    4. Щелкните на кнопке ОК, чтобы добавить все шесть полей, а затем закройте 
окно редактора полей. 
    5. Настройка хомпонента Query завершена. Теперь нужно поместить на форму 
компонент DataSource, с помощью которого запрос будет взаимодействовать с 
элементами управления для работы с данными. Поместите на форму модуля данных 
dmRENTMAN компонент DataSource и измените его свойство Name на dsTenantInfo. 
    6. Присвойте в качестве значения свойства DataSet компонента DataSource 
ссылку на компонент Query — quTenantInfo. 

    Подключение запроса к форме 

    Настроив запрос, нужно подключить его к форме WORDER вида 
"главная—подчиненная", чтобы она впоследствии могла с помощью этого запроса 
предоставлять информацию об арендаторе и адресе объекта недвижимости. После 
подключения запроса мы разместим на форме компоненты, предназначенные для 
отображения его результатов. 

    Подготовка запроса 

    Откройте форму WORDER вида "главная—подчиненная", fmRWORMDEO, в окне 
конструктора форм Delphi и выберите сам объект формы (можно воспользоваться 
раскрывающимся списком окна Object Inspector, так как занятую компонентами 
форму трудно выбрать с помощью мыши). Щелкните на вкладке Events в окне Object 
Inspector, а затем дважды щелкните на значении обработчика события OnCreate. В 
открывшемся окне редактирования введите следующий оператор: 

    dmRENTMAN.quTenantInfo.Prepare; 

    Как уже говорилось, предварительная подготовка (т.е. вызов метода Prepare) 
запроса, использующего передаваемые извне параметры (параметризированного 
запроса (parameterized query), согласно терминологии Delphi), может ускорить 
его работу. При вызове метода Prepare запрос отправляется системе обеспечения 
доступа к базе данных для предварительного синтаксического анализа и 
оптимизации. Поэтому, хотя вызывать это метод не обязательно, если вы не 
сделаете этого сами, то при открытии параметризированного запроса Delphi 
сделает это за вас. Более того — этот метод вызывается автоматически при 
каждом открытии запроса. Поэтому имеет смысл при открытии формы заранее 
подготовить запрос. 

    Подготовка формы 

    Настроив запрос, выполните следующие операции для того, чтобы использовать 
его в форме. 
    1. Для того чтобы освободить место для информации об арендаторе, 
переместите объекты dedStartedDate и dedEndDate и связанные с ними метки ближе 
к правому краю формы. 
    2. Добавьте на форму шесть компонентов DBText и шесть меток, разместив их 
у левого края формы. 
    3. Измените заголовки каждой из шести меток, чтобы они отображали 
назначение соответствующих полей. Включите использование атрибута Bold 
свойства Font. Это поможет отличать метку от значения поля. 
    4. В качестве источника данных (свойство DataSource) всех компонентов 
DBText укажите набор данных 

    dmRENTMAN.dsTenantInfo. 

    5. Назначьте в качестве значений свойств DataField всех компонентов DBText 
ссылки на соответствующие поля выходного набора данных объекта quTenantInfo. 
Для этого можно воспользоваться раскрывающимся списком, в котором перечислены 
все доступные значения. 

    Подключение запроса 

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

    244 Часть II. Учебный курс 

    Для того чтобы подключить запрос к форме, выберите сам объект формы (можно 
воспользоваться раскрывающимся списком окна Object Inspector, так как занятую 
компонентами форму трудно выбрать с помощью мыши), а затем щелкните на вкладке 
Events окна Object Inspector. Найдите обработчик события OnShow и дважды 
щелкните на его значении. В открывшемся окне редактирования исходного текста 
введите следующие операторы. 

    with dmRENTMAN.quTenantInfo do begin 
       If Active then Close; 
       ParamByName('PROPERTY_NUMBER').As Integer:= 
          dmRENTMAN.taWORDERPROPERTY_NUMBER.Aslnteger; 
       Open; 
    end; 

    Данный программный фрагмент закрывает открытый запрос, а затем снова 
открывает его. Поскольку все новые компоненты используют запрос в качестве 
источника данных dsTenantlnfo, то выполнение этих операций приведет к 
обновлению значений, отображаемых данными компонентами. 
    Создав обработчик события OnShow, в котором выполняется запрос, осталось 
связать этот запрос с событием OnClick компонента dlcbPROPERTY_ADDRESS. 
Нажмите <F12> для того, чтобы вернуться в окно конструктора форм, а затем 
щелкните на объекте dlcbPROPERTY_ADDRESS. Откройте вкладку Events окна Object 
Inspector, щелкните на раскрывающемся списке значений обработчика события 
OnClick и выберите из него ссылку на обработчик события FormShow. Это приведет 
к тому, что запрос будет выполняться как при каждом открытии формы, так и при 
раскрытии списка, представленного объектом dlcbPROPERTY_ADDRESS. 

    На рис. 11.10 показан вид формы на данном этапе. 


    Тестирование формы 

    Сохраните проект и запустите приложение на выполнение, чтобы проверить, 
как оно работает. На рис. 11.11 показан вид доработанной формы WORDER во время выполнения. 


    Рис. 11.10. Доработанная форма WORDER вида "главная-подчиненная " 


    Изменение размеров формы 

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

    Дальнейшие улучшения 

    Есть еще кое-что, что можно сделать для улучшения формы. Поскольку в 
таблице WORKTYPE имеются данные о сроках выполнения каждого вида работ, 
соответствующие значениям WORK_TYPE_CODE, то имеет смысл вычислять в 
приложении, сколько времени потребуется на выполнение составляемого наряда и, 
в соответствии с полученными результатами, планировать наряды. Лучше всего эту 
задачу можно выполнить с помощью запроса SQL. 


    Глава 11. Разработка форм 245 


    Рис. 11.11. Доработанная форма fmRMORMDEO во время выполнения приложения 


    Создание запросов с помощью Query Builder 


    На заметку 
    Средство Query Builder поставляется не во всех вариантах Delphi. Если в 
используемой вами системе Delphi нет средства Query Builder, перейдите к 
разделу "Тестирование улучшенной формы" 

    Для того чтобы запустить средство Query Builder, выполните следующие операции. 
    1. Закройте приложение RENTMAN и вернитесь в Delphi. 
    2. Откройте в окне конструктора форм форму dmRENTMAN и поместите на него 
компонент Query. 
    3. Измените имя объекта запроса на quWorkDuration, а свойству Database 
присвойте значение dbRENTMAN. 
    4. Щелкните правой кнопкой мыши на компоненте Query и из открывшегося меню 
выберите пункт Query Builder. 
    5. Если появится диалоговое окно с запросом на выбор используемой базы 
данных, щелкните дважды на элементе dbRENTMAN. 
    6. В следующем окне вам будет предложено выбрать таблицы. Щелкните дважды 
на таблице WODETAIL, затем — на таблице WORKTYPE. Закройте диалоговое окно Add 
Table. Появится окно, содержащее две таблицы, как показано на рис. 11.12. 
    7. Перетащите столбец WORK_TYPE_CODE из таблицы WODETAIL на столбец 
WORK_TYPE_CODE таблицы WORKTYPE. Между этими столбцами должна появиться линия, 
соединяющая таблицы. Эта линия означает объединение двух таблиц по столбцу WORK_TYPE_CODE. 
    8. Теперь нужно определить, какие данные вы хотите получить из 
объединенных таблиц. Щелкните дважды на столбце WORK_TYPE_CODE таблицы 
WODETAIL, чтобы добавить его к запросу. Название столбца должно появиться в 
нижней части окна Query Builder. 
    9. Прокрутите список полей запроса для столбца WORK_TYPE_CODE, пока не 
найдете поля Criteria, и введите в нем = : WORDER_NUMBER, чтобы определить 
возвращаемые данные. 
    10. Щелкните правой кнопкой мыши на поле Option и из открывающегося списка 
выберите пункт Group. Это означает, что все поля, возвращаемые запросом, 
группируются по значению столбца  WORDER_NUMBER. 
    11. Щелкните на кнопке Expression Builder (на ее пиктограмме изображено 
выражение а+Ь) панели инструментов средства Query Builder, чтобы открыть 
диалоговое окно Expression. С помощью этого окна 


    246 Часть II. Учебный курс 

(рис. 11.13) можно определить выражение, которое будет подсчитывать общую 
продолжительность работ по каждому конкретному наряду. 
    12. В строке Expression Name введите WorkDuration, а затем выберите 
таблицу WORKTYPE из раскрывающегося списка Tables. 
    13. Выбрав таблицу, дважды щелкните на функции sum() в списке Functions, а 
затем дважды щелкните на поле TaskDuration в списке Columns. 
    14. Проделав все это, щелкните на кнопке Save, а затем — на кнопке Close, 
чтобы вернуться в главное окно Query Builder. 


    Рис. 11.12. Вид окна средства Query Builder cucтемы Delphi 

    Рис. 11.13. Диалоговое окно Expression средства Query Builder 


    Щелкните на кнопке View SQL панели инструментов Query Builder (на 
пиктограмме этой кнопки изображены очки). На экране вы увидите примерно такой 
оператор SQL, сгенерированный системой Query Builder. 

   SELECT    WORKDETAIL.WORDER_NUMBER , 
       ( sum( WORKTYPE.TASKDURATION ) ) as WorkDuration 
   FROM    WODETAIL WODETAIL, WORKTYPE WORKTYPE 
   WHERE    ( WODETAIL.WORK_TYPE_CODE = WORKTYPE.WORK_TYPE_CODE ) 
             AND 
       ( 
       ( WODETAIL.WORDER_NUMBER = :WORDER_NUMBER ) 
       ) 
   GROUP BY 
       WODETAIL.WORDER_NUMBER 

    В более простом и "причесанном" виде это оператор можно записать следующим образом: 

    SELECT SUM(WORKTYPE.TaskDuration) WorkDuration 
    FROM WODETAIL, WORKTYPE 
    where WODETAIL.WORK_TYPE_CODE = WORKTYPE.WORK_TYPE_CODE 
    and WORDER_NUMBER = :WORDERJMUMBER 

    Данный запрос подсчитывает общую продолжительность работ, указанных в 
наряде. Параметр WORDER_NUMBER передается запросу во время выполнения, поэтому 
запросу остается только выбрать строки, соответствующие указанному наряду. 
    Закройте окно SQL Statement и окно средства Query Builder. Новый запрос 
будет автоматически сохранен в свойстве SQL компонента quWorkDuration. 

    Подключение запроса к форме 

    Для того чтобы подготовить новый запрос к использованию вместе с формой, 
выполните следующие операции. 
    1. Щелкните дважды на свойстве Property нового компонента Query, 
quWorkDuration, и выберите в качестве типа данных параметра WORDER_NUMBER тип 
Integer. Щелкните на кнопке ОК, чтобы сохранить внесенные изменения. 

    Глава 11. Разработка форм 247 

    2. Щелкните дважды на компоненте Query, чтобы открыть окно редактора полей. 
    3. Щелкните правой кнопкой мыши на окне редактора полей и из появившегося 
меню выберите пункт Add fields. 
    4. Из списка полей выберите WorkDuration, а затем щелкните на кнопке ОК и 
закройте окно редактора полей. Таким образом, вы сможете обращаться к данным, 
возвращаемым запросом, по имени столбца. 

    Событие AfterPost 

    Закончив настройку компонента Query, можно подключить его к таблице 
WODETAIL. Щелкните на табличном объекте taWODETAlL, а затем откройте вкладку 
Events в окне Object Inspector. Щелкните дважды на значении обработчика 
события OnAfterPost и в открывшемся окне редактирования введите следующие операторы: 

    with quWorkDuration do begin 
       If Active then Close; 
       ParamByName('WORDER_NUMBER').As Integer:= 
          taWODETAILWORDER_NUMBER.As Integer; 
       Open ; 
       taWORDEREndDate.AsFloat:=taWORDERStartDate.AsFloat+ 
          quWorkDurationWorkDuration.As Float ; 
       Close; 
    end; 


    Операции с датами в Delphi 

    Как видно из приведенного выше программного фрагмента, операторы выполняют 
запрос, а затем складывают возвращаемый им результат со значением столбца 
StartDate таблицы WORDER, чтобы вычислить дату завершения работы. Этот подход 
основывается на двух соглашениях. Во-первых, в языке Object Pascal значения 
даты и времени хранятся в виде чисел с плавающей точкой. Целая часть такого 
числа равняется количеству дней, прошедших с 31 декабря 1899 года, а дробная — 
времени, прошедшему с начала текущего дня. Это позволяет складывать и вычитать 
переменные, представляющие дату, как обычные числа. Во-вторых, мы считаем, что 
в момент выполнения запроса на экране отображается нужная запись таблицы 
WORDER. Иными словами, при выборе наряда из сеточной формы текущей записью 
таблицы WORDER становится запись, к которой пользователь решил применить 
операцию редактирования. Эта запись остается текущей записью таблицы WORDER до 
тех пор, пока курсор не переместится на новую запись. Это означает, что не 
надо искать строку, которую нужно обновить, — эта строка является текущей. 

    Событие AfterDelete 

    Обратите внимание, что событие post — не единственное, после которого 
должно выполняться повторное вычисление поля WorkDuration, это также нужно 
сделать после удаления строки из таблицы WODETAIL. Поэтому выберите обработчик 
события OnAfterDelete в окне Object Inspector, щелкните на раскрывающемся 
списке его значений, а затем выберите из этого списка ссылку на обработчик 
taWODETAlLAfterPost. Теперь оба обработчика будут выполнять одни и те же действия. 

    Тестирование улучшенной формы 

    Что ж, давайте запустим приложение и посмотрим, как оно работает. Теперь 
при добавлении или удалении строк наряда, вы сможете увидеть, как изменяется 
дата его исполнения. Обратите также внимание на то, что форма заполнения 
нарядов автоматически обновляется при изменении столбца EndDate формы модуля 
данных dmRENTMAN. Кроме того, как легко убедиться, хотя поле EndDate при 
добавлении или удалении работ автоматически пересчитывается, вы по-прежнему 
можете ввести в него любое значение, как и до использования второго компонента Query. 

    Еще один штрих 

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

    248 Часть II. Учебный курс 


    Фильтры 

    Лучший способ осуществления этого заключается в использовании локального 
фильтра таблицы EMPLOYEE. Локальным я называю фильтр, который обеспечивается 
приложением, а не СУБД. Приложение будет выбирать из всех записей таблицы 
EMPLOYEE только те, которые можно отобразить в форме заполнения наряда. 

    Создание запроса, используемого фильтром 

    Для того чтобы создать компонент Query, используемый локальным фильтром, 
выполните следующие операции. 
    1. Если приложение RENTMAN все еще работает, закройте его и вернитесь в Delphi. 
    2. Откройте форму модуля данных dmRENTMAN в окне конструктора форм. 
    3. Поместите на форму компонент Query и измените имя объекта запроса на 
quEMPLOYEEAVAlLABLE, а в качестве значения свойства Database используйте dbRENTMAN. 
    4. Щелкните дважды на свойстве SQL компонента Query, а затем введите 
следующие операторы: 

    select max(EndDate) EndDate 
    from WORDER 
    where EMPLOYEE_NUMBER = :EMPLOYEE_NUMBER 
    and WORDER_NUMBER <> :WORDER_NUMBER 
    and STARTDATE <= "TODAY" 

    Запрос возвращает последнюю дату, когда указанный рабочий был занят по 
какому-нибудь открытому наряду. Если поле STARTDATE содержит дату, которая не 
превышает текущую, — рабочего можно назначать на выполнение наряда. Эта 
информация получается по последней дате из таблицы WORDER для каждого рабочего. 
    Для настройки запроса выполните следующие операции. 
    1. Введите запрос и щелкните на кнопке ОК, чтобы закрыть окно редактора 
операторов SQL. 
    2. Щелкните дважды на свойстве Params запроса. Установите тип данных обоих 
параметров равным Integer, а затем щелкните на кнопке ОК, чтобы сохранить 
внесенные изменения. 
    3. Щелкните правой кнопкой мыши на списке полей и из открывшегося меню 
выберите команду Add fields. В появившемся окне щелкните на кнопке ОК, чтобы 
добавить поле, возвращаемое запросом. 
    4. Настроив запрос, нужно подключить его к таблице EMPLOYEE. Закройте окно 
редактора полей, щелкните на компоненте taEMPLOYEE, а затем дважды щелкните на 
значении обработчика события OnFilterRecord в окне Object Inspector. Введите 
следующий программный фрагмент: 

    if Screen.ActiveForm.Name = 'fmRWORMDEO' then begin 
       with quEmployeeAvailable do begin 
          If Active then Close; 
          ParamByName('EMPLOYEE_MUMBER').Aslnteger:= 
             taEMPLOYEEEMPLOYEE_NUMBER.AsInteger; 
          ParamByName('WORDER_NUMBER').Aslnteger:= 
             taWORDERWORDERJMUMBER.As Integer; 
         Open; 
         Accept := 
            (Trunc(quEmployeeAvailableEndDate.AsFloat)<=Date) ; 
       end; 
    end else Accept :=True; 

    Обратите внимание, что в данном фрагменте для определения того, является 
ли текущей форма WORDER формой вида "главная—подчиненная", используется 
свойство Name объекта ActiveForm. Если это не так, фильтрация не выполняется. 
Как вы увидите немного ниже, эта предосторожность является излишней, так как 
сейчас мы создадим механизм, обеспечивающий фильтрацию таблицы EMPLOYEE только 
тогда, когда это необходимо. 

    Глава 11. Разработка форм 249 


    СВОЙСТВО Filtered 

    После ввода исходного текста обработчика события OnFilterRecord табличного 
объекта taEMPLOYEE перейдите в окно конструктора форм, откройте форму 
fmRWORMDEO и дважды щелкните на значении ее обработчика события OnShow в окне 
Object Inspector. После оператора inherited; введите следующий оператор. 

    dmRENTMAN.taEMPLOYEE.Filtered:=True; 

    Вернитесь в окно Object Inspector и дважды щелкните на значении 
обработчика события OnClose. Введите в исходный текст обработчика следующий оператор: 

    dmRENTMAN.taEMPLOYEE.Filtered:=False; 

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

    Еще одно тестирование 

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

    Отключение фильтра 

    Несмотря на то, что в общем случае назначение одного рабочего на два 
наряда является не самым лучшим решением, могут быть ситуации, в которых 
компании Allodium может понадобиться такой подход. Например, это нужно в тех 
случаях, когда рабочий утром быстро выполняет несложную работу и в течении дня 
его можно снова назначить на другой наряд. Без возможности в определенных 
ситуациях отключать фильтрацию, служащему, заполняющему формы нарядов, 
придется при новом назначении рабочего возвращаться к данным старого наряда и 
каким-то образом отмечать, что работа выполнена. Поэтому давайте разработаем 
способ, с помощью которого пользователь мог бы при необходимости отключать 
механизм фильтрации. 
    Лучший способ, до которого я додумался, состоит в использовании флажка, 
включающего и выключающего фильтрацию. Конечно, можно применить и более 
изящные методы решения этой задачи, но данный способ является лучшим с точки 
зрения эффективности и простоты. Итак, для того чтобы создать флажок для формы 
fmRWORMDEO, выполните следующие операции. 
    1. Переместите компоненты dlcbEMPLOYEE_NAME и dlcbPROPERTY_ADDRESS как 
можно левее. 
    2. Поместите на форму компонент CheckBox справа от компонента 
dlcbEMPLOYEE_NAME и удалите его заголовок. 
    3. Назовите новый объект ckFilterAvailable. 
    4. Установите размеры компонента такими, чтобы был виден только сам 
прямоугольник флажка — название флажка будет создано с помощью метки. 
    5. Щелкните дважды на свойстве Checked компонента, чтобы установить флажок. 
    6. Поместите на форму компонент Label, разместив его сверху или справа 
флажка. Установите свойство AutoSize метки равным False, свойство Wordwrap — 
True, а заголовок — "Filter Employees?". 
    7. Щелкните дважды на флажке и в окне редактирования исходного текста 
откроется заготовка обработчика события OnClick. Введите следующий оператор: 

    dmRENTMAN.taEMPLOYEE.Filtered:=ckFilterAvailable.Checked; 

    Это обеспечит установку и сброс свойства Filtered табличного объекта 
taEMPLOYEE в соответствии с текущим состоянием флажка. 
    Теперь давайте точно так же настроим метку. Щелкните на компоненте Label, 
который вы разместили справа от флажка. Перейдите в окно Object Inspector, 
щелкните на кнопке раскрытия списка значений события OnClick и выберите из 
этого списка событие ckFilterAvailableClick. Это приведет к тому, что щелчок 
на метке будет иметь тот же эффект, что и щелчок на флажке. Обычно в такой 
настройке нет    

    250 Часть II. Учебный курс 

необходимости, так как при щелчке на заголовке флажка первый работает точно 
так же, как и сам флажок. Однако, поскольку встроенный заголовок компонента 
checkBox не поддерживает переноса слов, мы воспользовались меткой. В связи с 
этим и пришлось настраивать метку, чтобы она вела себя как заголовок флажка. 
    Альтернативный метод состоит в том, чтобы, удалив заголовок флажка, 
использовать не метку, а всплывающую подсказку. Когда пользователь будет 
помещать указатель мыши на флажок, через несколько мгновений на экране 
появится короткое описание назначения этого флажка. Подробнее вопрос создания 
всплывающих подсказок изложен в главе 13. 
    Последнее, что нужно сделать с этой формой, — перекрыть ее обработчик 
события OnShow таким образом, чтобы фильтрация выполнялась только при 
установке флажка. Теперь полагаться на свойство Filtered табличного объекта 
taEMPLOYEE нельзя: пользователь, работая с формой fmRWORMDEO, в любой момент 
может присвоить ему значение False. Поэтому щелкните на форме (или выберите ее 
в списке окна Object Inspector), а затем дважды щелкните на значении ее 
обработчика события OnShow. Замените оператор 

    dmRENTMAN.taEMPLOYEE.Filtered:=True; 

    следующим программным фрагментом: 

    with dmRENTMAN.taEMPLOYEE do begin 
        Filtered:=ckFilterAvailable.Checked; 
        If Filtered then begin 
          Close; 
          Open; 
       end; 
    end; 

    Эти операторы обеспечат учет состояния флажка путем присвоения 
соответствующего значения свойству Filtered таблицы taEMPLOYEE. Обратите 
внимание на то, что для обеспечения правильного применения фильтра таблица 
закрывается и снова открывается. 
    На этом работа над формой WORDER вида "главная—подчиненная", fmRWORMDEO, 
закончена. Нам осталось создать лишь форму CALL. 

    Форма CALL 

    Подобно таблице WORDER, таблица CALL обслуживается двумя формами — 
сеточной и формой ввода и редактирования данных. Первая форма будет 
использоваться для выбора нужной записи, а вторая — для редактирования этой записи. 

    Сеточная форма 

    Выберите из меню команду File=>New, а затем на вкладке Forms открывшегося 
окна New Items выберите элемент fmGridForm, после чего щелкните на 
переключателе Inherit и на кнопке ОК. Измените имя объекта новой формы на 
fmRCALGRDO. Сохраните форму в файле RCALGRDO.РАS. 

    Получение доступа к модулю данных 

    Как и при создании других форм, теперь нужно подключить новую форму к 
модулю данных системы RENTMAN, чтобы можно было воспользоваться объектами 
Table и DataSource, представляющими таблицу CALL. Выберите команду меню 
File=>Use Unit и в открывшемся окне выберите из списка элемент RENTDATA. Затем 
выберите компоненты DBGrid и DBNavigator, перейдите в окно Object Inspector и 
измените их свойства DataSource, присвоив им в качестве значения ссылку на 
объект dmRENTMAN.dsCALL. Таким образом, новая форма будет использоваться для 
редактирования таблицы CALL. 

    Поле выборки 

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


    Глава 11. Разработка форм 251 

    1. Откройте форму модуля данных dmRENTMAN в окне конструктора форм Delphi, 
дважды щелкните на наборе данных taCALL, чтобы открыть окно редактора полей. 
    2. Щелкните правой кнопкой мыши на окне редактора полей и выберите из 
открывшегося меню пункт New field.                                             
    3. В строке Name введите имя PROPERTY_ADDRESS, выберите переключатель типа 
поля String и установите размер поля равным 30. 
    4. Щелкните на переключателе Lookup, а затем настройте элемент Key Fields 
на PROPERTY_NUMBER. Выберите объект taPROPERTY в качестве набора данных поля 
выборки, а свойству Lookup Keys присвойте значение PROPERTY_NUMBER. В 
качестве значения свойства Result Field присвойте ссылку на поле ADDRESS. 
    5. Щелкните на кнопке ОК для закрытия диалогового окна. 
    Добавление поля выборки PROPERTY_ADDRESS к табличному объекту taCALL 
позволит по внутреннему номеру объекта недвижимости отображать его адрес. 
Благодаря имеющейся в компонентах Delphi поддержке полей выборки, вы можете 
пользоваться всеми преимуществами, предоставляемыми полями этого типа в 
компонентах, подобных DBLookupComboBox. 

    Назначение столбцу используемого по умолчанию значения 

    Теперь, создав поле выборки, давайте определим значения, которые будут 
присваиваться по умолчанию двум другим полям таблицы CALL. В данном случае, 
когда у нас имеются поля даты CALLDATE и времени CALLTIME, можно в качестве 
значений, используемых по умолчанию, присваивать им, соответственно, дату и 
время звонка, регистрируемого с помощью разрабатываемой формы. Щелкните на 
компоненте taCALL, затем дважды щелкните на значении обработчика события 
OnNewRecord в окне Object Inspector. Введите следующие два оператора, 
присваивающие данным полям при добавлении новой строки в таблицу CALL 
используемые по умолчанию значения. 

    taCALLDate.Value:=Date; 
    taCALLTime.Value:=TimeToStr(Time) ; 

    Настройка столбцов сетки данных 

    Для того чтобы настроить столбцы сетки данных, выполните следующие операции. 
    1. Переключитесь на форму fmRCALGRDO и щелкните на компоненте DBGrid. 
    2. Щелкните дважды на его свойстве Columns и в появившемся окне редактора 
столбцов щелкните на кнопке Add All Fields. 
    3. Удалите поле PROPERTY_NUMBER и перетащите поле PROPERTY_ADDRESS, 
разместив его под полем DESCRIPTION. 
    4. Установите свойство Read-only поля CALL_NUMBER и закройте окно 
редактора столбцов. 

    Преобразование сетки данных в средство выбора строк 

    Следующее, что надо сделать — настроить форму так, чтобы с ее помощью 
можно было выбирать строку, которая будет редактироваться другой формой. 
Лучший способ решения этой задачи состоит в присвоении свойству Readonly 
компонента DBGrid значения True. Это приведет к тому, что с помощью сетки 
данных можно будет только просматривать информацию, что нам, собственно, и нужно. 
    Кроме предотвращения редактирования, компонент DBGrid должен обеспечить 
возможность выбора текущей строки, например с помощью двойного щелчка — 
метода, широко распространенного в приложениях для Windows. Для этого 
присвойте в качестве значения обработчику события OnDbldick компонента DBGrid 
ссылку на обработчик события btEditClick. Немного ниже мы изменим поведение 
компонента DBNavigator, после чего двойной щелчок на строке сетки данных будет 
приводить к открытию второй формы, предназначенной для редактирования 
выбранной строки. 

    Максимизация формы 

    Присвойте свойству WindowState формы значение wsMaximized, а затем 
максимизируйте форму. Измените размеры компонента раТор таким образом, чтобы 
панель paMiddle не занимала слишком много места, а затем восстановите исходные 
размеры формы. 


    252 Часть II. Учебный курс 

    Доработка навигатора 

    Поскольку данная форма является только средством выбора строки, подлежащей 
редактированию, единственный механизм, который в ней надо настроить, — вызов 
другой формы. В нашем случае мы перекроем используемый по умолчанию обработчик 
события OnClick навигатора и заставим его вызывать вторую форму. Щелкните 
дважды на компоненте DBNavigator и введите следующие операторы в исходном 
тексте обработчика события OnClick навигатора: 

    With DBNavigatorl do 
       case Button of 
       nblnsert : begin 
          DataSource.DataSet.Insert; 
         //    fmRCALEDTO.Show; 
      end; 
      nbEdit : begin 
         DataSource.DataSet.Edit; 
         //    fmRCALEDTO.Show; 
      end; 
    end; 

    Поскольку обработчик события OnClick компонента btEdit вызывает обработчик 
события OnClick навигатора, а компонент DBGrid при щелчке на строке сетки — 
обработчик события OnClick компонента btEdit, то перекрытие обработчика 
события щелчка на кнопках навигатора скажется на работе всех вышеупомянутых 
компонентов. На каком бы из этих компонентов ни щелкнул пользователь — в любом 
случае произойдет открытые формы редактирования текущей строки. Обратите 
внимание, что в данный момент перед операторами, в которых вызывается метод 
Show второй формы, стоят символы комментария. Это объясняется тем, что пока 
такой формы не существует. Как только мы создадим ее, символы комментария 
можно будет убрать. 

    Подключение формы к приложению 

    Прежде чем переходить ко второй форме, давайте проверим, как работает 
сеточная форма. Откройте в окне конструктора форм Delphi главную форму 
приложения, fmRSYSMANO, и щелкните дважды на пункте Calls подменю Tables этой 
формы. В открывшемся окне введите следующий оператор. 

    fmRCALGRDO.Show; 

    Настроив работу меню, присвойте в качестве значения обработчика события 
OnClick объекта sbCalls главной формы ссылку на обработчик Calls1Click, чтобы 
оба компонента работали идентично. Закончив эту работу, добавьте программный 
модуль RCALGRDO в оператор uses программного модуля RSYSMANO. Теперь можете 
нажать <F9> и запустить приложение. Проверив, как работает новая форма, 
закройте приложение и вернитесь в Delphi. 
    На рис. 11.14 показан возможный вид новой формы. 

    Рис. 11.14. Завершенная сеточная форма CALL 


    Глава 11. Разработка форм 253 

    Форма редактирования 

    Итак, покончив с "примитивной" сеточной формой, можно перейти ко второй, 
более "интересной" из двух форм таблицы CALL, — форме редактирования. Форма 
редактирования таблицы CALL позволяет вводить и редактировать информацию по 
какому-то отдельному звонку. Для таблиц с большим количеством столбцов, а 
также таблиц, данные которых изменяются по многу раз в день, лучше всего 
применять подход "одна строка таблицы на форму". Сетки хороши для поиска 
информации в таблице, однако для ввода данных лучше применять специально 
созданные формы, что ускоряет работу по вводу информации. 
    В нашем примере с помощью новой формы в таблицу CALL будут заноситься 
данные, поступающие от потенциальных и настоящих арендаторов. В связи с этим 
оператору, отвечающему на звонки, нужно, чтобы компьютер как можно меньше 
влиял на его работу. Данные должны заноситься в таблицу в том темпе, в котором 
они поступают от абонента. Именно поэтому нам нужно две формы — одна для того, 
чтобы быстро найти нужные данные во всей таблице CALL, а вторая — чтобы 
занести информацию в соответствующую строку таблицы. 
    Создайте новую форму, являющуюся потомком класса fmEditForm. Присвойте имя 
новому объекту формы fmRCALEDTO, а в качестве заголовка укажите Call Edit Form. 

    Предотвращение изменений размеров формы 

    Для того чтобы предотвратить случайные изменения размеров формы, 
установите ее свойство BorderStyle равным одному из значений, используемых для 
форм постоянного размера, например bsDialog. Чтобы не вводить пользователя в 
заблуждение, присвойте также ее свойству Borderlcons значение biSystemMenu 
(установите biMaximize и biMinimaze равными False). 
    Как и для других форм, нужно обеспечить доступ новой формы к форме модуля 
данных dmRENTMAN. Выберите команду меню File=>Use Unit и в открывшемся окне 
выберите из списка элемент RENTDATA. Связав форму с формой модуля данных, 
сохраните программный модуль новой формы на диске в файле с именем CALEDTO.РАS. 

    Размещение компонентов на форме 

    Поместите на панель раТор новой формы три компонента DBEdit, один 
компонент DBLookupComboBox и семь компонентов DBText. Рядом с каждым 
компонентом разместите по одному компоненту Label. Выберите компонент 
DBNavigator, самый верхний компонент DBText и все компоненты DBEdit и в 
качестве значения их свойств DataSource укажите ссылку на набор данных 
dmRENTMAN.dsCALL. Все эти компоненты будут получать данные из таблицы CALL. 
Свойству DataSource остальных компонентов DBText присвойте в качестве значения 
ссылку на набор данных dmRENTMAN.dsTenantinfо. Эти компоненты будут 
отображать информацию, включающую сведения об арендаторе и его адрес, 
возвращаемую запросом quTenantInfo, который мы создали в предыдущих разделах. 
Здесь наглядно демонстрируется преимущество размещения компонента quTenantInfo 
на форме dmRENTMAN — вы можете использовать его в любой форме системы, к 
которой подключен модуль данных. 
    Проверьте еще раз, не забыли ли вы создать с помощью компонентов TLabel 
метки для всех элементов управления формы. Как и в форме редактирования 
нарядов, установите атрибут Bold свойства Font всех меток, чтобы их легче было 
отличить от данных. 
    Разместите компоненты таким образом, чтобы компонент DBText, который вы 
подключили к источнику данных dsCALL, находился в левом верхнем углу панели, а 
все остальные компоненты располагались примерно так, как показано на рис. 11.15. 
    Настройте значения остальных свойств компонентов в соответствии с табл. 11.6. 

    Таблица 11.6. Компоненты верхней панели формы редактирования и их свойства 

    Компонент		Name			DataField		Метка

    DBText			CALL_NUMBER		CALL_NUMBER		Call No. 
    DBEdit			CallDate		CALLDATE		Date 
    DBEdit			CallTime		CALLTIME		Time 
    DBLookupComboBox	PROPERTY_ADDRESS	PROPERTY_ADDRESS	Property 
    

    254 Часть II. Учебный курс 

    Окончание табл. 11.6 

    Компонент 	Name 		DataField		Метка 

    DBEdit		DESCRIPTION DESCRIPTION		Description 
    DBText		City 		CITY			City 
    DBText 		State 	STATE			State 
    DBText 		Zip 		ZIP			Zip 
    DBText	 	Addition 	ADDITION		Addition 
    DBText 		TenantName 	NAME			Tenant 
    DBText 		HomePhone 	HOMEPHONE		HomePhone 
    

    Рис. 11.15. Предварительный вид формы редактирования CALL 


    Подключение запроса 

    После размещения компонентов нужно решить, какое событие будет приводить к 
срабатыванию запроса quTenantInfo. Так же как и в случае формы редактирования 
наряда, имеются два таких события. Первое — когда форма появляется на экране. 
Если пользователь выбрал какую-то строку таблицы звонков и решил ввести или 
изменить данные, то перед открытием формы редактирования нужно выполнить 
запрос, чтобы на форме находилась вся соответствующая данной строке 
информация. Второе событие — выбор элемента списка, представленного 
компонентом DBLookupComboBox. Когда пользователь щелкает на строке этого 
списка, в котором помещены адреса объектов недвижимости, должен выполняться 
запрос, обновляющий отображаемые данные в соответствии с выбранным объектом 
недвижимости. Если объект недвижимости изменяется, то соответствующим образом 
должна также измениться информация об арендаторе и адресе. 
    Для того чтобы подключить запрос к форме, выберите объект формы (можете 
воспользоваться списком объектов в окне Object Inspector), а затем дважды 
щелкните на значении обработчика события OnShow формы в окне Object Inspector. 
Введите в окне редактирования исходного текста следующие операторы. 

    with dmRENTMAN.quTenantlnfo do begin 
       If Active then Close; 
       ParamByName('PROPERTY_NUMBER').Aslnteger:= 
         dmRENTMAN.taCALLPROPERTY_NUMBER.AsInteger ; 
       Open; 
    end; 

    Эти операторы закрывают открытый запрос quTenantlnfo, а затем повторно 
открывают его. Одновременно обновляются значения всех компонентов, для которых 
этот запрос является источником данных. 

    Глава 11. Разработка форм 255 

    Создав обработчик события OnShow формы, можно присвоить ссылку на него в 
качестве значения обработчика события OnClick компонента DBLookupComboBox. 
Нажмите <F12> для того, чтобы перейти в окно конструктора форм, а затем 
щелкните на компоненте dlcbPROPERTY_ADDRESS. Откройте вкладку Events окна 
Object Inspector, щелкните на списке значений обработчика события OnClick и 
выберите из этого списка ссылку на обработчик события FormShow. Таким образом, 
как при открытии формы, так и при выборе пользователем значения из списка 
адресов будет выполняться запрос. 

    Подключение формы редактирования CALL к формам fmRCALGRDO 
    и  fmRSYSMANO 

    Закончив работу над формой редактирования CALL, нужно внести имя ее 
программного модуля в оператор uses сеточной формы CALL, так как в последней 
имеются операторы, ссылающиеся на данную форму. Для этого выполните следующие операции. 
    1. Откройте форму fmRCALGRDO в окне конструктора форм. 
    2. Выберите команду File=>Use Unit и дважды щелкните на элементе RCALGRDO 
открывшегося списка. 
    Теперь нужно связать форму редактирования CALL с пунктом меню Log a Call 
главной формы. Для этого выполните следующие операции. 
    1. Откройте форму fmRSYSMANO в окне конструктора форм. 
    2. Щелкните на пункте меню File=>Use Unit и с помощью открывшегося окна 
добавьте форму fmRCALEDTO к оператору uses главной формы. 
    3. Щелкните дважды на пункте меню Log a Call и введите следующие операторы 
в исходном тексте обработчика события OnClick: 

    dmRENTMAN.taCALL.Insert; 
    fmRCALEDTO.Show; 

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

    Подключение формы редактирования CALL к форме WORDER вида "главная-подчиненная" 

    Для того чтобы оператору компании не приходилось, получив заявку от 
арендатора на выполнение работ, возвращаться в главное меню приложения 
RENTMAN, нужно предоставить ему возможность открывать форму подготовки наряда 
на работы прямо из формы редактирования CALL. 
    Не закрывая формы редактирования CALL в окне конструктора форм, выберите 
команду меню File=>Use Unit и в открывшемся списке щелкните дважды на элементе 
RWORMDEO, чтобы добавить соответствующий программный модуль в оператор uses 
программного модуля формы. Затем щелкните на панели paMiddle, поместите на нее 
компонент Button, расположив его справа от кнопки Delete. Измените имя объекта 
новой кнопки на btGenerateWorkOrder, а в качестве заголовка укажите &Generate 
Work Order. Измените размеры кнопки так, чтобы заголовок полностью помещался 
на ней. 
    Теперь дважды щелкните на новой кнопке и в открывшемся окне редактора 
введите следующий программный фрагмент: 

    With dmRENTMAN do begin 
       If (taCALLWORDER_NUMBER.AsInteger=0) then begin 
          taWORDER.Insert; 
          taWORDER.Refresh; //Нужен из-за хранения генераторов на сервере. 
          taWORDER.Edit; 
          try 
             taCALLWORDER_NUMBER.Value:= 
               taWORDERWORDER_NUMBER.Value ; 
            except 
            On Exception do begin 
               taCALL.Edit; 
               taCALLWORDER_NUMBER.Value:= 
                 taWORDERWORDER_NUMBER.Value ; 
          end; 
       end; 
    
   
 256 Часть II. Учебный курс 

       taWORDERPROPERTY_NUMBER.Value:= 
            taCALLPROPERTY_NUMBER.Value ; 
       end else begin 
            If not taWORDER.FindKey([taCALLWORDERJMUMBER.Value]) then 
                  raise Exception.Create('Work Order has been deleted'); 
       end; 
    end; 
    fmRWORMDEO.Show; 


    FindKey 

    Приведенный выше фрагмент выполняет несколько важных задач. Во-первых, с 
помощью проверки значения WORDER_NUMBER он выясняет, редактируется ли 
имеющаяся запись таблицы CALL или будут вводиться данные новой записи этой 
таблицы. Во-вторых, в случае редактирования приведенные в данном фрагменте 
операторы с помощью метода FindKey компонента taWORDER прокручивают набор 
данных таким образом, чтобы текущей стала нужная запись таблицы нарядов. 
Наконец, обратите внимание на проверку результата, возвращаемого методом 
FindKey, — если он неудачен, генерируется исключительная ситуация. Хотя это 
вряд ли когда-нибудь произойдет, так как на связь между таблицами CALL и 
WORDER наложено ограничение, требующее соблюдения ссылочной целостности, 
однако, если это случится, вместо формы заполнения наряда пользователь увидит 
сообщение о том, что наряд с нужным номером удален. После появления такого 
сообщения управление будет возвращено форме CALL. Именно такой реакции системы 
и ожидают пользователи. 
    Вы, должно быть, обратили внимание на блок try-end, в который заключен 
оператор присвоения значения столбцу WORDER_NUMBER таблицы taCALL. Причиной 
тому неопределенность состояния табличного объекта taCALL в момент щелчка на 
кнопке Generate Work Order. В определенных ситуациях наборы данных могут не 
выполнять операции присвоения элементу набора какого-либо значения. Например, 
если табличный объект taCALL сохранил на диске все внесенные изменения и 
перешел в состояние просмотра (dsBrowse), то при попытке присвоить ему 
значение будет сгенерирована исключительная ситуация. Обычно, чтобы избежать 
этого, проверяются значения свойства State на предмет взведения флагов 
dsInactive-dsCurVal. К сожалению, в нашем случае нельзя воспользоваться этими 
флагами, так как они объявлены в программном модуле, который не упоминается в 
операторе uses формы fmRCALEDTO. Конечно же, можно добавить этот модуль в 
оператор uses, но мне больше по душе обработка исключительной ситуации, 

    Динамическое изменение заголовка кнопки 

    И в завершение, хочу предложить небольшое дополнение, которое украсит нашу 
форму. Давайте создадим механизм динамического изменения заголовка кнопки 
btGenerateWorkOrder. Нужно сделать так, чтобы по заголовку можно было судить, 
приведет ли щелчок на кнопке к редактированию имеющегося наряда или к 
подготовке нового. Лучшим местом для этого является обработчик события OnShow 
формы редактирования CALL. Выберите форму и дважды щелкните на значении 
обработчика события OnShow в окне Object Inspector. В окне редактирования 
исходного текста добавьте приведенные ниже операторы к уже имеющимся 
операторам обработчика события OnShow формы. 

    If (dmRENTMAN.taCALLWORDER_NUMBER.AsInteger=0) then 
        btGenerateWorkOrder.Caption :='&Generate Work Order' 
    else 
       btGenerateWorkOrder.Caption :='Edit SWork Order'; 


    На заметку 
    Помните, что все операторы, добавляемые к обработчику события OnShow этой 
формы, выполняются также и при щелчке на компоненте dlcbPROPERTY_ADDRESS, так 
как в качестве значения обработчика события OnClick последнего мы указали 
ссылку на метод FormShow формы. В данном случае в этом нет ничего опасного. 
Однако при использовании такой техники, когда один и тот же метод вызывается 
разными объектами, особенно объектами разных классов, нужно проявлять 
определенную осторожность. 


    Глава 11. Разработка форм 257 
    9   Delphi 3 и системы клиент/сервер 

    Тестирование формы редактирования CALL 

    Сохраните проект и запустите приложение RENTMAN. На рис. 11.6 показан 
примерный вид только что созданной формы во время работы приложения. 

    Рис. 11.16. Окончательный вид формы редактирования CALL во время работы приложения 


    Резюме 

    В этой главе мы создали формы для работы с пятью таблицами базы данных 
RENTMAN: по одной форме для таблиц TENANT, PROPERTY и LEASE, а также по две 
формы для таблиц WORDER и CALL. Теперь вы знаете все тонкости процесса 
разработки форм, так как каждая из них имела свои нюансы и особенности. После 
создания этих форм вы получили хорошие навыки, которые наверняка пригодятся 
вам при работе над собственными проектами. 

    Что дальше... 

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

    258 Часть II. Учебный курc