Шаблоны: Когда я это писал, оно казалось хорошей идеей.

Автор: Иванов Борис

Публикуется без редактирования и должной проверки. Все замеченные замечания 🙂 слать в редакцию.

…мне почему-то показалось что с выходом с7 стало как-то больше молодежи на форуме, подумал что будет полезно. Плюс статиститика опроса на сайте https://www.clarionlife.net показывает что есть целый 1 человек младше 18, может ему будет полезно.

Просто статей как-то совсем не добавляется, решил евангелизм будет не лишним. Качество кода может и не шик, но хоть что-то 🙂

Собственно, все нижеследующее порождено в связи с отсутствием чего-либо внятного по поводу Clarion Template Language от классиков жанра, поэтому пишу я, а классики, понадеемся, исправят то, что получится :).
Как сказано ранее- шаблон начинает писаться в тот момент, когда понимаешь что «данный кусок уже писал», таким моментом для меня стал код «Раскраска листбокса», по сути это изподкнопочное окошко, с 4 кнопками настройки цвета столбца (бэкграунд, шрифт и то же для селектированного-бэк и шрифт). Определив глобальную цель, начнем с поиска литературы: в комплекте с c7 идет неплохой хелп (на английском естественно), на вторые сутки перевода которого, ощущение дежавю стало просто осязаемым- все тоже самое, но на русском есть на фтп форума в древнем таком файле авторства Alexandre Katalov. С тех пор ничего нового (в документации) не добавили( на счет самого языка шаблонов- не знаю, если кому есть что сказать — отпишите). Так что Alexandre Katalov это must read.
Далее осилив 10 страниц воды, в доке будет таблицасписок с мясом – классификация всех возможных шаблонов. Для моей задачи прекрасно подходил тип шаблона «Control», если интуитивно, то это все те шняшки, которые можно кидать на форму в режиме WindowFormatter. Все доступные шаблоны такого типа видны из меню PopulateControl Template…

Так как опыт был первый, я не решился браться полностью за разработку шаблона с ноля и полез в реестр шаблонов(Registry Template) с целью превратить HelpButton в конфетку, полностью поменяв ей функционал. Registry Template по сути есть сборник активированных шаблонов, хранящихся в папке %clariontemplate. Хорошо, что в тот раз кнопку Help Button я не нашел. Зато дочитал хелп, в нем говорилось, что править шаблон можно как в ручную, используя любимый Notepad (лежат в %clariontemplate файлы *.tpw), так и из реестра шаблонов(Registry Template) (в IDE меню setuptemplate registry):

И тут вот крайне рекомендуется сделать бэкап папки шаблонов, прежде чем начинать там гениально править 🙂 Хоть среда и делает бэкапы изменений шаблонов(кстати туда же-в %clariontemplate), но только последних.
Еще немного лирики – да, сейчас то я понимаю, что надо было создать шаблон с ноля, это очень просто, всего пара строчек о том, что это шаблон и какого он типа, и потом нажать вон ту кнопку «Register», но тогда я этого не знал, а сказка пишется для совсем новичков и будет полезнее соблюсти хронологию получения дао.

Итак, я полез править кнопку Help, оказалось она лежит в ClassABC->ControlTemplates. Я выбрал ее и нажал «Edit Definition», показалось окно редактора, даже прокрученное до нужного места. Путем нехитрой операции «Ctrl+C ctrl+v», вместо исправления кнопки Help, был-таки получен новый контрол, код которого я частями размещу ниже и постараюсь прокомментировать каждый кусок. Да, еще забыл сказать, могу ошибаться, но в какой-то момент мне запомнилось, что не всё тривиально с форматированием кода в шаблоне, если что-то не работает, хотя вроде должно, возможно перемудрили с пробелами, где не надо.

#!----------------------------------------------------------------
#CONTROL(ColorButton,'Colore the Browse'),WINDOW,multi

