Evgenii Legotckoi
Evgenii Legotckoi15 жовтня 2015 р. 12:05

QML - Урок 004. Сигнали та слоти в Qt QML

А ось ми й дісталися передачі даних між шаром QML і шаром C++. Чесно кажучи, принцип настільки ж простий, як і використання сигналів і слотів в одному шарі C++. Особливо Qt 5.5.

Приклад буде показаний на основі програмного коду з попереднього уроку, де ми створили діалогове вікно. Але скріншотів прикладу роботи на Android не буде показано, але запевняю Вас - Все працює як швейцарський годинник.

Структура проекту з QML

структура проекту з qml Порівняно з попереднім уроком, у нас з'явилися деякі зміни. А саме, додався новий клас, який буде ядром програми.

  • appcore.h - заголовний файл ядра програми;
  • appcore.cpp - файл вихідних кодів ядра програми.

А продовжувати працювати ми також будемо з QQMLApplicationEngine. Потрібно буде просто взяти від движка QML контекст і завантажити в нього об'єкт нового класу, від якого надходитимуть сигнали і який будуть передаватися дані.


appcore.h

Заголовний файл нашого класу простий, як три копійки. У ньому є один один лічильник (змінна типу int ), слот, який збільшуватиме лічильник на один і запускатиме сигнал, який також один у класі і який буде передавати значення лічильника в QML-інтерфейс.

#ifndef APPCORE_H
#define APPCORE_H

#include <QObject>

class AppCore : public QObject
{
    Q_OBJECT
public:
    explicit AppCore(QObject *parent = 0);

signals:
    // Сигнал для передачи данных в qml-интерфейс
    void sendToQml(int count);

public slots:
    // Слот для приёма данных из qml-интерфейса
    void receiveFromQml();

private:
    int count;  // Счетчик, которым будем оперировать
};

#endif // APPCORE_H

appcore.cpp

Та й логіка класу у його вихідному коді.

#include "appcore.h"

AppCore::AppCore(QObject *parent) : QObject(parent)
{
    count = 0;
}

void AppCore::receiveFromQml()
{
    count++;
    emit sendToQml(count);
}

main.cpp

А ось тут вже підключатимемо до нашого класу інтерфейс, написаний на QML.

#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "appcore.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    QQmlApplicationEngine engine; // Создаём движок qml

    AppCore appCore;    // Создаём ядро приложения
    QQmlContext *context = engine.rootContext();    // Создаём корневой контекст
    /* Загружаем объект в контекст для установки соединения,
     * а также определяем имя, по которому будет происходить соединение
     * */
    context->setContextProperty("appCore", &appCore);

    // И загружаем в него исходники qml
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

main.qml

Наведу частину коду, щоб відволікати Вас від головного. А саме, з'єднання в QML шарі здійснюється за допомогою об'єкта Connections , як target якого вказується Ваш клас встановлений у контекст. Звернення здійснюється за текстовим найменуванням, яке завантажується в контекст движка QML разом із самим об'єктом.

Щоб отримувати сигнали від шару C++ необхідно в Connections прописати функцію, яка іменуватиметься практично так само, як і сигнал цільового об'єкта, але починатися буде з on і далі ім'я сигналу з великої літери. Тобто наступна логіка

  • signalToQml - на C++
  • onSignalToQml - в QML

А ось виклик Слота відбуватиметься дещо інакше. Наприклад, у нас є об'єкт класу в C++ , до якого ми звертаємось на ім'я appCore (оголошений метою Connections ). А далі викликаємо функцію слот. Тобто наступним чином: appCore.slotSomething(count).

Виклик слота в даному коді здійснюється натисканням кнопки OK у діалозі, а по Cancel виклику не відбувається. При цьому об'єкт appCore в C++ коді збільшує лічильник на один і викликає сигнал, щоб помістити значення лічильника в текстовий лейбл програми.

