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

LXF143:QML

Материал из Linuxformat
Перейти к: навигация, поиск
Вид­же­ты QML Возь­мем са­мый кра­си­вый и пе­ре­та­щим его в на­шу про­грам­му на Qt


QML

Содержание

QML: На служ­бе при­ло­же­ний Qt

Ан­д­рей Бо­ров­ский го­тов по­спо­рить, что QML стал лю­би­мой иг­руш­кой ко­де­ров Qt, и все их уси­лия на­це­ле­ны на ри­со­ва­ние кра­си­вых вид­же­тов QML.

Рис. 1 Рис. 1. Ис­ход­ный вид­жет в про­грам­ме QML.

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

Сра­зу пре­ду­пре­ж­даю, что при­ме­ры из этой ста­тьи бу­дут ра­бо­тать с Qt 4.7.1, 4.7.2 и, на­де­юсь, с бо­лее поздними вер­сия­ми Qt, но не с бо­лее ранними. Так­же хо­чу на­помнить, что при ра­бо­те с QML в файл про­ек­та Qt нуж­но до­ба­вить строч­ку

QT += declarative

Демо QML Dial из ди­ст­ри­бу­ти­ва Qt – это са­мо­стоя­тель­ная про­грам­ма, на­пи­сан­ная на QML (я уже пи­сал о том, что QML мож­но ис­поль­зо­вать как са­мо­стоя­тель­ный язык про­грам­ми­ро­вания, на­по­до­бие JavaScript). Ме­ня же, как про­грам­ми­ста Qt, боль­ше ин­те­ре­су­ет при­менение QML для рас­ши­рения воз­мож­но­стей ин­тер­фей­сов про­грамм Qt. Вот та­кое рас­ши­рение мы се­го­дня и на­пи­шем, а за­од­но по­зна­ко­мим­ся с но­вы­ми воз­мож­но­стя­ми Qt и неко­то­ры­ми но­вы­ми ме­то­да­ми взаи­мо­дей­ст­вия Qt и QML. Сам объ­ект QML Dial пред­став­ля­ет со­бой поч­ти фо­то­реа­ли­стич­ную ими­та­цию стре­лоч­но­го ин­ди­ка­то­ра, та­ко­го как спи­до­метр или ин­ди­ка­тор дав­ления жид­ко­сти в тру­бе (рис. 1). Мы не толь­ко «пе­ре­та­щим» этот ин­ди­ка­тор в свою про­грам­му Qt, но и до­полним его неко­то­ры­ми эле­мен­та­ми (рис. 2).

Как мы уже зна­ем, важней­шей за­да­чей QML яв­ля­ет­ся соз­дание кра­си­вых гра­фи­че­­ских ин­тер­фей­сов, а это, как вы понимае­те, невоз­мож­но без мощ­ных средств ра­бо­ты с рас­тро­вой гра­фи­кой (для про­грам­ми­ста ри­со­вать кра­си­вые ин­тер­фей­сы внут­ри­про­грамм­но, век­тор­ны­ми функ­ция­ми, несколь­ко уто­ми­тель­но). Для ра­бо­ты с изо­бра­жения­ми, хранимы­ми в фай­лах, в язы­ке QML есть объ­ект Image{}. Этот объ­ект по­до­бен тэ­гу HTML <img…>, за ис­клю­чением то­го, что он мо­жет го­раз­до боль­ше. В про­стей­шем ва­ри­ан­те ис­поль­зо­вание объ­ек­та Image{} вы­гля­дит так:

Image { source: “background.png” }

