Журнал LinuxFormat - перейти на главную

LXF144:QML

Материал из Linuxformat
Перейти к: навигация, поиск
Самостоятельная программа не так уж отличается от модулей QML в приложениях Qt


Содержание

QML: Пишем программу

На этот раз мы напишем самостоятельную программу на QML, и 'Андрей Боровский даже расскажет, как превратить ее в исполнимый файл.

Самостоятельная программа на QML не так уж сильно отличается от модулей QML, используемых в приложениях Qt. Фактически одно почти всегда без особoго труда можно конвертировать в другое. Одно из существенных отличий – файл с расширением qmlproject. Само расширение говорит о том, что задача этого файла – описание проекта QML. Внутри файл описания проекта представляет собой типичный файл модуля, написанного на QML. Рассмотрим пример файла .qmlproject (напомню, что примеры из этой серии статей работают в Qt 4.7.1 и более поздних версиях).


 import QmlProject 1.0
 Project {
    /* Include .qml, .js, and image files from current directory and
 subdirectories */
    QmlFiles {
        directory: “.”
    }
    JavaScriptFiles {
        directory: “.”
    }
    ImageFiles {
        directory: “.”
    }
    /* List of plugin directories passed to QML runtime */
    // importPaths: [ “ ../exampleplugin “ ]
 }

Структура файла qmlproject похожа на структуру модуля QML, хотя модулем QML он, строго говоря, не является. Как и практически любой файл QML, файл описания проекта начинается с директивы import. Файл описания проекта импортирует пакет QmlProject, с обязательным, как и во всех подобных случаях, указанием версии – 1.0. Дальше следует ключевое слово Project, за которым в фигурных скобках следует сам модуль. Семантически это очень похоже на то, как если бы в QML был элемент «Project{}», подобно элементу Rectangle{} или MouseArea{}. Но в официальном перечне элементов QML такой элемент не упомянут, поэтому я воздержусь от того, чтобы называть его элементом. Вообще официальной документации по файлу описания проекта на момент написания этой статьи было как-то подозрительно мало. Вероятно, эта часть QML еще будет меняться в следующих версиях. На самом деле файл qmlproject и не является обязательным для запуска программы QML. Файлы описания проекта требуются прежде всего программе Qt Creator, которая черпает из этих файлов информацию о проекте. Qt Creator умеет выполнять синтаксический анализ файлов QML, находить в них ошибки, давать подсказки и да же выполнять автоматическое завершение идентификаторов, все как в C++ (рис. 1).

Контекстная справка по QML работает так же хорошо, как и справка по Qt (только, в связи с быстрыми изменениями языка, написанное в ней не всегда отражает реальность). При запуске программы QML на выполнение из Qt Creator вызывается утилита qmviewer. Писать программы QML в Qt Creator очень удобно, но если вы не хотите сталкиваться с неприятными сюрпризами, скачайте версию Qt Creator, которая заточена под ту версию QML, с которой вы собираетесь работать. На момент написания этой статьи новейшая версия Qt Creator имела номер 2.1. Эта версия может работать с QML из Qt 4.7.1 и 4.7.2.

Вернемся к файлу qmlproject. Имена структур QmlFiles{}, JavaScriptFiles{} и ImageFiles{} говорят сами за себя. У каждой из этих струк тур есть свойство directory, позволяющее указать директорию, где хранятся файлы QML, JavaScript, и графики соответственно. В нашем примере все эти файлы хранятся в той же директории, что и файл описания проекта. Свойство importPaths, вероятно, должно хранить списки директорий, из которых программа загружает расширения, хотя для каждого конкретного расширения в каждом загружающем его модуле можно указать ту же информацию.

Более того, если у нас есть директория, которая содержит файлы JavaScript, QML и графические файлы, составляющие некий вспомогательный модуль программы, мы можем указать в главном модуле QML имя этой директории (в кавычках, после ключевого слова import), и система QML сама найдет все, что нужно.

