21 января 2011

Скриптовой модуль, Демо-3

Продолжим изучение основ Lua! Стоит перейти к описанию третьей по счету демки, что я как-то выкладывал на суд читателей... Наверно, её можно обозначить как наиболее наглядную для понимания того, как вообще можно использовать скрипты. Строить безликие графики, считать никому ненужные формулы может каждый, но "это ведь простенькие тесты, а мы хотим настоящей бомбы" - скажете Вы! Поэтому сейчас на элементарном примере попытаюсь показать как можно очень быстро прикрутить скрипты к уже работающей программе с благой целью... Программисты пишут программы - это несомненно. А бывало ли у кого-нибудь, когда юзер говорит: "а не мог бы ты подвинуть вон ту кнопочку в своей программе?" или же "переименуй поле меню, а то неясно". В ответ говорим: "секунду! сейчас все сделаю!". Что происходит дальше? В худшем случае: открываем IDE, ковыряемся по диску в поисках проекта, открываем, компилируем, берем итоговый exe, который весит порядка мегабайта, отсылаем юзеру. Становится очевидным, что настройки  программы нужно выносить во внешние файлы. И тогда ответ был бы короче: "открой файл menuitems.ini и поменяй название менюшки как тебе угодно". Уже лучше, а что, если нас попросят поменять не только название, но логику отработки кнопки? Вот тогда приходят на помощь скрипты...

Итак, в этой демо можно увидеть самый элементарный способ настройки внешнего вида программы. Для этого научимся сначала управлять свойствами объектов из скриптов. Проблема заключается в том, что в скриптах мы не "знаем" про объекты хост-программы. Скрипты сами по себе, основная программа - сама по себе. Для связи одного с другим мы как раз и регистрируем функции-прослойки (их зачастую называют "glue-functions"), которые позволяли бы производить разумные операции из скриптов. Здесь также можно сконструировать функцию, которая выдавала бы в скрипт ссылку на объект. Но для нашей задачи можно поступить проще, ведь у каждого класса, отнаследованного от TComponent, уже существует поле Name, которое можно расценивать как уникальный идентификатор объекта. Таким образом нам всего лишь нужно сделать механизм, по которому в скриптах можно было написать просто:

Set_ControlProperty('Edit1', 'Left', 5);

первый аргумент - имя объекта, второй - название свойства, третий аргумент - значение свойства, которое нам необходимо установить.
Хотим подвинуть поле ввода немного в другое место? вызвали из скрипта

Set_ControlProperty('Edit1', 'Left', 5);
Set_ControlProperty('Edit1', 'Top', 35);

и все готово!
Отлично, как теперь все это обработать на стороне программы?
Для начала считываем из стека первые два аргумента:

ControlName := Lua_ToString(aLua_VM, 1);
PropName := Lua_ToString(aLua_VM, 2);

Затем находим компонент, с искомым именем (Name):

Control := frmMain.FindComponent(ControlName);

Последний третий аргумент парсим в переменную PropValue типа Variant:

if Lua_IsNumber(aLua_VM, 3) then
  PropValue := Lua_ToNumber(aLua_VM, 3)
else if Lua_IsString(aLua_VM, 3) then
  PropValue := String(Lua_ToString(aLua_VM, 3))
else
  Control := nil;

С третьим аргументом приходится повозиться, потому что мы заведомо не знаем, какого он типа. Для проверки типа элемента из стека имеются функции вроде Lua_IsNumber(), которые возвращают true, если элемент имеет заданный тип.
Ну и наконец, выставляем значение свойства:

if (Control <> nil) and IsPublishedProp(Control, PropName) then
SetPropValue(Control, PropName, PropValue);

Функции IsPublishedProp() и SetPropValue() находятся в модуле TypInfo, так что смело закидываем его в uses.
Вот, собственно, и все хитрости, которые можно найти в данной демке.
Все остальное идет стандартными методами (описанными более подробно в сообщениях о предыдущих демках):
  • Создание основного объекта: fScriptManager := TScriptManager.Create;
  • Регистрация функции, с помощью которой будем манипулировать свойствами компонент на форме: 
Lua_Register(fScriptManager.LuaVM, PChar('Set_ControlProperty'),   Set_ControlProperty);
  • Загрузка скрипта: fScriptManager.LoadScriptFile('demo_3.lua');
  • Выполнение скриптовой функции main(): fScriptManager.DoScript('main', [], false); 
Так как мало кто качает исходные коды, поэтому немного захламлю журнал, приведя скрипт прямо здесь:

function main()
    Set_ControlProperty('Edit1', 'Left', 5);
    Set_ControlProperty('Edit1', 'Top', 8);
    Set_ControlProperty('Edit1', 'Text', 'This is Edit control');
    
    Set_ControlProperty('btnReload', 'Left', 135);
    Set_ControlProperty('btnReload', 'Top', 5);
    
    Set_ControlProperty('RadioButton1', 'Left', 5);
    Set_ControlProperty('RadioButton1', 'Top', 35);
    Set_ControlProperty('RadioButton1', 'Caption', 'Hi, I''m a radio button');
end;

Итак, после запуска программы, можно увидеть следующее:




а после нажатия на кнопку "Script Reload", скриптовая функция изменит некоторые свойства визуальных компонент таким образом, что внешний вид программы немного преобразится:




Вот и все! Качаем, запускаем, задаем вопросы, радуемся!

Сообщения, схожие по тематике:

4 коммент.:

  1. Спасибо! Необычайно полезная статья.

    ОтветитьУдалить
  2. рад, что понравилось!
    осталась последняя (5ая) демка и все)).. тему для 4ой я так и не придумал, поэтому будет пропуск в номерах))

    ОтветитьУдалить
  3. Не логично *задумался*
    А что должно было быть в содержании 4 части?

    ОтветитьУдалить
  4. да не, все логично))
    я сначала думал сделать простой CollisionDetector на скриптах (типа простого арканоида на VCL), но затем подумал, что это неинтересно; забросил то дело и оставил пропуск в номерах))

    ОтветитьУдалить