Эта кон­ст­рук­ция за­гру­жа­ет фон на­ше­го ин­ди­ка­то­ра (опи­сание внешнего ви­да ин­ди­ка­то­ра хранит­ся в фай­ле Dial.qml, рас­по­ло­жен­ном в ди­рек­то­рии Dial и со­пут­ст­вую­щих фай­лах с рас­ши­рением png.) На пер­вый взгляд тут все по­нят­но без осо­бых по­яснений. Мы за­гру­жа­ем изо­бра­жение из фай­ла background.png... Стоп. А ку­да мы его за­гру­жа­ем? Где оно бу­дет рас­по­ло­же­но? Вспомним, что ви­зу­аль­ная часть QML осно­ва­на на Qt Graphics View Framework, а эта сис­те­ма по умол­чанию стре­мит­ся рас­по­ло­жить гра­фи­че­­ские эле­мен­ты так, что­бы гео­мет­ри­че­­ский центр сце­ны сов­па­дал с цен­тром ок­на вы­во­да. Так что по умол­чанию изо­бра­жение background.png про­сто зай­мет ме­сто в цен­тре ок­на на­ше­го вид­же­та (что нам и тре­бу­ет­ся).

Рис. 2 Рис. 2. Вид­жет на его ос­но­ве в на­шей про­грам­ме Qt.

По­ми­мо соб­ст­вен­ных свойств, к ка­ко­вым от­но­сит­ся, на­при­мер, свой­ст­во source, объ­ект Image{} унас­ле­до­вал от ба­зо­вых объ­ек­тов QML та­кие свой­ст­ва как x, y, scale, rotation, transform, anchors и мно­гие дру­гие. Сам дви­жок, вы­пол­няю­щий от­ри­сов­ку изо­бра­жения, об­ла­да­ет мно­ги­ми по­лез­ны­ми воз­мож­но­стя­ми. На­при­мер, ес­ли за­гру­жае­мый фор­мат под­дер­жи­ва­ет аль­фа-ка­нал, то при вы­во­де изо­бра­жения учи­ты­ва­ет­ся уро­вень про­зрач­но­сти его эле­мен­тов.

Рас­смот­рим неко­то­рые свой­ст­ва объ­ек­та Image{}. Ес­ли мы хо­тим вы­ло­жить сце­ну фо­но­вым ри­сун­ком как плит­кой, на­до до­ба­вить свой­ст­во

fillMode: Image.Tile

Свой­ст­ва scale, rotation и transform, как вы уже до­га­да­лись, по­зво­ля­ют вы­пол­нять пре­об­ра­зо­вания изо­бра­жения, та­кие как мас­шта­би­ро­вание и вра­щение. Вот как, на­при­мер, вы­пол­ня­ет­ся вра­щение тени под стрел­кой на­ше­го ин­ди­ка­то­ра:

transform: Rotation {
  origin.x: 9; origin.y: 67
  angle: needleRotation.angle
}

Вся эта кон­ст­рук­ция на­хо­дит­ся в те­ле объ­ек­та image, от­ве­чаю­ще­го за вы­вод тени под стрел­кой (у нас ведь поч­ти фо­то­реа­лизм, так что тень долж­на дви­гать­ся вме­сте со стрел­кой). В объ­ек­те Rotation мы за­да­ем ко­ор­ди­на­ты цен­тра вра­щения и угол по­во­ро­та (ес­ли кто не по­нял, в этом чу­дес­ном язы­ке двое­то­чие яв­ля­ет­ся опе­ра­то­ром при­сваи­вания).

А как вы­пол­ня­ет­ся вра­щение са­мой стрел­ки? Тут все еще ин­те­реснее. Вот как вы­гля­дит опи­сание стрел­ки:

Image {
 id: needle
 x: 98; y: 33
 smooth: true
 source: “needle.png”
 transform: Rotation {
   id: needleRotation
   origin.x: 5; origin.y: 65
   //! [needle angle]

angle: root.angle

   Behavior on angle {
     SpringAnimation {
       spring: 1.4
       damping: .15
     }
   }
   //! [needle angle]
 }
}

