משתנים קשורים ב QML
משתנים קשורים הם מאפיין אהוב של ספריות ממשק משתמש רבות מאחר והם מאפשרים לנתק בין הפעולה לבין השפעתה על המסך. אתם יכולים למצוא אותם בפלאש, באנגולר וכמובן גם ב 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