Синтаксически
все абсолютно правильно. Более того, когда Вы будете запускать главный файл, на
этапе разработки приложения все будет работать нормально. Но вот после того, как
Вы создадите готовый EXE-файл и запустите его, то вместо ожидаемого приложения
Вы увидите "странный" эффект: Окно FoxPro мелькнет на экране и тут же
закроется. Причиной такого "странного" поведения является то, что Вы
"забыли" указать FoxPro, в каком месте ему надо остановиться и подождать реакции
пользователя. На этапе отладки такой "точкой останова" является ранее открытая
среда FoxPro. Но в готовом файле EXE до него никакой среды FoxPro открыто не
было! Чтобы создать "точку останова" надо дать специальную команду
READ EVENTS
Т.е.
содержимое главного файла будет выглядеть примерно так:
DO MainMenu.mpr
READ EVENTS
Если
Вы строите приложение на базе "As Top-Level" форм, то вместо меню, основным
управляющим элементом является Ваша главная форма. И содержимое главного файла
будет выглядеть примерно так:
DO FORM MainForm.scx
READ EVENTS
Вот
теперь, в готовом файле EXE, когда программа дойдет до команды READ EVENTS, то
произойдет остановка в ожидании реакции пользователя.
Буквально, фраза
"READ EVENTS" переводится как "чтение событий". Т.е. вместо "точки останова" это
следовало бы назвать как-то вроде "обнаружение и выполнение событий,
инициализированных пользователем". Это более точно выражает смысл и задачи
данной команды. Но уж больно длинно получается. Поэтому, хотя "точка останова" -
это не совсем корректный термин, но в дальнейшем я буду использовать именно
его.
Имейте в виду, что одновременно, во всем приложении может быть
активна только одна команда READ EVENTS. Вызов другой команды READ EVENTS не
приведет к ошибке, но эта команда будет просто проигнорирована. Чтобы отменить
действие команды READ EVENTS надо дать специальную команду
CLEAR EVENTS
По
этой команде будет отменено действие команды READ EVENTS и выполнение перейдет
на команду, следующую за командой READ EVENTS. Если затем не будет подано еще
одной команды READ EVENTS, то главный файл будет выполнен до конца и приложение
автоматически закроется.
Если команда READ EVENTS была дана в главном
файле, то CLEAR EVENTS сработает как команда RETURN TO MASTER. Т.е. выполнение
той процедуры или метода, где собственно и была дана команда CLEAR EVENTS, будет
прервана на этой команде и все что стоит ниже этой команды просто никогда не
будет выполнено.
Так, где же давать команду CLEAR EVENTS? Разумеется, в
специальном пункте меню "Выход".
Если Вы создаете приложение на базе "As
Top-Level" форм, то команду CLEAR EVENTS надо давать в событии UNLOAD Вашей
главной формы.
Итого, получается примерно такая логика:
Запускается главный файл
Активизируется основное меню (или основная форма)
По команде READ EVENTS организуется "точка останова" для ожидания действий
пользователя
Команда CLEAR EVENTS прекращает действие команды READ EVNETS и передает
управление в главный файл на команду, непосредственно следующую за командой READ
EVENTS.
Завершается выполнение главного файла, что приводит к закрытию приложения
FoxPro.
Причина
такого сообщения в том, что осталась активной команда READ EVENTS. Именно она и
вызывает такое сообщение об ошибке в описанной ситуации. Чтобы перехватить
описанные события закрытия приложения используется специальная
настройка ON SHUTDOWN В принципе, можно просто написать
ON SHUTDOWN CLEAR EVENTS
Но
обычно перед закрытием приложения надо выполнить ряд предварительных операций.
Каких? Это уже зависит от Вашего приложения. Ну, например, можно спросить
пользователя о том, действительно ли он хочет выйти из приложения или это у него
"рука дрогнула". В общем случае одной команды недостаточно. Нужен вызов
процедуры. Например:
ON SHUTDOWN DO MyExitProcedure
И
вот уже в этой процедуре MyExitProcedure и надо дать команду CLEAR EVENTS.
Причем эта команда должна быть самой последней, поскольку в момент ее выполнения
управление будет передано на команду, следующую за командой READ EVENTS в
главном файле и все то, что написано после CLEAR EVENTS в процедуре
MyExitProcedure просто не будет выполнено.
Для универсальности,
желательно в пункте меню "Выход", вместо простой команды CLEAR EVENTS также
сделать вызов этой процедуры. Тогда у Вас будет один общий код, обрабатывающий
выход из Вашего приложения при любых ситуациях.
Поскольку выход из
приложения может быть выполнен в любой момент и из любого места, то процедура
MyExitProcedure должна быть доступна также в любой момент и из любого места.
Проще всего расположить это процедуру непосредственно в главном файле (в конце).
Поскольку главный файл - это "корневой" файл, из которого осуществляется вызов
любых других файлов, то расположенная таким образом процедура будет "видна" и
доступна из любого места Вашего приложения.
Если Вы делаете приложение на
базе "As Top-Level" форм, то вызов этой процедуры надо организовать еще и в
событии QueryUnload главной формы. Точнее так, в событии QueryUnload главной
формы надо перенаправить вызов на собственно метод, организующий закрытие всего
приложения.
В результате, содержимое главного файла приобретает следующий
вид
ON SHUTDOWN DO MyExitProcedure
DO MainMenu.mpr
READ EVENTS
PROCEDURE MyExitProcedure
* Необходимые действия по "штатному" закрытию приложения
CLEAR EVENTS
RETURN
Имя
того или иного ToolBar можно посмотреть в заголовке самого ToolBar (если он не
"приклеен" к меню) или через пункт меню View, подпункт ToolBars
Например,
скрыть стандартную панель можно командой
HIDE WINDOW "Standard"
Снова
активизировать стандартную панель можно командой
SHOW WINDOW "Standard"
Проверить
тот факт, что та или иная панель в настоящий момент активна можно используя
команду WEXIST()
IF WEXIST("Standard") = .T.
HIDE WINDOW "Standard"
ENDIF
Таким
образом, если Вам очень хочется скрыть системные ToolBar в режиме отладки, то
несложно написать простые процедуры их закрытия в начале главного файла и
восстановления после команды CLEAR EVENTS. Или же использовать класс,
поставляемый с FoxPro.
MODIFY CLASS _systoolbars OF
(Home()+"FFC_app.vcx")
Но, повторюсь, особого смысла в готовом приложении
это не имеет. Поскольку там их и так не будет.
Если это свойство
имеет значение 0, то мы находимся в режиме отладки. Т.е. получается что-то
вроде:
* Общие настройки вне зависимости от режима работы
IF _VFP.StartMode = 0
* Настройки только для режима отладки
ELSE
* Настройки только для готового приложения
ENDIF
Есть и
еще одна проблема настроек. Дело в том, что при открытии Private DataSession
часть настроек, связанных с работой с данными сбрасываются в значения по
умолчанию. Полный список таких настроек Вы можете посмотреть в описании к
команде "SET DataSession". Причем для некоторых настроек эти самые значения по
умолчанию отличаются в зависимости от того, в какой DataSession мы находимся.
Т.е. получается, что недостаточно просто один раз сделать настройки в
главном файле. Нужно еще повторить часть настроек в каждой Private
DataSession.
Следовательно, выполнение настроек среды FoxPro надо вынести
либо в отдельную процедуру, либо оформить как метод класса. Если как метод
класса, то либо как метод некоей базовой формы, на основе которой будут созданы
все формы Вашего проекта, либо как метод класса Custom, экземпляр которого будет
"бросаться" на нужные формы.
Наконец, еще одна проблема настроек. По
завершении работы приложения надо вернуть настройки в то состояние, в котором
они были на момент запуска приложения.
На первый взгляд, это кажется
абсолютно бессмысленным требованием. Но не надо забывать, что мы отлаживаем наше
приложение. Это значит, что есть часть настроек, которые просто обязаны
отличаться в зависимости от того, просто мы отлаживаем какую-то форму
(процедуру) или запустили приложение в режиме отладки, начиная с главного
файла.
Например, при отладке триггеров хорошо бы видеть записи помеченные
как удаленные. Но в готовом приложении, даже при запуске в режиме отладки, нам
видеть эти записи не надо. Это регулирует настройка SET DELETED
Да и в
любом случае желательно всегда следовать принципу: намусорил, убери за собой.
Для программирования это особенно важно во всех смыслах.
Таким образом, в
общем случае, работа с настройками выглядит примерно так:
Проверяем текущее состояние настройки
Если текущее состояние настройки отличается от нужного нам значения в данном
режиме работы в данном месте, то сохраняем старое значение настройки и
устанавливаем новое
По окончании работы восстанавливаем измененные настройки
Из
этой логики следует, что использовать процедуры в принципе можно, но очень уж
неудобно. Нужны две отдельные процедуры для установки и для восстановления
настроек. А, кроме того, нужно где-то хранить старые настройки. Более удобным
кажется использование классов. Создаются два метода одного и того же класса
(возможно больше, ведь для Private DataSession надо установить только часть
настроек). А для хранения старых значений настроек можно использовать свойства
(Properties) класса.
Тогда остается уточнить, использовать ли класс
Custom или класс на базе Form?
По большому счету - это абсолютно
одинаковые варианты. Следует только помнить, что методы класса Custom можно
запустить не ранее события Init этого класса. Но в любом случае методы
собственно формы или любых ее объектов еще не существуют на момент выполнения
события BeforOpenTables в DataEnvironment формы. Т.е. автоматическое открытие
таблиц, включенных в DataEnvironment формы, произойдет с использованием настроек
по умолчанию.
Но в большинстве случаев, это не столь уж и принципиально.
Почему? Это отдельный вопрос, выходящий за рамки данной статьи. Просто примите к
сведенью, что выполнение настроек в INIT-формы в большинстве случаев вполне
уместно и не будет "слишком поздно".
Возвращаясь к содержимому нашего
главного файла, получаем, что его содержимое уже выглядит примерно так:
* Подключаю библиотеку классов, содержащую ряд полезных классов общего назначения
LOCAL loSetting, llIsClass
IF UPPER("MyClass.VCX ALIAS MyClass") $ UPPER(SET("ClassLib"))
llIsClass = .T.
ELSE
llIsClass = .F.
SET CLASSLIB TO MyClass ADDITIVE
ENDIF
* Класс, устанавливающий глобальные настройки среды (находится в MyClass.VCX)
loSetting = CREATEOBJECT("Setting")
ON SHUTDOWN DO MyExitProcedure
PUSH MENU _MSYSMENU
DO MainMenu.mpr
_SCREEN.Visible = .T.
READ EVENTS
********* Восстановление исходных настроек
POP MENU _MSYSMENU
ON SHUTDOWN
IF m.llIsClass=.F.
RELEASE CLASSLIB MyClass
ENDIF
******** Процедура "аварийного" выхода из программы
PROCEDURE MyExitProcedure
* Необходимые действия по "штатному" закрытию приложения
CLEAR EVENTS
RETURN
Здесь я
предполагаю, что в библиотеке классов MyClass.VCX есть класс "Setting" в событии
Init, которого происходит установка нужных настроек, а в событии Destroy
восстановление исходных настроек. Т.е. удаление переменной m.loSetting означает
автоматическое восстановление исходных настроек.
Еще использованы
дополнительные команды PUSH MENU и POP MENU, которые сохраняют и восстанавливают
системное меню FoxPro.
Обратите внимание на то, что переменные
объявляются как LOCAL. Дело в том, что если не объявить переменные, то по
умолчанию они получат область видимости PRIVATE. А для переменных главного файла
это равнозначно объявлению их как PUBLIC, поскольку они будут видны во всех
вызванных формах и процедурах.
Еще один тонкий момент заключается в том,
что команды, записанные после команды READ EVENTS, будут выполнены только после
подачи команды CLEAR EVENTS. Т.е. все то, что обозначено, как "Восстановление
исходных настроек" будет выполнено непосредственно перед закрытием
приложения.
Как видите, главный файл начинает разрастаться. С этим надо
что-то делать. Чем больше листинг программы (код одного метода или процедуры),
тем сложнее такой код отлаживать и модифицировать.
По
сути, если опустить некоторые детали, содержимое главного файла в этом случае
будет выглядеть примерно так:
SET CLASSLIB TO MAIN, TSGEN
PUBLIC goApp
goApp = CREATEOBJECT("TasTrade")
goApp.Do()
В чем
тут "фокус"? А "фокус" в том, что в момент создания объекта на базе класса
TasTrade выполняется его событие Init. А уже из события Init этого класса
осуществляется вызов всех тех методов, которые необходимы в данном приложении в
момент загрузки приложения
Метод goApp.Do() содержит собственно команду
READ EVENTS, что и обеспечивает "точку останова" всего
приложения.
Команда CLEAR EVENTS дается в одном из методов класса goApp.
В данном случае - это метод goApp.CleanUp2()
Методы, которые необходимо
выполнить при закрытии приложения можно вызвать в том же методе goApp.Do() после
команды READ EVENTS или в событии goApp.Destroy()
Все те процедуры,
которые были описаны в данной статье, оформляются как отдельные методы класса
TasTrade. И вызываются в соответствующем месте класса.
Обратите внимание,
что собственно класс TasTrade из библиотеки классов Main.vcx является
наследником другого класса Application из библиотеки классов TSGen.vcx. Такая
"иерархия" вовсе не случайна. В этом случае Вы можете использовать класс
Application во всех Ваших приложениях, создавая на его основе собственный класс,
например, MyClass в собственной библиотеке классов MyClass.vcx. Т.е. Вы получите
некую общую библиотеку классов вообще для всех ваших проектов, как заготовку для
класса конкретного приложения.
Несколько слов о том, чего не должно быть
в классе goApp.
Дело в том, что возникает большое искушение "впихнуть" в
класс goApp вообще все глобальные методы, переменные памяти и разные
обработчики. Так вот, не надо этого делать.
Цель класса goApp - это
организовать корректную загрузку и выгрузку вашего приложения. Все! Все что
"сверх" этой задачи должно быть выделено в отдельные классы. Например:
Список можно
продолжать. Но основной принцип: не смешивать в одном объекте разные
задачи.
Будьте осторожны с использованием класса из проекта TasTrade.pjx.
Он достаточно далек от идеала. Его следует рассматривать скорее как учебное
пособие, но не как объект готовый к использованию. Не надо слепо копировать его
в свое приложение. Лучше напишите свой собственный класс. Да, это займет больше
времени, но Вы лучше поймете что это такое и для чего это используется.