Боль­шая часть это­го опи­сания долж­на быть вам уже по­нят­на. Мы уста­нав­ли­ва­ем ко­ор­ди­на­ты стрел­ки; при­своение зна­чения true свой­ст­ву smooth при­во­дит к то­му, что при вы­полнении пре­об­ра­зо­ваний изо­бра­жения стрел­ки (в на­шем слу­чае это вра­щение) вы­пол­ня­ет­ся спе­ци­аль­ная фильт­рация, сгла­жи­ваю­щая эф­фек­ты «ле­сен­ки», ко­то­рые мо­гут в хо­де этих пре­об­ра­зо­ваний возник­нуть. Сравните ко­ор­ди­на­ты стрел­ки и ко­ор­ди­на­ты тени. Ме­ж­ду про­чим, по­сколь­ку на изо­бра­жении (файл needle.png) стрел­ка смот­рит вверх, наш ин­ди­ка­тор по умол­чанию ука­зы­ва­ет на зна­чение в се­ре­дине диа­па­зо­на. Мож­но бы­ло бы из­менить это, по­вер­нув стрел­ку на кар­тин­ке или вы­полнив необ­хо­ди­мое вра­щение при инициа­ли­за­ции, но мы оста­вим все как есть, по­то­му что так ин­те­реснее.

Опи­сание объ­ек­та Rotation на­чи­на­ет­ся так же, как и в слу­чае с те­нью, од­на­ко даль­ше сле­ду­ет стран­ное. Кон­ст­рук­ция Behavior on angle {} ука­зы­ва­ет, что долж­на де­лать стрел­ка, когда ее угол по­во­ро­та ме­ня­ет­ся. Объ­ект SpringAnimation соз­да­ет спе­ци­аль­ный анима­ци­он­ный эф­фект при дви­жении стрел­ки – эф­фект за­ту­хаю­щих ко­ле­баний. Свой­ст­во spring ука­зы­ва­ет, на­сколь­ко ве­ли­ка долж­на быть из­на­чаль­ная ам­пли­ту­да ко­ле­баний, а свой­ст­во damping оп­ре­де­ля­ет ско­рость их за­ту­хания. В ре­зуль­та­те стрел­ка на­ше­го ин­ди­ка­то­ра бу­дет колебать­ся как на­стоя­щая стрел­ка на пру­жине.

С по­мо­щью свой­ст­ва visible, ко­то­рым об­ла­да­ют все объ­ек­ты QML, в том чис­ле и объ­ект image, мы мо­жем управ­лять ви­ди­мо­стью этих объ­ек­тов.

Воз­мож­но, вас удив­ля­ет, что свой­ст­во angle объ­ек­та needleRotation ме­ня­ет­ся вся­кий раз, когда ме­ня­ет­ся свой­ст­во root.angle. В та­ких язы­ках, как C++, од­на опе­ра­ция при­сваи­вания оз­на­ча­ет од­но из­менение зна­чения; но в QML, по­хо­же, опе­ра­ция при­сваи­вания про­дол­жа­ет ра­бо­тать по­сто­ян­но, и из­менение зна­чения свой­ст­ва в пра­вой час­ти вы­ра­жения при­сваи­вания при­во­дит к из­менению зна­чения свой­ст­ва в ле­вой час­ти, когда бы оно ни слу­чи­лось. И это дей­ст­ви­тель­но так. В QML этот ме­ханизм на­зван свя­зы­ванием свойств [property binding]. Стро­ка

angle: root.angle