import QtQuick 2.5
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World") // Ну как же без Приветствия Миру
    color: "white"

    /* С помощью объекта Connections
     * Устанавливаем соединение с классом ядра приложения
     * */
    Connections {
        target: appCore // Указываем целевое соединение
        /* Объявляем и реализуем функцию, как параметр
         * объекта и с имененем похожим на название сигнала
         * Разница в том, что добавляем в начале on и далее пишем
         * с заглавной буквы
         * */
        onSendToQml: {
            labelCount.text = count // Устанавливаем счётчик в текстовый лейбл
        }
    }

    MainForm {
        // Растягиваем объект главного окна по всему родительскому элементу
        anchors.fill: parent

        // Создадим текстовый лейбл
        Text {
            id: labelCount
            // А также установим его визуальные параметры
            anchors.left: parent.left
            anchors.right: parent.right
            anchors.top: parent.top
            height: 300
            verticalAlignment: Text.AlignVCenter
            horizontalAlignment: Text.AlignHCenter
            // Первым текстом будет традиционный Хеллоу Ворлд!
            text: "Hello, World !!!"
        }

        // Стилизуем первую кнопку
        button1.style: ButtonStyle {
            // Программный код из предыдущего урока
        }

        // Стилизуем вторую кнопку
        button2.style: ButtonStyle {
            // Программный код из предыдущего урока
        }

        // Запускаем диалог по нажатию любой из кнопок в главном окне
        button1.onClicked: dialogAndroid.open();
        button2.onClicked: dialogAndroid.open();

        // Создаём объект диалогового окна
        Dialog {
            id: dialogAndroid
            /* Когда деплоите под Android-устройства,
             * обязательно закоментируйте эти две строки,
             * иначе словите глюки в работе устройства
             */
            width: 600  // Задаём ширину диалога, работает на десктопе, но на Android не сработает
            height: 500 // Задаём высоту диалога, работает на декстопе, но на Android не сработает

            // Создаём содержимое диалогового окна
            contentItem: Rectangle {
                width: 600          // Устанавливаем ширину, необходимо для Android-устройства
                height: 500         // Устанавливаем высоту, необходимо для Android-устройства
                color: "#f7f7f7"    // Задаём цвет

                // Область для сообщения диалогового окна
                Rectangle {
                    // Программный код из предыдущего урока
                }

                // Создаём горизонтальный разделитель с помощью Rectangle
                Rectangle {
                    id: dividerHorizontal
                    // Программный код из предыдущего урока
                }

                /* Создаём подложку для кнопок в виде объекта Строки
                 * В данном объекте для объектов детей не работают некоторые параметры
                 * anchors, кроме параметров anchors.top и anchors.bottom
                 */
                Row {
                    id: row
                    // Программный код из предыдущего урока

                    Button {
                        id: dialogButtonCancel

                        // Программный код из предыдущего урока

                        // По нажатию кнопки закрываем диалог
                        onClicked: dialogAndroid.close()
                    }

                    // Создаём разделитель между кнопками шириной в 2 пикселя
                    Rectangle {
                        id: dividerVertical
                        // Программный код из предыдущего урока
                    }

                    Button {
                        id: dialogButtonOk

                        // Программный код из предыдущего урока

                        // По нажатию кнопки закрываем диалог
                        onClicked: {
                            /* Прежде, чем закрывать диалог по OK кнопке,
                             * отправим данные в слот ядра приложения
                             * */
                            appCore.receiveFromQml()
                            // А потом и закроем диалог
                            dialogAndroid.close()
                        }
                    }
                }
            }
        }
    }
}

Підсумок

В результаті отримуємо просту взаємодію між C++ і QML на основі тих самих сигналів і слотів. А результат роботи програми Ви можете побачити у відеоуроці.

Відеоурок

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Стабільний хостинг, на якому розміщується соціальна мережа EVILEG. Для проектів на Django радимо VDS хостинг.

Вам це подобається? Поділіться в соціальних мережах!

x
  • 22 січня 2018 р. 03:43

Не понял, как будет передаваться значение count в QML, если нигде он не описан через Q_PROPERTY

Evgenii Legotckoi
  • 22 січня 2018 р. 03:56

Так и будет передаваться. Это аргумент сигнала.

void sendToQml(int count);
Видите сигнатуру? аргумент называется count . Вот он и передаётся.
А описывать в Q_PROPERTY его не нужно. Не обязательно всё описывать через Q_PROPERTY . Зачастую достаточно объявлять метод сигналом, слотом или Q_INVOKABLE.
M
  • 05 березня 2018 р. 12:19

Статья интересная, как и реализация, но в упор ругается на

Qt\untitled1\main.cpp:21: ошибка: 'appCore' was not declared in this scope
appCore appCore;
^
Evgenii Legotckoi
  • 05 березня 2018 р. 15:47

А почему у вас название класса и имя переменной одинаковые?

