Copyright (c) Prolog Development Center SPb

Application Frame. Панели управления (Ribbons)

Application Frame в качестве главного органа управления приложением использует панели (ribbons)
Механизм панелей включен в PFC и представлен пакетом pfc\gui\controls\ribbonControl\ribbonControl.pack.

Конфигурация панели в Aplication Frame может быть определена либо непосредственно в коде либо в виде XML-структуры.
Определение панели в коде можно видеть в примере, входящем в состав Visual Prolog (ribbonDemo).
Правила представления панели в виде XML-структуры описывается в документе ApplicationFrame.RibbonScript.
В примере SpbExamples\Febe можно видеть примеры определения панелей и технику их применения.
Определение панели в виде XML-структуры является не только альтернативой определению в коде, но и позволяет  повысить адаптируемость приложения к непосредственным  потребностям пользователя.

Как это работает

Скрипт-панель является внешним по отношению к коду Application Frame xml-файлом, однако его содержимое интерпретируется кодом Application Frame.
Часть xml-представления является статическими определениями как, например, label, id.
Часть xml-представления определяет динамические параметры, как, например, render-menu или функция, вызываемая для определения текста tooltip.
Динамические параметры в скрипт-панелях Application Frame представляются текстовыми именами функций. Этим текстовым именам функций в коде Application Frame соответствуют предикаты через определение пары "текстовое имя"- "предикат". Детали реализации использования этих пар зависят от типа динамического параметра в каждом конкретном случае.

RibbonLoader

За загрузку файла скрипт-панели отвечают объекты пакета SpbVipTools\Packs\Gui\ribbonLoader\ribbonLoader.pack  и пакета SpbVipTools\Packs\Gui\ribbonLoader\cmdPerformer\cmdPerformer.pack.

Перед загрузкой панели создается объект класса cmdPerformer.

        ContextObj=cmdPerformers::new()

Ему передаются данные о контексте исполнения загрузки скрипт-панели в виде установки свойств этого объекта.

Property declaration xml-node Description
useDictionary_P:boolean defines whether dictionary must be used
dictionary_P:dictionary defines object of the class fe_Dictionary
noIconRender_P:function{binary} menu defines function, which must return the binary content of the icon, if no IconRender found
setRunner:(string RunnerID,predicate{command}) cmd defines predicate, which must be invoked, when the command button with the RunnerID is pressed
setChangeStateHandler:(string StateHandlerID,predicate{command}) cmd defines predicate, which must be invoked, when the command state is changed
setTooltipRender:(string TooltipRenderID, function{string}) tooltip defines function, which must return the tooltip text
setMenuRender:(string MenuRenderID, function{menuCommand::menuItem*}) menu defines function, which must return the list of menuItems
setIconByIDRender:(string IconRenderID,function{string IconKey, binary}) icon defines function, which must return the binary content of the icon with the given IconKey
setCustomFactory:(string CustomFactoryID,
function{string ControlID, function{control}})
custom defines function, which must create and return the control Object for the given ControlID

Пример установки таких функций приведен ниже

    setCommonPerformers(ContextObj):-
            ContextObj:useDictionary_P:=true,
            ContextObj:dictionary_P:=fe_Dictionary(),
            ContextObj:setRunner("test",fe_Tests():runTest),
            ContextObj:setRunner("pzl-extension",fe_CoreTasks():runPlugin),
            ContextObj:setCustomFactory("test-factory",fe_Tests():controlFactory),
            ContextObj:setMenuRender("language-list",languageList_Menu),
            ContextObj:noIconRender_P:=noIconRender,
            ContextObj:setIconByIDRender("default",fe_CoreTasks():getIconByID).

Затем вызывается ribbonLoader
       ...
    RibbonLoader=ribbonLoader::new(...),
    RibbonLoader:loadXmlData(XmlBinaryData),
    Layout=RibbonLoader:getLayout(ContextObj),
     ...