свя­зы­ва­ет ме­ж­ду со­бой свой­ст­ва root.angle и angle, так что из­менение зна­чения пер­во­го свой­ст­ва всегда бу­дет при­во­дить к из­менению зна­чения вто­ро­го. Свя­зы­вание свойств – это осо­бая фор­ма при­сваи­вания, ко­то­рая ис­поль­зу­ет­ся всегда, когда сле­ва от опе­ра­то­ра при­сваи­вания ука­за­но свой­ст­во объ­ек­та, а спра­ва – лю­бое син­так­си­че­­ски кор­рект­ное вы­ра­жение язы­ка JavaScript (свой­ст­во, функ­ция и т. д). То есть фак­ти­че­­ски свя­зы­вание свойств яв­ля­ет­ся стан­дарт­ным ме­то­дом при­сваи­вания в QML, при ко­то­ром свой­ст­во, стоя­щее сле­ва от опе­ра­то­ра при­сваи­вания, ста­но­вит­ся псев­донимом вы­ра­жения, стоя­ще­го спра­ва (вот по­че­му свя­зы­вать мож­но не толь­ко свой­ст­ва, но и та­кие «пас­сив­ные» объ­ек­ты, как функ­ции, ко­то­рые са­ми не мо­гут ниче­го иниции­ро­вать). Свя­зы­вание свойств – это тот ме­ханизм, ко­то­рый по­зво­ля­ет объ­ек­там QML об­менивать­ся дан­ны­ми друг с дру­гом, по­доб­но то­му, как объ­ек­ты Qt об­менива­ют­ся дан­ны­ми друг с дру­гом с по­мо­щью сиг­на­лов и сло­тов. Точ­но так же, как в слу­чае сиг­на­лов и сло­тов, свя­зы­вание уже свя­зан­ных свойств мож­но из­менить с по­мо­щью эле­мен­та QML PropertyChanges {}. Мне очень нра­вит­ся свя­зы­вание свойств, и ес­ли бы я про­ек­ти­ро­вал но­вый объ­ект­но-ори­ен­ти­ро­ван­ный язык про­грам­ми­ро­вания, то обя­за­тель­но вклю­чил бы в него этот ме­ханизм.

Даль­ше в ис­ход­ном при­ме­ре на осно­вание стрел­ки на­кла­ды­ва­ют­ся кол­па­чок и стек­ло с бли­ком, изо­бра­жения ко­то­рых хра­нят­ся в фай­ле overlay.png. Об­ра­ти­те внимание, что прак­ти­че­­ски вез­де мы ис­поль­зу­ем спо­соб­ность фор­ма­та PNG соз­да­вать час­тич­но про­зрач­ные изо­бра­жения, ина­че та­кой слож­ный эле­мент управ­ления у нас про­сто не по­лу­чил­ся бы. Об­ра­ти­те внимание так­же на то, что по­ря­док рас­по­ло­жения изо­бра­жений по­верх друг дру­га со­от­вет­ст­ву­ет по­ряд­ку сле­до­вания пред­став­ляю­щих их объ­ек­тов в тек­сте про­грам­мы QML. В об­щем-то это ес­те­ст­вен­но, ес­ли учесть, что вид­жет соз­да­ет­ся по ме­ре вы­полнения про­грам­мы.

Ко все­му это­му ве­ли­ко­ле­пию я до­ба­вил со­всем не­мно­го.

С объ­ек­том Rectangle мы уже встре­ча­лись:

Rectangle {
x: 61
y: 118
width: 80
height: 36
color: “black”
border.color: “#888888”
}

Но­вое здесь – свой­ст­во border.color, по­зво­ляю­щее за­дать цвет границы пря­мо­угольника. Объ­ект Text дуб­ли­ру­ет зна­чение, ко­то­рое ука­зы­ва­ет ин­ди­ка­тор.

Text {
color: “green”
text: root.angle/2 + 50
x: 80
y: 114
font.pointSize: 24; font.bold: true
style: Text.Raised
styleColor: “black”
}

Здесь я хо­тел бы об­ра­тить ва­ше внимание толь­ко на один мо­мент. По­сколь­ку по умол­чанию стрел­ка ин­ди­ка­то­ра смот­рит вверх, фак­ти­че­­ские уг­лы по­во­ро­та сле­ду­ет ука­зы­вать от­но­си­тель­но это­го по­ло­жения, при­чем по­вер­ну­тая на неко­то­рый угол стрел­ка при но­вом по­во­ро­те ин­тер­пре­ти­ру­ет но­вый угол так, как ес­ли бы она на­хо­ди­лась в ис­ход­ном по­ло­жении.

Соз­да­ем вид­жет

Пе­рей­дем ко вто­рой час­ти на­шей ра­бо­ты – пре­вра­щению мо­ду­ля QML в вид­жет Qt. Необ­хо­ди­мые для это­го основ­ные опе­ра­ции мы и­зучи­ли в про­шлый раз. Но и те­перь нам есть что до­ба­вить. Пред­ста­ви­те­лем вид­же­та QML в на­шей про­грам­ме Qt яв­ля­ет­ся класс Dial (фай­лы Dial.h, Dial.cpp). Здесь я при­ве­ду толь­ко объ­яв­ление клас­са, осталь­ное вы най­де­те на дис­ке.

