В Qt предусмотрен мощный механизм расширения функциональности приложений с помощью JavaScript-подобного языка сценариев, поддержка которого реализована в модуле Qt Script.
Рассмотрим пример использования этой технологии, в котором задействуем следующие возможности Qt Script:
- Считывание и запись свойств Qt-объектов;
- Установление соединения между сигналом и слотом Qt-объектов;
- Создание функции-слота внутри скрипта и его подключение к сигналу Qt-объекта.
Мы обеспечим доступ скрипта к полям ввода/вывода ( QLineEdit). Для редактирования скрипта предусмотрим текстовое поле QTextEdit. При этом в ресурсах приложения мы поместим три заранее подготовленных простых скрипта, о которых скоро поговорим.
Реализация
Обратите внимание , что для работы с Qt Script в файле проекта необходимо подключить соответствующую зависимость в дополнение к остальным:
1 |
QT += core gui script |
Файл mainwidget.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
#ifndef MAINWIDGET_H #define MAINWIDGET_H #include <QWidget> #include <QtScript/QScriptEngine> namespace Ui { class MainWidget; } class MainWidget : public QWidget { Q_OBJECT public: explicit MainWidget( QWidget* parent = 0 ); ~MainWidget(); private slots: void loadScript(); void runScript(); private: Ui::MainWidget* ui; QScriptEngine m_engine; }; #endif // MAINWIDGET_H |
В основе Qt Script лежит класс QScriptEngine, который и занимается интерпретацией кода скрипта. Именно его мы определили в качестве одного из полей нашего класса.
Файл mainwidget.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
#include "mainwidget.h" #include "ui_mainwidget.h" #include <QFile> MainWidget::MainWidget( QWidget* parent ) : QWidget( parent ), ui( new Ui::MainWidget ) { ui->setupUi( this ); connect( ui->bnRun, SIGNAL( clicked( bool ) ), SLOT( runScript() ) ); foreach( QRadioButton* rb, findChildren< QRadioButton* >() ) { connect( rb, SIGNAL( toggled( bool ) ), SLOT( loadScript() ) ); } QScriptValue inputVal = m_engine.newQObject( ui->edInput ); m_engine.globalObject().setProperty( "input", inputVal ); QScriptValue outputVal = m_engine.newQObject( ui->edOutput ); m_engine.globalObject().setProperty( "output", outputVal ); loadScript(); } MainWidget::~MainWidget() { delete ui; } void MainWidget::loadScript() { QString fileName = ""; if( ui->rbUpperDemo->isChecked() ) { fileName = "upper_demo.js"; } else if( ui->rbConnectDemo->isChecked() ) { fileName = "connect_demo.js"; } else if( ui->rbConnectFuncDemo->isChecked() ) { fileName = "connect_func_demo.js"; } QFile f( ":/" + fileName ); if( f.open( QIODevice::ReadOnly ) ) { ui->txtScript->setText( f.readAll() ); } } void MainWidget::runScript() { ui->edInput->disconnect(); ui->edOutput->disconnect(); ui->edOutput->clear(); QScriptValue result = m_engine.evaluate( ui->txtScript->toPlainText() ); if( result.isError() ) { ui->edOutput->setText( result.toString() ); } } |
Особый интерес представляет функция-член:
1 2 3 4 5 6 7 8 9 10 |
void MainWidget::runScript() { ui->edInput->disconnect(); ui->edOutput->disconnect(); ui->edOutput->clear(); QScriptValue result = m_engine.evaluate( ui->txtScript->toPlainText() ); if( result.isError() ) { ui->edOutput->setText( result.toString() ); } } |
Сначала мы подчищаем следы запуска прошлых скриптов (отключаем сигналы и очищаем содержимое поля вывода). Затем следует вызов скрипта с помощью QScriptEngine::evaluate(). В случае синтаксических ошибок мы выводим текст сообщения о проблеме в поле вывода.
Также важно заметить, что скрипт не имеет прямого доступа к элементам Qt-приложения. Поэтому мы должны передать ему все, что нужно, самостоятельно. Что мы и делаем для полей ввода/вывода в конструкторе класса:
1 2 3 4 5 |
QScriptValue inputVal = m_engine.newQObject( ui->edInput ); m_engine.globalObject().setProperty( "input", inputVal ); QScriptValue outputVal = m_engine.newQObject( ui->edOutput ); m_engine.globalObject().setProperty( "output", outputVal ); |
Теперь внутри скрипта к полю ввода можно обратиться по имени input, а к полю вывода по имени output.
Замечание: мы несколько превышаем потребности скрипта и даем ему больше, чем нужно. Например, скрипт сможет изменять содержимое поля input, хотя должен уметь только читать его. Но пока что не будем обращать на это внимания.
Примеры скриптов
Первый скрипт upper_demo.js копирует текст из input в output, преобразуя его к верхнему регистру:
1 |
output.text = input.text.toUpperCase(); |
Второй скрипт connect_demo.js выполняет соединение сигнала textChanged() поля input со слотом setText() поля output:
1 |
input.textChanged.connect( output.setText ); |
Запуск этого скрипта равносилен выполнению обычной привязки сигналов-слотов в Qt:
1 |
connect( ui->edInput, SIGNAL( textChanged( QString ) ), ui->edOutput, SLOT( setText( QString ) ) ); |
Последний скрипт connect_func_demo.js:
1 2 3 4 5 |
function onTextChanged( text ) { output.text = text.toUpperCase(); } input.textChanged.connect( onTextChanged ); |
В нем мы определяем свою функцию onTextChanged(), которая принимает параметр text. В теле функции мы делаем то же самое, что и ранее в скрипте upper_demo.js.
Само подключение нашей функции в качестве слота аналогично тому, что мы делали в connect_demo.js.