Все statement(по-русски это «операнды»?) clarion template language начинаются со знака #.
#! это комментарий, виден только разработчику шаблона. #control это указание, что все нижеследующее это шаблон типа «Control», в скобках его имя, а в кавычках подсказка-описание, слово multi, это указание на то, что таких контролов на одной форме можно наложить много. Вот у кнопки Cancel или Close такого нет, они будучи добавлены на окно, больше не появляются в списке доступных шаблонов.

CONTROLS
   BUTTON('~'),USE(?Coloriri),AT(,,8,8)
END
#!

Здесь сказано, что именно будет размещено на форме, когда программер укажет мышью место для размещения моего контрола. В данном случае это одна миллиметрическая кнопка, а например у FuzzyMatcher’а тут целый набор-пара кнопок, группа и ентри.

В принципе уже можно закрыть Реестр Шаблонов и попытаться поглядеть на кнопку из какого–либо проекта:

Кнопка уже умеет делать ничего, но для нее в оконной процедуре сформируется куча embed’ов.

Но вернемся к шаблону- напишем далее:

#ATSTART
#DECLARE(%myColorControl)
#FOR(%Control),WHERE(%ControlInstance = %ActiveTemplateInstance)
#SET(%myColorControl,%Control)
#ENDFOR
#ENDAT
#!

%Control это такая предопределенная обозначает все контролы окна, %ControlInstance зависит от %Control, значение которого внутри цикла на одном значении. Где-то здесь было бы неплохо вообще рассказать о структуре app файла- в нем хранятся начальные состояния шаблонов и их настройки. У каждого добавленного в продцедуру шаблона есть , все номера лежат в %activeTemplateInstance, для нас эта мульти-переменная видимо пофиксирована на номере добавленного экземпляра шаблона (хоть про это нигде и не написано).

Выше я сказал: . A! Переменные в шаблонах зовут Symbol’ами.

#PROMPT('Choose list',control),%ColoririList,default(%ColoririList)

Спросить у программиста, а какой брауз лист мы собираемся раскрашивать? %ColoririList тоже переменная, только ее не обязательно Declare. Она сама декларится, от того, что указана как промпт. это конечно громко сказано, на самом деле программисту надо самому лезть в Properties добавленного контрола, и устанавливать значение в выпадающем списке. Вид промпта задается с помощью второго параметра, тут это , поэтому он и выглядит как выпадающий список с контролами

#PREPARE
#FIX(%Control,%ColoririList)
#ENDPREPARE
#VALIDATE(%ControlType = 'LIST','Must select a list box')
#!

Перед валидатом выполнится блок Prepare, он может состоять только из операторов ClarionTemplateLanguage, и используется в основном для всевозможных инициализаций, предустановок и тп. %Control можно на 1 контроле, не только с помощью цикла, но и #Fix’a. Тогда станут доступны всякие пряники, типа %ControlType именно этого контрола. %ControlType это тип контрола, список типов есть в хелпе. #Validate это проверка того, что ввел программист, если программист неправ, ему покажут сообщение. С русским у сообщений, помнится, не дружба.

#!
#AT(%datasection),PRIORITY(5030)
#for (%control),where(%ControlTemplate=%ActiveTemplate)
#if (%myColorControl=%control)
QCColor              QUEUE,PRE()                           #!Очередь для колорирования
CNam                 STRING(20)                            #!
FGC                  LONG                                  #!
BGC                  LONG                                  #!
SFC                  LONG                                  #!
SBC                  LONG                                  #!
                     END                                   #!
WindowColor WINDOW('Колорирование'),AT(,,223,153),FONT('MS Sans Serif',8,,FONT:regular),GRAY
       LIST,AT(5,6,89,143),USE(?ListColor),FORMAT('90L(2)|M*~Колонка~#1#'),FROM(QCColor)
       STRING('Цвет колонки:'),AT(101,6),USE(?StringFC)
       BUTTON('ФОН'),AT(154,6,27,10),USE(?ButtonBGC)
       BUTTON('&OK'),AT(140,135,35,14),USE(?OkButton),LEFT,DEFAULT
       BUTTON('&Cancel'),AT(182,135,36,14),USE(?CancelButton),LEFT
       STRING('Под курсором:'),AT(101,18),USE(?StringSC)
       BUTTON('ШРИФТ'),AT(184,18,33,10),USE(?ButtonSFC)
       BUTTON('ШРИФТ'),AT(184,6,33,10),USE(?ButtonFGC)
       BUTTON('ФОН'),AT(154,18,27,10),USE(?ButtonSBC)
       END