Изменения и дополнения

Как известно, один из самых простых способов начать изучение нового языка – взять текст несложной программы на этом языке и пу тем небольших изменений превратить ее в то, что желательно программисту. При этом важно, чтобы на каждом этапе трансформации программа работала или четко давала понять, что именно программист сделал не так. Применительно к современному QML, однако, подобный подход наталкивается на совершенно специфические трудности, связанные с тем, что сам язык QML еще не обрел окончательную форму; и даже очень простые примеры программ, взятые не из той версии, могут не работать просто в силу несовместимости. Разумеется, все системы с годами меняются, если только разработчики не забросили их. Но с QML изменения происходят пока что уж слишком быстро. То, что работа ло в версии Qt 4.7, может не работать (и даже, скорее всего, не будет работать) в Qt 4.7.1, не говоря уже о коде из Qt 4.8. Вот, например, некоторые изменения, которые претерпел QML за последнее время:

  • Элемент LineEdit теперь называется TextInput, а элемент MouseRegionMouseArea.
  • Раньше индивидуальный идентификатор (значение свойства id) элемента QML мог начинаться с заглавной буквы, теперь – не может.
  • Раньше, если вы объявляли свойство с именем, аналогичным имени унаследованного свойства элемента QML, унаследованное свойство перекрывало ваше. Теперь – наоборот.
  • Раньше QML имел гораздо более мягкую систему типов, в которой любое значение изначально рассматривалось как строка, а уже затем приводилось к нужному типу. Например, можно было написать:
color: white

Теперь QML скажет на это, что переменная white не найдена. Все дело в том, что вы забыли кавычки:

color: “white”

А вот выражение

foo: “true”

теперь означает совсем не то же самое, что

foo: true

В первом случае переменной foo присваивает ся строка “true”, во втором случае – значение булевского типа (раньше строка “true” конвертировалась бы в булевский тип, при условии, что свойство foo принадлежит этому типу).

Это далеко не полный список изменений QML. Возможно, грядущий Qt 4.8 принесет новые изменения. Будем на деяться, что примеры, используемые в этих статьях, которые не копают слишком глубоко, не устареют морально в ближайшее время.

Наша программа

Ну, а теперь собственно сама программа на QML. Я переделал ее из демо-программмы clocks, которая входит в дистрибутив Qt. Стандартная демо-программа clocks показывает текущее время в трех разных городах Земли (рис. 2), используя время, установленное в компьютере и «зашитые» в код названия городов и смещения по времени. Мы дополним эту программу элементами интерактивности. В результате пользователь сам сможет выбирать имена городов и смещения времени относительно Гринвича, а также выбирать цвет фона окна программы с помощью специальной цветовой панели (рис. 3).

С точки зрения изучения языка QML эта программа интересна для нас прежде всего тем, что в ней мы впервые встречаемся с элементами, которые управляют расположением нескольких виджетов. В нашей программе присутствует три ряда виджетов: верхний ряд с дисками-циферблатами часов, ниже ряд для ввода имен городов и смещений, еще ниже – ряд для выбора цвета фона. Чтобы создать ряд виджетов, нам требуется элемент Row{}. Задача этого элемента – расположить дочерние элементы в один горизонтальный ряд. Чтобы создать ряд, например, прямоугольников, строим конструкцию типа

 Row {
 Rectangle {}
 Rectangle {
 id : leftCell
 …
 }
 Rectangle {
 anchors.left : leftCell.right}
 }

Если вам требуется более сложное расположение (с разными интервалами между элементами ряда, особым расположением элементов, различающихся по высоте и т.п.), можно поиграть со свойствами anchors, а также left и right. При этом, например, свойство anchors.left элемента может быть связано со свойством right соседа слева, если, конечно, у соседа слева инициализировано свойство id.

