В этой статье :
1. Запуск формы
2. Именование форм
3. Последовательность событий в форме
4. Сессии данных
5. Модальность и немодальность
6. Передача параметров между формами и методами
7. Делегирование методов
8. Закрытие формы
9. Горячие клавиши на формах
В большинстве случаев основным звеном программы является форма. Именно посредством форм пользователь оперирует данными – вводит и модифицирует их. По большому счёту, форма – вот и всё что видит пользователь проводимое за работой с программой время. Поэтому создатель программы должен уделять формам достойное внимание. Рассмотрим поподробнее работу с формами, свойства, методы и события форм, а также связанные с этим неочевидные подробности. Итак, приступим.
Обычно запуск форм осуществляется посредством тривиальной команды DO FORM <имя_формы> из кода программы или же в командном окне среды разработки VFP, а также путём выбора нужной формы в менеджере проектов на закладке Documents и нажатием кнопки Run (или же из меню среды разработки).
Вот тут и может возникнуть одна “тонкость”. Конечно, форма в самом простом случае может быть независимой от других объектов создаваемого приложения или иных условий. Но это только в самом простом случае. Зачастую ситуация складывается совсем по другому - форма запускается из меню или из другой формы. Формы должны уметь “разговаривать” друг с другом, обмениваться информацией. В общем случае запуск формы ставиться в зависимость от ряда условий, параметров или от других форм. Вот тут и приходит на помощь метод известный как принцип свободных связей. Конечно, это не есть панацея и не является обязательным для применения во всех случаях, но это стоит взять на вооружение как хорошую технику программирования. Заключается он в том, что форма должна иметь возможность запуска не только в конкретном месте с конкретными условиями, но и сама по себе (хотя бы в случае тестирования) вне зависимости от требований, налагаемых приложением и уметь функционировать в чрезвычайных ситуациях. Как пример рассмотрим возможную организацию вашего приложения:
- В приложении имеется некоторый “менеджер” форм, который отвечает за запуск форм, их состоянием, доступностью, закрытием и т.п.
- Этот менеджер имеет некий метод DoForm() для запуска и установки связей между формами и самим приложением
- Менеджер всегда устанавливает ссылочную связь для того, чтобы легко можно было обмениваться данными или сообщениями между формами и самим менеджером посредством передаваемых параметров.
Для того чтобы осуществить такой замысел, как вы понимаете стоит завести класс формы с методом DoForm(), который будет передавать имя формы и другие необходимые параметры менеджеру форм, сообщать ему, какую форму требуется запустить. Вполне допустимо передавать не только имя формы , но и саму ссылку на форму, для этого можно смело использовать слово THISFORM.
* Init() Method LPARAMETERS toFormManager IF toFormManager) = ""O"" ThisForm.oFormManger = toFormManager ENDIF * DoForm() Method LPARAMETERS tcFormName, tuParm1, tuParm2 IF VARTYPE(ThisForm.oFormManager) = ""O"" ; AND NOT ISNULL(ThisForm.oFormManager) ThisForm.oFormManger.DoForm(tcFormName,tuParm1,tuParm2) ELSE DO CASE CASE PCOUNT() = 1 DO FORM (tcFormName) WITH .NULL. CASE PCOUNT() = 2 DO FORM (tcFormName) WITH .NULL., tuParm1 CASE PCOUNT() = 3 DO FORM (tcFormName)WITH .NULL.,tuParm1,tuParm2 ENDCASE ENDIF |
Вся изюминка тут заключается в том, что форма может запускаться, используя этот менеджер форм, но в тоже время способна работать и без него. Этот пример показателен тем, как можно строить универсальные, на предмет зависимости от внешних условий , формы. Разработчик должен достаточно серьёзно относиться к этому вопросу хотя бы потому, что обычно на этапе разработки формы делаются в некой очередности, и не всегда до конца, но любая из них нуждается в независимом от других форм тестировании, которые могут к данному моменту или не существовать, или быть весьма “сыры”.
2. Именование форм.
По умолчанию запущенная форма принимает имя файла с расширением .scx в котором она собственно хранится. Однако внутри приложения, если не принять специальных мер, она известна по этому имени только в коллекции форм переменной _SCREEN. Но это имя можно изменить, явно указав имя командой при запуске формы.
DO FORM MyForm NAME frmMyForm |
причём выполнив команду
DO FORM MyForm NAME frmMyForm2 |
получим второй экземпляр формы, но с другим именем.
Необходимо обратить внимание на ещё один аспект команды DO FORM – выполнение её с ключевым словом LINKED:
DO FORM MyForm NAME frmMyForm2 LINKED |
В этом случае форма MyForm ассоциируется с переменной frmMyForm2, и теперь обращаться к свойствам и методам формы можно посредством этой переменной:
frmMyForm2.Caption =’Это заголовок формы’
|
Особенностью такого запуска является то, что форма MyForm будет закрыта при освобождении переменной frmMyForm2. Часто такую переменную определяют как public еще до запуска формы. При этом, если из формы вышли обычным способом, для ясности нужно или очистить переменную или также освободить ее – выполнить RELEASE frmMyForm2.
Если переменная локальна, то закрытие формы гарантируется после окончания выполнения метода или процедуры , в котором создавалась переменная. Следует однако заметить, что форма (равно как и любой объект) не может быть закрыта пока не окончено выполнение какого либо её метода или же имеется ссылка на неё в каком то из объектов приложения. Определения public или local сразу для всех вызванных позднее форм позволяют не следить, какая форма вызвана первой, а какая после нее, и тем самым облегчают организацию обмена между формами.
Рассмотрим так же ситуацию, когда форма не модальна и запущена без ассоциаций из метода (или события) какой либо формы:
DO FORM MyForm NAME MyStartedForm |
Переопределим имя вновь запущенной формы в пределах этого метода (поскольку переменная MyStartedForm будет освобождена после исполнения метода и станет недоступной)
MyStartedForm.Name="MyFormWithNewName"
|
Теперь, для того чтобы получить доступ к этой запущенной форме обратимся к коллекции форм объекта _Screen и выполним поиск:
LOCAL lnFormCount, lnFindForm lnFindForm=0 FOR lnFormCount = 1 TO _SCREEN.FORMCOUNT IF _SCREEN.FORMS(lnFormCount).NAME = "MyFormWithNewName" lnFindForm= lnFormCount EXIT ENDIF ENDFOR |
Если форма найдена и lnFindForm>0 , можно обращаться к форме через элемент коллекции _Screen :
_SCREEN.FORMS(lnFindForm).Caption=”Та самая форма” _SCREEN.FORMS(lnFindForm).SomeMethod() |
Эти операции по поиску формы в коллекции через её имя можно выделить в отдельную процедуру или метод объекта приложения (если таковой имеется) в собственном наборе классов или же в уже упомянутом менеджере форм. Стоит учесть, что поиск формы необходимо производить непосредственно перед обращением к ней, поскольку индекс формы в коллекции динамически изменяется при запуске или закрытии форм в приложении.
Иногда бывают ситуации, когда нужно обратиться к форме в отрыве от её контекста, например в методе глобального объекта приложения или при обращении из кнопки тулбара, когда обращение THISFORM не может быть применено. В этом случае также можно обратиться к форме через коллекцию _Screen :
_Screen.ActiveForm.Caption=”Это и есть активная форма” _Screen.ActiveForm.SomeMethod() |
Раз уж упомянут тулбар, ясно , что в нем могут быть разные клавиши с разной функциональностью - сохранить, отменить, новая запись, найти и другие. Все эти клавиши аналогичным образом могут вызывать методы активной в данное время формы. Однако, далеко не все формы являются однотипными, какие то могут быть только формами поиска без возможности ввода, другие напротив, только вводить информацию. Поэтому во избежании ошибочной ситуации кнопки тулбара должны уметь проверить, а есть ли на активной в данное время форме нужный им для работы метод, скажем так –
If type( ‘_Screen.ActiveForm’)=’O’ and pemstatus(_Screen.ActiveForm,’DoSomthing’,5) _Screen.ActiveForm.DoSomething endif |
В форме, требующей параметров, в методе Init() производят их описание командой LPARAMETERS.
Именно туда попадают параметры , переданные при запуски формы с опцией WITH. Запуск такой формы выглядит следующим образом :
* метод Init() формы MyForm LPARAMETERS lcNewCaption If !empty(lcNewCaption) Thisform.Caption= lcNewCaption Endif * запуск формы DO FORM MyForm WITH “Новый заголовок формы” |
Иногда нужно запустить форму, но до наступления определённых условий она должна быть скрыта.Например, такое требуется при запуске модальной формы, когда сразу после ее вызова, но до реального появления на экране, нужно вызвать некоторые методы этой формы или сразу изменить значения каких либо свойств. Это позволяет одну и ту же форму сделать более гибкой , работающей и в режиме модальности и способной быть немодальной, а следовательно и избежать изготовления большего числа близких по виду форм. Используйте для этого опцию NOSHOW
DO FORM MyForm NOSHOW |
При запуске формы из меню бывает необходимо исключить повторный её запуск. Для этого можно применить такой трюк – если в опциях пункта меню в раделе Skip For написать WEXIST ('имя_формы'), то при запуске формы пункт меню станет недоступным до её закрытия. Таким образом исключается повторный запуск желаемой формы. Правда стоит учитывать то, что если в процессе выполнения программы имя формы измениться, пункт восстановит свою доступность. Хотя мне кажется, что таких трюков стоит избегать, если хочется иметь по-настоящему мульти документный интерфейс.
3. Последовательность событий в форме.
Форма в чистом виде, без элементов управления и данных, которыми она должна манипулировать, интереса не представляет. Поэтому любая форма создаётся именно с целью оперирования данными с помощью элементов управления, включаемых в форму. Данные в VFP хранятся, естественно, в таблицах и подключаются к форме с помощью специального объекта Data environment. Рассмотрим последовательность событий при запуске формы, но останавливая внимание пока только на событиях самой формы.
Объект - Событие
Data environment - BeforeOpenTables
Form set - Load
Form - Load
Data environment cursor(s) - Init
Data environment - Init
Objects - Init
Form - Init
Form set - Init
Form set - Activate
Form - Activate
Object - When
Form - GotFocus
Object - GotFocus
Form - QueryUnload
Form - Destroy
Object - Destroy
Form - Unload
Form set - Unload
Data environment - AfterCloseTables
Data environment - Destroy
Data environment cursor(s) - Destroy
- Load() – первое в последовательности событий объекта форма. В этом событии ни один элемент лежащий на форме еще не родился и там бессмысленно помещать код вроде this.text1.value=’я’. Это событие рекомендуется использовать для выполнения настроек среды окружения. Особенно это актуально при использовании приватных сессий данных (этот вопрос рассмотрим ниже). Также, тут имеет смысл осуществить открытие таблиц и баз данных, не включаемых в объект Data environment.
RETURN .F. в событии Load() отменяет дальнейшую загрузку формы. Если форма имеет private data session все открытые таблицы и полученные курсоры закрываются. - Init() – событие при создании формы. Следует учесть, что элементы управления, помещённые на форму, создаются раньше формы, то есть событие Init() элементов происходит раньше, и поэтому в событии Init() формы уже можно обращаться к элементам управления формы. Включив оператор LPARAMETERS в событие формы, можно осуществить возможность передачи в неё параметров. Стоит заметить, что видимость этих переменных-параметров дальше события Init() не распространяется. Поэтому в случае необходимости стоит присвоить значения параметров созданным вами свойствам формы. Приведем пример достаточно типовой ошибки, переменные переданные в Init() используются в выражении для фильтра на какую либо таблицу , участвующую в работе формы. Естественно, после срабатывания события переменных уже нет, и фильтр становиться не работоспособным, так как его значение вычисляется в момент перемещения по записям. Если необходимо, создание и соответственно запуск формы можно прекратить, выполнив команду RETURN .F. Это удобно, например, для организации допуска к формам разным пользователям или проверки верности условий запуска формы, без правильности которых, далее форма не сможет верно функционировать.
- Activate() – активирование формы действиями пользователя, таким как щелчок мышью , или же когда вызывается метод SHOW() формы. Это событие при работе формы может срабатывать много раз, и иногда код, помещенный в него, может доставить разные проблемы. Скажем, там будет написан какой либо sql select
Выполнили из формы печать отчета, вернулись в форму, событие снова отработало, и явно помешало работе элементов формы, скажем гриду, чьим источником может быть курсор sql select. Рекомендуем, если нет возможности помещения кода в это событие, проверять необходимость повторного запуска кода в нем, скажем, таким образом -If not this.old This.DoSomething This.old=.t. endif
Не менее коварным может быть и элементарный код , помещенный в антипода упомянутого события – DEACTIVATE - которое тоже может многократно сработать. Куда смещается фокус из формы- в тулбар, просмотр отчета или в иное место, в этом событии неизвестно. Вот в отчете неожиданно становится видна одна запись вместо нескольких, а все потому, что в указанном событии написали всего одну строку - SELECT MyTable. Особенно кишат такими проблемами страничные формы, с кодами в указанных событиях на страницах: хотели указать алиас при смене страницы, когда пользователь остается на форме, а он в отчет пошел, и выбил у него табуретку из под него этим кодом.
- GotFocus() – получение фокусаДалее форма работает, и на ней могут срабатывать самые разные события, как то: нажатие клавиш, обновление содержания, перерисовка и другие. Пока опустим описание этих событий. При разгрузке формы формы происходят другие события
- QueryUnload() – это событие происходит при выполнении команд CLEAR WINDOWS, RELEASE WINDOWS или QUIT, а также при нажатии на крестик окна формы. Стоит отметить, что это событие не происходит при выполнении метода RELEASE() формы. Для того чтобы предотвратить закрытие формы исходя из необходимых разработчику условий , достаточно выполнить команду NODEFAULT в этом событии.
- Destroy() – происходит при уничтожении объекта
- Unload() – это последнее из событие в последовательности перед уничтожением формы, происходит после освобождения всех объектов включённых в форму. Все элементы управления, включённые в форму, в этом событии формы уничтожены и недоступны.
При использовании Default data session в форме именно в этом событии стоит закрыть таблицы, открытые с использованием команды USE , а также полученные курсоры.
Если форма модальна, используя команду RETURN можно вернуть значение переменной из формыDO FORM MyForm TO VarName
Тем, кто знаком с программированием в процедурных языках (таких как FoxPro 2.6), известно, каких трудов стоило создание форм с моделированием независимых друг от друга сессий. В VFP все это легко решается при помощи private data session (приватных сессий данных). Приватные сессии позволяют открывать таблицы, представления, индексные файлы, создавать связи, делать всевозможные настройки в окружении, совершенно не заботясь о том, что в какой то из другой форм аналогичные манипуляции выполняются с этими же таблицами. Каждая приватная сессия совершенно не зависима в этом плане от остальных. Приватная сессия подобна окружение данных на одной отдельной машине. Поэтому, если разработчик собирается создавать приложение с многодокументным интерфейсом, следует взять за правило строить формы на основе приватных сессий данных. Действительно, в одной форме могут участвовать несколько таблиц , связанными разными реляциями, в другой нечто подобное отчету - часть этих же таблиц без реляций, в третьей - они же, но с другой реляцией. Если использовать общую сессию, то нужно либо все время перестраивать реляции, менять запись, либо давать одним и тем же таблицам в разных формах разные названия.
Действительно , в форме накладных можем находиться на 30-той записи накладных, в отчете оборотная ведомость - сканировать записи, а справочники вообще находиться на разных записях. Если открыто одновременно 5-6 разных форм, то с названиями алиасов будет полный хаос. При частной сессии формы не видят окружения друг друга, одни и те же таблицы можно открыть с одним и тем же именем, связать их разными реляциями, находиться на разных записях одних и тех же таблиц. Следует обратить внимание на то, что по умолчанию в форме установлена Default data session (1) – общая сессия данных, что часто сбивает начинающих разработчиков с толку и заставляет их прибегать к изощрённым способам программирования MDI форм и многопользовательских приложений. Этого ложного пути легко избежать, установив в базовом классе формы Private data session (2). Однако не стоит забывать о том , что приватные сессии данных требуют дополнительных настроек среды, потому как настройки, производимые скажем в главном модуле программы действуют только в общей сессии данных (Default data session). То есть в этом типе (Private data session) сессии игнорируются общие настройки приложения и их необходимо выполнить повторно, а именно явно указать необходимые вам настройки из перечисленных:SET ANSI
SET AUTOSAVE
SET BLOCKSIZE
SET CARRY
SET CENTURY
SET COLLATE
SET CONFIRM
SET CURRENCY
SET DATABASE
SET DATE
SET DECIMALS
SET DELETED
SET DELIMITERS
SET EXACT
SET EXCLUSIVE
SET FIELDS
SET FIXED
SET HOURS
SET LOCK
SET MARK TO
SET MEMOWIDTH
SET MULTILOCKS
SET NEAR
SET NULL
SET POINT
SET REPROCESS
SET SAFETY
SET SECONDS
SET SEPARATOR
SET SYSFORMATS
SET TALK
SET UNIQUEОсобо обратите внимание на настройки выделенные цветом – они чаще всего становятся причиной разбитых лбов начинающих разработчиков.
Ввиду вышесказанного, стоит организовать в базовом классе формы некий метод, назовём его SetSession() в котором и производить соответствующие настройки среды окружения.
Вызывать этот метод из события Load() формы. Если метод переопределяется в конкретной форме, используйте команду DODEFAULT(), чтобы выполнить метод родителя – базового класса формы, в котором выполняются настройки.Для переключения между сессиями данных используется команда SET DATASESSION. Но пользоваться ей рекомендуется только на этапе разработки приложения для отладки форм, потому, что некоторые элементы ведут себя непредсказуемо и очень болезненно реагируют на смену сессии данных в момент выполнения программы, а при программной смене сессий некоторые элементы управления просто теряют свои источники данных. Пример, к combobox подключен источник данных в качестве алиаса таблицы или ее полей. При смене сессии это алиас может оказаться временно невидимым , и возникнет ошибка- can not access selected tables.
Нужно отметить, что последовательность вызова таких настроек может оказаться важной. Скажем, в LOAD формы выполняется sql select. Если сначала нет SET TALK OFF, то форма начинает рассказывать о своей работе по выполнению запроса, при этом может портится и изображение формы. Если вы забыли про SET DELETED ON, то с удивлением можете обнаружить в такой форме уже стертые ранее записи. Если забыли про SET DECIMAL , то при расчете сумм можете потерять точность.
Одной из особенностей форм с private data session является способность закрывать таблицы из окружения (Data Environment), таблиц , открытых при помощи команд USE (то есть так сказать “вручную”) и созданных во время выполнения программы курсоров. Это очень удобно – если в процессе открытия формы появились служебные курсоры или же при ее работе, то заботиться об их закрытии не нужно, они закроются автоматически.Прочтя это, естественно возникает вопрос – а каким же тогда образом формы могут обмениваться данными между собой ? Для таких дел предусмотрен принцип дочерних форм . Использовать его можно двояко. В первом случае родительская (немодальная) форма с приватной сессией запускает дочернюю модальную с общей сессией. Такой способ хорош для использования при создании справочников. Например, запущенная дочерняя модальная форма приобретает ту же сессию, что и родительская и все данные в дочерней форме ей легко доступны. Сессии ,помимо номера, имеют наименование, по умолчании совпадающее с именем запущенной формы. Так вот при таком подходе, сессия вначале имеет имя родительской формы, после запуска дочерней оно меняется на имя дочерней формы. После закрытия же дочерней формы имя сессии становится неопределённым - Unknown. Хотя разработчики и предупреждаются о том , что при закрытии дочерней формы имя сессии теряется, это не мешает с успехом использовать такой подход при создании приложений. Сделано это все с благой целью. Если вы из форм с частной сессией вызываете диалоговые формы к примеру с вопросом об удалении записи, или форму для запуска отчетов, то естественно в них видите те же самые таблицы на тех же записях, что были и в вызвавшей их форме с частной сессией. Поэтому советуем избегать открытия в окружение таких производных форм таблиц, которые уже открыты в форме с частной сессией. Также и в отчетах не следует в таких случаях класть эти таблицы в окружение. Иначе при вызове отчета вместо записи видимой на форме, увидим первую запись этой же таблицы.
Второй способ заключается в том, что дочерняя форма является немодальной с приватной сессией данных. Для запуска таким способом используется команда DO FORM … NAME … LINKED. Используйте вновь созданное свойство формы, скажем, назвав его oChildForm. Исполнив команду из метода родительской формы:DO FORM NAME ThisForm.oChildForm LINKED
Получим ссылку в родительской форме на дочернюю через свойство oChildForm и свободно можно таким образом обратиться из родительской формы к методам и свойствам дочерней. Кроме того, при закрытии родительской формы будет уничтожена и дочерняя потому как она ассоциирована со свойством родительской формы (о чём говорилось ранее). Конечно, не стоит забывать о ссылках и незавершённых методах, т.е. при закрытии родительской формы для полной уверенности выполнить:
ThisForm.oChildForm=.NULL.
5. Модальность и немодальность
Большинство форм в приложении обычно немодальны. Но модальные формы тоже имеют право на жизнь. Появляться им конечно же стоит в нужном месте и в нужное время – там где необходимо прервать выполнение программы для выполнения обязательных операций или получения необходимых данных. Отличие модальной формы от не модальной в том, что при запуске модальной формы программа останавливается на строке DO FORM и ждёт до тех пор пока форма не будет закрыта, немодальная же форма не вызывает такой задержки и код продолжает исполнятся после её запуска. Модальная форма также используется для возвращения какого либо значения из формы:
DO FORM MyFormName TO MyVariable
Модальность формы определяется свойством WindowType. Но это свойство доступно для изменения только на этапе разработки формы. Изменить модальность запущенной формы при необходимости можно, прибегнув к такой операции:
допустим форма MyFormName создана как немодальная форма в дизайнере и запускается таким образом
PUBLIC oForm DO FORM MyFormName NAME oForm NOSHOW
т.е. форма запущена , но скрыта, затем возникает необходимость отобразить её, причём сделать её модальной:
oForm.Show(1)
форма отобразится и примет статус модальной .
6. Передача параметров между формами и методами.
Наряду с тем, что некоторые данные в формах могут сохраняться в виде свойств формы, возможно передавать их в вызываемых методах и как параметры . Этот способ передачи представляется более гибким при вызове методов одной формы из другой формы. Но этот способ чреват тем, что передаваемые данные изолируются от других методов формы, т.е. передаваемые таким образом данные доступны только в вызывающем и вызываемом методах форм. Всегда существует вероятность того, что данные, используемые в двух методах разных форм, могут потребоваться и в третьем. Если такая ситуация не исключена, стоит присвоить их свойствам формы. И прежде чем написать LPARAMETERS в методе формы, подумайте, насколько вероятна такая ситуация. Пересылка данных между методами в виде параметров скорее “ситуационная” техника, во многом зависящая от конкретного случая, а не для повсеместного использования. Потому как в объектно-ориентированном программировании (в отличие от процедурного) методы и свойства неотделимы от объекта (принцип инкапсуляции) и для объекта нет большой разницы работать ли ему с переменными памяти или же со своими собственными свойствами, которые так или иначе тоже хранятся в памяти.
Программируя в объектно-ориентированном языке, которым является VFP, должно понимать, что гораздо легче распределить выполнение некоторой задачи между множеством методов, нежели выполнять кучу условий и проверок в одном месте, чтобы как можно шире охватить всевозможные варианты развития событий. К примеру, если в коде часто использованы конструкции IF-ENDIF, CASE-ENDCASE и он перегружен этими проверками – можно разделить его на более мелкие участки и вынести их исполнение в отдельные методы ; если код метода родителя зачастую переопределяется в наследнике, стоит подумать о расширении его функциональности в родителе ; если в метод передаётся слишком большое количество параметров – это свидетельствует о слишком узкой его направленности, возможно, что необходимо исключить его из родителя и перенести в методы наследника. Подход, при котором вся функциональность формы перекладывается на несколько элементов формы, зачастую не правильный. Нужно чётко представлять, что форма предназначена для того, чтобы отобразить информацию и неким способом обработать её. Совершенно не обязательно в методе Click() какой-нибудь кнопки писать несколько страниц кода по выполнению некоторой обработки представленных на форме данных. Необходимо структурировать их и разделить задачу на несколько этапов. Затем представить эти этапы в виде методов. Во-первых, это повысит читабельность, кода, а во вторых облегчит наследование и переопределение этих методов. Делегирование методов - это интерпретирование наполеоновского принципа “разделяй и властвуй” в объектно-ориентированном программировании 😉
Рассмотрим пример для иллюстрации делегирования методов: - Пусть на форме имеется грид, источником которого является таблица, открытая с буферизацией 5 (оптимистическая буферизация таблицы). В гриде идёт правка записей таблицы.
- На форме имеется кнопка “Записать изменения” Cmd_SaveЗаведем на форме метод Save()
Select MyTable Return TableUpdate(.T.)
В Click() кнопки “Записать изменения”
If !thisform.Save() Messagebox(‘Не удалось сохранить изменения !’,48, this.caption) Return .F. Endif Return .T.
В событии QueryUnload формы
This.Cmd_Save.Click()
Такое построение методов и событий является типичным примером делегирования.
Закрытие формы осуществляется методом RELEASE(). Если при выполнении закрытия формы нужно соблюсти некоторые условия – их можно прописать в этом методе. Перед закрытием формы лучше вручную очистить все ссылки на неё в дочерних формах, явно присвоив им значение .NULL., иначе форма не будет закрыта. Обычно на форму помещается кнопка в методе Click() которой и вызывается Release() формы. Эту кнопку можно сделать по умолчанию ассоциированной с Escape, назначив её свойство Cancel=.T. Тогда при нажатии кнопки Escape на клавиатуре выполнится метод Click() элемента управления “кнопка” на форме и форма закроется. Однако есть ещё одно “но” – закрытие по нажатию “крестика” на форме. При таких действиях выполняется событие QueryUnload формы. Надо заметить, что метод Release() и событие QueryUnload взаимонезависимы, то есть при Release() не происходит QueryUnload, а событие QueryUnload закрывает форму минуя Release(), что довольно часто и сбивает с толку. Чтобы отменить закрытие формы исходя из условий, достаточно выдать NODEFAULT в этих методах.
Помимо стандартного – “мышиного” манипулирования объектами формы принято организовывать так называемые “горячие” клавиши, при нажатии на которые выполняются некоторые действия (обычно часто повторяемые пользователями во время работы с программой). Считается, что пользователь в совершенстве владеет программой, если он способен работать с ней не прибегая к помощи мыши. Поэтому “горячие” клавиши – непременный атрибут любого грамотно построенного приложения. Нажатие клавиш на клавиатуре обрабатывается событием KeyPress. Сразу замечу, что этим событием не могут быть обработаны любые комбинации с клавишей Alt. Не забывайте об этом. Событие KeyPress формы происходит в следующих случаях :
- На форме нет элементов управления, либо эти элементы управления невидимы или недоступны ( свойства Visible и Enabled).
- Когда элемент не может обработать нажатие какой-то клавиши
- Когда свойство KeyPreview формы установлено в .T.
В последнем случае именно форма сначала обрабатывает событие KeyPress, и только затем выполняется событие KeyPress элемента управления, находящегося в фокусе на форме. Это очень удобно, так как позволяет поместить код обработки горячих клавиш в одном единственном месте, независимо от того, где находится фокус ввода. Иначе бы пришлось писать код обработки буквально в каждом элементе формы. Иными словами, если вы хотите обработать некоторые нажатия клавиш на форме (именно на форме), установите свойство KeyPreview в .T. При этом , чтобы отсечь всяческую реакцию системы на эти нажатия, а оставить только запрограммированную вами, примените команду NODEFAULT в самом начале обработки нажатий. Если вы желаете продолжить обработку нажатия определённой комбинации клавиш помимо формы и в элементе управления формы (если он находится в фокусе), выполните DODEFAULT(nKeyCode, nShiftAltCtrl). Если не указать последней команды, то форма может потерять возможность навигации между элементами на ней, а при вводе будет вводиться всего один символ. Советуем осторожнее работать в этом событии с обработкой клавиш ввод и табуляция (коды 13,10), иначе могут быть проблемы с кодом в событиях VALID и переходом с элементом на элемент. Другими словами, эти клавиши не слишком подходят на роль горячих. Не будут пойманы нажатия клавиш, которые вы определили до формы в командах типа ON KEY LABEL .