#endIF
#break
#endfor
#ENDAT
#!

Тут я пытаюсь в секцию данных(#AT(%DataSection)) оконной процедуры вставить код описывающий нужные мне переменные. Для такой операции вообще-то существует блок LocalData, но у меня тут рукописное окошко, его блок LocalData почему-то не съел(добавляет пробел перед именем окна). Цикл в этом блоке делает важную работу- так как шаблон объявлен в качестве multi, то каждый экземпляр его будет пытаться завести одни и теже переменные, а оно совсем не надо. Цикл бежит по всем контролам того же типа(видите там сравнение не по номеру instance, а по имени %ActiveTemplate), и разрешает вставить код только если текущий контрол-Первый своего типа(#if)- там #break, он рвет цикл после Первого прохода.

#!
#AT(%ControlEventHandling,%myColorControl,'Accepted'),PRIORITY(5000)
   free(qCColor)
   I#=0
   loop
      I#=I#+1
      #! Листозависимый код
      if %ColoririList{PropList:exists,I#}=1 else break.
      QCColor:Cnam=%ColoririList{PropList:header,I#}
      QCColor:BGC =%ColoririList{Proplist:BackColor,I#}
      QCColor:FGC =%ColoririList{Proplist:TextColor,I#}
      QCColor:SBC =%ColoririList{Proplist:BackSelected,I#}
      QCColor:SFC =%ColoririList{Proplist:TextSelected,I#}
      add(QCColor)
   end
  Open(windowColor)
  display(?listColor)
  ACCEPT #! ОБРАБОТКА ОКОШКА
  CASE FIELD()                                                                           of ?ButtonBGC    #!БЭКГРАУНД                                                              CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:BGC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonFGC    #!ШРИФТ                                                                  CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:FGC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonSBC    #!БЭКГРАУНД SELECTED                                                     CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:SBC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonSFC    #!FONT SELECTED                                                          CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:SFC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?OkButton                                                                              CASE EVENT()                                                                           OF EVENT:Accepted                                                                          close(WindowColor)                                                                 END !case event                                                                     of ?cancelButton                                                                          case event()                                                                           of EvenT:accepted                                                                         close(WindowColor)                                                                  end! Accepted cancelButton                                                          end !case field                                                                        end  ! ACCEPT                                                                                 loop i#=1  to records(QCCOlor)
            get(QCColor,I#)
            #! Листозависимый код
            %ColoririList{PropList:BackColor,I#}=QccOlor:BGC
            %ColoririList{PropList:TextColor,I#}=QCColor:FGC
            %ColoririList{PropList:BackSelected,I#}=QccOlor:SBC
            %ColoririList{PropList:TextSelected,I#}=QCColor:SFC
         end
#ENDAT

Вставка #AT(%ControlEventHandling,%myColorControl,’Accepted’),PRIORITY(5000) вобщем-то ничего нового не скажет(вместо %coloririList вставится имя Listbox’а указанного программистом и всё), разве что укажу способ поиска имен для возможных вставок-бродить по хелпу и файлам TPW, имена для вставок указаны после слова #EMBED.

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

Итого в abupdate.tpw добавился следующий код

#!----------------------------------------------------------------
#CONTROL(ColorButton,'Colore the Browse'),WINDOW,multi
CONTROLS
   BUTTON('~'),USE(?Coloriri),AT(,,8,5)
END
#!
#ATSTART
#DECLARE(%myColorControl)
#DECLARE(%scnd,long)
#UNFIX(%Control)
#FOR(%Control),WHERE(%ControlInstance = %ActiveTemplateInstance)
#SET(%myColorControl,%Control)
#ENDFOR
#ENDAT
#!
#PROMPT('Choose list',control),%ColoririList,default(%ColoririList)
#PREPARE
#FIX(%Control,%ColoririList)
#ENDPREPARE
#VALIDATE(%ControlType = 'LIST','Must select a list box')
#!
#!
#AT(%datasection),PRIORITY(5030)
#for (%control),where(%ControlTemplate=%ActiveTemplate)
#if (%myColorControl=%control)
QCColor              QUEUE,PRE()                           #!Очередь для колорирования
CNam                 STRING(20)                            #!
FGC                  LONG                                  #!
BGC                  LONG                                  #!
SFC                  LONG                                  #!
SBC                  LONG                                  #!
                     END                                   #!
WindowColor WINDOW('Колорирование'),AT(,,223,153),FONT('MS Sans Serif',8,,FONT:regular),GRAY
       LIST,AT(5,6,89,143),USE(?ListColor),FORMAT('90L(2)|M*~Колонка~#1#'),FROM(QCColor)
       STRING('Цвет колонки:'),AT(101,6),USE(?StringFC)
       BUTTON('ФОН'),AT(154,6,27,10),USE(?ButtonBGC)
       BUTTON('&OK'),AT(140,135,35,14),USE(?OkButton),LEFT,DEFAULT
       BUTTON('&Cancel'),AT(182,135,36,14),USE(?CancelButton),LEFT
       STRING('Под курсором:'),AT(101,18),USE(?StringSC)
       BUTTON('ШРИФТ'),AT(184,18,33,10),USE(?ButtonSFC)
       BUTTON('ШРИФТ'),AT(184,6,33,10),USE(?ButtonFGC)
       BUTTON('ФОН'),AT(154,18,27,10),USE(?ButtonSBC)
       END
#endIF
#break
#endfor
#ENDAT
#!
#!
#AT(%ControlEventHandling,%myColorControl,'Accepted'),PRIORITY(5000)
   free(qCColor)
   I#=0
   loop
      I#=I#+1
      #! Листозависимый код
      if %ColoririList{PropList:exists,I#}=1 else break.
      QCColor:Cnam=%ColoririList{PropList:header,I#}
      QCColor:BGC =%ColoririList{Proplist:BackColor,I#}
      QCColor:FGC =%ColoririList{Proplist:TextColor,I#}
      QCColor:SBC =%ColoririList{Proplist:BackSelected,I#}
      QCColor:SFC =%ColoririList{Proplist:TextSelected,I#}
      add(QCColor)
   end
  Open(windowColor)                                                                      
display(?listColor)                                                                    ACCEPT #! ОБРАБОТКА ОКОШКА                                                             CASE FIELD()                                                                           of ?ButtonBGC    #!БЭКГРАУНД                                                              CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:BGC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonFGC    #!ШРИФТ                                                                  CASE EVENT()                                                                             OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:FGC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonSBC    #!БЭКГРАУНД SELECTED                                                     CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:SBC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?ButtonSFC    #!FONT SELECTED                                                          CASE EVENT()                                                                           OF EVENT:ACCEPTED                                                                          get(qCColor,choose(Choice(?ListColor)>0,Choice(?ListColor),1))                         IF NOT COLORDIALOG('Choose Box Color',QCColor:SFC)                                     END                                                                                    put(QCCOLOR)                                                                       END                                                                                 of ?OkButton                                                                              CASE EVENT()                                                                           OF EVENT:Accepted                                                                          close(WindowColor)                                                                 END !case event                                                                     of ?cancelButton                                                                          case event()                                                                           of EvenT:accepted                                                                         close(WindowColor)                                                                  end! Accepted cancelButton                                                          end !case field                                                                        end  ! ACCEPT                                                                                 loop i#=1  to records(QCCOlor)
            get(QCColor,I#)
            #! Листозависимый код
            %ColoririList{PropList:BackColor,I#}=QccOlor:BGC
            %ColoririList{PropList:TextColor,I#}=QCColor:FGC
            %ColoririList{PropList:BackSelected,I#}=QccOlor:SBC
            %ColoririList{PropList:TextSelected,I#}=QCColor:SFC
         end
#ENDAT
#!----------------------------------------------------------------