В 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
.