class Dial : public QObject
{
 Q_OBJECT
 Q_PROPERTY(int angle READ angle WRITE setAngle NOTIFY angleChanged)
public:
 Dial();
 int angle();
 void setAngle(int a);
signals:
 void angleChanged();
 private:
 int m_angle;
};

Тем, кто чи­тал пре­ды­ду­щую ста­тью и зна­ет Qt, тут все долж­но быть по­нят­но. Класс экс­пор­ти­ру­ет един­ст­вен­ное свой­ст­во angle. На­пом­ню толь­ко, что ме­тод setAngle() дол­жен яв­ным об­ра­зом эми­ти­ро­вать сиг­нал angleChanged(), ина­че вид­жет QML никогда не уз­на­ет, что угол из­менил­ся. Зна­то­ки сиг­на­лов и сло­тов Qt мо­гут спро­сить, по­че­му в сиг­на­ле angleChanged() не пе­ре­да­ет­ся но­вое зна­чение уг­ла по­во­ро­та. Ес­ли бы сиг­нал пред­на­зна­чал­ся для дру­гих клас­сов Qt, я бы так и сде­лал, но сиг­нал пред­на­зна­чен для вид­же­та QML, а со­от­вет­ст­вую­щий объ­ект это­го вид­же­та все рав­но про­чи­та­ет зна­чение свой­ст­ва angle с по­мо­щью ме­то­да angle() (ука­зан­но­го по­сле клю­че­во­го сло­ва READ в мак­ро­се Q_PROPERTY). Так что пе­ре­да­вать ка­кой-ли­бо па­ра­метр в сиг­на­ле Qt про­сто нет нужды.

Вид­жет в окне про­грам­мы

Соз­дание вид­же­та в окне Qt то­же не пред­став­ля­ет со­бой ниче­го осо­бен­но но­во­го, за ис­клю­чением од­но­го мо­мен­та, о ко­то­ром бу­дет ска­за­но ниже. Вот как мы соз­да­ем вид­жет (это фраг­мент фай­ла dialcontrol.cpp):

QDeclarativeView *qmlView = new QDeclarativeView;
dial = new Dial();
qmlView->rootContext()->setContextProperty(“Dial”, dial);
qmlView->setSource(QUrl(“qrc:/Dial/Dial.qml”));
QVBoxLayout *layout = new QVBoxLayout(this);
layout->addWidget(qmlView);

Вы, на­вер­ное, сра­зу об­ра­ти­ли внимание на то, ка­кую ссыл­ку мы ис­поль­зу­ем для за­груз­ки ис­ход­но­го тек­ста мо­ду­ля QML. В про­шлый раз мы ис­поль­зо­ва­ли ссыл­ку на файл Linux. Это да­ва­ло нам ог­ром­ную сво­бо­ду в плане мо­ди­фи­ка­ции внешнего ви­да ок­на на­шей про­грам­мы, но де­ла­ло ее за­ви­си­мой от рас­по­ло­жения фай­лов QML. Те­перь мы по­сту­па­ем ина­че и вклю­ча­ем файл Dial.qml и все со­пут­ст­вую­щие ему фай­лы в мо­дуль ре­сур­сов Qt.

