Работа с классами в шаблонах

Автор: Lee White
Оригинальное название: Class Wrapper Templates The Easy Way
Дата публикации: 15 августа 2002г.
Дата перевода: 20 июля 2006г.
Уровень: advanced

Краткое описании: автор несколько стебно и опуская подробности рассказывает о том как реализовать стандартную закладку Classes, которую можно увидеть в стандартных шаблонах ABC, а также сгенерировать точки вставки для виртуальных методов вашего собственного класса.

Рисунок 1 – Закладка Classes в стд WindowManager-е

Как настоящий процедурный hand кодер и шаблонописатель, я никогда не задумывался, особенно в шаблоне, о классах. Изредка, общеизвестный «свет в конце туннеля» мерцал где-то вдали, но никогда надолго не задерживался, до сегодняшнего момента! Итак, со скромным количеством света, я напрягся и написал мой первый класс, да мой первый класс, только для того, чтобы столкнуться лицом к лицу со страшной задачей написания обертки (wrapper — враппер) для этого класса.

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

Текущее (Clarion 5.5) воплощение ABC шаблонов делает процесс создания враппера достаточно легким и точным, без перегрузки большим количеством кода, который требовался ранее. В конце я предоставлю вам результаты многочасового копания и усердного тестирования с многочисленными испытаниями и множеством ошибок. Но елки, это ведь это наша жизнь, так?!

Сокращая объем шаблонов

Большая масса шаблонного кода была необходима ранее для генерации точек вставки (embeds) и наследуемых методов класса. Что ж, теперь это не требуется. Весь этот код был заменен одним вызовом в существующих ABC-шаблонах.

Файл ABGROUP.TPW содержит группу %GenerateVirtuals. Это группа является ключевой для устранения большого количества кода, который был необходим для написания враппера для класса. Эта группа принимает 4 параметра и обрабатывает громадное большинство монотонной работы за вас.

Параметры по порядку: имя класса, описание дерева точек вставки, имя локально определенной группы (о ней позднее), и необязательный параметр используемый специально для глобальной генерации объекта. Этот необязательный параметр, по умолчанию равный %FALSE, изменяет прототип новых методов, наследуемых от вашего базового класса, избегая использование %ActiveTemplateInstance. Для тех кому интересно, %ActiveTemplateInstance это имеющий множество значений (multi-valued) шаблонный символ, который содержит уникальное значение для каждого экземпляра вашего шаблона. Это обеспечивает механизм обращения каждого индивидуального экземпляра шаблона к сгенерированному коду и другими шаблонами.

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

Код

Первой строкой моего кода в моем враппере класса идет подключение шаблонной ABC группы:

#INSERT(%OOPPrompts(ABC))

Это добавляет скрытые prompt-ы, требуемые вызовами шаблона. Тут я должен на самом деле остановиться и объяснить подробно что подключается, но я не хочу. Если вы хотите разобраться сами, то вы можете найти эту группу в файле ABGROUP.TPW (ABOOP.TPW для C6). Будет достаточным сказать, что это необходимо – не парьтесь!

Секция PREPARE подключает вызовы, которые должны выполнять корректное отображение propmpt-ов в вашем шаблоне.

#PREPARE
   #CALL(%ReadABCFiles(ABC))
   #CALL(%SetClassItem(ABC),'MyClass'&%ActiveTemplateInstance)
   #CALL(%SetOOPDefaults(ABC),'MyClass''&%ActiveTemplateInstance,'cMyClass')
#ENDPREPARE

Секция ATSTART является копией секции PREPARE и выполняет такие же вызовы, так как секция PREPARE используется только при доступе к prompt-ам шаблона, а ATSTART секция предшествует реальной кодогенерации.

#ATSTART
   #CALL(%ReadABCFiles(ABC))
   #CALL(%SetClassItem(ABC),''MyClass'&%ActiveTemplateInstance)
   #CALL(%SetOOPDefaults(ABC),'MyClass''&%ActiveTemplateInstance,'cMyClass')
#ENDAT

#CALL(%ReadABCFiles(ABC)), очищает связку шаблонных символов, которые затем перегружаются через вызов #SERVICE в C55TPLS.DLL. Что происходит там — кто бы знал!

#CALL(%SetClassItem(ABC)проверяет экземпляр класса определенного шаблоном.

#CALL(%SetOOPDefaults(ABC)устанавливает значения по умолчанию для базового класса и имени объекта.

Это возможно даже больше, чем вам в необходимости надо знать, но сейчас уже поздно метаться.

Секция SHEET определяет внешний вид шаблона, включая имя класса, код (ABC или другой), методы и свойства и прочее, все автоматически сгенерированно с использованием ABC группы:

#SHEET
   #TAB('Setup')
      #DISPLAY('Your template prompts')
   #ENDTAB
   #TAB('Class')
      #WITH(%ClassItem,'MyClass'&%ActiveTemplateInstance)
         #INSERT(%ClassPrompts(ABC))
      #ENDWITH
   #ENDTAB
#ENDSHEET

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

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

#AT(%GatherObjects)
   #CALL(%ReadABCFiles(ABC))
   #CALL(%SetClassItem(ABC),''MyClass'&%ActiveTemplateInstance)
   #ADD(%ObjectList,%ThisObjectName)
   #SET(%ObjectListType,'cMyClass')
