Программное объявление класса

Команды DEFINE CLASS и ENDDEFINE образуют программный блок, весь код внутри которого относится к определению класса. Класс может быть определён в любом программном файле (с расширением .prg). Как до команд определения класса, так и после могут следовать определения внешних процедур и функций, а так же различные команды. Существует только одно ограничение: класс не может быть определён внутри цикла или условного оператора.
В листинге 11.7. приведён синтаксис определения класса (некоторые предложения и ключевые слова, определяющие структуру класса COM объекта, здесь не приводятся).

 Листинг 11.7. Синтаксис определения класса

 
 DEFINE CLASS ClassName AS ParentClass [OF ClassLibrary]
    [[PROTECTED | HIDDEN] PropertyName1, PropertyName2 ...]
    [ADD OBJECT [PROTECTED] ObjectName AS ClassName [NOINIT] [WITH cPropertylist]]
    [[PROTECTED | HIDDEN] FUNCTION | PROCEDURE Name[_ACCESS |_ASSIGN]
         [([ParamName | ArrayName[]) | PARAMETERS | LPARAMETERS ParamName]
             cStatements
    [ENDFUNC | ENDPROC]
 ENDDEFINE
 

Вы должны соблюдать описанную в листинге 11.7 последовательность предложений класса. Так, после команды DEFINE CLASS, в первую очередь следуют объявления свойств, затем команды ADD OBJECT, и, наконец, объявления методов.

Команда DEFINE CLASS

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

 
 DEFINE CLASS ClassName1 AS ParentClass [OF ClassLibrary]
 

где:

  • ClassName1 — имя создаваемого класса
  • ParentClass — имя базового класса Visual FoxPro или пользовательского класса.
  • ClassLibrary — полная спецификация файла библиотеки файлов или программного файла, в котором определён класс-родитель. Если родитель определён в том же самом программном файле, что и определяемый класс, параметр можно не использовать.

Объявление свойств класса

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

 
 [[PROTECTED | HIDDEN] PropertyName1, PropertyName2 ...]
 

Ключевые слова PROTECTED и HIDDEN определяют уровень видимости свойства. Уровень PROTECTED (защищённый) запрещает доступ для считывания или изменения значений свойства извне определения класса. Методы и события, определённые в классе или дочернем классе, могут обращаться к защищенным свойствам; защищённые свойства и методы недоступны из включенных в класс объектов.
Уровень HIDDEN (скрытый) так же запрещает доступ и изменение свойства извне определения класса. Свойство недоступно из дочерних классов и из включенных в класс объектов.
Если вы не используете ни одно из этих ключевых слов, то уровень видимости свойства — PUBLIC.
Вы можете объявить произвольное количество новых свойств, а так же изменить уровень видимости свойств, наследуемых от класса-родителя.
Свойства с уровнем видимости PUBLIC можно связать с методами _ASSIGN и _ACCESS. В этом случае при попытке записи данных в свойство будет вызываться метод _ASSIGN, а при попытке чтения — метод _ACCESS, то есть непосредственный доступ к свойству извне класса будет невозможен.

Замечание
Методы _ASSIGN и _ACCESS могут быть связаны с любым свойством класса, в том числе и имеющим уровень видимости PROTECTED и HIDDEN, но их использование для этих уровней не актуально, так как основная масса ошибок возникает именно при обращении к свойствам извне класса.

Если вы хотите присвоить свойству значение по умолчанию, то делайте это в отдельной строке кода, например:

 
 PROTECTED Alpha
 Alpha = 10
 

Вы можете объявить массив как свойство класса, используя команду DIMENSION (для общедоступного свойства) или непосредственно в предложениях PROTECTED или HIDDEN:

 
 DEFINE CLASS myclass AS custom
    DIMENSION puArrayName[10,2] && Общедоступный массив
    puArrayName[1,1] = 100
    ... и т.д.
    PROTECTED ArrayName[10] && Защищённый массив
    ArrayName[1] = 100
    ... и т.д.
 ENDDEFINE

Объявление методов класса

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

 
 [PROTECTED | HIDDEN] FUNCTION | PROCEDURE Name [_ACCESS |_ASSIGN]
   [([ParamName | ArrayName[]) | PARAMETERS | LPARAMETERS ParamName ]
   cStatements
 [ENDFUNC | ENDPROC]

Ключевые слова PROTECTED и HIDDEN определяют уровень видимости метода (см. выше). Если вы не используете ни одно из этих ключевых слов, то уровень видимости метода — PUBLIC.
Ключевые слова PROCEDURE или FUNCTION определяют начало кода метода, а команды ENDPROC или ENDFUNC — его окончание. Как и в случае с обычными процедурами и функциями, вы можете не использовать команды ENDPROC и ENDFUNC, так как Visual FoxPro будет считать концом метода либо следующую команду PROCEDURE или FUNCTION, либо команду ENDDEFINE.

Параметр Name — это имя метода. Если используются ключевые слова _ACCESS или _ASSIGN, то Name — это имя свойства класса, запись и чтение которого контролируются соответствующими методами. Например, если вы хотите объявить метод _ASSIGN для свойства MyValue, то сделать это нужно так:

 
 PROCEDURE MyValue_ASSIGN
 

Для указания списка получаемых методом параметров может использоваться один из следующих форматов:

 
 PROCEDURE MyMethod(Param1, Param2, …)
 или
 PROCEDURE MyMethod
 PARAMETERS Param1, Param2, …
 

Первый формат равнозначен применению команды LPARAMETERS, то есть все получаемые параметры имеют локальную область видимости. При использовании второго формата получаемые параметры имеют область видимости PRIVATE.
Массивы передаются в методы по ссылке. Не забывайте использовать команду EXTERNAL ARRAY для объявления параметра как массива, как, например, в следующем фрагменте кода:

 
 PROCEDURE Alpha(taMyArray)
 EXTERNAL ARRAY taMyArray
 

Если вы изменяете код метода, унаследованный от класса-родителя, то в объявлении такого метода нужно перечислить все получаемые им параметры в том же порядке, в каком они перечисляются в методе класса-родителя. Например, метод — обработчик события KeyPress получает два параметра: код нажатой клавиши и флаг, сообщающий, была при этом нажата одна из клавиш Ctrl, Shift или Alt. При объявлении этого метода в своём классе вы должны указать все получаемые им параметры:

 
 PROCEDURE KeyPress(nKeyCode, nShiftAltCtrl)
 

Добавление объектов в класс-контейнер

Для добавления объектов (управляющих элементов) в класс-контейнер применяется команда ADD OBJECT.

 
 ADD OBJECT [PROTECTED] ObjectName AS ClassName [NOINIT] [WITH cPropertylist]
 

Параметры команды:

ADD OBJECT [PROTECTED] ObjectName AS ClassName

Определяет объект ObjectName, который наследуется от класса ClassName. В качестве класса-родителя может использоваться как базовый класс Visual FoxPro, так и пользовательский класс или ActiveX компонент. Необязательное ключевое слово PROTECTED предотвращает доступ для изменения свойств объекта из вне определяемого класса или подкласса.

NOINIT

Необязательное ключевое указывает, что метод Init добавляемого объекта не выполняется.

WITH

Определяет список свойств и их значений для объекта, которые вы добавляете в определение класса.

cPropertyList

Перечисленные через запятую операторы присваивания, в которых свойствам объектов присваиваются значения «по умолчанию».
В листинге 11.8 показан пример создания класса формы и размещения на ней командной кнопки.

 Листинг 11.8. Создание класса формы и размещение на ней управляющего элемента

 
 DEFINE CLASS MyForm AS Form
    Width = 300
    Height = 80
    ADD OBJECT Command1 as CommandButton WITH ;
       Left = 200, Top = 52, Width = 95, Height = 25, Caption = 'Закрыть'
 ENDDEFINE
 

Вы можете устанавливать значения только для существующих свойств добавляемого объекта. Visual FoxPro не контролирует правильность ввода наименований свойств, поэтому, если вы ошибётесь в имени какого-либо свойства, то это ошибка проявится только на этапе выполнения приложения.
Для ввода кода метода добавляемого объекта вы должны после ключевого слова, определяющего тип метода (PROCEDURE или FUNCTION) указать имя объекта и, через точку, наименование метода этого объекта. В следующем фрагменте кода показано, как добавить код в метод Click включенной в форму командной кнопки:

 
 PROCEDURE Command1.Click
    Строки_кода_метода
 ENDPROC
 

В листинге 11.9. приведен пример программного создания формы.

 Листинг 11.9. Пример программного создания формы

 
 PUBLIC oForm
 oForm = CREATEOBJECT('MyForm') && Создать объект из класса MyForm
 oForm.Show() && Показать форму на экране
 *
 * Объявление класса формы
 *
 DEFINE CLASS MyForm AS Form
    Width = 300
    Height = 80
 *
 * Добавляем в класс формы командную кнопку
 *
    ADD OBJECT Command1 as CommandButton WITH ;
       Left = 200, Top = 52, Width = 95, Height = 25, Caption = 'Закрыть'

    PROCEDURE Click()
       = MESSAGEBOX('Вы щёлкнули по мне!')
    ENDPROC
 *
 * Добавляем код в метод Click командной кнопки
 *
    PROCEDURE Command1.Click()
       thisform.release
    ENDPROC 
 ENDDEFINE
 

Первые три строки кода — это команды, которые создают форму и выводят её на экран.

Из объявления класса видим, что форма будет иметь размеры 300 на 80 пикселей; так как значения для свойств Top и Left формы не устанавливаются, то используются их значения по умолчанию (нули).
В форму добавляется объект — управляющий элемент Command1, создаваемый из базового класса CommandButton. Определяются заголовок, положение и размеры командной кнопки.
Добавляется код в метод Click формы. Теперь при щелчке мышью по форме будет выводиться окно с сообщением: «Вы щёлкнули по мне!». Далее следует код метода Click объекта Command1 — обратите внимание на то, что используется именно то имя объекта, которое вы дали ему в команде ADD OBJECT.
Сохраните этот код как программный файл. Запустите его на выполнение. Убедитесь, что форма ведёт себя именно так, как описано в её классе.

Замечание
Командой ADD OBJECT можно добавлять в класс-контейнер только объекты — управляющие элементы, наследуемые как от базовых классов Visual FoxPro, так и от компонентов ActiveX. Программно создание класса-потомка ActiveX компонента, а так же добавление в контейнер ActiveX компонентов рассматривается в главе 18 «Компоненты ActiveX»

Методы Init, Destroy и Error

Все пользовательские классы наследуют свойства и методы класса-родителя. Здесь вы познакомитесь с назначением и применением наследуемых методов Init, Destroy и Error, которые вы так же можете переопределять.

Метод Init

Метод Init выполняется после инициализации объекта — экземпляра класса. К моменту выполнения этого метода объект создан, а так же созданы все размещаемые на нём командой ADD OBJECT управляющие элементы. Особенностью метода Init является возможность получать параметры, передаваемые ему функциями CREATEOBJECT() и NEWOBJECT(), а для динамически создаваемых управляющих элементов — методами AddObject и NewObject объектов-контейнеров. Список получаемых параметров вы можете перечислить как в строке с именем метода, так и используя команду LPARAMETERS:

 
 PROCEDURE Init(Parameter1 [, Parameter2, ets… ])
 или
 PROCEDURE Init
 LPARAMETER Parameter1 [, Parameter2, ets… ]
 

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

 
 RETURN = .F.
 

то объект разрушатся, объектная ссылка не создаётся.
После отработки метода Init объект переходит в состояние ожидания событий.

Метод Destroy

Этот метод всегда выполняется перед разрушением объекта. При вызове этого метода все вложенные объекты и свойства существуют.

Метод Error

Метод Error вызывается, когда при выполнении кода в одном из методов класса возникает ошибка. Метод получает следующие параметры:

  • nError номер ошибки Visual FoxPro

  • cMethod имя метода, в котором произошла ошибка (или имя вызванной из метода процедуры, при выполнении которой произошла ошибка)

  • nLine строка кода, вызвавшая ошибку

Если вы не перегружаете этот метод, то при возникновении ошибки во время выполнении одного из методов класса будет выведено окно со следующим сообщением (рис.11.28).

Рис.11.29. Результат выполнения метода Error класса

Использование метода Error позволяет получить подробную информацию о месте, причине и характере ошибки. Всё зависит только от написанного вами кода.

Подключение программных модулей к приложению

Если вы объявляете классы в программных модулях (файлах типа .prg), то вы должны использовать команду

 
 SET PROCEDURE TO имя_файла [ ADDITIVE ]
 

для указания Visual FoxPro на необходимость просматривать эти файлы при выполнении функции (метода), создающей объект.
Ключевое слово ADDITIVE используется в том случае, если вы подключаете несколько программных модулей.

Управление временем жизни объектов

Каждый объект — экземпляр класса связан либо с переменной, выполняющей роль объектной ссылки (указателя на объект), либо с объектом-контейнером.
Если вы создаёте объект, используя функции CREATEOBJECT() или NEWOBJECT(), то время жизни объекта определяется временем жизни переменной, которой вы присваиваете ссылку на объект. Если переменная выходит из области видимости либо уничтожается явно (например, командой RELEASE), то уничтожается и связанный с ней объект. Но если существует несколько ссылок на объект, то объект будет уничтожен только после уничтожения последней ссылки:

 
 oObj1 = CREATEOCJECT('MyClass')
 oObj2 = oObj1
 RELEASE oObj1   && После уничтожения этой переменной объект ещё существует
 RELEASE oObj2   && Теперь объект уничтожен
 

Если существует внешняя ссылка на вложенный объект объекта-контейнера, то, если при уничтожении объекта эта ссылка будет существовать, это приведёт к тому, что объект не сможет уничтожить вложенный объект, и, следовательно, и он, и не уничтоженный вложенный объект останутся в памяти, что приведёт к утечке памяти и, возможно, ошибкам времени выполнения.
Если в качестве ссылки вы используете свойство существующего объекта, то создаваемый объект будет уничтожен при уничтожении объекта, содержащего это свойство. Уничтожение объекта происходит так же при присваивании переменной (или свойству), содержащей указатель на объект, нового значения:

 
 Obj = CREATEOBJECT('ClassName')
 Obj = NULL    && или, например, Obj = .F.
 

Вторая строка кода уничтожает объект — экземпляр класса ClassName.
Объект так же будет уничтожен, если в переменную, содержащую объектную ссылку, будет записана ссылка на другой объект:

 
 Obj = CREATEOBJECT('ClassName1')
 Obj = CREATEOBJECT('ClassName2')
 

Вторая строка кода уничтожает объект — экземпляр класса ClassName1.

Размещаемые в объектах-контейнерах объекты — управляющие элементы уничтожаются после выполнения метода Destroy контейнера, то есть на момент вызова этого метода все управляющие элементы ещё существуют. Для принудительного удаления объекта из объекта-контейнера используется его метод RemoveObject.
Перед уничтожением все объекты генерируют событие Destroy, метод обработки которого в своих классах вы можете переопределить.