В ре­зуль­та­те опи­сание вид­же­та QML станет ча­стью про­грам­мы Qt, и нам уже не при­дет­ся бес­по­ко­ить­ся о том, где хра­нят­ся со­от­вет­ст­вую­щие фай­лы. Обыч­но в мо­ду­ли ре­сур­сов вклю­ча­ют пик­то­грам­мы и эле­мен­ты ин­тер­на­цио­на­ли­за­ции при­ло­жения, но ничто не ме­ша­ет нам вклю­чить в них мо­ду­ли QML, тем бо­лее что дви­жок Qt QML уме­ет ра­бо­тать с про­стран­ст­вом ре­сур­сов при­ло­жения (и с дру­ги­ми про­стран­ст­ва­ми URL) как с локаль­ной фай­ло­вой сис­те­мой. Так что ес­ли вклю­чен­но­му в мо­дуль ре­сур­сов мо­ду­лю QML по­на­до­бит­ся файл, то­же вклю­чен­ный в мо­дуль ре­сур­сов, мо­дуль QML смо­жет без про­блем за­гру­зить его. Глав­ное, что­бы ссыл­ки на фай­лы бы­ли от­но­си­тель­ны­ми, а не аб­со­лют­ны­ми.

Что­бы пре­вра­тить мо­дуль QML в ре­сурс про­грам­мы Qt, нам по­на­до­бит­ся файл опи­сания ре­сур­сов *.qrc. Ра­бо­тать с фай­ла­ми с рас­ши­рением qrc мож­но мно­ги­ми спо­со­ба­ми – с по­мо­щью ди­зайнера гра­фи­че­­ских ин­тер­фей­сов Qt или с по­мо­щью про­грам­мы Qt Creator, но мож­но обой­тись и тек­сто­вым ре­дак­то­ром, ведь фор­мат QRC осно­ван на XML. Вот как, на­при­мер, вы­гля­дит файл QRC для на­шей про­грам­мы (файл dialcontrol.qrc):

<!DOCTYPE RCC><RCC version=”1.0”>
<qresource>
<file>Dial/background.png</file>
<file>Dial/Dial.qml</file>
<file>Dial/DialControl.qrc</file>
<file>Dial/needle.png</file>
<file>Dial/needle_shadow.png</file>
<file>Dial/overlay.png</file>
</qresource>
</RCC>

Внут­ри тэ­га <file> мы про­сто ука­зы­ва­ем путь к фай­лу ре­сур­са от­но­си­тель­но рас­по­ло­жения фай­ла QRC. Те­перь при сбор­ке при­ло­жения вид­жет QML бу­дет вклю­чен в на­шу про­грам­му. В ре­зуль­та­те на­ша про­грам­ма боль­ше не за­ви­сит от рас­по­ло­жения фай­лов QML, но хо­ро­шо это или пло­хо?

С од­ной сто­ро­ны, это уп­ро­ща­ет уста­нов­ку про­грам­мы: вам не нуж­но ду­мать о том, где долж­ны быть раз­ме­ще­ны фай­лы QML в со­от­вет­ст­вии со стан­дар­том XDG, а ес­ли вы пи­ше­те кросс-плат­фор­мен­ное при­ло­жение, ва­ша жизнь уп­ро­ща­ет­ся еще боль­ше. С дру­гой сто­ро­ны, при та­ком под­хо­де те­ря­ет­ся од­но из важней­ших пре­иму­ществ QML как сред­ст­ва опи­сания ин­тер­фей­сов про­грамм Qt: воз­мож­ность ра­дикаль­но сменить ин­тер­фейс без по­втор­ной сбор­ки при­ло­жения.

Су­ще­ст­ву­ет еще сво­его ро­да ком­про­мисс­ный ва­ри­ант: ском­пи­ли­ро­вать вид­жет QML как внешний двоич­ный ре­сурс про­грам­мы Qt (для это­го слу­жит ути­ли­та rcc). С од­ной сто­ро­ны, ра­бо­тать с внешним фай­лом ре­сур­са в про­грам­ме Qt не на­мно­го сложнее, чем со встро­ен­ным; с дру­гой сто­ро­ны, внешний файл ре­сур­са мо­жет быть за­менен без по­втор­ной сбор­ки про­грам­мы. Од­на­ко и у это­го под­хо­да есть свой ми­нус: ди­зайнеру ин­тер­фей­сов при­дет­ся иметь де­ло со спе­ци­аль­ны­ми ин­ст­ру­мен­та­ми Qt, та­ки­ми как rcc, тогда как в слу­чае рас­по­ло­жения вид­же­та QML це­ли­ком в соб­ст­вен­ных фай­лах ди­зайнеру для из­менения ин­тер­фей­са бу­дет доста­точ­но тек­сто­во­го ре­дак­то­ра и ре­дак­то­ра GIMP (или, на ху­дой конец, Photoshop). В об­щем, наи­бо­лее ра­зум­ный вы­бор за­ви­сит от кон­крет­ных це­лей – хо­ти­те ли вы, что­бы ка­ж­дый поль­зо­ва­тель, осво­ив­ший QML и рас­тро­вую гра­фи­ку, мог «сшить но­вую одеж­ку» для ва­шей про­грам­мы, или нет.