appCore appCore;
В моём примере название класса с заглавной буквы AppCore
M
  • 06 березня 2018 р. 11:52
AppCore appCore;
QQmlContext *context = engine.rootContext();
context->setContextProperty("appCore", &appCore);
Вставил эти строки, ошибка никуда не делась, заголовочный файл подключен. Почему не видит класс?
Evgenii Legotckoi
  • 06 березня 2018 р. 13:49

Проект ваш смотреть нужно, что ещё не доделали, либо просто пересобрать билд для начала, возможно, что при создании файлов класса не обновилась информация для qmake. То есть как минимум перезапустить qmake требуется.

Docent
  • 06 липня 2018 р. 15:46

Огромное спасибо за статью.

В очередной раз уже выручают ваши подсказки. Если не знаю как реализовать - знаю у кого найти ответ.
Дизайнеры показали проект интерфейса, была попытка через StyleSheet, но возможностей там меньше, в итоге изучил новую для себя технологию, и проект представленный дизайнерами выглядит и работает именно так как они его себе вообразили. Из меня дизайнер фиговый и таких красивых интерфейсов я бы не нарисовал никогда. В итоге технология показала свою замечательность на 90% (остальное еще стоит изучить и возрадоваться).




Evgenii Legotckoi
  • 10 липня 2018 р. 12:53

Спасибо за отзыв!

V
  • 26 січня 2019 р. 08:19

Здраствуйте сново обращаюсь к вам.
Ваш клас appcore.h и исходник appcore.cpp

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>

#include <QtGui/QGuiApplication>
#include <QScreen>

#include "appcore.h"
#include <QQmlContext>



int main(int argc, char *argv[])
{

    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    AppCore appCore; 


    QQmlApplicationEngine engine;
    QQmlContext *context = engine.rootContext(); 
    context->setContextProperty("appCore", &appCore);


    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;



    return app.exec();
}

вот main.qml

V
  • 26 січня 2019 р. 08:19
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2

import QtQuick 2.0
import QtQuick.Window 2.2

Window {
    flags: Qt.ToolTip | Qt.FramelessWindowHint | Qt.WA_TintedBackground |Qt.WindowStaysOnTopHint

    color: "#00000000"




visible: true
width: 100
height: 460
x: (Screen.width - width)
y: (Screen.height - height)





Rectangle {
    anchors.fill: parent
    color: "transparent"


}
AnimatedSprite {


    Connections {
    target: appCore // Указываем целевое соединение
    /* Объявляем и реализуем функцию, как параметр
    * объекта и с имененем похожим на название сигнала
    * Разница в том, что добавляем в начале on и далее пишем
    * с заглавной буквы
    * */
    onSendToQml: {
    Count = count // Устанавливаем счётчик в текстовый лейбл
    }
    }




    id: sprite;
    width: 100;
    height: 100;
    anchors.centerIn: parent;
     source: "Donald.png"
     frameX: 0
     frameY: Number (count)




     frameRate: 18;
     frameWidth: 100
     frameHeight: 100
     frameCount: 6
     running: folse;

}




}


V
  • 26 січня 2019 р. 08:22

Вот этот момент

frame Y:

не получается перенести значения из С++

Evgenii Legotckoi
  • 28 січня 2019 р. 02:45
  • (відредаговано)

Добрый день

И что вы хотели сделать в этом коде?

    target: appCore
    onSendToQml: {
    Count = count // конкретно это, где Count в вашем коде
    }

и это

frameY: Number (count) // откуда взялся Number?
V
  • 29 січня 2019 р. 12:36

Думал что через:

frameY: Number (count)

буду контролировать чередование анимаций, картинка 600 на 600 с кадром 100.
если сразу указать:
frameCount: 36
то вся анимация пролистается, а мне необходимо конкретная строка анимации
( ну например пролистать 6 кадров с Y= 200, а потом с Y= 400)

пока на сайтах искал наткнулся на Namber, но не получилось.

Evgenii Legotckoi
  • 30 січня 2019 р. 03:03

Ну ок, такой тип в документации есть, но откуда-то это взяли?

Count = count // конкретно это, где Count в вашем коде

я и намёка не вижу на сам вот этот Count.

Впрочем, после пояснения уже немного яснее стало, думаю, что можете так попробовать сделать

Connections {
    target: appCore 
    onSendToQml: {
        sprite.frameY = count
    }
    }