#ENDAT

Ну, эта секция кода должна быть легче для понимания. Эти «gathers objects» (дословно «накопительные объекты») используются для генерации кода. Следующая секция делает вызов, который записывает определение вашего класса в процедуру:

#AT(%LocalDataClasses)
   #CALL(%SetClassItem(ABC),''MyClass'&%ActiveTemplateInstance)
   #INSERT(%GenerateClassDefinition(ABC),%ClassLines)
#ENDAT

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

ОК, неплохо да? И прямо сейчас:

#AT(%ProcedureRoutines)
   #CALL(%GenerateVirtuals(ABC),
   ''MyClass'&%ActiveTemplateInstance,
   ''Local Objects|Abc Objects|MyClass Description',
   ''%EmbedVirtuals(MyTemplateSet)')
#ENDAT

Это может не выглядеть так, но в этих строках заключается красота этого враппера. Это не является огромным куском шаблонного кода для получения того, что вы хотите. Опуская обязательные AT и ENDAT, одна строка шаблонного кода обрабатывает генерацию всех наследуемых методов! Это все! Одна строка для замены массы кода, необходимого в ранних версиях ABC! И снова у вас может возникнуть хорошая идея заглянуть, что же скрывается за этой строкой, в ABGROUP.TPW.

Осталось три секции до завершения враппера. Первая, я думаю, обшая для всех врапперов. Последние две, думаю нет.

#AT(%MyClassMethodCodeSection,%ActiveTemplateInstance)
           ',PRIORITY(5000),DESCRIPTION('Parent Call')',WHERE(%ParentCallValid())
   #CALL(%GenerateParentCall(ABC))
#ENDAT

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

Последние две секции это просто две локальные группы, которые необходимы врапперу:

#GROUP(%EmbedVirtuals,%TreeText,'%DataText,%CodeText)
#EMBED(%MyClassMethodDataSection,
       ''MyClass Description Method Data Section'),
       '%ActiveTemplateInstance,%pClassMethod,
       '%pClassMethodPrototype,LABEL,DATA,
       'TREE(%TreeText&%DataText)
#?CODE
#EMBED(%MyClassMethodCodeSection,
       ''MyClass Description Method Code Section'),
       '%ActiveTemplateInstance,%pClassMethod,
       '%pClassMethodPrototype,TREE(%TreeText&%CodeText)

Эти четыре строки определены в группе вызываемой %GenerateVirtuals с параметром %EmbedVirtuals(MyTemplateSet). Это важный кусочек паззла, так как эта группа создает точки вставки для ваших методов!

И напоследок, эта группа вызывается из секции, которая создает вызов родительского метода PARENT.Method:

#GROUP(%ParentCallValid),AUTO
   #DECLARE(%RVal)
   #CALL(%ParentCallValid(ABC)),%RVal
#RETURN(%RVal)

Используя локальную группу, которую можно вызвать непосредственно из выражения WHERE, вы можете отделить ее от других групп, также необходимых в вашем враппере. Так как WHERE(), в данном случае, не может включать требуемые (set) параметры для вызова группы, которая расположена в отдельном наборе шаблонов, например ABCHAIN.TLP, есть только одна альтернатива – продублировать эту внешнюю группу, и все последующие вызовы группы делать через CALL, так как CALL может использовать параметры.

Это все. Это весь враппер.

Вы можете загрузить снабженную комментариями копию ниже. Я рекомендую использовать ее как стартовую позицию для ваших собственных оберток классов.

Некоторые подробности

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

Если вы посмотрите на подключение базового класса, вы заметите иногда встречающуюся последовательность !, EXTENDS следующую за прототипом метода. Это не комментарий! Это флаг для IDE для включения не виртуальных методов, используя текущий Кларион-шаблон и очевидно, это враппер.

Итак, если у вас есть не виртуальные методы, к которым вы хотите получить доступ в генерируемом коде, добавьте !, EXTENDS где-нибудь после описания прототипа метода. Этот текст не нуждается в специальном выравнивании. Он просто должен быть в той же строке, что и прототип. Например:

MethodName PROCEDURE() !,EXTENDS and any other comments if desired

Есть еще один момент для запоминания при написании шаблона для класса.
Всякий раз, когда вы определяете секцию шаблона для генерации исходного кода вне наследуемого кода, вам необходимо фиксировать (FIX) %ClassItem и использовать %ThisObjectName для вызовов метода классов и ссылки на свойства класса. Допустим, что ваш объект имеет имя MyClass3, вам необходимо зафиксировать переменную %ClassItem:

#FIX(%ClassItem,'MyClass'&%ActiveTemplateInstance)

В вашем шаблоне, вы можете ссылаться на объект так:

%ThisObjectName.Kill

Сгенерированный код будет выглядеть как:

MyClass3.Kill

Результатом использования этой комбинации будет правильное имя объекта, введенное на закладке Class. Игнорирование этой техники приведет к проблемам при смене имени объекта. Не будьте ленивыми, это все лишь одна строка шаблонного кода сохранит вас от часов печали.

А теперь вперед, обернем классы!

Скачать