OOP в Visual FoxPro® 6.0 (основные понятия и инструментальные средства среды разработки) Дроздов Михаил Компания «ИВС Софт» My Page: http://vfpdmur.narod.ru/ ICS Page: http://ics.perm.ru/ mailto:Drozdov@ics.perm.su Определение класса: свойства, методы и события 1(3) • Объект: совокупность данных и функций реализуется в виде класса. • В классе определяются: свойства, методы, и события. – событие - реакция на внешнее воздействие, предопределённая классом (нельзя создавать новые). Код, помещённый в событие выполняется при возникновении соответствующего события. Ряд, событие может быть возбуждено программно. – свойство - это VFP переменная (массив), определённая в классе. – метод - процедура-функция, определённая в классе. • Свойства, и методы могут быть добавлены/удалены из класса, однако нет возможности удаления свойств и методов, «предопределённых классом» и «унаследованных». Определение класса: область видимости свойств и методов 2(3) • Область видимость свойств и методов задаётся как Public, Protected или Hidden при их определении в классе: – Public - доступно извне, внутри и в производных данного класса. – Protected - доступно внутри и в производных данного класса. – Hidden - только внутри данного класса. • Класс существует в виде определения (DEFINE CLASS … AS ...) в коде prg-файла или в библиотеке классов (vcx-файле), структура последнего совпадает с dbfфайлом: – *.vcx - аналог dbf – *.vct - аналог fpt • Доступные извне (public) свойства, методы и события, определяют внешний интерфейс класса. • Определения не могут быть вложенными, т.е. Не может быть определён класс внутри класса или метод внутри метода. Определение класса: понятия инкапсуляции, наследования, полиморфизма 3(3) VFP классы обладают указанными в заголовке свойствами, каждое из которых означает следующее: • Инкапсуляция (encapsulation) - сокрытие части свойств и методов, для внешнего использования. • Наследование (inheritance) - на основе одного класса может быть создан другой, содержащий в себе все «открытые для него» методы и свойства первого. • Полиморфизм (polymorphism) - грубо говоря, возможность существования в разных классах одинаково названных методов, но выполняющих различные функции. Данное свойство не поддерживается в VFP напрямую, поскольку не существует в VFP понятия перегружаемой (virtual) функции, однако благодаря наличию команды NODEFAULT и функции DODEFAULT() вполне возможно смоделировать это свойство. (следует также отметить, что указанная возможность будет «работать» только в VFP классах и не будет в ActiveX компонентах) Контроль доступа к свойствам класса: методы Access и Assigne • • • В VFP включены два определяемые пользователем метода, обеспечивающих контроль типа чтения-запись любого свойства класса («стражи» свойства). Access - метод, вызываемый при попытки использования свойства, например, сохранение значения свойства некоторой переменной. Assigne - метод, вызываемый при попытки изменения свойства, например, при попытке присвоение значению свойства нового значения. Очевидно, что этот метод не будет никогда вызываться для свойства Read only (только для чтения) Могут быть созданы как в режиме проектирования, так и непосредственно из кода. PROCEDURE MyPropName_ASSIGN LPARAMETERS NewValueForMyProp … ENDPROC • • Поскольку метод Assigne получают новое значение свойства через параметр, то логика, заключённая в код этого метода определяет, в конечном счёте: будет ли присвоено новое значение этому свойству класса Даёт возможность писать реализацию, для полностью открытого интерфейса, тем не менее защищая значения свойств. Использование класса: создание объекта 1(1) • • • Ссылку нужно где-то хранить: На основании определения (описания) класса создаётся экземпляр класса, или объект класса. Ссылка на полученный экземпляр класса обычно сохраняется в некоторую область памяти: это может быть глобальная/локальная переменная или свойство некоторого другого объекта (созданная либо явно Вами или неявно в режиме Designe). Экземпляр класса (или объект) может быть создан явно в режиме выполнения (runtime) или неявно при проектировании (designe) При явном создать экземпляр класса с использованием функции CreateObject() или метода AddObject(), так или иначе, необходимы два действия [Visible = .F.]: – обеспечить «видимость» соответствующей библиотеки класса SET CLASS LIB TO... – выполнить функцию oMyObj = CreateObject(“MyClassLib.MyClassInLib”[, ...]) или This.AddObject(“oMyObj”, “MyClassLib.MyClassInLib”[,...]) • • При явном создать экземпляр класса с использованием функции NewObject() первое действие излишне, поскольку библиотека явно указывается в параметрах этой функции [Visible = .F.]. При создании объекта в режиме проектирования библиотека будет определена неявно, а свойство Visible Вы можете настроить в окне Properties Использование класса: уничтожение объекта 2(3) • Ссылку нужно где-то хранить: На основании определения (описания) класса создаётся экземпляр класса, или объект класса. Ссылка на полученный экземпляр класса обычно сохраняется в некоторую область памяти: это может быть глобальная/локальная переменная или свойство некоторого другого объекта (созданная либо явно Вами или неявно в режиме Designe). • Следите за уничтожением Ваших объектов: – Уничтожение переменной, содержащей ссылку на экземпляр класса, приводит к разрушению соответствующего экземпляра объекта. Для локальной переменной это будет происходить при «закрытии» её «области видимости». – Однако, если такая ссылка храниться как свойство некоторого другого объекта, то последний не может быть уничтожен до тех пор, пока не будут разрушены все его свойства-ссылки-на-экземпляры-объектов (Например, This.oMyObj = NULL обычно в событии Destroy этого класса). – Обычно не требуется никаких действий с Вашей стороны, для уничтожения экземпляров объектов, добавленных в экземпляр класса в Designe или RunTime с помощью This.AddObject(...) Использование класса: доступ к объектам 3(3) • Внутри объекта: Адресация осуществляется с использованием таких ключевых слов как This, ThisForm, ThisFormSet, а также Parent. В режиме Designe, для правильной адресации на объект лучше использовать Object List... (пункт меню, возникающего по нажатию правой клавиши мыши) Например, WITH This.Parent.Parent .ActiveCell(1, .ActiveColumn) ENDWITH • Извне объекта: Для адресации к свойствам/методам/событиям извне объекта, так или иначе, необходимо знать место хранения объектной ссылки на этот объект. CLEAR goMayObj PUBLIC goMayObj goMayObj = CreateObject(…) … goMayObj.MyMethod() LOCAL lnCntForn ACTIVATE SCREEN CLEAR FOR lnCntForn = 1 TO _SCREEN.FormCount ? _SCREEN.Forms(lnCntForn).Name ENDFOR Базовые классы VFP и некоторые особенности обработки событий • Control (исключая Custom) – не имеет метода AddObject() – все добавляемые свойства защищённые • Container: – все добавляемые объекты «открыты» • • • События Container происходят независимо от событий Control Это правило распространяется и на элементы Grid, т.е. каждый элемент «работает» независимо... И наоборот, в групповых элементах по событию на элементе отрабатывает событие группового элемента, но только в том случае, если нет кода в событии элемента. При наследовании объектов контроль: какой код надо выполнить идёт от производных к родительским. Лучший способ узнать, что происходит - «Журнал событий» Условные подгруппы (или категории) классов Не визуальных: • Класс-оболочка (покрытия или интерфейсные «преобразователи») • Класс-менеджер (управление другими классами «надзиратели») • Бизнес-класс (логика обработки данных или моделирование предметной области) Визуальные: • Одиночные элементы управления • Групповые элементы управления • Контейнеры • Экранные формы • Панели инструментов Object Events Data environment Form set Form Data environment cursor(s) Data environment Objects 1 Form Form set Form set Form Object1 2 Form Object1 Object1 Object1 Object1 Object2 3 Object2 Object2 Object2 Object2 Form Form Object 5 Form Form set Data environment Data environment Data environment cursor(s) BeforeOpenTables Load Load Init Init Init Init Init Activate Activate When GotFocus GotFocus Message Valid 3 LostFocus When GotFocus Message Valid 4 LostFocus QueryUnload Destroy Destroy Unload Unload AfterCloseTables Destroy Destroy 1 Для каждого объекта от самого внутреннего до самого внешнего 2 Первый объект в tab-последовательности 3 Следующий объект получает фокус 4 Поскольку объект теряет фокус 5 Для каждого объекта от самого внешнего к самому внутреннему Некоторые выводы из анализа последовательности создания элементов • Элемент, включённый в Control нельзя «расширить» (это можно сделать только за счёт родительского класса этого элемента). • Обращение к контейнеру из метода Init() включённого в него объекта недопустимо (поскольку контейнер ещё не создан). • И наоборот, всю «настройку» включённых в контейнер компонент, следует производить в событии Init() контейнера (когда все включённые в него элементы уже существуют). • Из методов/событий формы нет возможности управлять открытием таблиц в DataEnvironment. • Все установки для данных (SET DELETED ON, ...) в Private DataSession следует производить в событии MyForm.DataEnvironment.BeforOpenTables() (когда данные ещё не открывались) • Какую-либо настройку данных, с которыми работают элементы, включённые в форму, следует выполнить в событии Load() формы (пока эти объекты ещё не создавались). • Наличие класса Container позволяет применить модульный подход при программировании с использованием классов, - так называемое агрегирование Именование переменных 1(2) Область видимости: Типы переменных: Типы полей: Scope l p g t Description Local Private (default) Public (global) Parameter Example lnCounter pnStatus gnOldRecno tnRecNo Type a c y d t b f l n o u Description Array Character Currency Date Datetime Double Float Logical Numeric Object Unknown Example aMonths cLastName yCurrentValue dBirthDay tLastModified bValue fInterest lFlag nCounter oEmployee uReturnValue Type c d t b f g l m y n I Description Character Date Datetime Double Float General Logical Memo Currency Numeric Integer Example Customer.cLastName Customer.dBirthDay Customer.tLastMod Customer.bRate Customer.fValue Customer.gPicture Customer.lSellMail Customer.mComments Customer.yYearTDate Customer.nItems Customer.iCustID Именование переменных 1(2) Элементы/Классы: Prefix acd chk cbo cmd cmg cnt ctl <user-defined> edt frm frs grd grc grh hpl img lbl lin lst olb ole opt opg pag pgf prj sep shp spn txt tmr tbr Object ActiveDoc CheckBox ComboBox CommandButton CommandGroup Container Control Custom EditBox Form FormSet Grid Column Header HyperLink Image Label Line ListBox OLEBoundControl OLE OptionButton OptionGroup Page PageFrame ProjectHook Separator Shape Spinner TextBox Timer ToolBar Example acdHomePage chkReadOnly cboEnglish cmdCancel cmgChoices cntMoverList ctlFileList user-defined edtTextArea frmFileOpen frsDataEntry grdPrices grcCurrentPrice grhTotalInventory hplHomeURL imgIcon lblHelpMessage linVertical lstPolicyCodes olbObject1 oleObject1 optFrench opgType pagDataUpdate pgfLeft prjBuildAll sepToolSection1 shpCircle spnValues txtGetText tmrAlarm tbrEditReport Доступ к классам Из проекта приложения: … или пункт меню Tools\Class Browser[Component Gallery] Манипулирование классами из Project Manager Добавить библиотеку в проект Добавить новый класс как производный от указанного: Название производного Выбор библиотеки и название базового класса В какую библиотеку записываем «Перетащив и отпустив»: Вы можете скопировать класс из одной библиотеки в другую, как в рамках одного проекта , так и между проектами Предупреждение: Все библиотеки, используемых классов, должны быть помещены в проект при неявном использовании классов. Class Browser +CTRL Копирование Перемещение класса Типы файлов: Фильтр названия класса DO (_BROWSER) WITH […] Class Browser: панель команд • • • • • • • • Class Browser <-> Component Gallery Открыть новый файл Добавить новый файл Просмотр исходного кода класса Найти класс Создать новый класс Переименовать класс У активного класса – переопределить родительский класс на выбранный • Уничтожить записи, помеченные как удалённые Команды работы с классами в библиотеках классов • Создание библиотеки: CREATE CLASSLIB ClassLibraryName • Создание класса: CREATE CLASS ClassName | ? [OF ClassLibraryName1 | ?] [AS cBaseClassName [FROM ClassLibraryName2]] [NOWAIT] • Добавление/копирование: ADD CLASS ClassName [OF ClassLibraryName1] TO ClassLibraryName2 [OVERWRITE] • Удаление класса: REMOVE CLASS ClassName OF ClassLibraryName • Переименование класса: RENAME CLASS ClassName1 OF ClassLibraryName TO ClassName2 Для перемещения следует последовательно выполнить сначала копирование, затем удаление DEFINE CLASS ClassName1 AS ParentClass [OLEPUBLIC] [[PROTECTED | HIDDEN PropertyName1, PropertyName2 ...] [Object.]PropertyName = eExpression ...] [ADD OBJECT [PROTECTED] ObjectName AS ClassName2 [NOINIT] [WITH cPropertylist]]... [[PROTECTED | HIDDEN] FUNCTION | PROCEDURE Name[_ACCESS | _ASSIGN] | THIS_ACCESS [NODEFAULT] cStatements [ENDFUNC | ENDPROC]]... ENDDEFINE Предупреждения: • • • • • • Класс, помещённый в проект компилируется целиком и отсутствует возможность отключить (Exclude) от компилирования некоторого подмножества классов такой библиотеки. Т.е., если библиотека содержит классы, не используемые непосредственно в данном приложении, они всё равно помещаются в код исполняемого файла. Поэтому следует тщательно планировать состав библиотек. При использовании библиотек, не помещённых в проект, необходимо их присутствие на диске во время выполнения. Нужно очень сильно подумать прежде чем решиться на переименование и тем более удаление классов из библиотек. При переименовании Class Browser учтёт это для всех производных классов, открытых в данный момент в нём, но есть ли гарантия, что во время переименования точно все библиотеки, использующие пер именуемый класс открыты? Заведите себе за правило: всегда создавать дополнительную резервную копию перед попыткой выполнения переименования и/или удаления класса. Если всё-таки горе случилось, вспомните, что библиотека храниться в виде обычного dbf-файла. Не допускайте перемещения библиотек и используемых в них ресурсов из одних каталогов в другие, помните о межбиблиотечных связях, и используемых внешних элементов (ресурсов): иконок, include-файлов, и т.д, которые станут «без определённого места жительства». Следует исключит какое-либо копирование классов из одной библиотеки в другую. Поскольку, в конечном счёте, это приведёт к потере контроля над используемыми классами. Используйте технику создания производных классов, особенно классов, входящих в состав FFC, никогда не изменяйте их непосредственно. Помните, что Microsoft регулярно обновляет эту библиотеку, выпуская её новы версии. Регулярно выполняйте резервное копирование Ваши библиотек. Выделите должность администратора библиотек, который бы отвечал за их рабочее состояние. Галерея компонент Перемещение компонента реакция зависит от типа компонента •добавляет компонент •добавляет библиотеку •открывает на ред. DO (_GALLERY) - из кода Галерея компонент Галерея компонент Галерея компонент Редактор классов (Class Designer) Редактор классов (Class Designer) Редактор классов (Class Designer) Список команд и функций, связанных с ООП • • • • • • • • • • • • • • • • • • • • • @ ... CLASS Command _WIZARD System Memory Variable ACLASS( ) Function ADATABASES( ) Function ADBOBJECTS( ) Function ADD CLASS Command AddProperty Method AGETCLASS( ) Function AINSTANCE( ) Function AMEMBERS( ) Function AMOUSEOBJ( ) Function ASELOBJ( ) Function AVCXCLASSES( ) Function _BUILDER System Memory Variable COMCLASSINFO( ) Function COMPOBJ( ) Function CREATE CLASS Command CREATE CLASSLIB Command CREATEOBJECT( ) Function DEFINE CLASS Command DISPLAY OBJECTS Command • • • • • • • • • • • • • • • • GETOBJECT( ) Function GETHOST( ) Function ISHOSTED( ) Function LIST OBJECTS Command LOADPICTURE( ) Function MODIFY CLASS Command NEWOBJECT( ) Function NewObject Method RELEASE CLASSLIB Command REMOVE CLASS Command RENAME CLASS Command SAVEPICTURE( ) Function _SCREEN System Memory Variable SET CLASSLIB Command SET OLEOBJECT Command WITH ... ENDWITH Command