V
  • 30 січня 2019 р. 15:23

Нет. Не читает.
Попробывал сразу задать значение в appcore

#include "appcore.h"

AppCore::AppCore(QObject *parent) : QObject(parent)
{
    count = 200;
}

void AppCore::receiveFromQml()
{
    count++;
    emit sendToQml(count);
}

не работает, хотя без ошибок.

Evgenii Legotckoi
  • 04 лютого 2019 р. 03:18

Чтобы работало, нужно сигнал высылать, то есть

emit sendToQml(count);

V
  • 14 лютого 2019 р. 13:41

Спасибо огромное! Заработало!

m
  • 01 червня 2019 р. 15:22

Доброй ночи. А почему вы не используете MainForm.ui.qml.? В нем я так понимаю надо верстать а в main.qml реализовывать логику?

Evgenii Legotckoi
  • 04 червня 2019 р. 06:03

Добрый день. Формы в QML на мой взгляд не так удобно реализованы, как в классических виджетах. Да, их удобно набрасывать в дизайнере, но при этом много функционала недоступно полноценно через дизайнер. Поэтому мне проще верстать и напрямую писать логику без дизайнера и форм QML.

m
  • 04 червня 2019 р. 12:07

Добрый день. У меня при реализации проекта по идеалогии описаной выше возникла проблема. Немогу достучатся до элемента ListView.

Item {
    id: mainTabLayout
    property alias spinBoxPlusMinus:spinBoxPlusMinus
    clip: true
    ListView {
        id: view
        anchors.fill: parent
        cacheBuffer: 100 * 10
        anchors.margins: 10
        spacing: 10
        model: productListModel
        delegate: Rectangle {
            id: delegateproduct
            width: view.width
            height: 50
            Item  {
                property alias spinBoxPlusMinus: spinBoxPlusMinus
                id: row
                width: parent.width
                anchors.margins: 10

             Image {
                    id: image
                    width: 50
                    height: 50
                    anchors.left: parent.left
                    anchors.leftMargin: 0
                    source: model.imgresurs


                }

                     SpinBox {
                         anchors.right: parent.right
                         anchors.rightMargin: 10
                         id: spinBoxPlusMinus
                         visible: false
                         value: 1
                        /*property alias spinBoxPlusMinus: spinBoxPlusMinus*/
                     }

        }

    }

}

Пишет : Invalid alias reference. Unable to find id "spinBoxPlusMinus"
Не поможите?) Все таки хочется разделить логику от дизайна)

Evgenii Legotckoi
  • 05 червня 2019 р. 03:22

Ну вы не сможете так прокинуть

property alias spinBoxPlusMinus:spinBoxPlusMinus

На самый верх из делегата. Делегат отвечает за внешнее представление элемента в ListView, а таких элементов могут быть сотни и тысячи. Поэтому в третьей строке вашего кода такой alias является бессмысленным. Поскольку QML не знает к какому именно элементу в списке ему нужно пробрасывать alias.

А вообще ваш вопрос тут немного не по теме. Здесь вопрос сигналов и слотов. А ващ вопрос о доступе к элементу через его парента. Лучше создайте на форуме отдельное обсуждение.

m
  • 05 червня 2019 р. 11:14

Спасибо. Так и сделаю.

МБ
  • 20 червня 2019 р. 14:23
  • (відредаговано)

А если мне нужно сделать конект из дочернего qml?
Сигнал работает только из main.qml

Evgenii Legotckoi
  • 24 червня 2019 р. 05:52

Придётся делать ещё сигнал в дочернем qml и пробрасывать через коннекты и обработчики. А вообще нужно смотреть конкретный код и что вы пытаетесь сделать.

Так что лучше будет, если вы зададите вопрос на форуме , чтобы можно было подробнее обсудить вашу проблему.

МБ
  • 24 червня 2019 р. 06:21

Извиняюсь, все работает(из-за невнимательности).

Evgenii Legotckoi
  • 24 червня 2019 р. 06:23

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

Андрей Никольский
  • 30 липня 2019 р. 09:44

Приветствую всех! Внедрил данный урок в свой проект, идеально никакой ругани на синтаксис, но...

QML Connections: Cannot assign to non-existent property "onSendToQml"

Добрый день.
У вас сигнал sendToQml в вашем классе объявлен в секции singals? Просто вы говорите о том, что внедрили в свой проект, поэтому следует вопрос о том, что чего-то у вас не хватает.