Функция loadXmlData(XmlBinaryData) принимает xml-представление панели в виде bynary и возвращает Layout, соответствующий домену

domains

    layout = section*.


В процессе преобразования происходит создание всех команд и меню, а layout определяет только  их расположение на панели.

Применение словарей

Xml-представление панели содержит указание на nameSpace и файл-словарь. RibbonLoader при создании панели обращается к объекту класса fe_Dictionary и сообщает имя nameSpace и имя файла словаря.
Если это nameSpace отсутствует в fe_Dictionary, то fe_Dictionary запрашивает через fe_CoreTasks словарь у BackEnd так, как описано в документе DictionariesSupport.html.
Если словарь не найден, то ribbonLoader устанавливает для данного фрагмента панали имя nameSpace "NoNameSpace".

В xml-описании tooltip и menuLabel не имеют идентификаторов. Для поиска соответствующих словарных форм для этих сущностей применяется правило -
к идентификатору cmd или menu этих сущностей добавляетя суффикс ".tooltip" и ".menulabel", соответственно, в словарях эти суффиксы должны быть добавлены к ключам соответствующих словоформ.

Ключ для текстовых фраз сущностей cmd и menu имеет еще одну особенность - этим ключом является идентификатор сущности. А идентификатор сущности формируется добавлением префикса "<nameSpace>\" к объявленному идентификатору сущности. Это позволяет смешивать на одной панели элементы из разных nameSpace.

Например, если nameSpace в xml-представлении объявлен как "basic", а идентификатор команды (cmd) в xml-представлении объявлен как "file.open" то команда панели будет иметь идентификатор "basic\file.open".

Варианты создания панелей в Application Frame

Ядром Applicaion Frame предусмотрены три варианта начальной загрузки панели:
Вариант default представляет загрузку панели, представленной кодом пакета Common\AppFrontEnd\fe_Ribbons\fe_RibbonDefault\fe_RibbonDefault.pack и не имеет отношения к xml-представлению панели.
Вариант loadable представляет загрузку панели представленной любым xml-файлом, указанным в конфигурации Application Frame. Контекст загрузки устанавливается пакетом Common\AppFrontEnd\fe_Ribbons\fe_RibbonLoadable\fe_RibbonLoadable.pack.
Вариант embedded представляет загрузку панели представленной любым xml-файлом, включенным в качестве константы embeddedRibbonXml_C директивой #bininclude в файле Common\AppBackEnd\be_Options\be_Options.cl. Контекст загрузки устанавливается пакетом Common\AppFrontEnd\fe_Ribbons\fe_RibbonEmbedded\fe_RibbonEmbedded.pack.
Вариант embedded позволяет скрыть описание панели в условиях эксплуатации.

В вариантах loadable и embedded FrontEnd запрашивает содержимое xml-представления панели у BackEnd.

Способ начальной загрузки панели отражается в xml-файле конфигурации приложения bin\<projectName>AppData\<projectName>_Options.xml.

Расширение рабочей панели

При создании панели ribbonControl, как контейнер, содержит набор элементов, расположение которых определяется структурой layout.
Поэтому при работающем пользовательском интерфейсе (состояние isShown=true), можно добавлять новые элементы и дополнять существующую структуру размещения (layout) новыми секциями конкатенацией списков.

domains

    layout = section*.


Ядро Application Frame предусматривает возможность расширения рабочей панели путем загрузки в режиме run-time новых секций панели, представленных  xml-файлами.
Расширение панели возможно для любого из вариантов начальной загрузки панели (default, loadable, embedded).
Xml-файлы панелей должны располагаться в директории bin\<projectName>AppData.
Добавление панели производится вызовом предиката addExtension() объекта класса fe_CoreTasks. Предикат addExtension() запрашивает у BackEnd все xml-файлы панелей, хранящиеся в директории bin\<projectName>AppData и предлагает диалог со списком этих файлов.