Многие другие вещи в нашей программе должны быть вам уже знакомы. Обратите внимание на то, что когда вы вводите новое имя города, подпись под соответствующими часами меняется непосредственно в процессе ввода. Так работает связывание свойств text элементов TextInput и Text (первый предназначен для ввода текста, второй – для вывода его под часами). Для изменения цвета фона окна достаточно щелкнуть мышью соответствующий прямоугольник. Благодаря тому, что для отрисовки некоторых элементов часов используется полупрозрачная растровая графика, цвет этих элементов также будет меняться вместе с цветом фона.

Состояния и переходы

Две важные концепции языка QML – состояния и переходы. Поскольку основная задача QML – описание виджетов, состояния и переходы имеют отношение к виджетам. Что такое состояния и переходы, легко объяснить на примере такого элемента управления, как кнопка. У кнопки может быть два состояния – обычное и нажатое (под нажатым состоянием понимается состояние кнопки, когда на нее «надавили» указателем мыши). Между этими двумя состояниями существует два перехода – из обычного состояния в нажатое и обратно. Переходы определяют графические эффекты, которыми сопровождается изменение состояния виджета. В самом простом случае без переходов можно вообще обойтись – достаточно просто перерисовать виджет в новом состоянии; но наличие переходов делает поведение виджетов гораздо более привлекательным и натуралистичным (или, наоборот, необычным и привлекательным).

Применяя состояния и переходы, мы заставим цветные прямоугольники вести себя как настоящие кнопки. Вот как выглядит один из элементов Item, использующий состояния и переходы:

 Item {
 id: cell1; width: 40; height: 25
 Rectangle {
 id : rect1; color: “blue”; border.color: “white”; anchors.fill: parent
      }
 MouseArea {
  id : mouseArea; anchors.fill: parent; onClicked: root.color = “blue”
 }
 states: State {
       name: “down”; when: mouseArea.pressed == true
          PropertyChanges { target: rect1; border.color :#000088”;
 border.width : 2 }
       }
 transitions: Transition {
 from: “”; to: “down”; reversible: true
          }
  }

Свойству states элемента Item (в нашем примере этот элемент имеет идентификатор cell1) можно присвоить список дополнительных состояний. Мы описываем состояние “down”, которое должен принимать элемент cell1, когда кнопка мыши нажата в области этого элемента. Структура PropertyChanges описывает само состояние. Целью состояния является прямоугольник rect1, у которого должны измениться толщина и цвет окаймляющей линии. Вообще говоря, «описать новое состояние элемента» означает в QML задание новых значений различных свойств элемента.

Точно так же свойство transitions содержит список переходов. Мы описываем один переход – из состояния по умолчанию в со-стояние “down”. Обратите внимание, что свойству reversible присвоено значение true, которое указывает, что элемент должен переходить из состояния “down” в исходное, как только исчезнет условие, вызывающее состояние “down”. Причем этот переход будет зеркальным отражением перехода из состояния по умолчанию в состояние “down”. Если состояния определяют новые значения свойств элемента, то переходы определяют, как именно исходные значения свойств должны трансформироваться в новые. Например, с помощью переходов можно определить скорость изменения значения числового свойства. Для свойства типа color – цвет – можно также указать, с какой скоростью исходный цвет должен трансформироваться в конечный, и, возможно, задать промежуточные цвета.

Например, мы можем добавить в описание перехода Анимационный следующий эффект:

 ParallelAnimation {
 ColorAnimation { duration:500}
 NumberAnimation { duration:500}
 }

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

Утилита qmlviewer

В предыдущих статьях я писал о том, что утилита qmlviewer не представляет собой ничего особенного – в том смысле, что вы и сами без особого труда можете воспроизвести ее функциональность. Это, конечно, верно. Тем не менее, стоит ли изобретать велосипед, когда утилита, причем весьма удобная, у нас уже есть?