Еще о ме­то­де setContextProperty()

До сих пор мы ис­поль­зо­ва­ли ме­тод setContextProperty() для пе­ре­да­чи ука­за­те­ля на соз­дан­ные на­ми объ­ек­ты со свой­ст­ва­ми. Но его воз­мож­но­сти го­раз­до ши­ре. Пре­ж­де все­го, от­ме­тим, что су­ще­ст­ву­ет и дру­гой ва­ри­ант ме­то­да setContextProperty():

void	 setContextProperty ( const QString & name, const QVariant & value )

То есть, со свой­ст­вом кон­тек­ста QML мож­но свя­зать не толь­ко объ­ект, но и лю­бое зна­че­ние, при­во­ди­мое к ти­пу QVariant. На­при­мер:

qmlView->rootContext()->setContextProperty(“HelloText”, trUtf8(“Hello World!”);

Объ­ек­ты, ко­то­рые мы пе­ре­да­ем кон­тек­стам, мо­гут экс­пор­ти­ро­вать в QML не толь­ко свой­ст­ва, но и ме­то­ды. Что­бы мо­дуль QML уви­дел ме­тод объ­ек­та, этот ме­тод дол­жен быть объ­яв­лен в раз­де­ле public slots. На­при­мер, ес­лидо­ба­вить в класс Dial ме­тод

public slots:
int angleToPos(int angle)
{
…
}

то в фай­ле Dial.qml мож­но на­пи­сать так:

property int angleToPos : dial.angleToPos(angle)

Сбор ин­фор­ма­ции об ошиб­ках

По­сколь­ку на­ша слож­ная кон­ст­рук­ция QML мо­жет ну­ж­дать­ся в от­лад­ке, в про­грам­ме пре­ду­смот­рен спо­соб вы­во­да ин­фор­ма­ции об ошиб­ках, ко­то­рые возника­ют в хо­де вы­полнения про­грам­мы на QML. Во­об­ще-то в ОС Linux все со­об­щения об ошиб­ках QML вы­во­дят­ся в стан­дарт­ный по­ток вы­во­да той кон­со­ли, с ко­то­рой бы­ла за­пу­ще­на про­грам­ма. В ОС Windows Qt это­го по­че­му-то не де­ла­ет: да­же ес­ли за­пустить про­грам­му из ок­на команд­ной стро­ки Windows, ника­ких со­общений об ошиб­ках мы не уви­дим (да­же ес­ли ошиб­ки есть). В лю­бом слу­чае, на­ше при­ло­жение гра­фи­че­­ское, и его не обя­за­тель­но бу­дут за­пускать из ок­на кон­со­ли, так что непло­хо об­за­вес­тись соб­ст­вен­ным ме­то­дом вы­во­да ин­фор­ма­ции об ошиб­ках, осно­ван­ном на гра­фи­че­­ском ин­тер­фей­се.

Спи­сок ак­ту­аль­ных оши­бок воз­вра­ща­ет­ся ме­то­дом errors() QML-вид­же­та в ви­де объ­ек­та

QList<QDeclarativeError>;

Объ­ект клас­са QDeclarativeError вклю­ча­ет несколь­ко свойств и ме­то­дов, из ко­то­рых мы восполь­зу­ем­ся ме­то­дом toString(). Этот ме­тод воз­вра­ща­ет ин­фор­ма­цию об ошиб­ке на че­ло­ве­че­­ском язы­ке в пе­ре­мен­ной QString:

errors = qmlView->errors();
for(int i = 0; i < errors.count(); i++)
MessageBox::critical(this, “QML Error”, errors.at(i).toString());

где errors – объ­ект вы­шеука­зан­но­го клас­са, про­из­вод­но­го от Qlist.

Вы, на­вер­ное, об­ра­ти­ли внимание: я на­пи­сал, что ме­тод errors() воз­вра­ща­ет ин­фор­ма­цию об ак­ту­аль­ных ошиб­ках, то есть о тех, ко­то­рые су­ще­ст­ву­ют на мо­мент его вы­зо­ва. Тем, кто при­вык к ком­пи­ли­руе­мым язы­кам про­грам­ми­ро­вания, это мо­жет быть непри­выч­но, но в ди­на­ми­че­­ски ис­пол­няе­мых про­грам­мах QML ошиб­ки мо­гут возникать и ис­че­зать ди­на­ми­че­­ски, и не ка­ж­дая ошиб­ка обязательно при­во­дит к ава­рий­но­му за­вер­шению про­грам­мы.

В свя­зи с этим по­лез­но сравнить вы­вод про­грам­мы на кон­соль Linux (ку­да по­сту­па­ет ин­фор­ма­ция обо всех ошиб­ках) с на­шим вы­во­дом с по­мо­щью клас­са MessageBox. Та­ким об­ра­зом, на­при­мер, я уз­нал, что свя­зы­вать корневой кон­текст с объ­ек­том dial луч­ше до за­груз­ки тек­ста про­грам­мы QML, ведь вы­полнение про­грам­мы на­чи­на­ет­ся немед­лен­но по­сле вы­зо­ва ме­то­да setSource(). Ес­ли сра­зу по­сле вы­зо­ва setSource() про­грам­ма на QML не смо­жет инициа­ли­зи­ро­вать свой объ­ект Dial, это вы­зо­вет ошиб­ку, ко­то­рая, од­на­ко бу­дет уст­ранена, как толь­ко мы ука­жем про­грам­ме, чем имен­но сле­ду­ет инициа­ли­зи­ро­вать этот объ­ект. Ина­че говоря, инициа­ли­зи­ро­вать корневой кон­текст мож­но и до вы­зо­ва setSource(), и по­сле это­го вы­зо­ва, но пер­вый спо­соб ра­бо­та­ет чи­ще.

По­следние штри­хи

По­следнее, что мы сде­ла­ем для то­го, что­бы наш вид­жет вы­гля­дел, как лю­бой дру­гой вид­жет Qt – уда­лим бе­лый фон, за­бот­ли­во соз­дан­ный для нас объ­ек­том QDeclarativeView (не за­бы­вай­те, что это по­то­мок QGraphicsView). Мы сде­ла­ем это так:

qmlView->setBackgroundRole(QPalette::Background);

По­сколь­ку наш вид­жет толь­ко по­ка­зы­ва­ет зна­чения, но не по­зво­ля­ет их вво­дить, нам сле­ду­ет до­ба­вить в ок­но про­грам­мы еще один вид­жет, пред­на­зна­чен­ный для управ­ления вид­же­том QML. Как и в ис­ход­ном при­ме­ре про­грам­мы QML, мы восполь­зу­ем­ся для это­го пол­зун­ком, толь­ко в на­шем слу­чае это бу­дет объ­ект клас­са QSlider. Наш вид­жет бу­дет реа­ги­ро­вать на сиг­нал sliderChanged() это­го объ­ек­та. Мож­но бы­ло бы до­ба­вить в класс Dial слот и свя­зать этот слот с сиг­на­лом sliderChanged() на­пря­мую; я остав­ляю вам это в ка­че­­ст­ве до­машнего за­дания.

В сле­дую­щий раз мы, на­конец, рас­смот­рим про­грам­му на чис­том QML (для вы­полнения ко­то­рой все рав­но требуется ути­ли­та Qt).

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