Андрей Никольский
  • 30 липня 2019 р. 10:04

Добрый день.
У вас сигнал sendToQml в вашем классе объявлен в секции singals? Просто вы говорите о том, что внедрили в свой проект, поэтому следует вопрос о том, что чего-то у вас не хватает.

Внедрен.

Единственное, что было измененно, это название класса.

Внедрен.

Единственное, что было измененно, это название класса.

Наверное, нужно смотреть ваш код, поскольку других мыслей у меня нет, что может быть не так. Можете создать тему на форуме и там показать код, касающийся внедрённой части?

И вообще, qml только ругается этой строчкой, но при этом работает, или тот слот вообще не срабатывает? (уверен, что не срабатывает, но мало ли)

AlezZgo
  • 05 січня 2020 р. 02:34

Огромное спасибо вам! Очень понятно и наглядно

Y
  • 14 листопада 2021 р. 15:33

У связывания интерейса прогрммы с ядром через контекст (context->setContextProperty("appCore", &appCore);) есть один существенный недостаток, упоминание о котором я нигде не нашел, а выявился он мною империческим путём. А именно - при таком способе соединение сигнал-слот делается ИСКЛЮЧИТЕЛЬНО путем непосредственного доступа к функции (Qt::DirectConnection), задать соединение через событие (Qt::QueuedConnection) у меня не получилось, т.к. такой параметр просто некуда внести. Может это как-то в настройках компиляции можно задать не знаю. Отсюда серьёзная проблема - если интерфейс и тело программы крутятся в разных потоках (а это правильно), то получается, что интерфейсная часть обращается к телу не потокобезопасно. В итоге в моём случае пришлость вернуться к связыванию элементов интерфейса из .cpp фалов классическим connect().
Если знаете как это победить буду рад подсказке!

n
  • 31 серпня 2023 р. 09:47

Здравствуйте!
Прекрасный сайт, отличные статьи.
Не хватает только готовых проектов для скачивания.
Многих комментариев типа appCore != AppCore просто бы не было )))

Коментарі

Only authorized users can post comments.
Please, Log in or Sign up
AD

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:50бали,
  • Рейтинг балів-4
m
  • molni99
  • 26 жовтня 2024 р. 01:37

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:80бали,
  • Рейтинг балів4
m
  • molni99
  • 26 жовтня 2024 р. 01:29

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20бали,
  • Рейтинг балів-10
Останні коментарі
ИМ
Игорь Максимов22 листопада 2024 р. 11:51
Django - Підручник 017. Налаштуйте сторінку входу до Django Добрый вечер Евгений! Я сделал себе авторизацию аналогичную вашей, все работает, кроме возврата к предидущей странице. Редеректит всегда на главную, хотя в логах сервера вижу запросы на правильн…
Evgenii Legotckoi
Evgenii Legotckoi31 жовтня 2024 р. 14:37
Django - Урок 064. Як написати розширення для Python Markdown Добрый день. Да, можно. Либо через такие же плагины, либо с постобработкой через python библиотеку Beautiful Soup
A
ALO1ZE19 жовтня 2024 р. 08:19
Читалка файлів fb3 на Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов05 жовтня 2024 р. 07:51
Django - Урок 064. Як написати розширення для Python Markdown Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas505 липня 2024 р. 11:02
QML - Урок 016. База даних SQLite та робота з нею в QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
Тепер обговоріть на форумі
Evgenii Legotckoi
Evgenii Legotckoi24 червня 2024 р. 15:11
добавить qlineseries в функции Я тут. Работы оень много. Отправил его в бан.
t
tonypeachey115 листопада 2024 р. 06:04
google domain [url=https://google.com/]domain[/url] domain [http://www.example.com link title]
NSProject
NSProject04 червня 2022 р. 03:49
Всё ещё разбираюсь с кешем. В следствии прочтения данной статьи. Я принял для себя решение сделать кеширование свойств менеджера модели LikeDislike. И так как установка evileg_core для меня не была возможна, ибо он писался…
9
9Anonim25 жовтня 2024 р. 09:10
Машина тьюринга // Начальное состояние 0 0, ,<,1 // Переход в состояние 1 при пустом символе 0,0,>,0 // Остаемся в состоянии 0, двигаясь вправо при встрече 0 0,1,>…

Слідкуйте за нами в соціальних мережах