Сталкивались ли вы с ситуацией, когда приложение на C++ уже написано, но хочется дать пользователю больше свободы — без перекомпиляции и сложных плагинных систем? Каждый разработчик хотя бы раз ловил себя на мысли: «Сейчас запустим — узнаем», надеясь, что логика поведения всё-таки окажется гибкой.
Эта глава аккуратно подводит к неочевидному, но мощному решению. Здесь раскрывается, как Qt позволяет встроить JavaScript прямо в C++-приложение и превратить статичный код в управляемую среду. Вы обнаружите, почему профессиональные разработчики используют сценарии не ради «игрушек», а ради ускорения разработки, тестирования и экспериментов с логикой интерфейса.
В фокусе QJSEngine и QJSValue, связывание сигналов и слотов с JavaScript, расширение Qt-объектов «на лету» и практический пример с «черепашьей» графикой. Несколько десятков строк сценария — и результат появляется мгновенно, без пересборки проекта.
Если вы хотите перестать писать «наугад» и начать управлять поведением приложения осознанно, эту главу лучше не откладывать.
В конце доступ к архиву со всеми исходными кодами примеров, полностью готовыми к компиляции, а также 16 бесплатных глав, которые можно изучить уже сейчас и оценить стиль и глубину материала.
Самопроверка по главе
Какой класс Qt6 представляет собой среду для выполнения JavaScript-кода и почему нужен хотя бы один его объект?Ответ
Правильный ответ: Класс QJSEngine предоставляет среду выполнения JavaScript; без него невозможно интерпретировать и запускать сценарии, так как он управляет контекстом выполнения и предоставляет API для взаимодействия.
Зачем класс QJSValue предоставляет методы семейства isT() и toT()?Ответ
Правильный ответ: Методы isT() позволяют определить тип JavaScript-значения, а toT() — преобразовать его к нужному типу C++, обеспечивая безопасное взаимодействие между двумя языками.
Почему метод evaluate() возвращает QJSValue, а не просто выполняет код?Ответ
Правильный ответ: Возвращаемый QJSValue содержит результат выполнения сценария или информацию об ошибке, что позволяет C++-коду проверить успешность выполнения методом isError() и обработать результат.
Зачем нужен метод newQObject() при работе с Qt-объектами из JavaScript?Ответ
Правильный ответ: Метод создает JavaScript-обертку вокруг Qt-объекта, делая его свойства, сигналы и слоты доступными для JavaScript-кода через систему метаобъектов Qt.
Почему в примере с черепашьей графикой необходимо вызывать setObjectName() для всех виджетов?Ответ
Правильный ответ: Установленное имя объекта позволяет получить к нему доступ из JavaScript-кода по понятному идентификатору после регистрации через setProperty() в globalObject().
Почему после каждой команды рисования в классе Turtle вызывается метод repaint()?Ответ
Правильный ответ: Вызов repaint() немедленно перерисовывает виджет, показывая результат команды на экране; без него изменения накапливались бы и отображались только после завершения всего сценария.
Как JavaScript-функции могут подключаться к сигналам Qt и чем это отличается от обычного C++ подхода?Ответ
Правильный ответ: JavaScript-функции подключаются через метод connect() сигнала напрямую к функциям или методам объектов сценария; это позволяет динамически изменять поведение без перекомпиляции, в отличие от статических C++ соединений.
Что произойдет, если не проверить результат evaluate() методом isError()?Ответ
Правильный ответ: Ошибки выполнения сценария останутся незамеченными, приложение может работать некорректно или не выполнить ожидаемые действия, а пользователь не получит информации о проблеме.
Как можно расширить функциональность Qt-объекта новыми методами прямо из JavaScript?Ответ
Правильный ответ: Можно добавить новые методы к объекту, присваивая функции его свойствам (например, turtle.circle = function(){…}), как показано в примере рисования окружности.
Какие четыре способа соединения сигнала с JavaScript показаны в примере и в чем их различие?Ответ
Правильный ответ: Сигнал можно соединить с: (1) функцией сценария, (2) методом объекта JavaScript-класса, (3) слотом Qt-виджета напрямую, (4) анонимной функцией прямо в connect(). Выбор зависит от необходимости сохранения состояния и переиспользования кода.
Зачем создавать отдельный класс JSTools с функциями вместо прямого использования Qt API?Ответ
Правильный ответ: JSTools предоставляет удобный высокоуровневый интерфейс для JavaScript, скрывая сложность Qt API и создавая привычную для веб-разработчиков среду (alert, print и др.).
Почему для работы с языком сценариев все функции в JSTools объявлены как слоты?Ответ
Правильный ответ: Только слоты Qt доступны через систему метаобъектов и могут вызываться из JavaScript после регистрации объекта через newQObject(); обычные методы C++ недоступны для сценариев.
Что делает метод globalObject() и зачем устанавливать свойства именно в нем?Ответ
Правильный ответ: globalObject() возвращает глобальный контекст JavaScript-среды; установка свойств в нем делает объекты и переменные доступными во всей программе сценария без дополнительной передачи параметров.
Как в примере с черепашьей графикой используется оператор let вместо var и почему это важно?Ответ
Правильный ответ: Оператор let создает переменные с блочной областью видимости и соответствует современным стандартам ECMAScript, предотвращая ошибки из-за утечки переменных за пределы циклов и условий.
Практические задания
Простой уровень
Калькулятор на JavaScript
Создайте Qt-приложение с двумя текстовыми полями для ввода чисел, выпадающим списком для выбора операции (+, -, *, /) и кнопкой “Вычислить”. При нажатии на кнопку должен выполняться JavaScript-сценарий, который берет значения из полей, выполняет операцию и выводит результат в метку (QLabel). Используйте QJSEngine для выполнения вычислений.
Подсказки: Создайте QJSEngine и передайте ему виджеты через newQObject(). Сформируйте строку с JavaScript-выражением (например, “10 + 5”) и выполните через evaluate(). Результат преобразуйте в строку методом toString(). Не забудьте проверить ошибки через isError().
Средний уровень
Рисование фигур через JavaScript
Расширьте пример черепашьей графики, добавив возможность управления цветом пера и толщиной линии. Создайте JavaScript-функции setColor(r, g, b) и setWidth(width), которые будут изменять параметры QPainter. Реализуйте готовые сценарии для рисования: разноцветной спирали, радуги из концентрических кругов и абстрактного узора с меняющейся толщиной линий. Добавьте возможность сохранения результата в PNG-файл через JavaScript.
Подсказки: Добавьте в класс Turtle слоты setColor() и setWidth(), изменяющие QPen через QPainter::setPen(). Для сохранения файла создайте слот save(filename), использующий QPixmap::save(). В сценариях используйте циклы с изменяющимися параметрами цвета и толщины. Имя класса в JavaScript должно быть зарегистрировано через setProperty().
Сложный уровень
Интерактивный редактор с плагинами на JavaScript
Создайте текстовый редактор с поддержкой JavaScript-плагинов. Редактор должен загружать .js файлы из папки plugins и предоставлять им API для работы с текстом: getText(), setText(), insertText(), getSelection(), replaceSelection(). Реализуйте три плагина: (1) подсчет статистики текста (слова, символы, предложения), (2) автоматическое форматирование кода (добавление отступов), (3) поиск и замена с поддержкой регулярных выражений. Плагины должны иметь собственные кнопки в интерфейсе и регистрироваться динамически при запуске.
Подсказки: Создайте класс TextEditorAPI с слотами для работы с текстом QTextEdit. Сканируйте папку plugins через QDir::entryList(). Каждый плагин должен возвращать объект с методом execute() и свойствами name, description. Используйте QJSValue::property() для извлечения информации о плагине. Динамически создавайте кнопки для каждого плагина и соединяйте их clicked с вызовом соответствующей функции через evaluate(). Обработайте ошибки загрузки плагинов.
💬 Присоединяйтесь к обсуждению!
Удалось запустить черепашью графику? Интересно, какие узоры вы создали экспериментируя с углами?
Поделитесь своими JavaScript-плагинами для Qt, обсудите особенности интеграции QJSEngine в реальные проекты, или задайте вопросы о том, когда стоит использовать сценарии вместо C++!