При выборе файла панели объект класса ribbonLoader загружает новый фрагмент панели и помещает его в конец панели. Если необходимо, словарь для этого файла активизируется (при наличии). Расширения панели сохраняются в файле конфигурации. Пример структуры хранения расширений приведен ниже.

      <ribbon startup="default">
        <loadable script="febeAppData\ribbon_Basic.xml">
          <ext>.\febeAppData\ribbonFile.xml</ext>
          <ext>.\febeAppData\ribbonLanguageRender.xml</ext>
          <ext>.\febeAppData\ribbonTests.xml</ext>
        </loadable>
        <embedded />
        <default>
          <ext>.\febeAppData\ribbonTests.xml</ext>
          <ext>.\febeAppData\ribbonLanguageRender.xml</ext>
          <ext>.\febeAppData\ribbonPlugins.xml</ext>
        </default>
      </ribbon>

Узел <embedded/> для одноименного варианта начальной загрузки в этом примере не имеет  расширений.
Все элементы панели, включая вновь добавленные фрагменты,  можно перемещать в диалоге Design (инструмент, являющийся частью пакета ribbonControl системы Visual Prolog).
ApplicaionFrame сохраняет новое расположение панели в файле конфигурации bin\<projectName>AppData\Options.xml и повторяет его при старте приложения.
Удалять или добавлять панели расширения или изменять стартовую панель для варианта loadable можно простым редактированием файла конфигурации bin\<projectName>AppData\Options.xml текстовым редактором.
При этом возможна потеря предыдущего расположения элементов панели и может потребоваться новое редактирование расположения панели.

Процедура загрузки панелей

При создании главного окна приложения до обработки предиката show(), предикат initContent(...) создает объект класса fe_Command и инициирует панель

clauses
    initContent(FrontEnd):-
        generatedInitialize(),
        ...
        fe_Command_P:=fe_Command::new(This,FrontEnd),
        ...
        fe_Command_P:initRibbon(false), <- false - признак восстановления последнего состояния панели
        ...

В объекте класса fe_Command, если BackEnd доступен, выяснив  вариант начальной загрузки, предикатом initRibbon(...) Если при этом выяснено, что к панели должен быть применен словарь, то все текстовые заголовки панели корректируются с учетом действующих словарей предикатом
updateRibbonLabels().

Обработка команд панели

Для варианта начальной загрузки default, реакции на нажатия кнопок должны быть прописаны в классе fe_RibbonStartupDefault, по примеру ribbonDemo.
Для вариантов loadable и embedded обработка событий от органов управления рекомендуется прописать в классах, соответственно RibbonStartupLoadable и RibbonStartupEmbedded.
Для фрагментов, расширяющих панели обработка событий может быть размещена в любом удобном месте по усмотрению разработчика.

Стандартная реакция на нажатия кнопок управления определена пакетом ribbonControl и имеет форму
predicates
    commandHandler:(command RibbonCommand).

Учитывая, что объект RibbonCommand содержит в качестве свойства Id идентификатор команды, префиксом которой является <nameSpace>\,
выделение исходного идентификатора команды (представленного и в декларации скрипт-панели и в декларациях скрипт-словарей предлагается  выполнять как показано ниже

predicates

    commandHandler:(command RibbonCommand).
clauses
    commandHandler(RibbonCommand):-
        string::frontToken(RibbonCommand:id,NameSpace,CommandIDWithSlash),
        string::frontChar(CommandIDWithSlash,_,CommandID),
        nextLevelCommandHandler(CommandID,NameSpace,RibbonCommand:id),
        !.
    commandHandler(_RibbonCommand).

predicates
    nextLevelCommandHandler:(string CommandID,string NameSpace,string SourceRibbonCommand) multi.
clauses
    nextLevelCommandHandler("ribbon.cmd.design-ribbon",_NameSpace,_
SourceRibbonCommand):-
            designRibbonLayout().
    nextLevelCommandHandler("ribbon.cmd.restore-ribbon-layout",_NameSpace,_
SourceRibbonCommand):-
        tryBackEndAvailable(),
    ...