• בלוג
  • משתנים קשורים ב QML

משתנים קשורים ב QML

22/02/2016
C++

משתנים קשורים הם מאפיין אהוב של ספריות ממשק משתמש רבות מאחר והם מאפשרים לנתק בין הפעולה לבין השפעתה על המסך. אתם יכולים למצוא אותם בפלאש, באנגולר וכמובן גם ב QML. במקרה של QML אפשר לכתוב את הלוגיקה לעדכון ערכי המשתנים ב JavaScript או ב C++.

1. תיאור הממשק במנותק מהלוגיקה

ההבטחה של משתנים קשורים היא שתוכלו לתאר את הממשק שלכם באופן מנותק מהלוגיקה. ב QML קל מאוד לכתוב תיאור כזה בגלל אופי השפה. הקובץ הבא למשל מהווה תיאור של כזה ממשק:

import QtQuick 2.3
import QtQuick.Controls 1.2
import QtQuick.Layouts 1.1

ApplicationWindow {
    visible: true
    width: 640
    height: 480
    title: qsTr("Hello World")
    id: root

    ColumnLayout {
        width: parent.width
        Rectangle {
            id: r1
            color: "blue"
            Layout.fillWidth: true
            Layout.preferredHeight: root.height / 4

            Row {
                anchors.verticalCenter: parent.verticalCenter
                anchors.left: parent.left
                anchors.margins: {
                    left: 20
                }

                TextField {
                    id: keyword
                    width: r1.width - btn.width - 50
                }

                Button {
                    id: btn
                    text: "Search"
                    onClicked: {
                        omdb.search(keyword.text)
                    }
                }
            }
        }

        Rectangle {
            color: "gold"
            Layout.fillWidth: true
            Layout.preferredHeight: root.height / 4

            Row {
                anchors.verticalCenter: parent.verticalCenter
                anchors.left: parent.left
                anchors.margins: {
                    left: 20
                }

                Label {
                    text: "Title: "
                }

                Label {
                    text: omdb.title
                }
            }

        }


        Rectangle {
            color: "green"
            Layout.fillWidth: true
            Layout.preferredHeight: root.height / 4
            //height: 20

            Row {
                anchors.verticalCenter: parent.verticalCenter
                anchors.left: parent.left
                anchors.margins: {
                    left: 20
                }

                Label {
                    text: "Year: "
                }

                Label {
                    text: omdb.year
                }
            }

        }


        Rectangle {
            color: "red"
            Layout.fillWidth: true
            Layout.preferredHeight: root.height / 4
            id: r3

            Row {
                anchors.verticalCenter: parent.verticalCenter
                anchors.left: parent.left
                anchors.margins: {
                    left: 20
                }

                Label {
                    text: "Image: "
                }

                Image {
                    source: omdb.imageUrl
                    fillMode: Image.PreserveAspectFit
                    height: r3.height
                }
            }
        }


    }



}

הקובץ מתאר ממשק בו שתי תיבות טקסט מציגות תוכן של משתנים: תיבה אחת מציגה את omdb.title ותיבה אחרת מציגה את omdb.year. בשורה האחרונה יש לנו אלמנט תמונה שמציג את התמונה המתוארת במשתנה omdb.imageUrl.

2. והיכן הקוד?

אז זה בדיוק מה שיפה כאן– הגדרת האוביקט omdb וגם כל הפעולות והמשתנים שאוביקט זה תומך בהם הם חיצוניים לממשק ולכן לא מופיעים בקובץ. בקוד הדוגמא מדובר על אוביקט שמחפש מידע על סרטים ב omdb, אבל מבחינת הגדרת הממשק זה כלל לא משנה.

כשתיבת טקסט מוגדרת להציג את תוכן המשתנה omdb.title לא מעניין את כותב הממשק מאיפה המשתנה מקבל את ערכו והאם זה קשור לחיפוש שמבוצע או לא מבוצע, אלא רק שמה שמופיע בתוכן התיבה הוא שם הסרט עליו המשתמש מדבר.

היתרון בשיטה זו הוא כמובן היכולת להפריד בין פיתוח ותיאור הממשק לבין קוד התוכנית. אותו הקוד יכול לשרת מספר ממשקי משתמש שונים למשל יישום Mobile ב QML, יישום Desktop שכתוב ב Qt Widgets ויישום CLI שמציג את המידע דרך ממשק שורת הפקודה.

ב Qt המנגנון הרלוונטי לקשירת משתנים הוא Signals & Slots: המשתנה omdb אליו אני מתיחס מהממשק הוא אוביקט ממחלקה היורשת מ QObject. אוביקט זה מגדיר פונקציות גישה ל-3 המשתנים title, year ו imageUrl. המחלקה מגדירה גם signal עבור כל אחד ממשתנים אלו ושולחת את ה signal בכל פעם שיש שינוי בערך אחד המשתנים.

כל התיחסות ל omdb.title מקובץ ה QML מייצרת חיבור בין הממשק ל signal שנקרא titleChanged. בכל פעם שקוד ה C++ שולח את האות יש קריאה מחדש של הערך לצורך הצגתו על המסך. כך גם עבור השדות year ו imageUrl. כך נראה קוד המימוש של הקובץ omdbapi.cpp שמתאר את המחלקה:

#include "omdbapi.h"
#include <QtCore>
#include <QJsonArray>
#include <QJsonDocument>

OmdbApi::OmdbApi(QObject *parent) : QObject(parent)
{
    QObject::connect(&m_nam, SIGNAL(finished(QNetworkReply*)),
                     this, SLOT(queryFinished(QNetworkReply*)));
}

QString OmdbApi::title()
{
    return m_title;
}

QString OmdbApi::year()
{
    return m_year;
}

QString OmdbApi::imageUrl()
{
    return m_imageUrl;
}

void OmdbApi::search(QString keyword)
{
    QUrl url(QString("http://omdbapi.com/?s=%1").arg(keyword));
    QNetworkRequest req(url);
    m_nam.get(req);

}

void OmdbApi::queryFinished(QNetworkReply *r)
{
    auto doc = QJsonDocument::fromJson(r->readAll());
    QJsonObject root = doc.object();
    QJsonArray results = root.value("Search").toArray();

    QJsonObject first = results.at(0).toObject();
    setTitle(first.value("Title").toString());
    setYear(first.value("Year").toString());
    setImageUrl(first.value("Poster").toString());
}

void OmdbApi::setTitle(QString val)
{
    m_title = val;
    emit titleChanged(val);
}

void OmdbApi::setYear(QString val)
{
    m_year = val;
    emit yearChanged(val);
}

void OmdbApi::setImageUrl(QString val)
{
    m_imageUrl = val;
    emit imageUrlChanged(val);
}

קוד זה כולל את כל הלוגיקה של היישום כולל מנגנון התקשורת עם omdbiapi ועדכון משתני המחלקה. המשתנים הקשורים מאפשרים ניתוק בין קוד זה לבין הגדרת הממשק בקובץ QML.

אגב הסקרנים ביניכם מוזמנים להסתכל על קוד הדוגמא המלא שכולל גם ממשק מבוסס Widgets וקוד לחיפוש פרטי סרטים ב omdbiapi בקישור:
https://github.com/ynonp/qt-2016-02/tree/master/live/day3-2/labnetwork