Евгений Легоцкой15 октября 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 хостинг.
Поддержать автора Donate
x

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

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

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

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

Qt\untitled1\main.cpp:21: ошибка: 'appCore' was not declared in this scope
appCore appCore;
^

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

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

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

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

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




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

V

Здраствуйте сново обращаюсь к вам.
Ваш клас 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
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

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

frame Y:

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

Добрый день

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

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

и это

frameY: Number (count) // откуда взялся Number?
V

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

frameY: Number (count)

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

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

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

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

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

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

Connections {
    target: appCore 
    onSendToQml: {
        sprite.frameY = count
    }
    }
V

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

#include "appcore.h"

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

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

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

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

emit sendToQml(count);

V

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

m

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

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

m

Добрый день. У меня при реализации проекта по идеалогии описаной выше возникла проблема. Немогу достучатся до элемента 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"
Не поможите?) Все таки хочется разделить логику от дизайна)

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

property alias spinBoxPlusMinus:spinBoxPlusMinus

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

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

m

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

МБ

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

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

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

МБ

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

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

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

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

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

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

Внедрен.

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

Внедрен.

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

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

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

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

Y
  • #
  • 14 ноября 2021 г. 15:33

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

Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
Timeweb

Позвольте мне порекомендовать вам отличный хостинг, на котором расположен EVILEG.

В течение многих лет Timeweb доказывает свою стабильность.

Для проектов на Django рекомендую VDS хостинг

Посмотреть Хостинг
ВА

C++ - Тест 001. Первая программа и типы данных

  • Результат:13баллов,
  • Очки рейтинга-10
М
  • Макс
  • 18 сентября 2022 г. 7:04

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

  • Результат:70баллов,
  • Очки рейтинга1
ПБ

Qt - Тест 001. Сигналы и слоты

  • Результат:78баллов,
  • Очки рейтинга2
Последние комментарии

Qt/C++ - Урок 035. Скачивание файла по HTTP с помощью QNetworkAccessManager

Здравствуйте! Подскажите, пожалуйста, как сделать так, чтобы программа срабатыала без нажатия кнопки? Ну чисто при загрузке формы... Я так понимаю, надо что-то поменять в этой строчке con…
Р5

Qt/C++ - Урок 051. QMediaPlayer - Аудио плеер на Qt

Здравствуйте. Подскажите пожалуйста, как решить проблему multimedia модуль не распознается

Qt/C++ - Урок 009. QTimer или Как работать с таймером в Qt?

Да, именно так. Но в коде без this написано - это ошибка в статье.
f
  • fastrex
  • 7 сентября 2022 г. 23:00

Qt/C++ - Урок 009. QTimer или Как работать с таймером в Qt?

QTimer унаследован от QObject и ему передан this, идиома Qt предпологает что при вызове деструктора обьекта класса MyServer, обьект *timer тоже будет освобожден. Поправьте если ошибаюсь!

Qt/C++ - Урок 009. QTimer или Как работать с таймером в Qt?

Потому что 7 лет назад я был бестолковее, чем сейчас.
Сейчас обсуждают на форуме

Задать другой класс div-у

Добрый день. Попробуйте использовать Selenium. Это библиотека есть в виде Python модуля и она позволяет загружать страницу и манипулировать html элементами. Как я понимаю, в ней можно…
АБ

Sorting the added QML elements in the ListModel

I am writing an alarm clock in QML, I am required to sort the alarms in ascending order (depending on the date or time (if there are several alarms on the same day). I've done the sorting …
АЧ

Списки на QML

Вопрос решен с применением базы данных. Кому интересно, можете поюзать проект:) Отдельное спасибо Евгению за помощь)))Вход под админом Логин:1, пароль:1Вход под диспетчером Логин:22, пароль:2Вх…

Хочу переместить QMenuBar

Просто взять и заменить в пару строчек не получится. Qt предусматривает крайне ограниченный функционал по работе с обрамлением окон, к которому относится заголовок окна. Вообще это фу…

Django переводы фраз JavaScript файлов

Столкнулся с проблеммой некоторых библиотек javascript файлов. Мне нужно что бы переводы работали и в них. Выполнив инструкции из документации ничего не получаю. Помогите разобра…
О нас
Услуги
© EVILEG 2015-2022
Рекомендует хостинг TIMEWEB