Что может qmlviewer? Как вы уже поняли, утилита может загружать модули и программы QML и выполнять их. Если программа QML содержит элементы анимации, в целях отладки скорость анимации можно снизить. Но этим возможности qmlviewer не исчерпываются. В одной из прошлых статей я сетовал на то, что в Windows нельзя увидеть сообщения об ошибках, которые система интерпретации QML вводит на консоль. Впрочем, в Windows это трудно сделать чисто технически, поскольку, в отличие от Linux, графическая программа Windows тут же теряет связь с консолью, из-под которой она запущена. Программа qmlviewer исправляет этот недостаток (рис. 4). Команда меню «Debugging | Show warnings…» открывает специальное окно, где можно увидеть подробное описание проблемы и номер строки исходного текста, в которой она возникла. Самая удобная «фишка» утилиты qmlviewer – возможность быстро перезагрузить модуль QML после внесения изменений. Для этого достаточно нажать клавишу F5.

Единственный, на мой взгляд, недостаток программы qmlviewer заключается в том, что она не интегрирована с текстовым редактором, в котором можно было бы сразу исправить обнаруженные недочеты. Впрочем, наличие Qt Creator с поддержкой QML делает этот недостаток не таким уж существенным. Можно было бы ожидать, что утилита qmlviewer будет загружать файлы qmlproject, но это не так. Если программа состоит из нескольких файлов QML, загрузить нужно главный из них. А для того, чтобы понять, какой файл главный, нужно разобраться в структуре программы. Впрочем, обычно это несложно. Еще утилита qmlviewer умеет загружать файлы QML из Сети по протоколу HTTP и даже позволяет настроить HTTP-прокси.

Делаем модуль QML исполнимым

