Скрипт ================================ Синтаксис скриптовых операций ----------------------------- +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+-------------------+-----------------+-----------------+ | Описание ограничения разбора текста операции | **Выполнение 4X** | **Выполнение 5X** | **Проверка 4X** | **Проверка 5X** | +===============================================================================================================================================================================================================================================================================================+===================+===================+=================+=================+ | Проверка, что все блоки закрыты (все открывающие теги имеют соответствующие им закрывающие теги) | √ | √ | √ | √ | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+-------------------+-----------------+-----------------+ | Между тегами могут быть только пробельные символы и Xml-коментарии (). Исключение составляют пары языковых тегов (открывающий и закрывающий) | | | | √ | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+-------------------+-----------------+-----------------+ | Проверка наличия блока с кодом для исполнения - в случае Pascal-операции - блока | | | √ | √ | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+-------------------+-----------------+-----------------+ | Проверка блоков на уникальность. Наличие двух(и более) блоков Pascal, или нескольких одноимённых SQL-блоков. Если проверка не выполняется - берётся первый в порядке следования их в тексте. Если есть неиспользующиеся блоки - предполагается их коментировать Xml-комментарием. | | | | √ | +-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-------------------+-------------------+-----------------+-----------------+ Условная компиляция ~~~~~~~~~~~~~~~~~~~ Условная компиляция (Первичная реализаци) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Основные положения '''''''''''''''''' Условная компиляция текста паскаль операций необходима при написании кода, рассчитанного для выполнения в различных окружниях, или различных версиях окружений. Синтаксически анализатор должен поддерживать следующие выражения: .. code-block:: delphi {$IFDEF MY_DEFINE} {$ENDIF} {$IFDEF MY_DEFINE} {$ELSE} {$ENDIF} {$IFNDEF MY_DEFINE} {$ENDIF} {$IFNDEF MY_DEFINE} {$ELSE} {$ENDIF} {$IF MY_DEFINE} {$ENDIF} // Если MY_DEFINE отсутствует, услове равно FALSE. // Если MY_DEFINE = null, услове равно FALSE. // Если MY_DEFINE = False (строка) или 0, услове равно FALSE. // Если MY_DEFINE = True (строка) или 1, услове равно TRUE. // Если MY_DEFINE = {Иное значение}, предкомпиляци выдаёт ошибку {$IF MY_DEFINE > 2.0.0.1} {$ENDIF} {$IF MY_DEFINE > 2.0.0.1} {$ELSE} {$ENDIF} Условия предкомпиляции могут быть вложенными. * Директивы компиляции указываются глобально, при старте приложения. * Каждая директива компиляции является парой ключ-значение. Директива может иметь значение null, что равносильно отсутсвию значения. * Разработчик предметной области не может управлять значениями директив в процессе выполнения приложения. * Предполагается, что в список директив компиляциии будет настраиваться для модулей предметной области и загружаться из БД при старте приложения. Правила именования директив ''''''''''''''''''''''''''' * Имена директив регистроНЕзависимы * Имена могут содержать алфавитные символы, числа, символ '\_' * Имена не могут содержать пробелов * Имя должно начинаться с алфавитного символа Правила сравнения значений директив ''''''''''''''''''''''''''''''''''' Директива может иметь следующие типы значений: #. Нет зачения #. Число дробное или целое: 1000 , 200.5 , 0.5 #. Строка: 'Любаястрока' #. № версии: a , a.b.c.d  , где a,b,c.d целые числа. Формат ввода: a.[b].[c[.[d]]] Возможны следующие условия сравнения: < , >, = , <>, >=, <= Директива не имеющая значения, приравнивается к отсутствующей. Выражения типа {$IF MY\_DEFINE > 2.0.0.1} вычисляются по следующим правилам: #. MY\_DEFINE не существует. (MY\_DEFINE > 2.0.0.1) == False #. MY\_DEFINE существует и равно Null. (MY\_DEFINE > 2.0.0.1) == False #. MY\_DEFINE существует и равно 1.0.2. (MY\_DEFINE > 2.0.0.1) == False #. MY\_DEFINE существует и равно 4.0.2. (MY\_DEFINE > 2.0.0.1) == True Системные директивы ''''''''''''''''''' При старте приложения в окружение регистриует следующие директивы * Web - не имеет значения. Регистрируется при запуске Web-окружения  * WebVer - Версия web-окружения. Регистрируется при запуске Web-окружения * Desktop - не имеет значения. Регистрируется при запуске desktop-окружения  * DesktopVer - Версия desktop-окружения. Регистрируется при запуске desktop-окружения Примеры ''''''' 1. Проверка версии десктопного приложения. .. code-block:: delphi   {$IF DesktopVer <= 4..}     ShowMessage( 4 );   {$ELSE}              ShowMessage( 5 );   {$ENDIF} Используемые определения '''''''''''''''''''''''' - Desktop Приложение - DesktopVer - версия приложения, тип данных - версия - DesktopBuild - номер сборки приложения, тип данных - целое. Тоже значение что и в 4-й позиции версии (DesktopVer) Sqlj ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Вместо ExecSql и ExecSqlEx. Когда размер паскаль-скрипта переваливает за два экрана становится неудобно переходить от вызова к тексту sql и обратно. Так же при большом количестве входных\\выходных параметров теряется связь между названием, типом и значением параметра и вероятность опечаток сильно увеличивается. Эти недостатки можно исправить использованием стандарта `SqlJ `__ при вызове sql из паскаль-операций. Примеры ^^^^^^^ +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | ExecSql и ExecSqlEx | SqlJ | +================================================================================================================+====================================================================================================+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | | // Найти значение свойства EnableRefService | | [out bprefsrv] | z := #ExecSQL{ | | begin | begin | | :bprefsrv := nvl(BTK_ClientGate.GetClassProperty(:idpClass, BTK.EnableRefService), 1); | :bprefsrv := nvl(BTK_ClientGate.GetClassProperty(:idpClass, BTK.EnableRefService), 1); | | end; | end; | | | } using { | | | var bprefsrv:ftFloat = 0 | | | val idpClass:ftFloat = GetVar('IDCLASS') | | // Найти значение свойства EnableRefService | }; | | z := ExecSQLEx('refsrv', 'bprefsrv;idpClass', [ftFloat, ftFloat], [0, GetVar('IDCLASS')]); | AddVar('EnableRefService#', z.bprefsrv, ftFloat); | | AddVar('EnableRefService#', z[0], ftFloat); | | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | | selection.execopscript('INSERTITEM_Before'); | | [OUT id] | #ExecSQL{ | | begin | begin | | :id := STK_StockAPI.INSERTITEMEX(:idParent#,:IDPStockType#); | :id := STK_StockAPI.INSERTITEMEX(:idParent#, :IDPStockType#); | | if :idParent# is null then | if :idParent# is null then | | STK_StockAPI.SetidDepartment( :id, :FILTER$FLT_IDDEPOWNER ); | STK_StockAPI.SetidDepartment(:id, :FILTER$FLT_IDDEPOWNER); | | end if; | end if; | | end; | end; | | | } using { | | | var id out | | | }; | | selection.execopscript('INSERTITEM_Before'); | if SelfVarExists('idItem#') then SetVar('idItem#', getVar('id')) | | ExecSQL('ins'); | else AddVar('idItem#', getVar('id'), ftFloat); | | if SelfVarExists('idItem#') then | Setvar('idParent', GetVar('idParent#')); | | SetVar('idItem#', getVar('id')) | | | else AddVar('idItem#', getVar('id'), ftFloat); | | | Setvar('idParent', GetVar('idParent#')); | | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | | #ExecSQL{ | | begin | begin | | STM_SettlementDocApi.CheckTransBeforeReturnToCreate(:id, :idState); | STM_SettlementDocApi.CheckTransBeforeReturnToCreate(:id, :idState); | | end; | end; | | | }; | | | | | | | | ExecSql('CheckTransBeforeReturnToCreate'); | | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | | #ExecSQL{ | | begin | begin | | STM_InvoiceBillApi.DoChangeStateForInvoiceBill(:idSelf, :npStVal); | STM_InvoiceBillApi.DoChangeStateForInvoiceBill(:idSelf, :npStVal); | | end; | end; | | | } using { | | | val npStVal:ftFloat = 0 | | | val idSelf:ftFloat = GetVar('id') | | ExecSQLEx('DoChangeIBState','npStVal;idSelf',[ftFloat,ftFloat],[0,GetVar('id')]); | }; | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | | v := #ExecSQL{ | | [out npRes] | begin | | begin | :npRes := STM_InvoiceBillApi.DoChangeStateForInvoiceBill(:idSelf, :npStVal); | | :npRes := STM_InvoiceBillApi.DoChangeStateForInvoiceBill(:idSelf, :npStVal); | end; | | end; | } using { | | | var npRes:ftFloat = 0 | | | val npStVal:ftFloat = 0 | | | val idSelf:ftFloat = GetVar('id') | | v := ExecSQLEx('DoChangeIBState','npRes;npStVal;idSelf',[ftFloat,ftFloat,ftFloat],[0,0,GetVar('id')]); | }; | | | | | if v[0] = 1 then showmessage(1); | if v.npRes = 1 then showmessage(1); | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | .. code-block:: delphi | .. code-block:: delphi | | | | | | | | args := CreateArgs(); | v = #sql{ | | args.sSqlText := 'select t.num from btk_solidseq t where t.num < :npMaxNum order by t.num'; | select t.num from btk_solidseq t where t.num < :npMaxNum order by t.num | | args.aParam := [['npMaxNum',ftFloat,10]]; | } using { | | ovCur := libs['SEL_BTS_SqlCursorLib'].Execute('getSqlCursor', args); | val npMaxNum:ftFloat = 10 | | | }; | | if not ovCur.IsEmpty then | while not v.Eof do | | repeat | begin | | showMessage(ovCur.GetSelfVar('num')); | showMessage(v.rec.num); | | until not ovCur.NextRecord; | | | | v.Next; | | | end; | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ | | .. code-block:: delphi | | | | | | | | | #SelectSQl{ | | | select t.num from btk_solidseq t where t.num = :npMaxNum | | | } into { | | | nvNum:ftFloat = $num | | | } using { | | | val npMaxNum:ftFloat = 10 | | | }; | | | showMessage(nvNum); | | | | +----------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------+ Реальные примеры ^^^^^^^^^^^^^^^^ На базе devBTK в редакторе операций выборки в меню под молоточками добавлена операция "Сравнение ExecSql и SqlJ". Если ее вызвать, откроется двухпанельный редактор: слева обычный скрипт, справа конвертированный в sqlj. Скрипт слева можно менять и после нажатия на кнопку обновить обновится конвертированная версия. Асинхронное выполнение операций ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Есть возможность запускать операции на выполнение асинхронно. Для этого используться метод :class:`InternalLibrary.TbtkScriptSelectionObject.AsyncExecOpScript`. Рассмотрим пример. Есть операция *Op1*, которая принадлежит выборке *Selection1*. В операции есть вызов ``Selection.ExecOpScript('Op2')``. В данном случае, до тех пор пока *Op2* не вернет управление, выполнение *Op1* не продолжится. Это накладывает определенные ограничения на те действия, которые можно выполнять в *Op2*. Например, нельзя удалить выборку *Selection1*. Если запустить выполнения *Op2* асинхронно: ``Selection2.AsyncExecOpScript('Op2')`` , то управление сразу вернется в *Op1*. Выполнение *Op2* начнется только после того, как завершится *Op1* (и весь стек вызовов, который привел к вызову *Op1*). Также добавлен новый параметр в функцию ``CreateFormEx(...['UseActiveForm'],[1])``. Если он установлен в 1, то создание новой формы не произойдет. Вместо этого выборка откроется на активной форме. Все это нужно для решения следующей задачи. Есть MDI список, из которого открывается MDI карточка. На карточке есть операции перемещению по списку. При этом объекты в списке могут быть разных типов и соответственно должны отображаться в разных карточках. Сделать это при помощи синхронных вызовов невозможно, т. к. при уничтожении карточки, уничтожаются все операции выборки (в том числе и операция перехода на следующую запись). Достичь нужного результата можно при помощи асинхронного запуска. Для этого в карточке в операции перемещения на следующую запись пишется следующий код: .. code-block:: delphi S := Selection.Master; //находим выборку-список S.PriorRecord; if {здесь определяем, что нужно будет сменить карточку} then begin S.AddVar('UseActiveForm', 1, ftInteger); //имя переменной может быть любым S.AsyncExecOpScript('CardEdit'); // операция открытия карточки запускается асинхронно end else Selection.Refresh; В выборке-списке в операции открытия карточки пишется: .. code-block:: delphi CreateFormEx(...['UseActiveForm'], [GetVar(UseActiveForm)]); AddVar('UseActiveForm', 0); Шина событий ~~~~~~~~~~~~~ Основные положения ^^^^^^^^^^^^^^^^^^ - Шина событий предназначена для обмена сообщениями между выборками, в том числе не связанными отношением мастер-деталь. - Шина событий является единой для всего приложения. - Шина событий позволяет обеспечивать взаимодействие между выборками по принципам: один к одному, один ко многим, многие к одному и многие ко многим. - Шина событий обеспечивает только однонаправленное взаимодействие, предназначенное, прежде всего, для оповещения о происходящих событиях и передачи связанных с этими событиями каких-либо данных. - У каждого события передаваемого по Шине событий может быть как множество обработчиков, так и не быть ни одного обработчика вовсе. - Обработчики событий не могут прерывать, отменять или изменять порядок вызова других обработчиков этих событий. - Порядок вызова обработчиков событий не зависит от порядка их регистрации или каких-либо свойств обработчиков, и не может изменяться каким-либо образом. При отправке событий по Шине необходимо быть уверенным в том, что порядок вызова обработчиков не играет никакой роли, и не будет сказываться на работоспособности приложения. - Возникновение исключений в обработчиках события не прерывает процесс вызова других обработчиков этого события. - Шина событий не предоставляет возможности получить информацию об обработчиках подписанных на какое-либо событие и их количестве. Событие ^^^^^^^ Для удобства событие представлено в виде отдельного объекта класса :class:`InternalLibrary.TbtkScriptSelectionEventObject`, передаваемого в обработчики событий. Экземпляр этого объекта автоматически создаётся для каждого события, и автоматически разрушается после обработки во всех обработчиках. Свойства события '''''''''''''''' - Name(String), - системное имя события. Указывается при отсылке. - Args(TbtkNamedArg), - набор именованных аргументов данного события. Может быть указано при отсылке. - TopicName(String), - имя подписки. Используется для персонализации получателя. Указывается при отсылке. - SelectionId(String), - ID выборки, из которой отправили сообщение. Присваивается автоматически. - EntityName(String), - системное имя выборки, из которой отправили сообщение. Присваивается автоматически. - Representation(String), - отображение выборки, из которой отправили сообщение. Присваивается автоматически. Все свойства события доступны только на чтение. .. attention:: Внимание!!! Значения аргументов свойства Args, доступны на запись. Это связано с особенностями реализации этого свойства. Настоятельно не рекомендуется использовать эту особенность в прикладной логике, т.к. изменения этих аргументов могут повлиять на работу ещё не вызванных обработчиков этого события. Кроме того поддержка описанного поведения не гарантируется с выходом новых версий Global. Обработчики событий ''''''''''''''''''' Обработка всех событий передаваемых по Шине осуществляется в операции выборки ``HandleEvents``. При вызове, в операцию передаётся единственный аргумент, - объект события, класса TbtkScriptSelectionEventObject. Пример обработчика события: .. code-block:: delphi case Event.Name of 'Event1': begin //какие-либо действия end; 'Event2': begin //какие-либо действия end; else begin //какие-либо действия end; end; Подписка на событие ''''''''''''''''''' Для обработки событий необходимо подписать выборку на соответствующие события. Делается это вызовом методов Шины Subscribe и Hook. .. code-block:: delphi :caption: Подписка по топику(subscribe) EventBus.Subscribe(Selection, 'TopicName'); //подписка на событие с указанием топика. .. code-block:: delphi :caption: Подписка без топика(hook) EventBus.Hook(Selection); //подписка на все события передаваемые по шине. Фильтрация событий ''''''''''''''''''' При подписке, для более точного определения набора обрабатываемых событий, можно передавать параметры фильтрации. В качестве параметров фильтрации используются следующие свойства события: EventName, TopicName, SelectionId, EntityName и Representation. .. code-block:: delphi EventBus.Subscribe(Selection, 'TopicName', CreateArgs(['EventName', 'TestEvent', 'EntityName', 'Sel_XXX'])); //подписка на событие с дополнительными условиями фильтрации. EventBus.Hook(Selection, CreateArgs(['EventName', 'TestEvent', 'EntityName', 'Sel_XXX'])); //подписка на все события, ограниченные фильтром. .. attention:: Внимание!!! До версий 5.0.6 и 5.1 имена параметров фильтрации были не регистрозависимы, а их значения были регистрозависимы. До этих версий, если выборка была подписана на событие с именем "EventTest", то она не могла обработать событие с именем "EVENTTEST". При отправке сообщения по шине параметры фильтров EntitiName, и Representation всегда должны были быть указаны в верхнем регистре. Значение топика должно было указываться в том-же регистре что и при подписке. Начиная с версий 5.0.6 и 5.1 И имена и значения параметров фильтрации не регистрозависимы. Отписка от событий '''''''''''''''''' Для того что-бы отписываться от событий используются методы Unsubscribe и Unhook, в которые должны передаваться такие-же параметры что были переданы при подписке. .. code-block:: delphi EventBus.Unsubscribe(Selection, 'TopicName'); //отписка от события без дополнительных условий фильтрации. EventBus.Unsubscribe(Selection, 'TopicName', CreateArgs(['EventName', 'TestEvent', 'EntityName', 'Sel_XXX'])); //подписка от события с дополнительными условиями фильтрации. EventBus.Unhook(Selection); //отписка от всех событий передаваемых по шине. EventBus.Unhook(Selection, CreateArgs(['EventName', 'TestEvent', 'EntityName', 'Sel_XXX'])); //отписка от всех событий, ограниченные фильтром. Отправка события ^^^^^^^^^^^^^^^^ Синхронная '''''''''' Вызов обработчиков событий отправленных синхронно выполняется сразу при отправке. .. code-block:: delphi :caption: Пример синхронной отправки: EventBus.Send('TopicName', 'EventName');//отправка события без аргументов EventBus.Send('TopicName', 'EventName', CreateArgs(['Arg1','Value1','Arg2','Value2']));//отправка события с аргументами Асинхронная ''''''''''' Вызов обработчиков событий отправленных асинхронно, откладывается и выполняется с помощью `асинхронных команд <Асинхронное_выполнение_команд>`__. .. code-block:: delphi :caption: Пример асинхронной отправки: EventBus.Post('TopicName', 'EventName');//отправка события без аргументов EventBus.Post('TopicName', 'EventName', CreateArgs(['Arg1','Value1','Arg2','Value2']));//отправка события с аргументами Клиентские библиотеки ~~~~~~~~~~~~~~~~~~~~~ В данном разделе описание клиентские библиотек методов, способов их использования и вариантов использования. Клиентскими библиотеками являются выборки который загружаются один раз при работе клиента и предоставляют доступ к своим методом из паскаль скрипта других выборок. Общие положения ^^^^^^^^^^^^^^^ Основой клиентской библиотеки является выборка, по-этому в качестве библиотеки может служить любая выборка системы, а библитечным методом является любая операция этой выборки. Имя библиотеки состоит из *Имя выборки* или *Имя выборки.Отображение*. Перед использованием библиотеки, её необходимо загрузить в память приложения, после чего она будет доступна из любой операции в приложении. Загружка библиотеки осуществляется методом ``LoadLib(LibraryName)`` или ``Libs[LibraryName].`` Выгрузка библиотеки осуществляется методом  ``UnLoadLib(LibraryName)`` или очисткой кэша метаданных. Библиотечные методы: - выполняются в контексте выборки, из которой был произведён вызов (т.е. ``GetVar`` и ``SetVar`` будут обращаться не к библиотечной выборке, а к вызывающей). - могут содержать SQL-блоки (выполняются в сессии вызывающей выборки). - могут открывать дочерние окна (выборки дочерних окон, будут деталями к вызывающей выборке). - могут принимать входящие параметры (список входящих параметров указывается в открывающем теге ). - могут возвращать результат (установите значение переменной *Result*). Пример библиотечного метода: '''''''''''''''''''''''''''' .. code-block:: delphi ShowMessage('Вызван метод "TestOperation" из библиотеки "SEL_ClientLibraryInstance"'); Result := 'Результат вложенной функции.'+ #10#13 +' A := ' + VarToStr(A) + #10#13 +' B := ' + VarToStr(B) + #10#13 +' C := ' + VarToStr(C) + #10#13 ; Примеры обращения к библиотечному методу: ''''''''''''''''''''''''''''''''''''''''' 1. .. code-block:: delphi res := Libs['SEL_ClientLibraryInstance'].Execute('TestOperation', [1,2,3]); ShowMessage(VarToStr(res)); 2. .. code-block:: delphi lib := Libs['SEL_ClientLibraryInstance']; res := lib.Execute('TestOperation', [1, 2, 3]); ShowMessage(VarToStr(res)); Передача аргументов в библиотечный метод. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Библиотечные методы, как и обычные паскаль операции, могут принимать входящие аргументы. Список входящих аргументов указывается в открывающем теге с тексте операции. .. code-block:: delphi - Параметры перечисляются через запятую. - Имя параметра должно начинаться с алфавитного символа - Имя параметра не должно содержать пробелов - Параметры имеют тип Variant Передача значений аргументов производится путём указания второго параметра при вызове метода ``Execute()`` 1. .. code-block:: delphi res := Libs['SEL_ClientLibraryInstance'].Execute('TestOperation', [1, 2, 3]); 2. .. code-block:: delphi res := Libs['SEL_ClientLibraryInstance'].Execute('TestOperation', 1); В первом примере 3 значения передаются в виде масива значений. Если же передаётся только один аргумент, передавать массив нет необходимости (пример 2). Аргументы методов ''''''''''''''''' Способ передачи именованных аргументов в библиотечный метод """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" В случае большого количества входящих аргументов, удобно использовать именованные параметры или структуры. К сожалению, скриптер не поддерживает именованных параметров, по-этому для передачи именованных параметров предлагается использовать объект, представляющий собой коллекцию пар 'имя - значение'. Модифицируем пример библиотечного метода и способ его вызова с использованием коллекции аргументов. Примеры обращения к библиотечному методу  с использованием коллекции аргументов: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code-block:: delphi // Создаём коллекцию аргументов и инициализируем в ней 3 аргумента args := CreateArgs(['A', 11 ,'B', 22 ,'C', 33]); res := Libs['SEL_ClientLibraryInstance'].Execute('TestOperation', args); ShowMessage(VarToStr(res)); Пример библиотечного метода с использованием коллекции аргументов: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ .. code-block:: delphi // Проверяем список переданных аргументов, на предмет передачи лишних аргументов. args.Validate(['A','B','C']); ShowMessage('Вызван метод "TestOperation" из библиотеки "SEL_ClientLibraryInstance"'); Result := 'Результат вложенной функции.'+ #10#13 +' A := ' + VarToStr(args.A) + #10#13 +' B := ' + VarToStr(args.B) + #10#13 +' C := ' + VarToStr(args.C) + #10#13 ; Коллекции аргументов возможно не только передавать в методы, но и возвращать из методов """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" .. code-block:: delphi // Создаём коллекцию аргументов и инициализируем в ней 3 аргумента Result := CreateArgs(['A', 11 ,'B', 22 ,'C', 33]); Создание коллекции аргументов """"""""""""""""""""""""""""" Создание коллекции аргументов производится с помощью метода ``CreateArgs``. Обращение к элементам коллекции аргументов """""""""""""""""""""""""""""""""""""""""" Обращение к элементам коллекции аргументов реализовано через динамическое разрешение имён (диспечеризацию). Для получения или установки значения аргумента, вы обращаетесь к одноимённому свойству коллекции. .. code-block:: delphi // Создаём коллекцию аргументов. args := CreateArgs(); // В коллекции создаётся аргумент Param1 и ему устанавливается значение 100. args.Param1 := 100; // В коллекции создаётся аргумент MyParam и ему устанавливается значение 200. args.MyParam := 200; // Получаем значение аргумента Param1. v := args.Param1; Отдельно инициализировать аргументы нет необходимости. При обращении к свойству коллекции, аргумент будет создан автоматически. При попытке чтения значения из несуществующего аргумента будет сгенерирована ошибка. Описание классов скриптового языка ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Класс клиентской библиотеки ''''''''''''''''''''''''''' .. code-block:: delphi type TbtkClientLibrary = class; // Функция вызова библиотечного метода function Execute(MethodName: String): Variant; // Функция вызова библиотечного метода с параметрами function Execute(MethodName: String; Values: array of Variant): Variant; function Execute(MethodName: String; Values: array of Variant; Context: IContext): Variant; // Метод возвращает ссылку на скриптовый объекта "Выборка", соответствующий данной библиотеке. function Selection: TbtkScriptSelectionObject; // Имя библиотеки property LibraryName: String; Методы для работы с библиотеками: ''''''''''''''''''''''''''''''''' .. code-block:: delphi // Загружает библиотеку в коллекцию библиотек. Если библиотека уже загружена, кернёт ссылку на ранее загруженную библиотеку. function LoadLib(LibName: String): TbtkClientLibrary; // Выгружает библиотеку из памяти, а так же метаданные соответствующей выборки из кэша метаданных. function UnloadLib(LibName: String): Boolean; // Проверяет, была ли библиотека загружена ранее. function IsLibLoaded(LibName: String): Boolean; // Индексированное свойство, возвращающее ссылку на библиотеку по её имени. Если библиотека не была загружена ранее, будет произведена её загрузка. property Libs[LibName: String]:TbtkClientLibrary; Класс - коллекция аргументов '''''''''''''''''''''''''''' .. code-block:: delphi type TbtkNamedArgs = class; // Метод проверяет коллекцию переданных аргументов на соответствие списку ожидаемых аргументов. Если переданная коллекция будет иметь аргумент, не упомянутый в списке ожидаемых, будет сгенерирована ошибка. procedure Validate(ArgNames: array of String); // Метод инициализирует аргументы значениями по умолчанию. Если значение в коллекции аргументов равно Null, или не существует, оно будет инициализировано переданным в метод значением по умолчанию. // NamesValues - чередующийся массив имён и значений аргументов имеющий следующий вид: [Имя , Значение, Имя, Значение] procedure SetDefaults(NamesValues: array of Variant); // Метод проверяет существование в коллекции аргумента с именем Name.' function Exists(Name: String): Boolean; Методы для работы с коллекциями аргументов '''''''''''''''''''''''''''''''''''''''''' .. code-block:: delphi // Создаёт коллекцию аргументов function CreateArgs(): TbtkNamedArgs ; // Создаёт коллекцию аргументов и инициализует элементы коллекции // NamesValues - чередующийся массив имён и значений аргументов имеющий следующий вид: [Имя , Значение, Имя, Значение] function CreateArgs( NamesValues: array of Variant): TbtkNamedArgs ; // Создаёт коллекцию аргументов и инициализует элементы коллекции // Names - массив имён аргументов. // Values - массив значений аргументов. function CreateArgs( Names: Array of String; Values: Array of Variant): TbtkNamedArgs ; Глобальные параметры приложения ------------------------------- Скриптовые методы получения версии клиента ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +--------------------------------------------------+---------------------------+-------------------------+ | Название метода | Описание | Пример | +==================================================+===========================+=========================+ | GetApplicationVersion | Полная версия клиента | 4.30.0 milestone 17 | +--------------------------------------------------+---------------------------+-------------------------+ | TbtkScriptApplicationObject.VersionMajor | Мажорная версия | 4 | +--------------------------------------------------+---------------------------+-------------------------+ | TbtkScriptApplicationObject.VersionMinor | Минорная версия | 30 | +--------------------------------------------------+---------------------------+-------------------------+ | TbtkScriptApplicationObject.VersionRelease | Релизная версия | 0 | +--------------------------------------------------+---------------------------+-------------------------+ | TbtkScriptApplicationObject.VersionMilestone | Промежуточная версия | 17 | +--------------------------------------------------+---------------------------+-------------------------+ .. code-block:: delphi :caption: Шаблон использования: function GetApplicationVersion: string; function TbtkScriptApplicationObject.VersionMajor: Integer; function TbtkScriptApplicationObject.VersionMinor: Integer; function TbtkScriptApplicationObject.VersionRelease: Integer; function TbtkScriptApplicationObject.VersionMilestone: Integer; .. code-block:: delphi :caption: Пример использования fullVersionName := GetApplicationVersion; // 4.30.0 milestone 17 majorVersion := Application.VersionMajor; // 4 minorVersion := Application.VersionMinor; // 30 releaseVersion := Application.VersionRelease; // 0 milestoneVersion := Application.VersionMilestone; // 17 str := 'Полное имя версии: ' + fullVersionName + #13; str := str + 'Номер мажорной версии = ' + IntToStr(majorVersion) + #13; str := str + 'Номер минорной версии = ' + IntToStr(minorVersion) + #13; str := str + 'Номер релизной версии = ' + IntToStr(releaseVersion) + #13; str := str + 'Номер промежуточной версии = ' + IntToStr(milestoneVersion); ShowMessage(str); Работа с базовыми сущностями фреймворка --------------------------------------- Работа с коллекциями пиктограмм ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Работа обеспечивается посредством класса *TbtkImageList* имеющего порт в скриптер. Подробное описание его свойств и методов можно посмотреть в BDN Сфера применения ^^^^^^^^^^^^^^^^ Экспорт пиктограмм, используется Global3 '''''''''''''''''''''''''''''''''''''''' Необходимость обусловлена невозможностью использовать внутренний формат хранения Global1. .. code-block:: SQL begin BTK_COMPONENTapi.SETCLOBDATAXML(:ID, :XMLDATA); end; .. code-block:: delphi {$if DesktopVer >= 4.31.} a := TbtkImageList.CReateFromBlob(GetVar('blobDataVCL')); xml := null; if Assigned(a) then try listH := VarToStr(a.Height); listW := VarToStr(a.Width); xml := ''#13; xml := xml + ' ' for i := 0 to a.Count - 1 do xml := xml + ' '; xml := xml + ''; if VarExists('XMLDATA') then SetVar('XMLDATA', xml) else AddVar('XMLDATA', xml, ftOraClob); ExecSQL('SetClobDataXML'); finally a.Free; end; {$else} ShowMessage('XML(Global3) версия набора не была сгенерирована'#13+ 'Рекомендуется вовспользоваться более поздней версией, от 4.31.x'); {$endif} Починить прозрачность "старой коллекции" '''''''''''''''''''''''''''''''''''''''' Некоторое время тому назад была реализована возможность использовать в наших коллекциях пиктограмм полупрозрачность(alpha-слой); старые же коллекции не содержат валидных данных в новообразовавшемся alpha-слое. Наиболее верным решением проблемы со старой коллекцией будет - залить коллекцию заново, правильными, полноценными пиктограммами в формате PNG. Одна же, как выясняется, на различных проектах содержатся уникальные коллекции изображений и их, по идее, тоже нужно перезаполнять. Если открыть старую коллекцию на редактирование в клиенте, в соответствующем фрейме,- там она будет отображаться не так, скорее всего с чёрным фоном. Если сохранить редактирование такой коллекции - в последствии и везде в клиенте она будет отображаться чёрной, либо не будет отображаться(т.к. альфаслой возможно заместится полной прозрачностью). Решение проблемы - пересоздать коллекцию с альфа-слоем построенном на основании "цвета прозрачности", технологии более ранней чем полупрозрачность, и использовавшейся, соответственно, в старых коллекциях. Это действие реализуется выгрузкой изображений из коллекци в формат 'png.realpha', формат представляет собой самый обычный NPG, но при его формировании альфа-слой заполняется валидными значениями прозрачности в соответствии с цветом прозрачности(цвет верхнего-левого пикселя изображения). .. code-block:: delphi Selection.Edit; old := TbtkImageList.CReateFromBlob(GetVar('blobDataVCL')); try new := TbtkImageList.Create(old.Width, old.Height); try for i := 0 to a.Count - 1 do new.AddImage(old.GetImageBlob(i, 'png.realpha')); SetVar('blobDataVCL', new.SerializeToBlob); finally new.Free; end; finally old.Free; end; В фрейме "Коллекции изображений" на ПКМ есть операция "Пересоздать слой прозрачности". Эту операцию можно вызывать для коллекции у которой нет слоя прозрачности. После этого ее можно редактировать в 5.х клиенте, почернения изображений не будет.  Создание выборок ~~~~~~~~~~~~~~~~ ``CreateFormEx`` и ``DoLookUpEx`` Указание отображения выборки ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ В методах ``CreateForm``, ``CreateFormEx``, ``DoLookUp`` и ``DoLookUpEx`` можно указывать выборку и отображение в формате <Выборка>.<Отображение>. .. code-block:: delphi :caption: Пример: CreateForm('', 'SEL_RPT_Report.List', fcfMDI); Если при вызове ``CreateFormEx`` или ``DoLookUpEx`` были указаны два разных отображения: CreateFormEx(\*, 'SEL\_RPT\_Report.List', fcfMDI, ['Representation', 'TabVisible','ExParam1'] ,['RoList', 0, 'Значение']); то **будет использовано отображение, указанное после имени выборки.** Передача значений параметров фильтров ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Реализована возможность передачи свойств атрибутов выборки и свойств атрибутов фильтра через параметры методов ``CreateFormEx`` и ``DoLookUpEx``. Значения свойств атрибутов, переданные в скриптовые методы, перекрывают значения свойств атрибутов, загруженных в метаданных. Таким образом, через параметры можно указать видимость, редактируемость и т.д. атрибута. Свойства атрибутов задаются в формате "<имя атрибута>.<имя свойства атрибута>" или "<имя параметра фильтра>.<имя свойства параметра>". .. code-block:: delphi :caption: Пример: DoLookUpEx('' , 'SEL_ExTransmitProperty.List' , ['sCaption'], [], [], false , ['filter$Flt_Attr1', 'filter$Flt_Attr1.ReadOnly', 'filter$Flt_Attr2.Visible', 'sSystemName.ReadOnly', 'dDate.Visible'] , ['Значение параметра', 1, 0, 1, 0] ); Для атрибутов выборки могут быть переданы значения любых свойств. Для параметров фильтра - только значения свойств "Visible" и "ReadOnly". При этом, значения параметров фильтров, для которых был выставлен флаг "ReadOnly", не будут сбрасываться при вызове ``ResetFilter``. Ранее имелась возможность в качестве параметров Ex-вызова передать значения параметров фильтрации. Это открывало большие возможности по предустановки значений фильтров согласно требованиям к открываемому для выбора окну. Однако в полной мере использовать эти возможности не удавалось, т.к. пользователь всегда мог изменить условие фильтрации или сбросить фильтр. Данный механизм позволяет более гибко управлять свойствами атрибутов и параметров стандартного фильтра. Теперь не нужно создавать отдельные отображения для специфических выборов. Обычный список может стать по-настоящему универсальным, обладая возможностью инвариантного использования. Открытие выборки в новом окне ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Что-бы гарантировано открыть выборку в новом окне нужно передать параметр "CreatingMode" со значением "CreateNewForm". .. code-block:: delphi :caption: Пример: CreateFormEx('', 'SEL_RPT_Report.List', fcfMDI, ['CreatingMode'], ['CreateNewForm']); Работа с полями датасета ~~~~~~~~~~~~~~~~~~~~~~~~ Метод Selection.SetFieldsDesc ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ В методе ``Selection.SetFieldsDesc`` для передачи параметра новых наименований атрибутов используется следующий формат: 1. список наименований представляется в виде строки, ограниченной с обеих концов апострофами '. 2. наименования должны разделяться запятыми и, при желании, пробелами. 3. наименования должны быть заключены между двойными ковычками. За исключением тех, которые состоят из единого слова (не содержат пробелов, запятых и т.д.) - их можно не заключать в кавычки. 4. наименования, заключенные в " ", могут содержать запятые. Для использования двойных ковычек в наименовании их следует заменять на пару таких ковычек (вместо " писать ""). .. code-block:: delphi :caption: Пример: Selection.SetFieldsDesc('sCaptions,SystemName', '"Наименование с запятой и двойными ковычками "","" " , НовоеCистемноеИмя'); Метод Selection.SetVarDesc ^^^^^^^^^^^^^^^^^^^^^^^^^^ Метод указывает заголовок атрибута. Значение ACaption через символы "\|" может содержать имена бандов вверх по иерархии, к которым будет привязан указанный атрибут (начиная с 30 версии клиента. Ранние версии поддерживают только одноэтажные банды). .. code-block:: delphi :caption: Шаблон использования: procedure TbtkScriptSelectionObject.SetVarDesc(AAttrName, ACaption: string); .. code-block:: delphi :caption: Пример: Selection.SetVarDesc('Attr1', 'Атрибут1|Банд-1|Супер-банд-1|Гига-банд-1'); Selection.SetVarDesc('Attr2', 'Атрибут2|Банд-1|Супер-банд-1|Гига-банд-1'); Selection.SetVarDesc('Attr3', 'Атрибут3|Банд-2|Супер-банд-1|Гига-банд-1'); Функции работы с формами, связь с выборками ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Управление операциями (http://wiki.bitec.ru/index.php/%D0%9E%D0%BF%D0%B5%D1%80%D0%B0%D1%86%D0%B8%D0%B8_%D0%B2%D1%8B%D0%B1%D0%BE%D1%80%D0%BA%D0%B8) ------------------------------------------------------------- .. _selection-params-label: Параметры выборки ~~~~~~~~~~~~~~~~~ Имена параметров ^^^^^^^^^^^^^^^^ Служебные префиксы '''''''''''''''''' Для доступа к параметрам используются следующие префиксы: - super$, - параметры мастера; - old$, - предыдущее значение. Используется только для чтения, и не может использоваться для установки значений; - filter$, - параметры фильтрации выборки. При обращении к параметрам допускается так-же использование комбинаций префиксов, со следующими ограничениями: #. префикс super$ может использоваться несколько раз, но должен всегда находиться перед именем параметра и другими префиксами; #. префикс old$ может использоваться только один раз, должен находиться перед именем параметра и префиксом filter$ если таковой имеется. #. префикс filter$ может встречаться только один раз и указываться непосредственно перед именем параметра; .. code:: delphi :caption: Примеры: id1 := GetVar('id'); //получение значение параметра 'id' текущей выборки id2 := GetVar('super$id'); //получение значение параметра 'id' мастер-выборки id3 := GetVar('super$super$id'); //получение значение параметра 'id' мастер-выборки мастер-выборки flt1 := GetVar('filter$flt'); //получение значение параметра фильтрации 'flt' текущей выборки flt2 := GetVar('super$filter$flt'); //получение значение параметра фильтрации 'flt' мастер-выборки flt2OldValue := GetVar('super$old$filter$flt'); //получение предыдущего значение параметра фильтрации 'flt' мастер-выборки Служебные префиксы используются только для получения доступа к уже существующим параметрам, и не могут использоваться при создании новых параметров. При необходимости создать внутренний параметр в мастере выборки используйте свойство 'Master'. .. code:: delphi :caption: Примеры: Selection.Master.AddVar('param1', 'value', ftString); //Правильно. Selection.AddVar('super$param1', 'value', ftString); //Не правильно. Типы параметров ^^^^^^^^^^^^^^^ +---------+-------------------------+-------------------------------------------+ | TFieldT | Описание | Примечание, допустимые значения | | ype | | | +=========+=========================+===========================================+ | ftUnkno | не известный / не | Используется для добавления параметров | | wn | определённый тип | тип которых будет определён по значению | +---------+-------------------------+-------------------------------------------+ | ftStrin | символ или строка | | | g | | | +---------+-------------------------+-------------------------------------------+ | ftSmall | 16-битное целое число | -32767..32768 | | int | | | +---------+-------------------------+-------------------------------------------+ | ftInteg | | в Global эквивалентен типу ftFloat | | er | | | +---------+-------------------------+-------------------------------------------+ | ftWord | 16-битное беззнаковое | 0..65535 | | | целое число | | +---------+-------------------------+-------------------------------------------+ | ftBoole | | false, true; 0, 1; при установке значения | | an | | отличного от перечисленных, будет | | | | установлено true | +---------+-------------------------+-------------------------------------------+ | ftFloat | число с плавающей | | | | точкой | | +---------+-------------------------+-------------------------------------------+ | ftCurre | денежный тип | | | ncy | | | +---------+-------------------------+-------------------------------------------+ | ftBCD | | | +---------+-------------------------+-------------------------------------------+ | ftDate | дата | | +---------+-------------------------+-------------------------------------------+ | ftTime | время | | +---------+-------------------------+-------------------------------------------+ | ftDateT | дата и время | | | ime | | | +---------+-------------------------+-------------------------------------------+ | ftBytes | | | +---------+-------------------------+-------------------------------------------+ | ftVarBy | | | | tes | | | +---------+-------------------------+-------------------------------------------+ | ftAutoI | | | | nc | | | +---------+-------------------------+-------------------------------------------+ | ftBlob | | | +---------+-------------------------+-------------------------------------------+ | ftMemo | | | +---------+-------------------------+-------------------------------------------+ | ftGraph | | | | ic | | | +---------+-------------------------+-------------------------------------------+ | ftFmtMe | | | | mo | | | +---------+-------------------------+-------------------------------------------+ | ftParad | | | | oxOle | | | +---------+-------------------------+-------------------------------------------+ | ftDBase | | | | Ole | | | +---------+-------------------------+-------------------------------------------+ | ftTyped | | | | Binary | | | +---------+-------------------------+-------------------------------------------+ | ftCurso | | | | r | | | +---------+-------------------------+-------------------------------------------+ | ftFixed | | | | Char | | | +---------+-------------------------+-------------------------------------------+ | ftWideS | | | | tring | | | +---------+-------------------------+-------------------------------------------+ | ftLarge | | | | int | | | +---------+-------------------------+-------------------------------------------+ | ftADT | | | +---------+-------------------------+-------------------------------------------+ | ftArray | | | +---------+-------------------------+-------------------------------------------+ | ftRefer | | | | ence | | | +---------+-------------------------+-------------------------------------------+ | ftDataS | | | | et | | | +---------+-------------------------+-------------------------------------------+ | ftOraBl | | | | ob | | | +---------+-------------------------+-------------------------------------------+ | ftOraCl | | | | ob | | | +---------+-------------------------+-------------------------------------------+ | ftVaria | Данные не привязанные к | | | nt | определённому типу | | | | данных | | +---------+-------------------------+-------------------------------------------+ | ftInter | | | | face | | | +---------+-------------------------+-------------------------------------------+ | ftIDisp | | | | atch | | | +---------+-------------------------+-------------------------------------------+ | ftGuid | | | +---------+-------------------------+-------------------------------------------+ | ftTimeS | | | | tamp | | | +---------+-------------------------+-------------------------------------------+ | ftFMTBc | | | | d | | | +---------+-------------------------+-------------------------------------------+ Алгоритм поиска параметров ^^^^^^^^^^^^^^^^^^^^^^^^^^ При обращении к параметрам через паскаль операции используется следующий алгоритм поиска параметра: - имя параметра начинается с префикса 'super$' отбрасываем префикс, ищем параметр в мастере - имя параметра начинается с префикса 'filter$' отбрасываем префикс, ищем в параметрах фильтрации если параметр не найден ищем параметр в мастере - ищем в атрибутах выборки если параметр не найден ищем в параметрах фильтрации - ищем во внутренних параметрах если параметр не найден ищем параметр в мастере Методы паскаль операций ^^^^^^^^^^^^^^^^^^^^^^^ VarExists ''''''''' Для проверки существования параметров используется функция ``VarExists``. .. code:: delphi procedure VarExists(AName: string); SelfVarExists ''''''''''''' AddVar '''''' Для добавления внутренних параметров выборки используется метод ``AddVar``. .. code:: delphi procedure AddVar( AName: string; AValue: Variant; ADataType: TFieldType; APersistent: TbtkPersistentType); - ADataType, - тип добавляемого параметра; - `APersistent <Стойкие_параметры_(Persistent_parameters)>`__, - флаг `стойкости параметра <Стойкие_параметры_(Persistent_parameters)>`__. Но при добавлении новых внутренних параметров следует избегать использования зарезервированных префиксов в названиях и не использовать в качестве названия пустую строку. .. code:: delphi :caption: Примеры корректного вызова: Selection.AddVar('var1', 'Value', ftString, ptRegistry); //добавление строкового стойкого параметра в текущую выборку AddVar('id', 123, ftInteger, ptNone); //добавление не стойкого целочисленного параметра в текущую выборку CustomSel.AddVar('anyVar', null, ftUnknown); //добавление не стойкого параметра типа ftUnknown в выборку "CustomSel" .. code:: delphi :caption: Примеры некорректного вызова: Selection.AddVar('Var1', 'Value', ftFloat); //задаваемое значение ('Value') невозможно представить в указанном типе (ftFloat) AddVar('', 123, ftInteger); //не указано имя параметра CustomSel.AddVar('super$anyVar', null, ftUnknown); //в имени параметра используется зарезервированный префикс 'super$' SetVar '''''' Метод ``SetVar`` используется для установки значений: - внутренним параметрам; - параметрам фильтрации; - полям выборки. GetVar '''''' GetSelfVar '''''''''' SetVarDesc '''''''''' GetVarDesc '''''''''' Макросы выборок ^^^^^^^^^^^^^^^ Макросы фильтрации и условия Alias '''''''''''''''''''''''''''''''''' +-------------------+--------------------------------------------------------+ | &DefUniFltMacros | Макрос универсального (серверного) фильтра выборки | +-------------------+--------------------------------------------------------+ | &DefUniFltMacrosR | Условие, дописываемое к условию макроса | | O | &DefUniFltMacros | +-------------------+--------------------------------------------------------+ | &ServFilterAliasM | Макрос условия Alias | | acros | | +-------------------+--------------------------------------------------------+ | &LoadData# | Используется если установлено свойство выборки "Не | | | загружать данные при первом открытии" | +-------------------+--------------------------------------------------------+ Эти макросы используются для "оборачивания" исходного запроса данных следующим образом: .. code:: sql select ServFilterAliasMacros.* from (<исходный запрос>) &ServFilterAliasMacros where &DefUniFltMacros and &DefUniFltMacrosRO Оборачивание происходит на применении фильтра. Вид обертки может меняться, если в исходном запросе уже имеются некоторые из указанных макросов, или соответствующие им значения пусты - в этом случае они не приписываются. Условия добавления макросов: &ServFilterAliasMacros - добавляется с оборотом запроса только в случае, если: 1. в исходном запросе еще не встречается ни один из трех указанных макросов (недопустимо использование макросов фильтрации внутри обертки Alias). 2. значение макроса непусто (недопустим запрос \*select .\* from ... \* с пустотой перед '.'). Если оба условия не выполнены, запрос не оборачивается. &DefUniFltMacros и &DefUniFltMacrosRO независимо дописываюся если: 1. они еще не встречаются в исходном запросе; 2. соответствующие им значения непусты или запрос начинается с макроса Alias. Примечание: &ServFilterAliasMacros заполняется(может быть прописан в запросе вручную) только при активных операциях универсального фильтра. Об использовании макросов фильтрации и операций серверного фильтра см. в статье: `http://wiki.gs.local/index.php/Фильтрация\_серверая#Паскаль-события\_фильтрации `__> Макросы сортировки: ''''''''''''''''''' +--------------------+-------------------------+ | &SortOrder# | Макрос сортировки | +--------------------+-------------------------+ Макрос сортировки, содержащий накладываемые фильтрами значения. Подробнее см. в статье: `http://wiki.gs.local/index.php/Сортировка\_серверная `__ Свойства выборки, отключающие применение макросов фильтрации и сортировки: '''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' +--------+-------------------------------------------------------------------+ | ApplyS | Свойство отвечает за наложение макросов фильтрации | | ervFil | &DefUniFltMacros и &DefUniFltMacrosRO. По умолчанию включено - | | terMac | значения макросов накладываются при запросе данных. Если свойство | | ros | выключить, значения макросов все равно соответствуют настройкам | | | фильтров, однако на формирование запроса н влияют. | +--------+-------------------------------------------------------------------+ | ApplyS | Аналогичное свойство, отвечающее за применение макроса сортировки | | ervSor | &SortOrder#. | | tOrder | | | Macros | | +--------+-------------------------------------------------------------------+ Перечисленные свойства располагаются в свойствах выборки на закладке "дополнительные свойства", в группе "макросы". Смена макросов и переоткрытие выборки ''''''''''''''''''''''''''''''''''''' Во избежание лишних переоткрытий датасета, решено поддерживать следующую логику при смене значений макросов: 1. при добавлении нового макроса, или смене значения макроса, не входящего в GST, ничего не происходит. 2. при смене значения макроса, входящего в текст запроса, выполняется переоткрытие выборки (если не установлено свойство "Не закрывать датасет при обновлении макроса"). Работа с окружением ------------------- Вызов внешних dll из паскаль скрипта ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Общая информация ^^^^^^^^^^^^^^^^ Возможность вызова методов из подгружаемых dll появилась с версии клиента 1.4.25.94. Если библиотека еще не была загружена в приложение, то загрзуку библиотеки может выполнить явно и не явно. Неявная загрузка Dll ^^^^^^^^^^^^^^^^^^^^ Неявно вызов происходит перед вызовом метода описаного в паскаль скрипте с использованием специальной сигнатуры.  .. code-block:: delphi :caption: Сигнатура метода следующая: function functionName(Аргументы): ВозвращаемыйТип; [СоглашениеВызова]; external 'ИмяБиблиотеки' [ИмяМетодаВоВнещнейБиблиотеке]; .. code-block:: delphi :caption: Пример описания метода c именем MyFunction из библиотеки MyCustomLib.dll: function MyFunction(arg: integer): integer; external 'MyCustomLib.dll' В качестве соглашений вызова могут использоваться stdcall, register, pascal, cdecl, safecal. Более подробно `Соглашение о вызове `__ - В описание функции поддерживаются следующие типы: - Integer - Boolean - Char - Extended - String - Pointer - PChar - Object - Class - WideChar - PWideChar - AnsiString - Currency - Variant - Interface - WideString - Int64 - Longint - Cardinal - Longword - Single - Byte - Shortint - Word - Smallint - Double - Real - DateTime - Comp - TObject  - наследники( Класс должнен быть зарегестрирован в скрипторе) .. code-block:: delphi :caption: Пример неявной загрузки библиотеки, вызова методов dll из паскаль скрипта: { declaration of external functions } function MessageBox(hwnd: pointer; text, caption: AnsiString; msgtype: integer): integer; stdcall; external 'User32.dll' name 'MessageBoxA'; function GetDiskFreeSpace(root: AnsiString; var secPerCluster, bytesPerCluster, freeClusters, totalClusters: integer): boolean; stdcall; external 'Kernel32.dll' name 'GetDiskFreeSpaceA'; function FindWindow(className, windowName: AnsiString): integer; stdcall; external 'User32.dll' name 'FindWindowA'; function GetKeyState(virtKey: integer): short; stdcall; external 'User32.dll'; function FreeSpaceInfo(drive: String): string; var a, b, c, d: word; begin if GetDiskFreeSpace(drive, a, b, c, d) then result := Format('%d sections per cluster; %d bytes per cluster; %d free clusters; %d total clusters', [a, b, c, d]) else result := '(could not get drive information)'; end; const { windows consts } MB_ICONQUESTION = $20; MB_YESNO = $4; IDYES = 6; VK_SHIFT = $10; begin OutTxt := ''; if MessageBox(nil, 'Are you sure?', 'Confirmation', MB_ICONQUESTION + MB_YESNO) = IDYES then OutTxt := OutTxt + 'MessageBox: user answered YES' + #13#10 else OutTxt := OutTxt + 'MessageBox: user answered NO' + #13#10 ; OutTxt := OutTxt + 'Drive C: free space: ' + FreeSpaceInfo('C:') + #13#10 ; if FindWindow('TAppBuilder', '') > 0 then OutTxt := OutTxt + 'Delphi is open' + #13#10 else OutTxt := OutTxt + 'Delphi is not open' + #13#10 ; if GetKeyState(VK_SHIFT) < 0 then OutTxt := OutTxt + 'Shift is pressed' + #13#10 else OutTxt := OutTxt + 'Shift is not pressed' + #13#10 ; ShowMessage(OutTxt); end; Явная загрузка Dll ^^^^^^^^^^^^^^^^^^ Явно загрузка выполняется с использованием метода LoadLibrary. Использование явной загрузки позволяет вручную управлять загрузкой и выгрузкой dll, тем самым писать более оптимизированный код. Использовать метод LoadLibrary следует в паре с методом FreeLibrary(dll) и конструкцией try finally. .. code-block:: delphi :caption: Пример: { declaration of external functions } function CreateCustomForm(caption: AnsiString; color: integer): TObject; stdcall; external 'CustomLib.dll'; procedure ShowForm(form: TObject); external 'CustomLib.dll'; procedure GetBounds(form: TObject; var l, t, w, h: integer); cdecl; external 'CustomLib.dll' name 'FormGetBounds'; function DecodeToday(var year, month, day: integer): boolean; stdcall; external 'CustomLib.dll'; function tsr(x, y: double): double; external 'CustomLib.dll' name 'TimesSquareRoot'; const clRed = $0000FF; begin { explicit dynamic load of the library } dll := LoadLibrary('CustomLib'); try ShowMessage(Format('CustomLib.dll loaded: handle = %d', [dll])); frm := CreateCustomForm('My form', clRed); try ShowForm(frm); l := 0; t := 0; w := 0; h := 0; GetBounds(frm, l, t, w, h); ShowMessage(Format('Form bounds: left=%d, top=%d, width=%d, height=%d', [l, t, w, h])); finally frm.Free; end; finally FreeLibrary(dll); { unload } ShowMessage(Format('DLL %d unloaded', [dll])); end; { automatic load of the library on demand } y := 0; m := 0; d := 0; leap := DecodeToday(y, m, d); ShowMessage(Format('Today: day=%d, month=%d, year=%d', [d, m, y])); if leap then ShowMessage(Format('%d is leap', [y])) else ShowMessage(Format('%d is not leap', [y])); ShowMessage('Square root of ' + IntToStr(m * d) + ' is ' + FloatToStr(tsr(m, d))); end; Работа с файлами ~~~~~~~~~~~~~~~~ API работы с файлами обеспечивает передачу блобов между сервером и клиентом. Блобы ^^^^^ В разделе затронуты темы сохранения, загрузки и передачи :term:`Blob`-ов. Типичный порядок работы 1. Выполнить запрос паскаль операции #. Параметр запроса сохранить в переменную выборки #. Переменную выборки сохранить на диск либо в поле выборки Используется также 1. Открыть выборку с блоб-полем #. Сохранить поле на диск Порядок работы с большими данными 1. Выполнить запрос паскаль операции #. В параметре запроса вернуть лоб-локатор #. Через лоб-локатор сохранить поток на диск .. note:: Под "Блоб"-ом понимается одна из трёх сущностей: - тип sql-данных - Blob - поле выборки куда выбрано поле типа Blob из sql-запроса - переменная в :term:`скриптер`-е где хранится массив байт Во всех перечисленных случаях речь идёт о большом блоке данных. .. attention:: Большинство методов напрямую работают с блобами и полностью копируют данные в память приложения. Из-за этого при больших объемах велика вероятность нехватки памяти. Решением проблемы работы с большими блобами/файлами является испопльзование лоб-локаторов. Блоб в скрипторе '''''''''''''''' Блоб в скрипторе - обычная переменная, такая же как переменная хранящая число или строку. .. note:: фактический тип данных Блоба в скрипторе - ``Variant array of Byte``. В описаниях методов чаще всего описывается просто ``Variant``, реже как ``Array``. .. note:: Блобы могут участвовать в работе с запросами и полями выборки, полностью аналогично значениям более простых типов (строки, числа..). Тип данных :term:`БД` который соответствует блобу в скрипторе - ``ftOraBlob``. .. attention:: Каждое присвоение Блоба в скрипотре, или передача не по ссылке, создаёт его копию и всего его содержимого. Если в переменной хранится значительный объём данных, то это чревато возникновением ошибки ``Out of memory`` .. seealso:: - :class:`InternalLibrary.LoadFromFileToBlob` - :class:`InternalLibrary.SaveBlobToFile` Блоб-поле ''''''''' Работа с блоб-полем, как и с прочими блобами, сводится к загрузке и сохранению данных. Можно сразу работать непосредственно с файлами, или через "Блоб в скрипторе". .. seealso:: Прямые методы работы: - :class:`InternalLibrary.TbtkScriptSelectionObject.DownloadFile` - :class:`InternalLibrary.TbtkScriptSelectionObject.UploadFile` Для работы с использование блобов в скрипторе: - :class:`InternalLibrary.TbtkScriptSelectionObject.GetVar` - :class:`InternalLibrary.TbtkScriptSelectionObject.SetVar` .. attention:: **Лучше использовать методы прямой работы**. Использовать "блобы в скрипторе" при работе с блоб-полями не рекомендуется без необходимости. Этот подход потребляет значительно больше памяти. Примеры ''''''' .. code-block:: delphi :caption: Типичный порядок работы с блобом. Чтение. :name: example_SaveBlobToFile [out blob] select bBlob into :blob from TSG_BlobExaple where id = :id; V := ExecSQLEx('GetBlob', 'blob', [ftOraBlob], [Null]); SaveBlobToFile(AFileName, V[0]); .. code-block:: delphi :caption: Типичный порядок работы с блобом. Запись. update TSG_BlobExaple set bBlob = :blob where id = :id; fileContent := LoadFromFileToBlob(AFileName); ExecSQLEx('SetBlob', 'blob', [ftOraBlob], [fileContent]); Лоб-локатор ''''''''''' Методика работы полностью аналогична работе с обычными параметрами типа ``ftOraBlob``, но в качестве передаваемого значения используется не байтовый массив, а объект класса :class:`InternalLibrary.TbtkScriptOraLobLocator`, тип такого параметра следует задавать как ``ftOraLobLocator``. .. note:: С помощью локаторов нельзя работать с полями выборки - только с параметрами запроса. .. note:: Явным показанием к применению локатора является **большой размер файла**, тк. он не использует кеширование данных. Отправляет и принимает файл малыми порциями (потоково). .. note:: Время жизни lob-локатора управляется ссылками, т.е. его можно передавать из операции в операцию и даже ``AddVar``-ить. Но не стоит забывать о его связи с сессией: не следует использовать локатор созданный в одной сессии в запросах выполняемых в другой. .. code-block:: delphi :caption: Пример чтения Blob-а с сервера :name: example_TbtkScriptOraLobLocator_SaveToFile [out blob] select bBlob into :blob from TSG_BlobExaple where id = :id; lob := Selection.CreateLobLocator; ExecSQLEx('GetBlob', 'lob', [ftOraLobLocator], [lob]); lob.SaveToFile(AFileName); .. code-block:: delphi :caption: Пример отправки Blob-а на сервер :name: example_TbtkScriptOraLobLocator_LoadFromFile update TSG_BlobExaple set bBlob = :blob where id = :id; lob := Selection.CreateLobLocator; lob.CreateTemporary; lob.LoadFromFile(AFileName); ExecSQLEx('SetBlob', 'lob', [ftOraLobLocator], [lob]); Открытие документа ^^^^^^^^^^^^^^^^^^ Запускается стороннее приложение ассоциированное с типом документа в системе. Под документом понимается файл на жёстком диске. Тип документа, как это принято в ОС Windows, соответствует расширению файла. Действие совершенно аналогично двойному клику по файлу ОС Windows. Открытие файла с диска '''''''''''''''''''''' Методы позволяют открывать документы или запускать на выполнение программы. Различаются возможностями печати и ожиданием закрытия файла (работа в модальном режиме). .. seealso:: - :class:`InternalLibrary.ShellExec` - :class:`InternalLibrary.ShellExecute` Открытие из поля выборки '''''''''''''''''''''''' Поле выборки должно быть типа :term:`Blob` и содержать документ. Этот блоб сохраняется на диск и дальше происходит открытие файла с диска. Сохранение происходит во временный файл. После того как стороннее приложение будет закрыто ``клиент Global`` удалит этот временный файл. .. note:: При закрытии приложения Global, если какие-то из открытых документов ещё заняты (открыты в стороннем приложении) - выдаёт соответствующее предупреждение. .. seealso:: - :class:`InternalLibrary.TbtkScriptSelectionObject.BlobOpen` - :class:`InternalLibrary.TbtkScriptSelectionObject.BlobOpenEx` - :class:`InternalLibrary.TbtkScriptSelectionObject.BlobOpenEx2` Диалоги ^^^^^^^ Диалоги навигации по файловой системе, такие как диалог открытия или сохранения файла, выбора каталога. .. seealso:: :ref:`Навигация по файловой системе ` FAQ ^^^ * Каким методом узнать что файл заблокирован? Проверять файл на доступность перед открытием плохое решение. Никто не сможет гаратнировать что между провоеркой файла на доступность и его открытие не произойдет его блокировка.  Оставлять такие случаи без рассмотрения дурной тон и ненадежное программирование. Лучшим решением это обработка исключений от метода открытия файла. К сожалению наш скриптер на данный момент не поддерживает полноценную структурную обработку исключений. Но кое что придумать все же можно: .. code-block:: delphi ExecSQL( 'func' ); try Selection.BlobOpenEx2('blobe2', 'D:\Temp\foo.doc', Selection , '' ,wmAuto, true); except //ShowMessage(LastExceptionClassName); if SameText(LastExceptionClassName,'EFCreateError') then ShowMessage('Кажется что то пошло не так.'); end; Если конечно все же очень хочется проверить файл на доступность заранее то могу вот такой код привести .. code-block:: delphi uses System; file := FileOpen('D:\31.log' ,fmOpenWrite); try If System.GetLastError <> 0 then ShowMessage(SysUtils.SysErrorMessage(System.GetLastError)); finally FileClose(File); end; Криптографические методы ~~~~~~~~~~~~~~~~~~~~~~~~~ Описание функций ^^^^^^^^^^^^^^^^ 1. Создание подписи сообщения. .. code-block:: delphi function SignMessage(data: String; certID: String; out rslt: String): Boolean; 2. Проверка подписи сообщения. Изменён 3й параметр. Теперь возвращается лог проверки, а не идентификатор сертификата. .. code-block:: delphi function VerifyMessage(data: String; sign: String; out spLog: String): Boolean; 3. Добавление списка отозванных в хранилище (по умолчанию в Доверительные корневые центры сертификации) .. code-block:: delphi function AddCRLToRoot(
crl: String
): Boolean; 4. Получение даты подписи. По умолчанию при подписании данных ставится дата UTC .. code-block:: delphi function GetDateFromSign(sign: String; out date: TDateTime): Boolean; 5. Проверка по дате подписи актуальность списков CRL. Возвращает: **True** - если есть список и дата удовлетворяет, **False** - нет списка или нужно обновить CRL .. code-block:: delphi function IsCRLOutOfDate(sign: String): Boolean; 6. Функция загружает по заданному адресу CRL список. .. code-block:: delphi function LoadCRLByURL(url: String; out crl: String): Boolean; 7. Обновляет списки отозванных сертификатов и обновляет их по необходимости. Если установлен флаг *bpForce* в **False**, то функция проверяет действие CRL списков и обновляет только по необходимости. Иначе обновляет в любом случае. .. code-block:: delphi function RefreshCRL(sign: String; spLog: String; bpForce: String): Boolean; 8. Проверяет наличие сертификата из подписи в списках отозванных. .. code-block:: delphi function VerifyCertFromSignInCRL(sign: String): Boolean; 9. Проверяет наличие сертификата в списках отозванных по идентификатору сертификата. .. code-block:: delphi function VerifyCertInCRL(CertID: String): Boolean; 10. Отпечаток сертификата в виде строки шестнадцитиричных цифр без пробелов. .. code-block:: delphi function GetThumbprintHexStringByCertID(CertID: String): string; 11. Массив данных сертификата. Возвращается в воде массива байт. .. code-block:: delphi function GetRawDataByCertID(CertID: String): variant array of byte; Пример ^^^^^^ .. code-block:: delphi var MyMessage, Sign, spLog, crl: string; d: DateTime; begin MyMessage := 'Сообщение, которое нужно подписать'; // Откроем диалоговое окно и выберем сертификат для подписи CertID := Cryptor.OpenCertificateDlg; if VarIsNull(CertID) then begin ShowMessage('Сертификат не выбран'); Exit; end; // Проверим цепочку сертификатов if not Cryptor.VerifyCertificate(CertID, spLog) then begin // Сертификат не прошёл проверку ShowMessage(spLog); Exit; end; // По желанию можно обновить список отозванных вручную if not Cryptor.LoadCRLByURL('http://test.ru/my.crl', crl) then begin ShowMessage('Неудалось загрузить CRL'); Exit; end; // Попытка установки CRL if not Cryptor.AddCRLToRoot(crl) then ShowMessage('Не удалось установить список отозванных. Возможно он уже установлен'); // Проверка в списках отозванных if not Cryptor.VerifyCertInCRL(CertID) then begin ShowMessage('Сертификат в списке отозванных'); Exit; end; // Подписание сообщения if not Cryptor.SignMessage(MyMessage, CertID, Sign) then begin ShowMessage('Ошибка при подписании.'); Exit; end; // Получим дату подписания if Cryptor.GetDateFromSign(Sign, d) then ShowMessage('Сообщение подписано ' + DateTimeToStr(d)) else ShowMessage('Не удалось получить дату из подписи'); // Проверка сообщения // Обновим CRL, если это требуется. IsCRLOutOfDate так же используется // в RefreshCRL, если флаг bpForce = False if Cryptor.IsCRLOutOfDate(Sign) then if not Cryptor.RefreshCRL(Sign, spLog, true) then begin ShowMessage('Неудалось обновить списки отозванных'); Exit; end; // Проверка сертификата, которым было подписано сообщение, на отозванность // происзодит при проверке сообщения, но данную проверку можно сделать отдельно if not Cryptor.VerifyCertFromSignInCRL(Sign) then begin ShowMessage('Сертификат, которым было подписано сообщение в списке отозванных'); Exit; end; if not Cryptor.VerifyMessage(MyMessage, Sign, spLog) then begin // Ошибка ShowMessage(spLog); Exit; end else ShowMessage('Подпись подтверждена.'); // Получение отпечатка сертификата CertID := Cryptor.OpenCertificateDlg; thumbPrint := Cryptor.GetThumbprintHexStringByCertID(CertID); ShowMessage('Отпечаток выбранного сертификата = ' + thumbPrint); // Получение массива байт данных сертификата CertID := Cryptor.OpenCertificateDlg; rawData := Cryptor.GetRawDataByCertID(CertID); // красивый вывод на экран, чотбы все нормально уместилось strRawData := 'RawData = ' + #10#13; for i := 0 to length(rawData)-1 do begin strRawData := strRawData + IntToStr(rawData[i]) + ', '; if (i mod 20 = 0) and (i <> 0) then strRawData := strRawData + #10#13; end; ShowMessage(strRawData); end; Перехват нажатия клавиш ~~~~~~~~~~~~~~~~~~~~~~~ Установка перхватчика для всего приложения ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ``procedure HookKeyboardKeyDownEvent(Operation: TbtkScriptDataSetOperationObject)`` - Установить перехватчик. В параметре Operation необходимо указать операцию обратного вызова. Данная операция будет вызываться при каждом нажатии клавишы. Перхват можно отключить с помощью ``procedure UnHookKeyboardKeyDownEvent()`` , либо будет снять автоматически при выгрузке метаданных. В приложении можно одновременно установить не более одного перехватчика на нажатие клавиш .При попытке установить перехватчик при уже установленном будет возбуждено исключение. Операция-обработчик вызывается с аргументами в которых передается информация о нажатой клавише: #. VirtualKey - код виртуальной клавиши `MSDN `__ #. Char - виртуальный код преобразованный в символ клавиатуры(Сканкод) #. ShiftState - состояние зажатых модификаторов типа TShiftState. #. Handled - флаг в который можно вернуть результат обработки нажатия. По умолчанию False - это значит, что нажатие клавишы будет передано следующим обработчикам. True - нажатие обработано и другим обработчикам передавать обработку нажатия не надо. В этом случае элементы управления никак не узнают о срабатывании клавиши. Пример операции-обработчика: .. code-block:: delphi args.Validate(["VirtualKey", "Char", "ShiftState", "Handled"]); str := Format("VK: %s; Char: %s; CTRL: %s; SHIFT: %s;ALT:%s;", [IntToStr(args.VirtualKey), args.Char,  IntToStr(GetShiftState(ssCTRL)), IntToStr(GetShiftState(ssSHIFT)), IntToStr(GetShiftState(ssALT))]); SetVar("Caption", str);   args.Handled := true; ``procedure UnHookKeyboardKeyDownEvent`` - Снять перехватчик.  Перехватчик ловит нажатия на всех формах и контролах в рамках одного приложения. Пример регистрации перехватчика: .. code-block:: delphi Application.HookKeyboardKeyDownEvent(Selection.OperationByName("HookProc")); Дерегистрация: .. code-block:: delphi Application.UnHookKeyboardKeyDownEvent; Перехват в выборке ^^^^^^^^^^^^^^^^^^ В "Основных свойствах" у операции выборки открыть редактор свойства "Горячие клавиши", там можно поставить флаг "Нажатие любых клавиш", в этом случае операция будет срабатывать на любые нажатия клавиш. Либо можно задать определенную комбинацию "горячию клавишу".  Фрейм выборки должен быть активен, что бы поймать нажатие клавишь.  Работа с принтером ~~~~~~~~~~~~~~~~~~ Описание ^^^^^^^^ Для работы с принтером определено пространство имен **Printer** в него входят следующие функции: - **function SelectPrinter(var APrinterName: String; var ACopiesCount: Integer): Boolean** Позволяет выбрать принтер для печати, для этого она выводит на экран стандартный диалог выбора принтера. В первом параметре **APrinterName** она возвращает имя принтера, а во втором **ACopiesCount** указанное количество копий. Если в диалоге пользователь нажал **OK**, то результатом функции будет **True** в противном случае **False**. - **function Escape(in APrinterName: String; in AEscapeCode: String): Boolean** Оправляет последовательность кодов, указанную в параметре **AEscapeCode**, непосредственно драйверу принтера **APrinterName** минуя GDI. Предназначена, например, для работы с принтерами фирмы `Zebra Technologies `__ поддерживающими язык `ZPL или ZPL II `__. Возвращает **True** в случае успеха, в противном случае **False**. Пример ^^^^^^ Данный пример демонстрирует печать этикетки на принтере поддерживающим язык ZPL II. .. code-block:: delphi var n: String; i: Integer; t: String; begin Printer.SelectPrinter(n,i); t := '^XA'+#13#10+ '^FX Top section with company logo, name and address.'+#13#10+ '^CF0,60'+#13#10+ '^FO50,50^GB100,100,100^FS'+#13#10+ '^FO75,75^FR^GB100,100,100^FS'+#13#10+ '^FO88,88^GB50,50,50^FS'+#13#10+ '^FO220,50^FDInternational Shipping, Inc.^FS'+#13#10+ '^CF0,40'+#13#10+ '^FO220,100^FD1000 Shipping Lane^FS'+#13#10+ '^FO220,135^FDShelbyville TN 38102^FS'+#13#10+ '^FO220,170^FDUnited States (USA)^FS'+#13#10+ '^FO50,250^GB700,1,3^FS'+#13#10+ '^FX Second section with recipient address and permit information.'+#13#10+ '^CFA,30'+#13#10+ '^FO50,300^FDJohn Doe^FS'+#13#10+ '^FO50,340^FD100 Main Street^FS'+#13#10+ '^FO50,380^FDSpringfield TN 39021^FS'+#13#10+ '^FO50,420^FDUnited States (USA)^FS'+#13#10+ '^CFA,15'+#13#10+ '^FO600,300^GB150,150,3^FS'+#13#10+ '^FO638,340^FDPermit^FS'+#13#10+ '^FO638,390^FD123456^FS'+#13#10+ '^FO50,500^GB700,1,3^FS'+#13#10+ '^FX Third section with barcode.'+#13#10+ '^BY5,2,270'+#13#10+ '^FO175,550^BC^FD1234567890^FS'+#13#10+ '^FX Fourth section (the two boxes on the bottom).'+#13#10+ '^FO50,900^GB700,250,3^FS'+#13#10+ '^FO400,900^GB1,250,3^FS'+#13#10+ '^CF0,40'+#13#10+ '^FO100,950^FDShipping Ctr. X34B-1^FS'+#13#10+ '^FO100,1000^FDREF1 F00B47^FS'+#13#10+ '^FO100,1050^FDREF2 BL4H8^FS'+#13#10+ '^CF0,190'+#13#10+ '^FO485,920^FDCA^FS'+#13#10+ '^XZ'+#13#10; Printer.Escape(n, t); end; Использование OLE(ActiveX компонентов) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Получить экземпляр OLE-объекта ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ В Pascal-скрипте что бы получить экземпляр ole-объекта (создать) необходимо воспользоваться функцией ``CreateOleObject``. На вход принимается строка - идентификатор сласса объета, зарегистрированный в системе, например: *'Word.Application'*, или *'OPOS.Scanner'*. Возвращается методом интерфейс **IDispatch**, который в скрипторе сразу заворачивается в Variant для хранения и дальнейших манипуляций. Обращение к методам и свойствам ole-объекта реализуется синтаксисом совершенно аналогичным синтаксису обращения к методам и свойствам обычного скриптового объекта.  Утилизация OLE-объекта ^^^^^^^^^^^^^^^^^^^^^^ Время жизни объекта управляется счётчиком ссылок. Всё что требуется от разработчика - занулить все ссылки на полученный им объект, и полученные в процессе эксплуатации объекта ссылки на его составные части. Пример: .. code-block:: delphi oleobject := CreateOleObject('SomeRegisteredOLE.Factory'); nestedObject := oleobject.NestedObject;    nestedObject.DoSomthing; oleobject := null; nestedObject := null; Если полученный объект не передавался в какое-либо постоянное хранилище, только использовался в операции, - зануление, в принципе, не обязательно, т.к. по окончанию выполнения операции все имевшиеся в выполнявшем её экземпляре скриптора значения всё равно будут занулены. И, соответственно, наоборот: если требуется распространить время жизни полученного ole-объекта за пределы времени выполнения операции - следует сохранить ссылку где-либо. Например: .. code-block:: delphi Selection.AddVar('MyOleObject', oleobject, ftVariant); В этом случае время жизни ole-объекта, будет идентично времени жизни выборки, если значение MyOleObject не будет изменено раньше. Обработка событий возбуждаемых OLE-объектом ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Общее описание работы с обработчиками событий ''''''''''''''''''''''''''''''''''''''''''''' В технологии OLE как подмножество технологии COM обработка событий реализована через обращение к переданному(подключенному) от владельца объекта интерфейсу. Интерфейс callback-ов (зачастую в один интерфейс включены несколько обработчиков - методов, которые вызывает ole-объект при наступлении соответствующего события, объединённых смысловой нагрузкой) в каждом отдельном случае реализации COM-объекта имеет чёткую однозначную спецификацию (описанную в документации объекта и, в случае ActiveX компонента, в TypeLibrary библиотеки) и идентификатор (GUID. Так же описан в документации или TypeLibrary). COM-объект может специфицировать несколько разных callback-интерфейсов. К COM-объекту может быть подключено разом несколько callback-интерфесов, как разных, так и однотипных. Для OLE-объектов интерфейсы обработчиков(callback-ов) специфицируются как наследник **IDispatch** - **dispinterface**. **Dispinterface** описывает какие методы и свойства содержатся в OLE-объекте, их индексы (OLE практикует индексное обращение к методам и свойствам), параметры и типы возвращаемых значений. Реализация обработчика в скрипторе '''''''''''''''''''''''''''''''''' В скрипторе имеется возможность подключить операцию выборки в качестве обработчика событий к OLE-объекту. Возможность реализована по средствам скриптового класса **TbtkScriptComEvents**, он реализует **IDispatch** и перенаправляет все вызовы в операцию выборки. Свойства и методы **TbtkScriptComEvents**: - **class function Create(EventsIID: String): TbtkStriptComEvents**; Создаёт новый экземпляр адаптера обработки событий: события генерируемые COM-объектом вызывают операцию выборки. Экземпляр представляющий заданный в параметре *EventsIID* класс COM-обработчика. *EventsIID* - GUID представленный строкой вида *{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}*. GUID dispinterface-а обработчика событий COM-объекта. - **procedure Connect(ComObject: Variant; Operataion: TbtkScriptOperationObject)**; Установить обработчик событий COM-объекту (подключить к COM-объекту). COM-объекту передаётся ссылка на **IDispatch** реализуемый данным экземпляром **TbtkScriptComEvents**, так же ссылка остаётся на COM-объект у экземпляра **TbtkScriptComEvents**. Ссылка на COM-объект (после успешного подключения) доступна через свойство **ComObject**. Проверить успех подключения можно через свойство **Connected**. В последствии вызовы событий данного интерфейса COM-объектом будут перенаправляться в операцию выборки переданную как параметр *Operation*. Параметры передаваемые в операцию-обработчик: #. *Sender* - COM(OLE)-объект вызвавший обработчик. #. *DispId* - индекс вызванного метода интерфейса-обработчика (dispinterface) #. *Params* - параметры переданные при вызове. VariantArray of Variant. - **procedure Disconnect**; Отключить обработчик от COM-объекта. Убрать ссылку на себя из COM-объекта и занулить ссылку на COM-объект. - **property Connected: Boolean**; Свойство сообщает о состоянии обработчика, True - подключен (успешно выполнен метод ``Connect``). - **property ComObject: Variant**; COM-объект к которому подключен обработчик (для которого выполнен метод Connect, он же передаётся как Sender в операцию-обработчик). Время жизни экземпляров TbtkScriptComEvents в скрипторе управляется счётчиком ссылок. .. code-block:: delphi // Создать новый экземпляр COM-объекта device := OPOS_OpenNewDevice('Scanner', 'MyScanner1', True); //Создать адаптер обработки событий COM-объекта events := TbtkScriptComEvents.Create('{CCB90183-B81E-11D2-AB74-0040054C3719}'); //Подключить обработчик к COM-объекту events.Connect(device, Selection.OperationByName('TestOp')); if events.Connected then //передать ссылку выборке, что бы иметь возможность управлять им в последствии. Selection.AddVar('scanner', events) else Raise('Невозможно установить обработчик. Объект не соответствует спецификации.'); Примечания: """"""""""" #. Экземпляр обработчика(TbtkScriptComEvents) будучи успешно подключенным к COM(или OLE)-объекту **требует обязательного отключения ** (вызова ``Disconnect``), в противном случае ни COM-объект, ни экземпляр обработчика никогда не будут уничтожены до самого завершения программы - они будут ссылаться друг на друга и их счётчики не занулятся. Примеры работы с OLE ^^^^^^^^^^^^^^^^^^^^ Библиотеки компонентов ActiveX (\*.ocx) имею исчерпывающую информацию о способах взаимодействия с OLE-объектами реализованными в данной библиотеке - TypeLibrary - ресурс самой библиотеки (OCX). Microsoft `Windows SDK `__ имеет инструменты для чтения библиотеки типов - "OleView". Его использование может стать серьёзным подспорьем при разработке методов взаимодействия с тем или иным ActiveX компонентом. На корпоративном FTP есть `локальная копия Windows SDK `__, `пакет непосредственно содержащий "OleView" `__ Работа с компонентами OPOS (OLE POS) '''''''''''''''''''''''''''''''''''' `Скачать дистрибутив ActiveX компонентов OPOS `__ `Документация OPOS `__ (В современности описывается стандартом UnifiedPOS) .. raw:: html
**Пример создания/инициализации OPOS-устройства** .. code-block:: delphi function OPOS_OpenNewDevice(DeviceClass: String; DeviceName: String; Exclusive: Boolean): Variant; begin device := CreateOleObject('OPOS.' + DeviceClass); if device.Open(DeviceName) <> 0 then Raise('OPOS.' + DeviceClass + '.Open error ' + IntToStr(device.OpenResult)); if Exclusive and (device.ClaimDevice(1000) <> 0) then begin Device.Close; Raise('OPOS.' + DeviceClass + '.ClaimDevice error'); end; device.DeviceEnabled := True; if not device.DeviceEnabled then begin Device.ReleaseDevice; Device.Close; if not Exclusive then Raise('device not Enabled. May Exclusive needed.') else Raise('device not Enabled'); end; Result := device; end; .. code-block:: delphi device := OPOS_OpenNewDevice('Scanner', 'ScannerName', True); - Имена классов OPOS устройств можно наблюдать среди зарегистрированных в системе утилитой "OleView" - Имена устройств(и настройки) хранятся по пути реестра "HKEY\_LOCAL\_MACHINE\\SOFTWARE\\OLEForRetail\\ServiceOPOS\\" ключ ServiceOPOS содержит классы зарегистрированных в системе устройств, такие как "Scanner". ключ конкретного типа устройств содержит ключи с Именами зарегистрированных устройств В приведённом примере подключается устройство описанное в ключе "HKEY\_LOCAL\_MACHINE\\SOFTWARE\\OLEForRetail\\ServiceOPOS\\Scanner\\ScannerName" **Пример завершения работы с OPOS-устройством** .. code-block:: delphi procedure CloseDevice(Device: Variant); begin Device.DeviceEnabled := False; Device.ReleaseDevice; Device.Close; end; Пример работы со сканером штрихкодов .. code-block:: delphi var events: TbtkScriptComEvents; begin Selection.ExecOpScript('CloseScanner'); device := OPOS_OpenNewDevice('Scanner', ScannerDeviceName, True); events := TbtkScriptComEvents.Create('{CCB90183-B81E-11D2-AB74-0040054C3719}'); events.Connect(device, Selection.OperationByName('OnScanner')); if not events.Connected then begin CloseDevice(device); Raise('Сканер отверг обработчик событий'); end; device.DecodeData := True; device.DataEventEnabled := True; if Selection.VarExists('StaticScannerVariableName') then Selection.SetVar('StaticScannerVariableName', events) else Selection.AddVar('StaticScannerVariableName', events, ftVariant); end; var events: TbtkScriptComEvents; begin events := Selection.GetVar('StaticScannerVariableName') if VarIsNull(events) then Exit; CloseDevice(events.ComObject); events.Disconnect; Selection.GetVar('StaticScannerVariableName', Null); end; Selection.ExecOpScript('CloseScanner'); function ScanDataType2String(AType: Integer): String; begin ... end; begin try case ADispID of 1: begin ShowMessage('Данные сканирования: '+ ScanDataType2String(ASender.ScanDataType)+' '+ ASender.ScanDataLabel); end; finally ASender.DataEventEnabled := True; end; end; **function ScanDataType2String(AType: Integer): String**; .. code-block:: delphi begin case AType of 101: Result := 'Digits'; 102: Result := 'Digits'; 103: Result := 'EAN 8'; 104: Result := 'EAN 13'; 105: Result := 'Discrete 2 of 5) Digits'; 106: Result := 'Interleaved 2 of 5) Digits'; 107: Result := 'Digits, -, $, :, /, ., +; 4 start/stop characters (a, b, c, d)'; 108: Result := 'Full ASCII feature'; 109: Result := 'Same characters as Code 39'; 110: Result := '128 data characters'; 111: Result := 'UPC-A with supplemental barcode'; 112: Result := 'UPC-E with supplemental barcode'; 113: Result := 'UPC-D1'; 114: Result := 'UPC-D2'; 115: Result := 'UPC-D3'; 116: Result := 'UPC-D4'; 117: Result := 'UPC-D5'; 118: Result := 'EAN 8 with supplemental barcode'; 119: Result := 'EAN 13 with supplemental barcode'; 120: Result := 'EAN 128'; 121: Result := 'OCR "A"'; 122: Result := 'OCR "B"'; 131: Result := 'Reduced Space Symbology - 14 digit GTIN'; 132: Result := 'RSS - 14 digit GTIN plus additional fields'; 131: Result := 'GS1 DataBar Omnidirectional (normal or stacked)'; 132: Result := 'GS1 DataBar Expanded (normal or stacked)'; 133: Result := 'Interleaved 2 of 5 check digit verified and transmitted'; 134: Result := 'GS1 DataBar Limited'; 135: Result := 'Ames Code'; 136: Result := 'Matrix 2 of 5'; 137: Result := 'Code 39 with check character verified and transmitted'; 138: Result := 'Code 39 with Mod 32 check character'; 139: Result := 'Code 39 CIP'; 140: Result := 'Tri-Optic Code 39'; 141: Result := 'ISBT-128'; 142: Result := 'Code 11'; 143: Result := 'MSI Code'; 144: Result := 'Plessey Code'; 145: Result := 'Telepen'; 151: Result := 'Composite Component A.'; 152: Result := 'Composite Component B.'; 153: Result := 'Composite Component C.'; 154: Result := 'TLC-39'; 201: Result := 'PDF 417'; 202: Result := 'Maxicode'; 203: Result := 'Data Matrix'; 204: Result := 'QR Code'; 205: Result := 'Micro QR Code'; 206: Result := 'Aztec'; 207: Result := 'Micro PDF 417'; 208: Result := 'GS1 DataMatrix'; 209: Result := 'GS1 QR Code'; 210: Result := 'Code 49'; 211: Result := 'Code 16K'; 212: Result := 'Codablock A'; 213: Result := 'Codablock F'; 214: Result := 'Codablock 256'; 215: Result := 'Han Xin Code'; end else Result := 'Unknown(' + IntToStr(AType) + ')'; end; Взаимодействие с последовательным (RS232, RS485) портом ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ В системе Global выполнять чтение из последовательного (COM) порта можно используя класс TbtkScriptSerialPortObject. Получить объект связанный с последовательным портом можно через метод Application.GetSerialPort(APortName: String); Описание классов ^^^^^^^^^^^^^^^^ TbtkScriptSerialPortObject '''''''''''''''''''''''''' Методы """""" - Close - Закрытие COM порта. На закрытие выполняет освобождение занятых системных ресурсов, **важно после работы с COM портом закрыть его**. - DeregisterHandler - Дерегистрация обработчика на получение данных COM портом. - GetCOMMTimeouts: TbtkScriptSerialPortTimeouts - Получение параметров времение ожидания(timeout) последовательного порта. После изменения параметров, можно записать в порт используя SetCOMMTimeouts. - GetDCBProperties: TbtkScriptSerialPortDcbObject - Получение управляющий настроек порта в виде DCB структуры. С помощью функции можно получить текущие настройки порта, изменить нужные поля и записать в порт через SetDCBProperties. - Open - Открытие COM порта. После открытия порта, можно производить чтение и запись. - RegisterHandler(var Operation: TbtkScriptOperationObject) - Регистрация обработчика на получение данных COM портом. Зарегестрированная операция будет вызвана, когда устройство подключенное к COM порту выполнит запись в порт. Обработчик должен обладать входным параметров строковго типа. Через параметр передается имя порта из которого получены данные. - SetCOMMTimeouts(var TimeOuts: TbtkScriptSerialPortTimeouts ) - Запись параметровов таймаутов в порт. Параметром является структура COMMTIMEOUTS. - SetDCBProperties(var DCB: TbtkScriptSerialPortDcbObject) - Запись управляющей структуры DCB в порт. Свойства """""""" - Data: String - Данные считанные из последовательного порта, после их поступления. Поле позволяет получить данные в обработкичке на поступление данных в последовательный порт. - Port: String - Имя последовательного порта. Свойство только для чтения - Opened: boolean; - Возвращает состояние актиности порта. Если true - порт открыт, false - закрыт. - Registered: boolean; - Зарегестрирован обработчик на последовательный порт или нет. True - зарегестрирован, False - незарегестрирован. TbtkScriptSerialPortDcbObject ''''''''''''''''''''''''''''' Свойства """""""" - BaudRate: Integer; - Для задания значения можно использовать константы CBR\_110, CBR\_300, CBR\_600, CBR\_1200, CBR\_2400, CBR\_4800, CBR\_9600, CBR\_14400, CBR\_19200, CBR\_38400, CBR\_56000, CBR\_57600, CBR\_115200, CBR\_128000, CBR\_256000; - ByteSize: Integer; - Parity: Integer; - Для задания значения можно использовать константы NOPARITY, ODDPARITY, EVENPARITY, MARKPARITY, SPACEPARITY; - StopBits: Integer; Константы ONESTOPBIT, ONE5STOPBITS, TWOSTOPBITS. `Описание DCB структуры в msdn `__ TbtkScriptSerialPortTimeouts '''''''''''''''''''''''''''' Эту структуру лучше, не использовать в режиме аснхронного чтения, когда вызывается обработчик после чтения порта. Это обусловлденно внутренними особенностями работы. Кому вдруг интерсенно, то там используется overlapped режим и Windows нам сообщается о завершение на на входе записи в ком порт сразу '''блока '''данных. Дальше мы получаем объем данных в COM порте и под этот объем подготавливаем буфер в который и уже записываем данные через ReadFile.  Timeouts влияет на ReadFile, но в нашем случае мы в любом случае не прочитаем больше, чем размер подготовленного буфера. Но все это относиться к асинхронному режиму, если будет реализован не асинхронный, то структура снова будет на коне. Свойства """""""" - ReadIntervalTimeout: Integer - ReadTotalTimeoutConstant: Integer - ReadTotalTimeoutMultiplier: Integer - WriteTotalTimeoutConstant: Integer - WriteTotalTimeoutMultiplier: Integer `Описание Timeouts структуры в msdn `__ Настройки TbtkScriptSerialPortDcbObject и TbtkScriptSerialPortTimeouts задавать не обязательно, можно использовать значения по умолчанию.  В коде ниже приведен пример как следуюет задавать настройки, а именно в начале надо получить текущие, затем установить новые пользовательские значения  потом уже эту структуру записать в последовательный порт, тем самым мы гарантируем, что не перетрем свойства которые не задали в структуре пустыми значениями. Пример работы ^^^^^^^^^^^^^ Листинг тестовой операции: .. code-block:: delphi ComPortName := GetVar('sSerialPortName'); SerialPort := Application.GetSerialPort(ComPortName); // ComPortName = "COM3" SerialPort.Open; // Дальше идет задание настроек, но этот момент можно пропустить // тогда будут использованы значения по умолчанию // точно такие же, какие заданы в этом примере dcb := SerialPort.GetDCBProperties; dcb.BaudRate := CBR_19200; dcb.ByteSize := 8; dcb.Parity := ONESTOPBIT; dcb.StopBits := NOPARITY; SerialPort.SetDCBProperties(dcb); timeouts := SerialPort.GetCOMMTimeouts; timeouts.ReadIntervalTimeout := 50; timeouts.ReadTotalTimeoutConstant := 40; timeouts.ReadTotalTimeoutMultiplier := 40; timeouts.WriteTotalTimeoutConstant := 40; timeouts.WriteTotalTimeoutMultiplier := 40; SerialPort.SetCOMMTimeouts(timeouts); SerialPort.RegisterHandler(Selection.OperationByName('OnComPortReaded') ); Листинг операции-обработчика OnComPortReaded: .. code-block:: delphi SerialPort := Application.GetSerialPort(APortName); ShowMessage(SerialPort.Data); Эмулирование COM порта ^^^^^^^^^^^^^^^^^^^^^^ Для теста можно эмулировать COM порт и записывать в него поток данных. Существуют бесплатные(условно-бесплатные) версии подобных утилит. Проверенно работает утилита VSPE (VIRTUAL SERIAL PORTS EMULATOR) для эмуляции виртуального последовательно порта. Для генерации трафика в цикле можно использовать утилиту AGG Com port data emulator (Freeware). Сканеры штрих-кодов ~~~~~~~~~~~~~~~~~~~