Еще одна вещь, которой лично мне, как линуксоиду, не хватает в QML – это поддержка строки с префиксом #!. Напомню, что в Unix-системах такие строки традиционно используются для вызова интерпретатора, которому потом передается имя файла текста. Интерпретатор QML расценивает эту конструкцию как синтаксическую ошибку. Впрочем, на то мы и линуксоиды, чтобы решать такие проблемы. Вот как это делается на основе простейшего приложения qmltest, с которым мы знакомились в первой части серии:

 qmltest::qmltest(QWidget *parent, Qt::WFlags flags)
 : QDialog(parent, flags)
 {
    ui.setupUi(this);
    setWindowTitle(«qmltest interpreter»);
    QDeclarativeView *qmlView = new QDeclarativeView;
    QFile f;
    if (QCoreApplication::arguments().count() < 2) {
        QMessageBox::critical(0, trUtf8(«Ошибка”), trUtf8(“Не указано имя файла”));
        QCoreApplication::exit(1);
    }
    f.setFileName(QCoreApplication::arguments().at(1));
    if (!f.open(QIODevice::ReadOnly)) {
        QMessageBox::critical(0, trUtf8(“Ошибка”), trUtf8(“Невозможно открыть файл “) +QCoreApplication::arguments().at(1));
        QCoreApplication::exit(2);
    }
    QByteArray ba = f.readLine();
    if (QString(ba).startsWith(#!”)) {
        ba.clear();
        QFile f1(f.fileName() + “.tmp);
        ba.resize(f.size());
        ba = f.read(f.size());
        f1.open(QIODevice::WriteOnly);
        f1.write(ba);
        f1.close();
        f.close();
        qmlView->setSource((QUrl::fromLocalFile(f1.fileName())));
        f1.remove();
    } else {
        f.close();
        qmlView->setSource((QUrl::fromLocalFile(f.fileName())));
    }
    int lm, rm, tm, bm;
    QVBoxLayout *layout = new QVBoxLayout(this);
    layout->getContentsMargins(&lm, &tm, &rm, &bm);
    setGeometry(0, 0, qmlView->rootObject()->property(“width”).toInt() + lm + rm,
             qmlView->rootObject()->property(“height”).toInt()+ tm + bm);
    layout->addWidget(qmlView);
 }

Идея, лежащая в основе этой программы, очень проста. Встречая в начале исполнимого файла строку, начинающуюся с символов #!, интерпретатор оболочки, например Bash, вызывает программу, путь к которой указан вслед за этими символами, и передает этой программе имя исполнимого файла как аргумент. Так что если в начале файла somefile.qml, помеченного как ис-полнимый, поместить строку

!#/bin/qmltest

командная оболочка выполнит нечто наподобие

bin/qmltest somefile.qml

И все бы было хорошо, но встретив строку !# /bin/qmltest в тексте программы, интерпретатор откажется выполнять ее дальше. Поэтому в предложенном выше решении мы проверяем, содержит ли файл QML строку, начинающуюся с !#, и если содержит – «откусываем» эту строку от текста программы, который передается на исполнение интерпретатору.

Казалось бы, все, что остается сделать – передать модифицированный текст программы интерпретатору, но тут мы сталкиваемся с еще одной неожиданной проблемой: интерпретатор QML не принимает текст программы QML непосредственно. Чтобы запустить программу на выполнение, интерпретатору нужно передать URL файла программы. Я проделал довольно глубокие экскурсы в недра классов QdeclarativeViewPrivate и QdeclarativeEngine, и везде методы загрузки программы требовали URL. Иначе гово-ря, написать свой метод, который бы загружал программу из переменной типа QString или QbyteArray, не получится, если только не вносить очень серьезные изменения в саму Qt library.

Я использовал другой метод: модифицированный текст сохраняется в той же директории, что и исходный файл, в файле с именем как у исходного и с добавленным расширением .tmp. Далее имя этого файла преобразуется в URL и передается интерпретатору вместо имени исходного файла. После загрузки модифицированного файла интерпретатором сам файл можно удалить, он больше не нужен. Недостаток этого подхода очевиден: у программы-интерпретатора QML должно быть право записывать данные в директорию, которая содержит программу QML. Модифицированный файл QML нельзя сохранить в директории /var/tmp или ей подобной, так как он может содержать ссылки на другие QML-файлы, которые имеют смысл только относительно директории, в которой расположен исходный файл программы.

Но если эта проблема не останавливает вас, то вы можете попробовать запускать файлы QML как «настоящие» программы, прямо из командной строки.

Скомпилируйте проект qmltest-interpreter из примера, который поставляется на диске. Скопируйте программу qmltest в какуюнибудь общедоступную директорию, выберите файл программы QML и добавьте в него строку наподобие

!#/bin/qmltest

Теперь сделайте этот файл исполнимым:

chmod +x somefile.qml

И, о чудо, вы можете запускать программу из командной строки как

./somefile.qml

Между прочим, если программа QML не содержит строки #!, ее можно запускать с интерпретатором как

qmltest somefile.qml

и в этом случае интерпретатор не станет сохранять временную модифицированную версию файла somefile.qml, так как в ней, оче-видно, нет надобности.

Один интересный момент в представленном выше коде на C++ связан с конфигурацией геометрии окна программы-интерпретатора. У каждого геометрического элемента QML есть свойства width и height, которые, соответственно, определяют его ширину и высоту. Получить доступ к свойству корневого элемента можно с помощью метода qmlView->rootObject()->property(), которому передается имя свойства. Значение свойства возвращается в переменной типа QVariant. Таким образом, мы можем получить значения свойств width и height и настроить соответственно ширину и высоту окна, учитывая при этом размеры отступов, которые устанавливает менеджер компоновки.

Что день грядущий нам готовит?

На этом мы завершаем наш обзор языка QML. Сейчас еще трудно точно предсказать, что его ждет – станет ли QML основой построения графических интерфейсов будущих версий Qt или умрет, как некоторые другие технологии. В любом случае, QML демонстрирует новое направление в развитии виджетов – направление, в котором растровая графика и сложные геометрические преобразования будут играть ключевую роль.

Персональные инструменты
купить
подписаться
Яндекс.Метрика