table of contentsread.pudn.com/downloads767/ebook/3045877/qt 5 blueprints.pdftable of contents qt 5...
TRANSCRIPT
TableofContentsQt5BlueprintsCreditsAbouttheAuthorAbouttheReviewerswww.PacktPub.comSupportfiles,eBooks,discountoffers,andmore
Whysubscribe?FreeaccessforPacktaccountholders
PrefaceWhatthisbookcoversWhatyouneedforthisbookWhothisbookisforConventionsReaderfeedbackCustomersupport
DownloadingtheexamplecodeErrataPiracyQuestions
1.CreatingYourFirstQtApplicationCreatinganewprojectChangingthelayoutofwidgetsUnderstandingthemechanismofsignalsandslotsConnectingtwosignalsCreatingaQtQuickapplicationConnectingC++slotstoQMLsignalsSummary
2.BuildingaBeautifulCross-platformClockCreatingabasicdigitalclockTweakingthedigitalclockSavingandrestoringsettingsBuildingontheUnixplatformsSummary
3.CookinganRSSReaderwithQtQuickUnderstandingmodelandviewParsingRSSFeedsbyXmlListModelTweakingthecategoriesUtilizingScrollViewAddingBusyIndicator
MakingaframelesswindowDebuggingQMLSummary
4.ControllingCameraandTakingPhotosAccessingthecamerainQtControllingthecameraDisplayingerrorsonthestatusbarPermanentwidgetsinthestatusbarUtilizingthemenubarUsingQFileDialogQMLcameraSummary
5.ExtendingPaintApplicationswithPluginsDrawingviaQPainterWritingstaticpluginsWritingdynamicpluginsMergingpluginsandmainprogramprojectsCreatingaC++pluginforQMLapplicationsSummary
6.GettingWiredandManagingDownloadsIntroducingQtnetworkprogrammingUtilizingQNetworkAccessManagerMakinguseoftheprogressbarWritingmultithreadedapplicationsManagingasystemnetworksessionSummary
7.ParsingJSONandXMLDocumentstoUseOnlineAPIsSettingupQtforAndroidParsingJSONresultsParsingXMLresultsBuildingQtapplicationsforAndroidParsingJSONinQMLSummary
8.EnablingYourQtApplicationtoSupportOtherLanguagesInternationalizationofQtapplicationsTranslatingQtWidgetsapplicationsDisambiguatingidenticaltextsChanginglanguagesdynamicallyTranslatingQtQuickapplicationsSummary
9.DeployingApplicationsonOtherDevicesReleasingQtapplicationsonWindowsCreatinganinstaller
PackagingQtapplicationsonLinuxDeployingQtapplicationsonAndroidSummary
10.Don'tPanicWhenYouEncounterTheseIssuesCommonlyencounteredissues
C++syntaxmistakesPointerandmemoryIncompatiblesharedlibrariesDoesn'trunonAndroid!
DebuggingQtapplicationsDebuggingQtQuickapplicationsUsefulresourcesSummary
Index
Chapter1.CreatingYourFirstQtApplicationGUIprogrammingisnotasdifficultasyouthink.Atleastit'snotwhenyoucometotheworldofQt.Thisbookwilltakeyouthroughthisworldandgiveyouaninsightintothisincrediblyamazingtoolkit.Itdoesn'tmatterwhetheryou'veheardofitornot,aslongasyouhaveessentialknowledgeofC++programming.
Inthischapter,wewillgetyoucomfortablewiththedevelopmentofQtapplications.Simpleapplicationsareusedasademonstrationforyoutocoverthefollowingtopics:
CreatinganewprojectChangingthelayoutofwidgetsUnderstandingthemechanismofsignalsandslotsConnectingtwosignalsCreatingaQtQuickapplicationConnectingC++slotstoQMLsignals
CreatinganewprojectIfyouhaven'tinstalledQt5,refertohttp://www.qt.io/downloadtoinstallthelatestversionofit.It'srecommendedthatyouinstalltheCommunityversion,whichistotallyfreeandcompliantwithGPL/LGPL.Typically,theinstallerwillinstallbothQtLibraryandQtCreatorforyou.Inthisbook,wewilluseQt5.4.0andQtCreator3.3.0.Laterversionsmayhaveslightdifferencesbuttheconceptremainsthesame.It'shighlyrecommendedthatyouinstallQtCreatorifyoudon'thaveitonyourcomputer,becauseallthetutorialsinthisbookarebasedonit.ItisalsotheofficialIDEforthedevelopmentofQtapplications.AlthoughyoumaybeabletodevelopQtapplicationswithotherIDEs,ittendstobemuchmorecomplex.Soifyou'reready,let'sgoforitbyperformingthefollowingsteps:
1. OpenQtCreator.2. NavigatetoFile|NewFileorProject.3. SelectQtWidgetsApplication.4. Entertheproject'snameandlocation.Inthiscase,theproject'snameis
layout_demo.
Youmaywishtofollowthewizardandkeepthedefaultvalues.Afterthisprocess,QtCreatorwillgeneratetheskeletonoftheprojectbasedonyourchoices.TheUIfilesareundertheFormsdirectory.Whenyoudouble-clickonaUIfile,QtCreatorwill
redirectyoutotheintegrateddesigner.ThemodeselectorshouldhaveDesignhighlighted,andthemainwindowshouldcontainseveralsub-windowstoletyoudesigntheuserinterface.Thisisexactlywhatwearegoingtodo.FormoredetailsaboutQtCreatorUI,refertohttp://doc.qt.io/qtcreator/creator-quick-tour.html.
Dragthreepushbuttonsfromthewidgetbox(widgetpalette)intotheframeofMainWindowinthecenter.ThedefaulttextdisplayedonthesebuttonsisPushButton,butyoucanchangethetextifyouwantbydouble-clickingonthebutton.Inthiscase,IchangedthebuttonstoHello,Hola,andBonjour,accordingly.Notethatthisoperationwon'taffecttheobjectNameproperty.Inordertokeepitneatandeasytofind,weneedtochangetheobjectNameproperty.Theright-handsideoftheUIcontainstwowindows.Theupper-rightsectionincludesObjectInspectorandthelower-rightsideincludesPropertyEditor.Justselectapushbutton;youcaneasilychangeobjectNameinPropertyEditor.Forthesakeofconvenience,Ichangedthesebuttons'objectNamepropertiestohelloButton,holaButton,andbonjourButtonrespectively.
TipIt'sagoodhabittouselowercaseforthefirstletterofobjectNameandanuppercaseletterforClassname.Thishelpsyourcodetobemorereadablebypeoplewhoarefamiliarwiththisconvention.
Okay,it'stimetoseewhatyouhavedonetotheuserinterfaceofyourfirstQtapplication.ClickonRunontheleft-handsidepanel.Itwillbuildtheprojectautomaticallyandthenrunit.It'samazingtoseethattheapplicationhastheexactsameinterfaceasthedesign,isn'tit?Ifeverythingisalright,theapplicationshouldappearsimilartowhatisshowninthefollowingscreenshot:
Youmaywanttolookatthesourcecodeandseewhathappenedthere.So,let'sgobacktothesourcecodebyreturningtotheEditmode.ClickontheEditbuttoninthemodeselector.Then,double-clickonmain.cppintheSourcesfolderoftheProjectstreeview.Thecodeformain.cppisshownasfollows:
#include"mainwindow.h"
#include<QApplication>
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
MainWindoww;
w.show();
returna.exec();
}
NoteTheQApplicationclassmanagestheGUIapplication'scontrolflowandthemainsettings.
Actually,youdon'tneedtoandyouprobablywon'tchangetoomuchinthisfile.Thefirstlineofthemainscopejustinitializestheapplicationsonauser'sdesktopandhandlessomeevents.Thenthereisalsoanobject,w,whichbelongstotheMainWindowclass.Asforthelastline,itensuresthattheapplicationwon'tterminateafterexecutionbutwillkeepinaneventloop,sothatitisabletorespondtoexternaleventssuchasmouseclicksandwindowstatechanges.
Lastbutnotleast,let'sseewhathappensduringtheinitializationoftheMainWindow
object,w.Itisthecontentofmainwindow.h,shownasfollows:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
};
#endif//MAINWINDOW_H
YoumayfeelabitsurprisedseeingaQ_OBJECTmacroifthisisyourfirsttimewritingaQtapplication.IntheQObjectdocumentation,itsays:
TheQ_OBJECTmacromustappearintheprivatesectionofaclassdefinitionthatdeclaresitsownsignalsandslotsorthatusesotherservicesprovidedbyQt'smeta-
objectsystem.
Well,thismeansthatQObjecthastobedeclaredifyou'regoingtouseQt'smeta-objectsystemand(or)itssignalsandslotsmechanism.Thesignalsandslots,whicharealmostthecoreofQt,willbeincludedlaterinthischapter.
Thereisaprivatemembernamedui,whichisapointeroftheMainWindowclassoftheUinamespace.DoyouremembertheUIfileweeditedbefore?WhatthemagicofQtdoesisthatitlinkstheUIfileandtheparentalsourcecode.WecanmanipulatetheUIthroughcodelinesaswellasdesignitinQtCreator'sintegrateddesigner.Finally,let'slookintotheconstructionfunctionofMainWindowinmainwindow.cpp:
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
}
MainWindow::~MainWindow()
{
deleteui;
}
Didyouseewheretheuserinterfacecomesfrom?It'sthemembersetupUifunctionofUi::MainWindowthatinitializesitandsetsitupforus.Youmaywanttocheckwhathappensifwechangethememberfunctiontosomethinglikethis:
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
ui->holaButton->setEnabled(false);
}
Whathappenedhere?TheHolabuttoncan'tbeclickedonbecausewedisabledit!Ithasthesameeffectiftheenabledboxisuncheckedinthedesignerinsteadofwritingastatementhere.Pleaseapplythischangebeforeheadingtothenexttopic,becausewedon'tneedadisabledpushbuttontodoanydemonstrationsinthischapter.
ChangingthelayoutofwidgetsYoualreadyknowhowtoaddandmovewidgetsintheDesignmode.Now,weneedtomaketheUIneatandtidy.I'llshowyouhowtodothisstepbystep.
AquickwaytodeleteawidgetistoselectitandpresstheDeletebutton.Meanwhile,somewidgets,suchasthemenubar,statusbar,andtoolbarcan'tbeselected,sowehavetoright-clickontheminObjectInspectoranddeletethem.Sincetheyareuselessinthisexample,it'ssafetoremovethemandwecandothisforgood.
Okay,let'sunderstandwhatneedstobedoneaftertheremoval.Youmaywanttokeepallthesepushbuttonsonthesamehorizontalaxis.Todothis,performthefollowingsteps:
1. SelectallthepushbuttonseitherbyclickingonthemonebyonewhilekeepingtheCtrlkeypressedorjustdrawinganenclosingrectanglecontainingallthebuttons.
2. Right-clickandselectLayout|LayOutHorizontally,ThekeyboardshortcutforthisisCtrl+H.
3. ResizethehorizontallayoutandadjustitslayoutSpacingbyselectingitanddragginganyofthepointsaroundtheselectionboxuntilitfitsbest.
Hmm…!YoumayhavenoticedthatthetextoftheBonjourbuttonislongerthantheothertwobuttons,anditshouldbewiderthantheothers.Howdoyoudothis?Youcanchangethepropertyofthehorizontallayoutobject'slayoutStretchpropertyinPropertyEditor.Thisvalueindicatesthestretchfactorsofthewidgetsinsidethehorizontallayout.Theywouldbelaidoutinproportion.Changeitto3,3,4,andthereyouare.Thestretchedsizedefinitelywon'tbesmallerthantheminimumsizehint.Thisishowthezerofactorworkswhenthereisanonzeronaturalnumber,whichmeansthatyouneedtokeeptheminimumsizeinsteadofgettinganerrorwithazerodivisor.
Now,dragPlainTextEditjustbelow,andnotinside,thehorizontallayout.Obviously,itwouldbeneaterifwecouldextendtheplaintextedit'swidth.However,wedon'thavetodothismanually.Infact,wecouldchangethelayoutoftheparent,MainWindow.That'sit!Right-clickonMainWindow,andthennavigatetoLayout|LayOutVertically.Wow!AllthechildrenwidgetsareautomaticallyextendedtotheinnerboundaryofMainWindow;theyarekeptinaverticalorder.You'llalsofindLayoutsettingsinthecentralWidgetproperty,whichisexactlythesamethingastheprevioushorizontallayout.
Thelastthingtomakethisapplicationhalfwaydecentistochangethetitleofthewindow.MainWindowisnotthetitleyouwant,right?ClickonMainWindowinthe
objecttree.Then,scrolldownitspropertiestofindwindowTitle.Nameitwhateveryouwant.Inthisexample,IchangedittoGreeting.Now,runtheapplicationagainandyouwillseeitlookslikewhatisshowninthefollowingscreenshot:
UnderstandingthemechanismofsignalsandslotsItisreallyimportanttokeepyourcuriosityandtoexplorewhatonearththesepropertiesdo.However,pleaseremembertorevertthechangesyoumadetotheapp,asweareabouttoenterthecorepartofQt,thatis,signalsandslots.
NoteSignalsandslotsareusedforcommunicationbetweenobjects.ThesignalsandslotsmechanismisacentralfeatureofQtandprobablythepartthatdiffersthemostfromthefeaturesprovidedbyotherframeworks.
HaveyoueverwonderedwhyawindowclosesaftertheClosebuttonisclickedon?DeveloperswhoarefamiliarwithothertoolkitswouldsaythattheClosebuttonbeingclickedonisanevent,andthiseventisboundwithacallbackfunctionthatisresponsibleforclosingthewindow.Well,it'snotquitethesameintheworldofQt.SinceQtusesamechanismcalledsignalsandslots,itmakesthecallbackfunctionweaklycoupledtotheevent.Also,weusuallyusethetermssignalandslotinQt.Asignalisemittedwhenaparticulareventoccurs.Aslotisafunctionthatiscalledinresponsetoaparticularsignal.Thefollowingsimpleandschematicdiagramhelpsyouunderstandtherelationbetweensignals,events,andslots:
Qthastonsofpredefinedsignalsandslots,whichcoveritsgeneralpurposes.However,it'sindeedcommonplacetoaddyourownslotstohandlethetargetsignals.Youmayalsobeinterestedinsubclassingwidgetsandwritingyourownsignals,whichwillbecoveredlater.Themechanismofsignalsandslotswasdesignedtobetype-safebecauseofitsrequirementofthelistofthesamearguments.Infact,theslotmayhaveashorterargumentslistthanthesignalsinceitcanignoretheextras.Youcanhaveasmanyargumentsasyouwant.Thisenablesyoutoforgetaboutthe
wildcardvoid*typeinCandothertoolkits.
SinceQt5,thismechanismisevensaferbecausewecanuseanewsyntaxofsignalsandslotstodealwiththeconnections.Aconversionofapieceofcodeisdemonstratedhere.Let'sseewhatatypicalconnectstatementinoldstyleis:
connect(sender,SIGNAL(textChanged(QString)),receiver,
SLOT(updateText(QString)));
Thiscanberewritteninanewsyntaxstyle:
connect(sender,&Sender::textChanged,receiver,
&Receiver::updateText);
Inthetraditionalwayofwritingcode,theverificationofsignalsandslotsonlyhappensatruntime.Inthenewstyle,thecompilercandetectthemismatchesinthetypesofargumentsandtheexistenceofsignalsandslotsatcompiletime.
NoteAslongasitispossible,allconnectstatementsarewritteninthenewsyntaxstyleinthisbook.
Now,let'sgetbacktoourapplication.I'llshowyouhowtodisplaysomewordsinaplaintexteditwhentheHellobuttonisclickedon.Firstofall,weneedtocreateaslotsinceQthasalreadypredefinedtheclickedsignalfortheQPushButtonclass.Editmainwindow.handaddaslotdeclaration:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
privateslots:
voiddisplayHello();
private:
Ui::MainWindow*ui;
};
#endif//MAINWINDOW_H
Asyoucansee,it'stheslotskeywordthatdistinguishesslotsfromordinaryfunctions.Ideclareditprivatetorestrictaccesspermission.Youhavetodeclareitapublicslotifyouneedtoinvokeitinanobjectfromotherclasses.Afterthisdeclaration,wehavetoimplementitinthemainwindow.cppfile.TheimplementationofthedisplayHelloslotiswrittenasfollows:
voidMainWindow::displayHello()
{
ui->plainTextEdit->appendPlainText(QString("Hello"));
}
ItsimplycallsamemberfunctionoftheplaintexteditinordertoaddaHelloQStringtoit.QStringisacoreclassthatQthasintroduced.ItprovidesaUnicodecharacterstring,whichefficientlysolvestheinternationalizationissue.It'salsoconvenienttoconvertaQStringclasstostd::stringandviceversa.Besides,justliketheotherQObjectclasses,QStringusesanimplicitsharingmechanismtoreducememoryusageandavoidneedlesscopying.Ifyoudon'twanttogetconcernedaboutthescenesshowninthefollowingcode,justtakeQStringasanimprovedversionofstd::string.Now,weneedtoconnectthisslottothesignalthattheHellopushbuttonwillemit:
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
connect(ui->helloButton,&QPushButton::clicked,this,
&MainWindow::displayHello);
}
WhatIdidisaddaconnectstatementtotheconstructorofMainWindow.Infact,wecanconnectsignalsandslotsanywhereandatanytime.However,theconnectiononlyexistsafterthislinegetsexecuted.So,it'sacommonpracticetohavelotsofconnectstatementsintheconstructionfunctionsinsteadofspreadingthemout.Forabetterunderstanding,runyourapplicationandseewhathappenswhenyouclickontheHellobutton.Everytimeyouclick,aHellotextwillbeappendedtotheplaintext
edit.ThefollowingscreenshotiswhathappenedafterweclickedontheHellobuttonthreetimes:
Gettingconfused?Letmewalkyouthroughthis.WhenyouclickedontheHellobutton,itemittedaclickedsignal.Then,thecodeinsidethedisplayHelloslotgotexecuted,becauseweconnectedtheclickedsignaloftheHellobuttontothedisplayHelloslotofMainWindow.WhatthedisplayHelloslotdidisthatitsimplyappendedHellototheplaintextedit.
Itmaytakeyousometimetofullyunderstandthemechanismofsignalsandslots.Justtakeyourtime.I'llshowyouanotherexampleofhowtodisconnectsuchaconnectionafterweclickedontheHolabutton.Similarly,addadeclarationoftheslottotheheaderfileanddefineitinthesourcefile.Ipastedthecontentofthemainwindow.hheaderfile,asfollows:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
privateslots:
voiddisplayHello();
voidonHolaClicked();
private:
Ui::MainWindow*ui;
};
#endif//MAINWINDOW_H
It'sonlydeclaringaonHolaClickedslotthatdifferedfromtheoriginal.Here'sthecontentofthesourcefile:
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
connect(ui->helloButton,&QPushButton::clicked,this,
&MainWindow::displayHello);
connect(ui->holaButton,&QPushButton::clicked,this,
&MainWindow::onHolaClicked);
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::displayHello()
{
ui->plainTextEdit->appendPlainText(QString("Hello"));
}
voidMainWindow::onHolaClicked()
{
ui->plainTextEdit->appendPlainText(QString("Hola"));
disconnect(ui->helloButton,&QPushButton::clicked,this,
&MainWindow::displayHello);
}
You'llfindthattheHellobuttonnolongerworksafteryouclickedontheHolabutton.ThisisbecauseintheonHolaClickedslot,wejustdisconnectedthebindingbetweentheclickedsignalofhelloButtonandthedisplayHelloslotofMainWindow.Actually,disconnecthassomeoverloadedfunctionsandcanbeusedinamoredestructiveway.Forexample,youmaywanttodisconnectallconnectionsbetweenaspecific
signalsenderandaspecificreceiver:
disconnect(ui->helloButton,0,this,0);
Ifyouwanttodisconnectalltheslotsassociatedwithasignal,sinceasignalcanbeconnectedtoasmanyslotsasyouwish,thecodecanbewrittenlikethis:
disconnect(ui->helloButton,&QPushButton::clicked,0,0);
Wecanalsodisconnectallthesignalsinanobject,whateverslotstheymightbeconnectedto.ThefollowingcodewilldisconnectallthesignalsinhelloButton,whichofcourseincludestheclickedsignal:
disconnect(ui->helloButton,0,0,0);
Justlikeasignal,aslotcanbeconnectedtoasmanysignalsasyouwant.However,there'snosuchfunctiontodisconnectaspecificslotfromallthesignals.
TipAlwaysrememberthesignalsandslotsthatyouhaveconnected.
Apartfromthenewsyntaxfortraditionalconnectionsofsignalsandslots,Qt5hasofferedanewwaytosimplifysuchabindingprocesswithC++11lambdaexpressions.Asyoumayhavenoticed,it'skindoftedioustodeclareaslotintheheaderfile,defineitinthesourcecodefile,andthenconnectittoasignal.It'sworthwhileiftheslothasalotofstatements,otherwiseitbecomestimeconsumingandincreasesthecomplexity.Beforewegoanyfurther,weneedtoturnonC++11supportonQt.Edittheprofile(layout_demo.proinmyexample)andaddthefollowinglinetoit:
CONFIG+=c++11
NoteNotethatsomeoldcompilersdon'tsupportC++11.Ifthishappens,upgradeyourcompiler.
Now,youneedtonavigatetoBuild|Runqmaketoreconfiguretheprojectproperly.
Ifeverythingisokay,wecangobacktoeditingthemainwindow.cppfile.Thisway,thereisnoneedtodeclareaslotanddefineandconnectit.JustaddaconnectstatementtotheconstructionfunctionofMainWindow:
connect(ui->bonjourButton,&QPushButton::clicked,[this](){
ui->plainTextEdit->appendPlainText(QString("Bonjour"));
});
It'sverystraightforward,isn'tit?Thethirdargumentisalambdaexpression,whichwasaddedtoC++sinceC++11.
NoteFormoredetailsaboutlambdaexpression,visithttp://en.cppreference.com/w/cpp/language/lambda.
Thispairofsignalandslotconnectionisdoneifyoudon'tdoneedtotodisconnectsuchaconnection.However,ifyouneed,youhavetosavethisconnection,whichisaQMetaObject::Connectiontype.Inordertodisconnectthisconnectionelsewhere,itwouldbebettertodeclareitasavariableofMainWindow.Sotheheaderfilebecomesasfollows:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
privateslots:
voiddisplayHello();
voidonHolaClicked();
private:
Ui::MainWindow*ui;
QMetaObject::ConnectionbonjourConnection;
};
#endif//MAINWINDOW_H
Here,IdeclaredbonjourConnectionasanobjectofQMetaObject::Connectionsothatwecansavetheconnectiondealingwithanunnamedslot.Similarly,thedisconnectionhappensinonHolaClicked,sotherewon'tbeanynewBonjourtextonscreenafterweclickontheHolabutton.Hereisthecontentofmainwindow.cpp:
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
connect(ui->helloButton,&QPushButton::clicked,this,
&MainWindow::displayHello);
connect(ui->holaButton,&QPushButton::clicked,this,
&MainWindow::onHolaClicked);
bonjourConnection=connect(ui->bonjourButton,
&QPushButton::clicked,[this](){
ui->plainTextEdit-
>appendPlainText(QString("Bonjour"));
});
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::displayHello()
{
ui->plainTextEdit->appendPlainText(QString("Hello"));
}
voidMainWindow::onHolaClicked()
{
ui->plainTextEdit->appendPlainText(QString("Hola"));
disconnect(ui->helloButton,&QPushButton::clicked,this,
&MainWindow::displayHello);
disconnect(bonjourConnection);
}
TipDownloadingtheexamplecode
Youcandownloadtheexamplecodefilesfromyouraccountathttp://www.packtpub.comforallthePacktPublishingbooksyouhavepurchased.Ifyoupurchasedthisbookelsewhere,youcanvisithttp://www.packtpub.com/supportandregistertohavethefilese-maileddirectlytoyou.
Thisisindeedanothernewusageofdisconnect.IttakesinaQMetaObject::Connectionobjectastheonlyargument.You'llthankthisnewoverloadedfunctionifyou'regoingtousethelambdaexpressionasaslot.
ConnectingtwosignalsDuetotheweakcouplingsoftheQtsignalsandslotmechanisms,itisviabletobindsignalstoeachother.Itmaysoundconfusing,soletmedrawadiagramtomakeitclear:
Whenaneventtriggersaspecificsignal,thisemittedsignalcouldbeanotherevent,whichwillemitanotherspecificsignal.Itisnotaverycommonpractice,butittendstobeusefulwhenyoudealwithsomecomplexsignalsandslotconnectionnetworks,especiallywhentonsofeventsleadtotheemissionofonlyafewsignals.Althoughitdefinitelyincreasesthecomplexityoftheproject,bindingthesesignalscouldsimplifythecodealot.AppendthefollowingstatementtotheconstructionfunctionofMainWindow:
connect(ui->bonjourButton,&QPushButton::clicked,ui-
>helloButton,&QPushButton::clicked);
You'llgettwolinesinaplaintexteditafteryouclickontheBonjourbutton.ThefirstlineisBonjourandthesecondoneisHello.Apparently,thisisbecausewecoupledtheclickedsignaloftheBonjourbuttonwiththeclickedsignaloftheHellobutton.Theclickedsignalofthelatterhasalreadybeencoupledwithaslot,whichresultsinthenewtextline,Hello.Infact,ithasthesameeffectasthefollowingstatement:
connect(ui->bonjourButton,&QPushButton::clicked,[this](){
emitui->helloButton->clicked();
});
Basically,connectingtwosignalsisasimplifiedversionofconnectingasignalandaslot,whiletheslotismeanttoemitanothersignal.Asforpriority,theslot(s)ofthelattersignalwillbehandledwhentheeventloopisreturnedtotheobject.
However,itisimpossibletoconnecttwoslotsbecausethemechanismrequiresasignalwhileaslotisconsideredareceiverinsteadofasender.Therefore,ifyouwanttosimplifytheconnection,justwraptheseslotsasoneslot,whichcanbeusedfor
connections.
CreatingaQtQuickapplicationWealreadycoveredhowtocreateaQt(C++)application.HowaboutgivingthenewlyintroducedQtQuickapplicationdevelopmentatry?QtQuickwasintroducedinQt4.8anditisnowbecomingmatureinQt5.BecausetheQMLfileisusuallyplatform-independent,itenablesyoutodevelopanapplicationformultipletargets,includingmobileoperatingsystemswiththesamecode.
Inthischapter,I'llshowyouhowtocreateasimpleQtQuickapplicationbasedonQtQuickControls1.2,asfollows:
1. CreateanewprojectnamedHelloQML.2. SelectQtQuickApplicationinsteadofQtWidgetsApplication,whichwe
chosepreviously.3. SelectQtQuickControls1.2whenthewizardnavigatesyoutoSelectQt
QuickComponentsSet.
QtQuickControlshasbeenintroducedsinceQt5.1andishighlyrecommendedbecauseitenablesyoutobuildacompleteandnativeuserinterface.Youcanalsocontrolthetop-levelwindowpropertiesfromQML.GettingconfusedbyQMLandQtQuick?
NoteQMLisauserinterfacespecificationandprogramminglanguage.Itallowsdevelopersanddesignersaliketocreatehighlyperformant,fluidlyanimated,andvisuallyappealingapplications.QMLoffersahighlyreadable,declarative,JSON-likesyntaxwithsupportforimperativeJavaScriptexpressionscombinedwithdynamicpropertybindings.
WhileQtQuickisthestandardlibraryforQML,itsoundsliketherelationbetweenSTLandC++.ThedifferenceisthatQMLisdedicatedtouserinterfacedesignandQtQuickincludesalotofvisualtypes,animations,andsoon.Beforewegoanyfurther,IwanttoinformyouthatQMLisdifferentfromC++butsimilartoJavaScriptandJSON.
Editthemain.qmlfileundertherootoftheResourcesfile,qml.qrc,whichQtCreatorhasgeneratedforournewQtQuickproject.Let'sseehowthecodeshouldbe:
importQtQuick2.3
importQtQuick.Controls1.2
ApplicationWindow{
visible:true
width:640
height:480
title:qsTr("HelloQML")
menuBar:MenuBar{
Menu{
title:qsTr("File")
MenuItem{
text:qsTr("Exit")
shortcut:"Ctrl+Q"
onTriggered:Qt.quit()
}
}
}
Text{
id:hw
text:qsTr("HelloWorld")
font.capitalization:Font.AllUppercase
anchors.centerIn:parent
}
Label{
anchors{bottom:hw.top;bottomMargin:5;
horizontalCenter:hw.horizontalCenter}
text:qsTr("HelloQtQuick")
}
}
IfyouhaveevertouchedJavaorPython,thefirsttwolineswon'tbetoounfamiliartoyou.ItsimplyimportsQtQuickandQtQuickControls,andthenumberfollowingistheversionofthelibrary.Youmayneedtochangetheversionifthereisanewerlibrary.ImportingotherlibrariesisacommonpracticewhendevelopingQtQuickapplications.
ThebodyofthisQMLsourcefileisactuallyintheJSONstyle,whichenablesyoutounderstandthehierarchyoftheuserinterfacethroughthecode.Here,therootitemisApplicationWindow,whichisbasicallythesamethingasMainWindowintheprevioustopics,andweusebracestoenclosethestatementsjustlikeinaJSONfile.AlthoughyoucoulduseasemicolontomarkanendingofastatementjustlikewedoinC++,thereisnoneedtodothis.Asyoucansee,thepropertydefinitionneedsacolonifit'sasingle-linestatementandenclosingbracesifitcontainsmorethanonesubproperty.
ThestatementsarekindofselfexplanatoryandtheyaresimilartothepropertiesthatwesawintheQtWidgetsapplication.AqsTrfunctionisusedforinternationalizationandlocalization.StringsmarkedbyqsTrcouldbetranslatedbyQtLinguist.In
additiontothis,youdon'tneedtocareaboutQStringandstd::stringanymore.AllthestringsinQMLareencodedinthesamecodingastheQMLfileandtheQMLfileiscreatedinUTF-8bydefault.
AsforthesignalsandslotsmechanisminQtQuick,it'seasyifyouonlyuseQMLtowritethecallbackfunctiontothecorrespondingslot.Here,weexecuteQt.quit()insidetheonTriggeredslotofMenuItem.It'sviabletoconnectthesignalofaQMLitemtoaC++object'sslot,whichI'llintroducelater.
WhenyourunthisapplicationinWindows,youcanbarelyfindthedifferencebetweentheTextitemandtheLabelitem.However,onsomeplatforms,orwhenyouchangethesystemfontand/oritscolor,you'llfindthatLabelfollowsthefontandthecolorschemeofthesystem,whileTextdoesn't.AlthoughyoucanusethepropertiesofTexttocustomizetheappearanceofLabel,itwouldbebettertousethesystemsettingstokeepthelooksoftheapplicationnative.Well,ifyourunthisapplicationrightnow,itwillappearsimilartowhatisshowninthefollowingscreenshot:
BecausethereisnoseparateUIfilefortheQtQuickapplications,onlyaQMLfile,weusetheanchorspropertytopositiontheitems,andanchors.centerInwillpositiontheiteminthecenteroftheparent.ThereisanintegratedQtQuickDesignerinQt
Creator,whichcouldhelpyoudesigntheuserinterfaceofaQtQuickapplication.Ifyouneedit,justnavigatetoDesignmodewhenyou'reeditingaQMLfile.However,IsuggestyoustayinEditmodetounderstandthemeaningofeachstatement.
ConnectingC++slotstoQMLsignalsTheseparationoftheuserinterfaceandbackendallowsustoconnectC++slotstotheQMLsignals.Althoughit'spossibletowriteprocessingfunctionsinQMLandmanipulateinterfaceitemsinC++,itviolatestheprincipleoftheseparation.Therefore,youmaywanttoknowhowtoconnectaC++slottoaQMLsignalatfirst.AsforconnectingaQMLslottoaC++signal,I'llintroducethatlaterinthisbook.
Inordertodemonstratethis,weneedtocreateaC++classinthefirstplacebyright-clickingontheprojectintheProjectspanelandselectingAddNew….Then,clickonC++Classinthepop-upwindow.ThenewlycreatedclassshouldatleastinheritfromQObjectbychoosingQObjectasitsbaseclass.ThisisbecauseaplainC++classcan'tincludeQt'sslotsorsignals.Theheaderfile'scontentisdisplayedasfollows:
#ifndefPROCESSOR_H
#definePROCESSOR_H
#include<QObject>
classProcessor:publicQObject
{
Q_OBJECT
public:
explicitProcessor(QObject*parent=0);
publicslots:
voidonMenuClicked(constQString&);
};
#endif//PROCESSOR_H
Here'sthecontentofthesourcefile:
#include<QDebug>
#include"processor.h"
Processor::Processor(QObject*parent):
QObject(parent)
{
}
voidProcessor::onMenuClicked(constQString&str)
{
qDebug()<<str;
}
TheC++fileisthesameastheonewedealtwithintheprevioustopics.TheonMenuClickedslotIdefinedissimplytooutputthestringthatpassesthroughthesignal.NotethatyouhavetoincludeQDebugifyouwanttousethebuilt-infunctionsofqDebug,qWarning,qCritical,andsoon.
Theslotisprepared,soweneedtoaddasignaltotheQMLfile.TheQMLfileischangedtothefollowingcode:
importQtQuick2.3
importQtQuick.Controls1.2
ApplicationWindow{
id:window
visible:true
width:640
height:480
title:qsTr("HelloQML")
signalmenuClicked(stringstr)
menuBar:MenuBar{
Menu{
title:qsTr("File")
MenuItem{
text:qsTr("Exit")
shortcut:"Ctrl+Q"
onTriggered:Qt.quit()
}
MenuItem{
text:qsTr("ClickMe")
onTriggered:window.menuClicked(text)
}
}
}
Text{
id:hw
text:qsTr("HelloWorld")
font.capitalization:Font.AllUppercase
anchors.centerIn:parent
}
Label{
anchors{bottom:hw.top;bottomMargin:5;
horizontalCenter:hw.horizontalCenter}
text:qsTr("HelloQtQuick")
}
}
Asyoucansee,IspecifiedtheIDoftherootApplicationWindowitemtowindowanddeclaredasignalnamedmenuClicked.Inadditiontothis,thereisanotherMenuIteminthemenufile.ItemitsthemenuClickedsignalofwindow,usingitstextasthe
parameter.
Now,let'sconnecttheslotintheC++filetothisnewlycreatedQMLsignal.Editthemain.cppfile.
#include<QApplication>
#include<QQmlApplicationEngine>
#include"processor.h"
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
QQmlApplicationEngineengine;
engine.load(QUrl(QStringLiteral("qrc:///main.qml")));
QObject*firstRootItem=engine.rootObjects().first();
ProcessormyProcessor;
QObject::connect(firstRootItem,
SIGNAL(menuClicked(QString)),&myProcessor,
SLOT(onMenuClicked(QString)));
returnapp.exec();
}
TheitemintheQMLfileisaccessedasQObjectinC++anditcouldbecasttoQQuickItem.Fornow,weonlyneedtoconnectitssignal,soQObjectwilldo.
YoumaynoticethatIusedtheold-stylesyntaxoftheconnectstatement.ThisisbecauseQMLisdynamicandtheC++compilercan'tdetecttheexistenceofthesignalintheQMLfile.SincethingsinQMLarecheckedatruntime,itdoesn'tmakesensetousetheoldsyntaxhere.
WhenyourunthisapplicationandnavigatetoFile|ClickMeinthemenubar,you'llseeApplicationOutputinQtCreator:
"ClickMe"
Let'sreviewthisprocessagain.TriggeringtheClickMemenuitemresultedintheemissionofthewindow'ssignalmenuClicked.ThissignalpassedthetextofMenuItem,whichisClickMe,totheslotinC++classProcessor,andtheprocessormyProcessorslotonMenuClickedprintedthestringtotheApplicationOutputpanel.
SummaryInthischapter,welearnedthefundamentalsofQt,whichincludedstepsforhowtocreateaQtapplication.Then,wehadawalk-throughofbothQtWidgetsandQtQuick,andhowtochangethelayout.Finally,weroundedoffbycoveringanimportantconceptaboutthemechanismofsignalsandslots.
Inthenextchapter,wewillhaveachancetoputthisknowledgeintopracticeandgetstartedonbuildingareal-world,andofcoursecross-platform,Qtapplication.
Chapter2.BuildingaBeautifulCross-platformClockInthischapter,youwilllearnthatQtisagreattooltobuildcross-platformapplications.AQt/C++clockexampleisusedasademonstrationhere.Thetopicscoveredinthischapter,whicharelistedhere,areessentialforanyreal-worldapplications.Theseareasfollows:
CreatingabasicdigitalclockTweakingthedigitalclockSavingandrestoringsettingsBuildingonUnixplatforms
CreatingabasicdigitalclockIt'stimetocreateanewproject,sowewillcreateaQtWidgetsapplicationnamedFancy_Clock.
NoteWewon'tutilizeanyQtQuickknowledgeinthischapter.
Now,changethewindowtitletoFancyClockoranyothernamethatyoulike.Then,themainwindowUIneedstobetailoredbecausetheclockisdisplayedatthetopofthedesktop.Themenubar,statusbar,andtoolbarareallremoved.Afterthat,weneedtodraganLCDNumberwidgetintocentralWidget.Next,changethelayoutofMainWindowtoLayOutHorizontallyinordertoautoresizethesubwidget.ThelastthingthatneedstobedonetotheUIfileistochangeframeShapetoNoFrameundertheQFramecolumninthepropertyoflcdNumber.Ifyou'vedonethisright,you'llgetaprototypeofadigitalclock,asshownhere:
InordertoupdatetheLCDnumberdisplayrepeatedly,wehavetomakeuseoftheQTimerclasstosetupatimerthatemitsasignalrepetitively.Inadditiontothis,weneedtocreateaslottoreceivethesignalandtoupdatetheLCDnumberdisplaytothecurrenttime.Thus,theQTimeclassisalsoneeded.ThisishowtheheaderfileofMainWindowmainwindow.hwilllooknow:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
privateslots:
voidupdateTime();
};
#endif//MAINWINDOW_H
Asyoucansee,theonlymodificationmadehereisthedeclarationofaprivateupdateTimeslot.Asusual,we'resupposedtodefinethisslotinmainwindow.cpp,
whosecontentispastedhere.NotethatweneedtoincludeQTimerandQTime.
#include<QTimer>
#include<QTime>
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
QTimer*timer=newQTimer(this);
connect(timer,&QTimer::timeout,this,
&MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::updateTime()
{
QTimecurrentTime=QTime::currentTime();
QStringcurrentTimeText=currentTime.toString("hh:mm");
if(currentTime.second()%2==0){
currentTimeText[2]='';
}
ui->lcdNumber->display(currentTimeText);
}
InsidetheupdateTimeslot,theQTimeclassisusedtodealwiththetime,thatis,theclock.Thisclasscanprovideaccuracyofupto1millisecond,iftheunderlyingoperatingsystemsupportsit.However,QTimehasnothingtodowiththetimezoneordaylightsavingtime.Itis,atleast,sufficientforourlittleclock.ThecurrentTime()functionisastaticpublicfunction,whichisusedtocreateaQTimeobjectthatcontainsthesystem'slocaltime.
AsforthesecondlineoftheupdateTimefunction,weusedthetoStringfunctionprovidedbyQTimetoconvertthetimetoastring,andthensaveditincurrentTimeText.TheargumentsthatarepassedtotoStringareintheformatofthetimestring.ThefulllistofexpressionscanbeobtainedfromQtReferenceDocumentation.Thecoloninthemiddleoftheclockshouldbeflashing,justasinthecaseofarealdigitalclock.Hence,weusedanifstatementtocontrolthis.Thecolonwillvanishwhenthesecond'svalueiseven,anditwillreappearwhenthesecond's
valueisodd.Here,insidetheifblock,weusedthe[2]operatortogetamodifiablereferenceofthethirdcharacterbecausethisistheonlywaytododirectmodificationstoacharacterinsideastring.Here,thecountingofthecurrentTimeTextstringstartsfrom0.Meanwhile,theat()functionofQStringreturnsaconstantcharacter,whichyouhavenorighttochange.Atlast,thisfunctionwillletlcdNumberdisplaythetimestring.Now,let'sgetbacktotheconstructorofMainWindow.AftertheinitializationoftheUI,thefirstthingitdoesistocreateaQTimerobject.Whycan'tweusealocalvariable?TheanswertothatquestionisbecausethelocalvariableswillbedestroyedaftertheconstructionofMainWindow.Ifthetimerhasgone,there'snowaytotriggerupdateTimerepetitively.Wedon'tuseamembervariablebecausethereisnoneedtoperformthedeclarationworkintheheaderfile,sincewewon'tusethistimerelsewhere.
TheQTimerclassisusedtocreatearepetitiveandsingle-shottimer.Itwillemitthetimeoutsignalatconstantintervalsafterstartiscalled.Here,wecreateonetimerandconnectthetimeoutsignaltotheupdateTimeslotsothatupdateTimeiscalledeverysecond.
ThereisanotherimportantaspectinQtcalledparent-childmechanism.Althoughit'snotaswell-knownassignalsandslots,itplaysacrucialroleinthedevelopmentoftheQtapplications.Basicallyspeaking,whenwecreateanQObjectchildwithaparentorexplicitlysetaparentbycallingsetParent,theparentwilladdthisQObjectchildtoitslistofchildren.Then,whentheparentisdeleted,it'llgothroughitslistofchildrenanddeleteeachchild.Inmostcases,especiallyinthedesignofaUI,theparent-childrelationshipissetupimplicitly.Theparentwidgetorlayoutautomaticallybecomestheparentobjecttoitschildrenwidgetsorlayouts.Inothercases,wehavetoexplicitlysettheparentforaQObjectchildsothattheparentcantakeoveritsownershipandmanagethereleaseofitsmemory.Hence,wepasstheQObjectparent,whichisthis,aMainWindowclasstotheconstructorofQTimer.ThisensuresthatQTimerwillbedeletedafterMainWindowisdeleted.That'swhywedon'thavetoexplicitlywritethedeletestatementsinthedestructor.
Attheendoftheconstructor,weneedtocallupdateTimeexplicitly,whichwillallowtheclocktodisplaythecurrenttime.Ifwedon'tdothis,theapplicationwilldisplayazeroforaseconduntilthetimeoutsignalisemittedbytimer.Now,runyourapplication;itwillbesimilartothefollowingscreenshot:
TweakingthedigitalclockIt'stimetomakethisbasicdigitalclocklookmorebeautiful.Let'saddsomethinglikeatransparentbackground,whichsitsontopoftheframelesswindow.Usingatransparentbackgroundcandeliverafantasticvisualeffect.Whiletheframelesswindowhideswindowdecorations,includingaborderandthetitlebar,adesktopwidget,suchasaclock,shouldbeframelessanddisplayedontopofthedesktop.
Tomakeourclocktranslucent,simplyaddthefollowinglinetotheconstructorofMainWindow:
setAttribute(Qt::WA_TranslucentBackground);
TheeffectoftheWA_TranslucentBackgroundattributedependsonthecompositionmanagersontheX11platforms.
Awidgetmayhavelotsofattributes,andthisfunctionisusedtoswitchonorswitchoffaspecifiedattribute.It'sturnedonbydefault.YouneedtopassafalseBooleanasthesecondargumenttodisableanattribute.ThefulllistofQt::WidgetAttributecanbefoundintheQtReferenceDocumentation.
Now,addthefollowinglinetotheconstructoraswell,whichwillmaketheclocklookframelessandmakeitstayontopofthedesktop:
setWindowFlags(Qt::WindowStaysOnTopHint|
Qt::FramelessWindowHint);
Similarly,Qt::WindowFlagsisusedtodefinethetypeofwidget.Itcontrolsthebehaviorofthewidget,ratherthanofitsproperties.Thus,twohintsaregiven:oneistostayontopandtheotheristobeframeless.Ifyouwanttopreserveoldflagswhilesettingnewones,youneedtoaddthemtothecombination.
setWindowFlags(Qt::WindowStaysOnTopHint|
Qt::FramelessWindowHint|windowFlags());
Here,thewindowFlagsfunctionisusedtoretrievethewindowflags.OnethingyoumaybeinterestedtoknowisthatsetWindowFlagswillresultintheinvisibilityofthewidgetaftertheshowfunction.So,youcaneithercallsetWindowFlagsbeforetheshowfunctionofthewindoworwidgetorcallshowagainaftersetWindowFlags.
Afterthemodificationtotheconstructor,thisishowtheclockisexpectedtolook:
Thereisausefultrickthatyoucanusetohidetheclockfromthetaskbar.Ofcourse,aclockdoesn'tneedtobedisplayedamongtheapplicationsinataskbar.YoushouldneversetaflagsuchasQt::ToolorQt::ToolTipalonetoachievethisbecausethiswillcausetheexitbehavioroftheapplicationtobeabnormal.Thistrickisevensimpler;hereisthecodeofmain.cpp:
#include"mainwindow.h"
#include<QApplication>
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
QWidgetwid;
MainWindoww(&wid);
w.show();
returna.exec();
}
TheprecedingcodemakesourMainWindowwobjectachildofQWidgetwid.Thechildwidgetswon'tdisplayonthetaskbarbecausethereshouldbeonlyonetopparentwidget.Meanwhile,ourparentwidget,wid,doesn'tevenshow.It'stricky,butit'stheonlyonethatdoesthetrickwithoutbreakinganyotherlogic.
Well,anewproblemhasjustsurfaced.TheclockisunabletomoveandtheonlywaytocloseitisbystoppingitthroughtheQtCreator'spanelorthroughakeyboard
shortcut.Thisisbecausewedeclareditasaframelesswindow,whichledtoaninabilitytocontrolitviaawindowmanager.Sincethereisnowaytointeractwithit,it'simpossibletocloseitbyitself.Hence,thesolutiontothisproblemistowriteourownfunctionstomoveandclosetheclock.
Closingthisapplicationmaybemoreurgent.Let'sseehowtoreimplementsomefunctionstoachievethisgoal.First,weneedtodeclareanewshowContextMenuslottodisplayacontextmenuandreimplementmouseReleaseEvent.Thefollowingcodeshowsthecontentofmainwindow.h:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
privateslots:
voidupdateTime();
voidshowContextMenu(constQPoint&pos);
protected:
voidmouseReleaseEvent(QMouseEvent*);
};
#endif//MAINWINDOW_H
Therearetwonewclassesdefinedintheprecedingcode:QPointandQMouseEvent.TheQPointclassdefinesapointintheplanebyusingintegerprecision.Relatively,thereisanotherclassnamedQPointF,whichprovidesfloatprecision.Well,theQMouseEventclassinheritsQEventandQInputEvent.Itcontainssomeparametersthatdescribeamouseevent.Let'sseewhyweneedtheminmainwindow.cpp:
#include<QTimer>
#include<QTime>
#include<QMouseEvent>
#include<QMenu>
#include<QAction>
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::WindowStaysOnTopHint|
Qt::FramelessWindowHint|windowFlags());
connect(this,&MainWindow::customContextMenuRequested,this,
&MainWindow::showContextMenu);
QTimer*timer=newQTimer(this);
connect(timer,&QTimer::timeout,this,
&MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::updateTime()
{
QTimecurrentTime=QTime::currentTime();
QStringcurrentTimeText=currentTime.toString("hh:mm");
if(currentTime.second()%2==0){
currentTimeText[2]='';
}
ui->lcdNumber->display(currentTimeText);
}
voidMainWindow::showContextMenu(constQPoint&pos)
{
QMenucontextMenu;
contextMenu.addAction(QString("Exit"),this,SLOT(close()));
contextMenu.exec(mapToGlobal(pos));
}
voidMainWindow::mouseReleaseEvent(QMouseEvent*e)
{
if(e->button()==Qt::RightButton){
emitcustomContextMenuRequested(e->pos());
}
else{
QMainWindow::mouseReleaseEvent(e);
}
}
NotethatyoushouldincludeQMouseEvent,QMenu,andQActioninordertoutilizetheseclasses.ThereisapredefinedcustomContextMenuRequestedsignal,whichiscoupledwiththenewlycreatedshowContextMenuslot.Forthesakeofconsistency,wewillfollowtherulethatQtdefined,whichmeansthattheQPointargumentincustomContextMenuRequestedshouldbealocalpositioninsteadofaglobalposition.That'swhyweneedamapToGlobalfunctiontotranslatepostoaglobalposition.AsfortheQMenuclass,itprovidesamenuwidgetforamenubar,contextmenu,orotherpop-upmenus.So,wecreatethecontextMenuobject,andthenaddanewactionwiththeExittext.ThisiscoupledwithacloseslotofMainWindow.ThelaststatementisusedtoexecutethecontextMenuobjectatthespecifiedglobalposition.Inotherwords,thisslotwilldisplayapop-upmenuatthegivenposition.
ThereimplementationofmouseReleaseEventisdonetocheckthetriggeredbuttonoftheevent.Ifit'stherightbutton,emitthecustomContextMenuRequestedsignalwiththelocalpositionofthemouse.Otherwise,simplycallthedefaultmouseReleaseEventfunctionofQMainWindow.
Makeuseofthedefaultmemberfunctionsofthebaseclasswhenyoureimplementit.
Runtheapplicationagain;youcanquitbyright-clickingonitandthenselectingExit.Now,weshouldcontinuethereimplementationtomaketheclockmovable.Thistime,weneedtorewritetwoprotectedfunctions:mousePressEventandmouseMoveEvent.Therefore,thisishowtheheaderfilelooks:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
QPointm_mousePos;
privateslots:
voidupdateTime();
voidshowContextMenu(constQPoint&pos);
protected:
voidmouseReleaseEvent(QMouseEvent*);
voidmousePressEvent(QMouseEvent*);
voidmouseMoveEvent(QMouseEvent*);
};
#endif//MAINWINDOW_H
Thereisalsoadeclarationofanewprivatemembervariableintheprecedingcode,m_mousePos,whichisaQPointobjectusedtostorethelocalpositionofthemouse.ThefollowingcodedefinesmousePressEventandmouseMoveEvent:
voidMainWindow::mousePressEvent(QMouseEvent*e)
{
m_mousePos=e->pos();
}
voidMainWindow::mouseMoveEvent(QMouseEvent*e)
{
this->move(e->globalPos()-m_mousePos);
}
It'seasierthanyouthought.Whenamousebuttonispressed,thelocalpositionofthemouseisstoredasm_mousePos.Whenthemouseismoving,wecallthemovefunctiontomoveMainWindowtoanewposition.Becausethepositionpassedtomoveisaglobalposition,weneedtouseglobalPosoftheeventminusthelocalpositionofthemouse.Confused?Them_mousePosvariableisthemouse'srelativepositiontothetop-leftpointoftheparentwidget,whichisMainWindowinourcase.Themovefunctionwillmovethetop-leftpointofMainWindowtothegivenglobalposition.Whilethee->globalPos()functionistheglobalpositionofthemouseandnotMainWindow,weneedtosubtracttherelativepositionofm_mousePostotranslatethemouse'sglobalpositiontothetop-leftpointpositionofMainWindow.Afterallthiseffort,theclockshouldlookmuchmoresatisfying.
SavingandrestoringsettingsAlthoughtheclockcanbemoved,itwon'trestoreitslastpositionafterrestarting.Inadditiontothis,wecangiveuserssomechoicestoadjusttheclock'sappearance,suchasthefontcolor.Tomakeitwork,weneedtheQSettingsclass,whichprovidesplatform-independentpersistentsettings.Itneedsacompanyororganizationnameandthenameofanapplication.AtypicalQSettingsobjectcanbeconstructedbyusingthisline:
QSettingssettings("Qt5Blueprints","FancyClock");
Here,Qt5Blueprintsistheorganization'snameandFancyClockistheapplication'sname.
ThesettingsarestoredinthesystemregistryonWindows,whiletheyarestoredintheXMLpreferencesfilesonMacOSXandtheINItextfilesontheotherUnixoperatingsystems,suchasLinux.However,wedonotusuallyneedtobeconcernedwiththis,sinceQSettingsprovideshigh-levelinterfacestomanipulatethesettings.
Ifwe'regoingtoreadand/orwritesettingsinmultipleplaces,we'dbettersettheorganizationandapplicationinQCoreApplication,whichisinheritedbyQApplication.Themain.cppfile'scontentisshownasfollows:
#include"mainwindow.h"
#include<QApplication>
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
a.setOrganizationName(QString("Qt5Blueprints"));
a.setApplicationName(QString("FancyClock"));
QWidgetwid;
MainWindoww(&wid);
w.show();
returna.exec();
}
ThisenablesustousethedefaultQSettingsconstructortoaccessthesamesettings.InordertosavethegeometryandstateofMainWindow,weneedtoreimplementcloseEvent.First,weneedtodeclarecloseEventtobeaprotectedmemberfunction,asfollows:
voidcloseEvent(QCloseEvent*);
Then,let'sdefinethecloseEventfunctioninmainwindow.cpp,asfollows:
voidMainWindow::closeEvent(QCloseEvent*e)
{
QSettingssts;
sts.setValue("MainGeometry",saveGeometry());
sts.setValue("MainState",saveState());
e->accept();
}
Remembertoadd#include<QSettings>inordertoincludetheQSettingsheaderfiles.
ThankstosetOrganizationNameandsetApplicationName,wedon'tneedtopassanyargumentstotheQSettingsconstructornow.Instead,wecallasetValuefunctiontosavethesettings.ThesaveGeometry()andsaveState()functionsreturntheMainWindowgeometryandstaterespectivelyastheQByteArrayobjects.
Thenextstepistoreadthesesettingsandrestorethegeometryandstate.ThiscanbedoneinsidetheconstructorofMainWindow.Youjustneedtoaddtwostatementstoit:
QSettingssts;
restoreGeometry(sts.value("MainGeometry").toByteArray());
restoreState(sts.value("MainState").toByteArray());
Here,toByteArray()cantranslatethestoredvaluetoaQByteArrayobject.Howdowetesttoseeifthisworks?Todothis,performthefollowingsteps:
1. Rebuildthisapplication.2. Runit.3. Moveitsposition.4. Closeit.5. Runitagain.
You'llseethattheclockwillappearatexactlythesamepositionasitwasbeforeitclosed.Nowthatyou'reprettymuchfamiliarwithwidgets,layouts,settings,signals,andslots,it'stimetocookapreferencedialogbyperformingthefollowingsteps:
1. Right-clickontheFancy_ClockprojectintheProjectspanel.2. SelectAddNew….3. SelectQtintheFilesandClassespanel.4. ClickonQtDesignerFormClassinthemiddlepanel.
5. SelectDialogwithButtonsBottom.6. FillinPreferenceunderClassname.7. ClickonNext,andthenselectFinish.
QtCreatorwillredirectyoutotheDesignmode.First,let'schangewindowTitletoPreference,andthendosomeUIwork.Performthefollowingstepstodothis:
1. DragLabeltoQDialogandchangeitsobjectNamepropertytocolourLabel.Next,changeitstexttoColour.
2. AddQComboBoxandchangeitsobjectNamepropertytocolourBox.3. AddtheBlack,White,Green,andReditemstocolourBox.4. ChangethelayoutofPreferencetoLayOutinaFormLayOut.
ClosethisUIfile.Gobacktoeditingthepreference.haddaprivateonAcceptedslot.Thefollowingcodeshowsthecontentofthisfile:
#ifndefPREFERENCE_H
#definePREFERENCE_H
#include<QDialog>
namespaceUi{
classPreference;
}
classPreference:publicQDialog
{
Q_OBJECT
public:
explicitPreference(QWidget*parent=0);
~Preference();
private:
Ui::Preference*ui;
privateslots:
voidonAccepted();
};
#endif//PREFERENCE_H
Asusual,wedefinethisslotinthesourcefile.Besides,wehavetosetupsomeinitializationsintheconstructorofPreference.Thus,preference.cppbecomessimilartothefollowingcode:
#include<QSettings>
#include"preference.h"
#include"ui_preference.h"
Preference::Preference(QWidget*parent):
QDialog(parent),
ui(newUi::Preference)
{
ui->setupUi(this);
QSettingssts;
ui->colourBox->setCurrentIndex(sts.value("Colour").toInt());
connect(ui->buttonBox,&QDialogButtonBox::accepted,this,
&Preference::onAccepted);
}
Preference::~Preference()
{
deleteui;
}
voidPreference::onAccepted()
{
QSettingssts;
sts.setValue("Colour",ui->colourBox->currentIndex());
}
Similarly,weloadthesettingsandchangethecurrentitemofcolourBox.Then,it'sthesignalandslotcouplingthatfollow.NotethatQtCreatorhasautomaticallygeneratedtheacceptandrejectconnectionsbetweenbuttonBoxandPreferenceforus.TheacceptedsignalofbuttonBoxisemittedwhentheOKbuttonisclicked.Likewise,therejectedsignalisemittediftheuserclicksonCancel.YoumaywanttocheckSignals&SlotsEditorintheDesignmodetoseewhichconnectionsaredefinedthere.Thisisshowninthefollowingscreenshot:
AsforthedefinitionoftheonAcceptedslot,itsavescurrentIndexofcolourBoxtothesettingssothatwecanreadthissettingelsewhere.
Now,whatwe'regoingtodonextisaddanentryforPreferenceinthepop-upmenuandchangethecoloroflcdNumberaccordingtotheColoursettingvalue.Therefore,youshoulddefineaprivateslotandaprivatememberfunctioninmainwindow.hfirst.
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
QPointm_mousePos;
voidsetColour();
privateslots:
voidupdateTime();
voidshowContextMenu(constQPoint&pos);
voidshowPreference();
protected:
voidmouseReleaseEvent(QMouseEvent*);
voidmousePressEvent(QMouseEvent*);
voidmouseMoveEvent(QMouseEvent*);
voidcloseEvent(QCloseEvent*);
};
#endif//MAINWINDOW_H
ThesetColourfunctionisusedtochangethecoloroflcdNumber,whiletheshowPreferenceslotwillexecuteaPreferenceobject.Thedefinitionsofthesetwomembersareinthemainwindow.cppfile,whichisdisplayedinthefollowingmanner:
#include<QTimer>
#include<QTime>
#include<QMouseEvent>
#include<QMenu>
#include<QAction>
#include<QSettings>
#include"mainwindow.h"
#include"preference.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
setAttribute(Qt::WA_TranslucentBackground);
setWindowFlags(Qt::WindowStaysOnTopHint|
Qt::FramelessWindowHint|windowFlags());
QSettingssts;
restoreGeometry(sts.value("MainGeometry").toByteArray());
restoreState(sts.value("MainState").toByteArray());
setColour();
connect(this,&MainWindow::customContextMenuRequested,this,
&MainWindow::showContextMenu);
QTimer*timer=newQTimer(this);
connect(timer,&QTimer::timeout,this,
&MainWindow::updateTime);
timer->start(1000);
updateTime();
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::updateTime()
{
QTimecurrentTime=QTime::currentTime();
QStringcurrentTimeText=currentTime.toString("hh:mm");
if(currentTime.second()%2==0){
currentTimeText[2]='';
}
ui->lcdNumber->display(currentTimeText);
}
voidMainWindow::showContextMenu(constQPoint&pos)
{
QMenucontextMenu;
contextMenu.addAction(QString("Preference"),this,
SLOT(showPreference()));
contextMenu.addAction(QString("Exit"),this,SLOT(close()));
contextMenu.exec(mapToGlobal(pos));
}
voidMainWindow::mouseReleaseEvent(QMouseEvent*e)
{
if(e->button()==Qt::RightButton){
emitcustomContextMenuRequested(e->pos());
}
else{
QMainWindow::mouseReleaseEvent(e);
}
}
voidMainWindow::mousePressEvent(QMouseEvent*e)
{
m_mousePos=e->pos();
}
voidMainWindow::mouseMoveEvent(QMouseEvent*e)
{
this->move(e->globalPos()-m_mousePos);
}
voidMainWindow::closeEvent(QCloseEvent*e)
{
QSettingssts;
sts.setValue("MainGeometry",saveGeometry());
sts.setValue("MainState",saveState());
e->accept();
}
voidMainWindow::setColour()
{
QSettingssts;
inti=sts.value("Colour").toInt();
QPalettec;
switch(i){
case0://black
c.setColor(QPalette::Foreground,Qt::black);
break;
case1://white
c.setColor(QPalette::Foreground,Qt::white);
break;
case2://green
c.setColor(QPalette::Foreground,Qt::green);
break;
case3://red
c.setColor(QPalette::Foreground,Qt::red);
break;
}
ui->lcdNumber->setPalette(c);
this->update();
}
voidMainWindow::showPreference()
{
Preference*pre=newPreference(this);
pre->exec();
setColour();
}
WecallsetColourintheconstructorinordertosetthecoloroflcdNumbercorrectly.InsidesetColour,wefirstreadtheColourvaluefromthesettings,andthenuseaswitchstatementtogetthecorrectQPaletteclassbeforecallingsetPalettetochangethecoloroflcdNumber.SinceQtdoesn'tprovideadirectwaytochangetheforegroundcoloroftheQLCDNumberobjects,weneedtousethistediousmethodtoachievethis.Attheendofthismemberfunction,wecallupdate()toupdatetheMainWindowuserinterface.
NoteDon'tforgettoaddthePreferenceactiontocontextMenuinsideshowContextMenu.Otherwise,therewillbenowaytoopenthedialog.
IntherelevantshowPreferenceslot,wecreateanewPreferenceobject,whichisthechildofMainWindow,andthencallexec()toexecuteandshowit.Lastly,wecallsetColour()tochangethecoloroflcdNumber.AsPreferenceismodalandexec()hasitsowneventloop,itwillblocktheapplicationuntilpreisfinished.Afterprefinishesexecuting,eitherbyacceptedorrejected,setColourwillbecallednext.Ofcourse,youcanusethesignal-slotwaytoimplementit,butwehavetoapplysomemodificationstothepreviouscode.Firstly,deletetheaccepted-acceptsignal-slotcoupleinpreference.uiintheDesignmode.Then,addaccept()totheendofonAcceptedinpreference.cpp.
voidPreference::onAccepted()
{
QSettingssts;
sts.setValue("Colour",ui->colourBox->currentIndex());
this->accept();
}
Now,showPreferenceinmainwindow.cppcanberewrittenasfollows:
voidMainWindow::showPreference()
{
Preference*pre=newPreference(this);
connect(pre,&Preference::accepted,this,
&MainWindow::setColour);
pre->exec();
}
TipTheconnectstatementshouldn'tbeplacedafterexec(),asitwillcausethe
bindingtofail.
Nomatterwhichwayyouprefer,theclockshouldhaveaPreferencedialognow.Runit,selectPreferencefromthepop-upmenu,andchangethecolortowhateveryouplease.Youshouldexpectaresultsimilartowhatisshowninthefollowingscreenshot:
BuildingontheUnixplatformsSofar,wearestilltrappedwithourapplicationsonWindows.It'stimetotestwhetherourcodecanbebuiltonotherplatforms.Inthischapter,thecodeinvolvedwithonlydesktopoperatingsystems,whilewe'llgetachancetobuildapplicationsformobileplatformslaterinthisbook.Intermsofotherdesktopoperatingsystems,thereareplentyofthem,andmostofthemareUnix-like.QtofficiallysupportsLinuxandMacOSX,alongwithWindows.Hence,usersofothersystems,suchasFreeBSD,mayneedtocompileQtfromscratchorgetprebuiltpackagesfromtheirowncommunities.Inthisbook,theLinuxdistributionFedora20isusedasademonstrationtointroduceplatformcrossing.PleasebearinmindthattherearelotsofdesktopenvironmentsandthemingtoolsonLinux,sodon'tbesurprisediftheuserinterfacediffers.Well,sinceyou'recurious,letmetellyouthatthedesktopenvironmentisKDE4withQtCurve,unifyingGTK+/Qt4/Qt5inmycase.Let'sgetstartedassoonasyou'reready.Youcanperformthefollowingstepstodothis:
1. CopythesourcecodeofFancyClocktoadirectoryunderLinux.2. DeletetheFancy_Clock.pro.userfile.3. OpenthisprojectinQtCreator.
Now,buildandrunthisapplication.Everythingisgoodexceptthatthere'sataskbaricon.Smallissuessuchasthiscan'tbeavoidedwithouttesting.Well,tofixthis,justmodifyasinglelineintheconstructorofMainWindow.Changingthewindowflagswillamendthis:
setWindowFlags(Qt::WindowStaysOnTopHint|
Qt::FramelessWindowHint|Qt::Tool);
Ifyourunthefileagain,FancyClockwon'tshowupinthetaskbaranymore.PleasekeeptheMainWindowobject,w,asachildofQWidgetwid;otherwise,theapplicationwon'tterminateafteryouclickonClose.
NotethatthePreferencedialogusesnativeUIcontrols,ratherthanbringingtheotherplatform'scontrolstothisone.ThisisoneofthemostfascinatingthingsthatQthasprovided.AlltheQtapplicationswilllookandbehavelikenativeapplicationsacrossallplatforms.
It'snotahustlebutthetruthisthatonceyoucodetheQtapplication,youcanruniteverywhere.Youdon'tneedtowritedifferentGUIsfordifferentplatforms.Thatdarkagehaslonggone.However,youmaywanttowritesomefunctionsforspecificplatforms,eitherbecauseofparticularneedsorworkarounds.Firstly,I'dliketointroduceyoutosomeQtAdd-Onmodulesdedicatedforseveralplatforms.
TakeQtWindowsExtrasasanexample.SomecoolfeaturesthatWindowsprovides,suchasThumbnailToolbarandAeroPeek,aresupportedbyQtthroughthisadd-onmodule.
Well,addingthismoduletotheprojectfiledirectly,whichinthiscaseisFancy_Clock.profile,willdefinitelyupsetotherplatforms.Abetterwaytodothisistotestwhetherit'sonWindows;ifso,addthismoduletotheproject.Otherwise,skipthisstep.ThefollowingcodeshowsyoutheFancy_Clock.profile,whichwilladdthewinextrasmoduleifit'sbuiltonWindows:
QT+=coregui
win32:QT+=winextras
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Fancy_Clock
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp\
preference.cpp
HEADERS+=mainwindow.h\
preference.h
FORMS+=mainwindow.ui\
preference.ui
Asyoucansee,win32isaconditionalstatement,whichistrueonlyifthehostmachineisWindows.Afteraqmakererunforthisproject,you'llbeabletoincludeand
utilizethoseextraclasses.
Similarly,ifyouwanttodosomethingontheUnixplatforms,simplyusethekeywordunix,butunixwillbetrueonlyonLinux/X11orMacOSX.TodistinguishMacOSXfromLinux,here'sanexample:
win32{
message("BuiltonWindows")
}
else:unix:macx{
message("BuiltonMacOSX")
}
else{
message("BuiltonLinux")
}
Infact,youcanjustuseunix:!macxastheconditionalstatementtodosomeplatform-specificworkonLinux.It'sacommonpracticetohavemanyplatform-specificstatementsintheprojectfile(s),especiallywhenyourprojectneedstobelinkedwithotherlibraries.Youhavetospecifydifferentpathsfortheselibrariesondifferentplatforms,otherwisethecompilerwillcomplainaboutmissinglibrariesorunknownsymbols.
Inadditiontothis,youmaywanttoknowhowtowriteplatform-specificcodewhilekeepingitfromotherplatforms.SimilartoC++,it'sapredefinedmacrothatishandledbyvariouscompilers.However,thesecompilermacrolistsmaydifferfromonecompilertoanother.So,itisbettertouseGlobalQtDeclarationsinstead.I'lluseathefollowingshortexampletoexplainthisfurther:
voidMainWindow::showContextMenu(constQPoint&pos)
{
QMenucontextMenu;
#ifdefQ_OS_WIN
contextMenu.addAction(QString("Options"),this,
SLOT(showPreference()));
#elifdefined(Q_OS_LINUX)
contextMenu.addAction(QString("Profile"),this,
SLOT(showPreference()));
#else
contextMenu.addAction(QString("Preference"),this,
SLOT(showPreference()));
#endif
contextMenu.addAction(QString("Exit"),this,SLOT(close()));
contextMenu.exec(mapToGlobal(pos));
}
TheprecedingcodeshowsyouthenewversionofshowContextMenu.ThePreferencemenuentrywillusedifferenttextsondifferentplatforms,namelyWindows,Linux,and
MacOSX.ChangeyourshowContextMenufunctionandrunitagain.You'llseeOptionsonWindows,ProfileonLinux,andPreferenceonMacOSX.Belowisalistconcerningtheplatform-specificmacros.Youcangetafulldescription,includingothermacros,functions,andtypesontheQtGlobaldocument.
Macro CorrespondPlatform
Q_OS_ANDROID Android
Q_OS_FREEBSD FreeBSD
Q_OS_LINUX Linux
Q_OS_IOS iOS
Q_OS_MAC MacOSXandiOS(Darwin-based)
Q_OS_WIN AllWindowsplatforms,includingWindowsCE
Q_OS_WINPHONE WindowsPhone8
Q_OS_WINRT WindowsRuntimeonWindows8.WindowsRTandWindowsPhone8
SummaryInthischapter,information,includingsometricks,aboutUIdesigningisincluded.Furthermore,therearebasicyetusefulcross-platformtopics.Now,you'reabletowriteanelegantQtapplicationinyourfavorite,andpossiblyalreadymastered,C++.
Inthenextchapter,wearegoingtolearnhowtowriteanapplicationinQtQuick.However,fearnot;QtQuickiseveneasierand,ofcourse,quickertodevelop.
Chapter3.CookinganRSSReaderwithQtQuickInthischapter,wewillfocusondevelopingapplicationswithQtQuick.Fortouchscreen-enableddevices,QtQuickapplicationsaremuchmoreresponsiveandeasytowrite.AnRSSreaderisusedasademonstrationinthischapter.ThefollowingtopicswillenableyoutobuildelegantQtQuickapplications:
UnderstandingmodelandviewParsingRSSFeedsbyXmlListModelTweakingthecategoriesUtilizingScrollViewAddingBusyIndicatorMakingaframelesswindowDebuggingQML
UnderstandingmodelandviewAsmentionedbefore,QtQuickapplicationsaredifferentfromtraditionalQtWidgetsapplications.YouaregoingtowriteQMLinsteadofC++code.So,let'screateanewproject,aQtQuickapplicationnamedRSS_Reader.Thistime,wewilluseQtQuick2.3asthecomponentset.Sincewewon'tusethewidgetsprovidedbyQtQuickControls,we'llwriteourownwidgets.
Beforegettingourhandsdirty,let'ssketchoutwhatthisapplicationlookslike.Accordingtothefollowingdiagram,therewillbetwosections.Theleft-handpanelprovidessomecategoriessothatuserscanchooseinterestingcategories.Theright-handpanelisthemainarea,whichdisplaysnewsunderthecurrentcategory.ThisisatypicalRSSnewsreader'suserinterface.
WecanimplementtheCategoriespanelbyusingListView.Thistype(wesay"type"insteadof"class"inQML)isusedtodisplaydatafromallsortsoflistmodels.Solet'seditourmain.qmltosomethingsimilartothis:
importQtQuick2.3
importQtQuick.Window2.2
Window{
id:mainWindow
visible:true
width:720
height:400
ListView{
id:categories
width:150
height:parent.height
orientation:ListView.Vertical
anchors.top:parent.top
spacing:3
}
}
ListViewneedsamodeltogetdatafrom.Inthiscase,wecanutilizeListModelforitssimplicity.Toachievethis,let'screateanewFeeds.qmlfile,whichwillcontainacustomListModelexample:
1. Right-clickontheproject.2. SelectAddNew….3. NavigatetoQt|QMLFile(QtQuick2).4. EntertheFeeds.qmlfilename.
HereisthecontentofFeeds.qml:
importQtQuick2.3
ListModel{
ListElement{name:"TopStories";url:
"http://feeds.bbci.co.uk/news/rss.xml"}
ListElement{name:"World";url:
"http://feeds.bbci.co.uk/news/world/rss.xml"}
ListElement{name:"UK";url:
"http://feeds.bbci.co.uk/news/uk/rss.xml"}
ListElement{name:"Business";url:
"http://feeds.bbci.co.uk/news/business/rss.xml"}
ListElement{name:"Politics";url:
"http://feeds.bbci.co.uk/news/politics/rss.xml"}
ListElement{name:"Health";url:
"http://feeds.bbci.co.uk/news/health/rss.xml"}
ListElement{name:"Education&Family";url:
"http://feeds.bbci.co.uk/news/education/rss.xml"}
ListElement{name:"Science&Environment";url:
"http://feeds.bbci.co.uk/news/science_and_environment/rss.xml"
}
ListElement{name:"Technology";url:
"http://feeds.bbci.co.uk/news/technology/rss.xml"}
ListElement{name:"Entertainment&Arts";url:
"http://feeds.bbci.co.uk/news/entertainment_and_arts/rss.xml"
}
}
NoteWeuseBBCNewsRSSasfeeds,butyoumaywishtochangeittoanother.
Asyoucansee,theprecedingListModelexamplehastworoles,nameandurl.A"role"isbasicallyafancywayofsayingthechilditem.ThesecanbeboundtobytheListViewdelegatethatweareabouttocreate.Inthisway,rolesusuallyrepresentthepropertiesofanentityorcolumnsofatable.
Letmeexplaintherelationbetweenview,model,anddelegate,whichisanotherimportantyetdifficultconceptintheworldofQt.Thisisofficiallycalledmodel-viewarchitecture.Inadditiontothetraditionalview,Qtdecouplestheviewandcontrollersothatthedatacanberenderedandeditedinmanycustomizedways.Thelatterismuchmoreelegantandefficient.Thefollowingdiagramhelpsyouunderstandthisconcept:
TakeListModel,whichisamodelusedtoarrangedata,asanexampletoelaboratetherelationship.CategoriesDelegate,showninthefollowingcode,isadelegateandisusedtocontrolhowtodisplaytherolesfromthemodel.Lastly,weuseaview,whichisListViewinthiscase,torenderthedelegate.
Thecommunicationbetweenthemodels,views,anddelegatesarebasedonthesignalsandslotsmechanism.It'lltakeyousometimetofullyunderstandthisconcept.Hopefully,wecanshortenthistimebypracticingthisexample.Atthisstage,wealreadyhaveaviewandamodel.Wehavetodefineadelegate,whichisCategoriesDelegateasmentionedbefore,tocontrolthedatafromthemodelandrenderitontheview.AddanewCategoriesDelegate.qmlfile,whosecontentispastedinthisway:
importQtQuick2.3
Rectangle{
id:delegate
propertyrealitemWidth
width:itemWidth
height:80
Text{
id:title
anchors{left:parent.left;leftMargin:10;right:
parent.right;rightMargin:10}
anchors.verticalCenter:delegate.verticalCenter
text:name
font{pointSize:18;bold:true}
verticalAlignment:Text.AlignVCenter
wrapMode:Text.WordWrap
}
}
Youshouldhavesomeideaabouttherelationbetweenthemodel,delegate,andview.Here,weuseRectangleasthedelegatetype.InsidetheRectangletypeisaTextobjectthatdisplaysthenamefromourListModelexample.Asforthefontproperty,hereweusepointSizetospecifythesizeoftext,whileyoucanusepixelSizeasanalternative.
Tofinishthemodel-viewarchitecture,gobacktothemain.qmledit:
importQtQuick2.3
importQtQuick.Window2.2
import"qrc:/"
Window{
id:mainWindow
visible:true
width:720
height:400
Feeds{
id:categoriesModel
}
ListView{
id:categories
width:150
height:parent.height
orientation:ListView.Vertical
anchors.top:parent.top
spacing:3
model:categoriesModel
delegate:CategoriesDelegate{
id:categoriesDelegate
width:categories.width
}
}
}
Takenoteofthethirdline;it'scrucialtoimportthisdirectoryintoqrc.Weuse"qrc:/"becauseweneedtoputtheQMLfilesintherootdirectory.ModifyitifyouuseasubdirectorytokeepFeeds.qmlandCategoriesDelegate.qml.Inthisexample,thesefilesareleftunorganized.Butit'shighlyrecommendedtokeepthemcategorizedasadifferentmodule.Ifyoudidn'timportthedirectory,youwon'tbeabletousetheseQMLfiles.
InsidetheWindowitem,wecreateFeeds,whichisexactlyanelementofListModelfromFeeds.qml.Then,wegivethisFeedsitemacategoriesModelIDanduseitasthemodelofListView.Specifyingthedelegateisquitesimilartospecifyingthemodelforviews.InsteadofdeclaringitoutsideListView,wehavetodefineitinsidethedelegatescope,otherwisethedelegateitem,CategoriesDelegate,won'tbeabletogetdatafromthemodel.Asyoucansee,wecanmanipulatethewidthofcategoriesDelegate.Thisistoensurethatthetextwon'tlieoutsidetheboundaryofListView.
Ifeverythingisdonecorrectly,clickonRunandyou'llseeitrunlikethis:
ParsingRSSFeedsbyXmlListModelIt'struethatwenowhavecategories,buttheydon'tseemtobeinvolvedwithRSSatall.Also,ifyoudigdeeper,you'llfindthattheRSSfeedsareinfacttheXMLdocuments.Qtalreadyprovidesausefultypetohelpusparsethem.Wedon'tneedtoreinventthewheel.Thispowerfultypeistheso-calledXmlListModelelementanditusesXmlRoletoquery.
Firstly,weneedtoexposetheurlroleofcategoriesModeltothemainscope.Thisisdonebydeclaringthepropertystoringthemodel'scurrentelement,url,insideListView.Then,wecanaddanXmlListModelelementandusethaturlelementasitssource.Accordingly,themodifiedmain.qmlfileispastedhere:
importQtQuick2.3
importQtQuick.Window2.2
importQtQuick.XmlListModel2.0
import"qrc:/"
Window{
id:mainWindow
visible:true
width:720
height:400
Feeds{
id:categoriesModel
}
ListView{
id:categories
width:150
height:parent.height
orientation:ListView.Vertical
anchors.top:parent.top
spacing:3
model:categoriesModel
delegate:CategoriesDelegate{
id:categoriesDelegate
width:categories.width
}
propertystringcurrentUrl:categoriesModel.get(0).url
}
XmlListModel{
id:newsModel
source:categories.currentUrl
namespaceDeclarations:"declarenamespacemedia=
'http://search.yahoo.com/mrss/';declarenamespaceatom=
'http://www.w3.org/2005/Atom';"
query:"/rss/channel/item"
XmlRole{name:"title";query:"title/string()"}
XmlRole{name:"description";query:
"description/string()"}
XmlRole{name:"link";query:"link/string()"}
XmlRole{name:"pubDate";query:"pubDate/string()"}
//XPathstartsfrom1not0andthesecondthumbnailis
largerandmoreclear
XmlRole{name:"thumbnail";query:
"media:thumbnail[2]/@url/string()"}
}
}
NoteObjects'valuesarechangeddynamicallyandupdatedimplicitlyinQtQuick.Youdon'tneedtogivenewvaluesexplicitly.
Inordertousethiselement,youwillneedtoimportthemodulebyaddinganimportQtQuick.XmlListModel2.0line.Additionally,XmlListModelisaread-onlymodelwhichmeansthatyoucan'tmodifythedatasourcethroughthismodel.ThisiscompletelyacceptablesincewhatweneedistoretrievethenewsdatafromtheRSSfeeds.TakeTopStoriesasanexample;thefollowingcodeisapartofthisXMLdocumentcontent:
<?xmlversion="1.0"encoding="UTF-8"?>
<?xml-stylesheettitle="XSL_formatting"type="text/xsl"
href="/shared/bsp/xsl/rss/nolsol.xsl"?>
<rssxmlns:media="http://search.yahoo.com/mrss/"
xmlns:atom="http://www.w3.org/2005/Atom"version="2.0">
<channel>
<title>BBCNews-Home</title>
<link>http://www.bbc.co.uk/news/#sa-
ns_mchannel=rss&ns_source=PublicRSS20-sa</link>
<description>ThelateststoriesfromtheHomesectionofthe
BBCNewswebsite.</description>
<language>en-gb</language>
<lastBuildDate>Mon,26Jan201523:19:42GMT</lastBuildDate>
<copyright>Copyright:(C)BritishBroadcastingCorporation,
seehttp://news.bbc.co.uk/2/hi/help/rss/4498287.stmforterms
andconditionsofreuse.</copyright>
<image>
<url>http://news.bbcimg.co.uk/nol/shared/img/bbc_news_120x60.g
if</url>
<title>BBCNews-Home</title>
<link>http://www.bbc.co.uk/news/#sa-
ns_mchannel=rss&ns_source=PublicRSS20-sa</link>
<width>120</width>
<height>60</height>
</image>
<ttl>15</ttl>
<atom:linkhref="http://feeds.bbci.co.uk/news/rss.xml"
rel="self"type="application/rss+xml"/>
<item>
<title>GermanywarnsGreeceoverdebts</title>
<description>TheGermangovernmentwarnsGreecethatitmust
meetitscommitmentstolenders,aftertheelectionwinofthe
Greekanti-austeritySyrizaparty.</description>
<link>http://www.bbc.co.uk/news/business-30977714#sa-
ns_mchannel=rss&ns_source=PublicRSS20-sa</link>
<guid
isPermaLink="false">http://www.bbc.co.uk/news/business-
30977714</guid>
<pubDate>Mon,26Jan201520:15:56GMT</pubDate>
<media:thumbnailwidth="66"height="49"
url="http://news.bbcimg.co.uk/media/images/80536000/jpg/_80536
447_025585607-1.jpg"/>
<media:thumbnailwidth="144"height="81"
url="http://news.bbcimg.co.uk/media/images/80536000/jpg/_80536
448_025585607-1.jpg"/>
</item>
……
ThenamespaceDeclarationspropertyneedstobesetbecausetheXMLdocumenthastheXMLnamespaces.
<rssxmlns:media="http://search.yahoo.com/mrss/"
xmlns:atom="http://www.w3.org/2005/Atom"version="2.0">
Here,xmlnsstandsfortheXMLnamespace,sowedeclarethenamespaceaccordingly.
namespaceDeclarations:"declarenamespacemedia=
'http://search.yahoo.com/mrss/';declarenamespaceatom=
'http://www.w3.org/2005/Atom';"
Infact,youcanjustdeclareamedianamespaceandsafelyignoreanatomnamespace.However,ifyoudidn'tdeclarethemedianamespace,theapplicationwouldendupfailingtoparsetheXMLdocument.Hence,gobacktoseetheXMLdocumentandyou'llfindithasahierarchytoorderdata.Whatwewantherearetheseitems.Takethetop-levelasroot,/,sothepathofitemcanbewrittenas/rss/channel/item.Thisisexactlywhatweputinquery.
AlltheXmlRoleelementsarecreatedusingqueryasthebase.ForXmlRole,namedefinesitsname,whichdoesn'tneedtobethesameasintheXMLdocument.It's
similartoidfortheregularQtQuickitems.However,thequeryofXmlRolemustusearelativepathtothequeryofXmlListModel.Althoughit'sastring()typeinmostcases,itstillmustbedeclaredexplicitly.Ifthereareelementssharingthesamekeys,it'dbeanarraywheretheelementlistedfirsthasthefirstindex.
NoteThefirstindexinXPathis1insteadof0.
Sometimes,weneedtogetanattributethumbnail.Thisistheurlattributeofthemedia:thumbnailtag.Inthiscase,it'sthe@symbolthatwilldoallthemagicweneed.
Similartothesecategories,wehavetowriteadelegatefortheXmlListModelelementtorendertheview.ThenewQMLNewsDelegate.qmlfileisshownhere:
importQtQuick2.3
Column{
id:news
spacing:8
//usedtoseparatenewsitem
Item{height:news.spacing;width:news.width}
Row{
width:parent.width
height:children.height
spacing:news.spacing
Image{
id:titleImage
source:thumbnail
}
Text{
width:parent.width-titleImage.width
wrapMode:Text.WordWrap
font.pointSize:20
font.bold:true
text:title
}
}
Text{
width:parent.width
font.pointSize:9
font.italic:true
text:pubDate+"(<ahref=\""+link+"\">Details</a>)"
onLinkActivated:{
Qt.openUrlExternally(link)
}
}
Text{
width:parent.width
wrapMode:Text.WordWrap
font.pointSize:10.5
horizontalAlignment:Qt.AlignLeft
text:description
}
}
ThedifferenceisthatthistimeweuseColumntoorganizethenewsdataandrepresentitinanintuitiveway.Therelevantdiagramissketchedasfollows:
So,thisiswhyweuseRowinsideColumntoboxThumbnailandTitletogether.Thus,weneedtoputanemptyitemelementinfronttoseparateeachnewsdelegate.Apartfromtheseself-explanatorylines,thereisatipfordealingwithlinks.YouneedtospecifytheslotfortheonLinkActivatedsignal,whichisQt.openUrlExternally(link)inthiscase.Otherwise,nothingwillhappenwhenyouclickonthelink.
Afterallthis,it'stimetowriteaviewinmain.qmltodisplayournews:
importQtQuick2.3
importQtQuick.Window2.2
importQtQuick.XmlListModel2.0
import"qrc:/"
Window{
id:mainWindow
visible:true
width:720
height:400
Feeds{
id:categoriesModel
}
ListView{
id:categories
width:150
height:parent.height
orientation:ListView.Vertical
anchors.top:parent.top
spacing:3
model:categoriesModel
delegate:CategoriesDelegate{
id:categoriesDelegate
width:categories.width
}
propertystringcurrentUrl:categoriesModel.get(0).url
}
XmlListModel{
id:newsModel
source:categories.currentUrl
namespaceDeclarations:"declarenamespacemedia=
'http://search.yahoo.com/mrss/';declarenamespaceatom=
'http://www.w3.org/2005/Atom';"
query:"/rss/channel/item"
XmlRole{name:"title";query:"title/string()"}
XmlRole{name:"description";query:
"description/string()"}
XmlRole{name:"link";query:"link/string()"}
XmlRole{name:"pubDate";query:"pubDate/string()"}
//XPathstartsfrom1not0andthesecondthumbnailis
largerandmoreclear
XmlRole{name:"thumbnail";query:
"media:thumbnail[2]/@url/string()"}
}
ListView{
id:newsList
anchors{left:categories.right;leftMargin:10;right:
parent.right;rightMargin:4;top:parent.top;bottom:
parent.bottom;}
model:newsModel
delegate:NewsDelegate{
width:newsList.width
}
}
}
RemembertodefinethewidthofNewsDelegateincaseitdisplaysabnormally.ClickonRun;theapplicationwilllooklikethefollowingscreenshot:
TweakingthecategoriesThisapplicationisstillincomplete.Forexample,thenewsviewwon'tchangeatallafteryouclickontheothercategories.Inthisstage,we'regoingtoworkthisoutandmakeitmorebeautiful.
WhatweneedtodoisaddMouseAreatoCategoriesDelegate.Thiselementisusedtodealwithavarietyofmouseinteractions,includingclicking.ThenewCategoriesDelegate.qmlfile'scodeispastedhere:
importQtQuick2.3
Rectangle{
id:delegate
height:80
Text{
id:title
anchors{left:parent.left;leftMargin:10;right:
parent.right;rightMargin:10}
anchors.verticalCenter:delegate.verticalCenter
text:name
font{pointSize:18;bold:true}
verticalAlignment:Text.AlignVCenter
wrapMode:Text.WordWrap
}
MouseArea{
anchors.fill:delegate
onClicked:{
categories.currentIndex=index
if(categories.currentUrl==url)
newsModel.reload()
else
categories.currentUrl=url
}
}
}
Asyoucansee,onceadelegategetsclickedon,it'llchangecategories.currentIndexandcurrentUrlifnecessary,orsimplyletnewsModelreload.Asmentionedbefore,QMLisadynamiclanguage,whichchangescategories.currentUrl,thesourcepropertyofnewsModel,andwouldautomaticallycausenewsModeltoreload.
Tohelpusersdistinguishacurrently-selectedcategoryfromothers,wemaywishtochangeitssizeandscale.Therearesomeattachedproperties,whichareattachedtoeachinstanceofadelegateoraresimplysharedamongthem.The.isCurrentItem
propertyistheonethatwouldsousafavor.It'saBooleanvaluethatholdswhetherthisdelegateisthecurrentitemornot.However,onlytherootitemofadelegatecanaccesstheseattachedpropertiesdirectly.Inordertocodeinacleanway,weaddalinetoRectangleofCategoriesDelegatetoholdthisproperty:
propertyboolselected:ListView.isCurrentItem
Now,wecanutilizeselectedinTextbyaddingthefollowinglinestotheTextitem:
scale:selected?1.0:0.8
color:selected?"#000":"#AAA"
Textwillbescaledto0.8ifit'snotselectedandwillbehaveasusualwhenit'sactive.Asimilarconditionisinplaceforitscolor.The#AAAcolorcodeisanextremelylightgraycolor,whichmakestheactiveblacktextstandoutmore.However,thereisnoanimationforthesechanges.Whilewewantthesetransitionstobemorenatural,QtQuickprovidesBehaviorwithStatetomakethesetransitionshappen.ByaddingtheselinestotheTextitem,wegetthefollowingcode:
Behavioroncolor{ColorAnimation{duration:300}}
Behavioronscale{PropertyAnimation{duration:300}}
Animationsareexpectedtopresentwhenyouchangethecurrentdelegate,whichresultsinchangesinthecolorandscale.Ifyou'renotsurewhetheryou'veperformedthecorrectmodification,thefollowingcodeshowsyouthenewlymodifiedCategoriesDelegate.qmlfile:
importQtQuick2.3
Rectangle{
id:delegate
height:80
propertyboolselected:ListView.isCurrentItem
Text{
id:title
anchors{left:parent.left;leftMargin:10;right:
parent.right;rightMargin:10}
anchors.verticalCenter:delegate.verticalCenter
text:name
font{pointSize:18;bold:true}
verticalAlignment:Text.AlignVCenter
wrapMode:Text.WordWrap
scale:selected?1.0:0.8
color:selected?"#000":"#AAA"
Behavioroncolor{ColorAnimation{duration:300}}
Behavioronscale{PropertyAnimation{duration:300}}
}
MouseArea{
anchors.fill:delegate
onClicked:{
categories.currentIndex=index
if(categories.currentUrl==url)
newsModel.reload()
else
categories.currentUrl=url
}
}
}
Thereisroomtoimprovethecategories,includingthebackgroundimagewhichissimplyanImageelement,andcouldformpartofyourexercises.However,itwon'tbecoveredinthischapter.Here,whatwedonextistochangethedisplayingfontsontheWindowsplatform.We'regoingtochangethefonttoTimesNewRomanbyaddingafewlinesinmain.cpp(notmain.qml).
#include<QGuiApplication>
#include<QQmlApplicationEngine>
#include<QFont>
intmain(intargc,char*argv[])
{
QGuiApplicationapp(argc,argv);
#ifdefQ_OS_WIN
app.setFont(QFont(QString("TimesNewRoman")));
#endif
QQmlApplicationEngineengine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
returnapp.exec();
}
Here,weuseapredefinedmacrotolimitthischangefortheWindowsplatforms.BysettingthefontofappwhosetypeisQGuiApplication,allthechildrenwidgets,includingengine,aresubjectedtothischange.Nowruntheapplicationagain;youshouldexpectanewRSSreaderwiththisnewspaper-likefont:
UtilizingScrollViewOurRSSnewsreaderisshapingupnow.Fromnowon,let'sfocusontheunpleasantdetails.Thefirstthingwe'regoingtoaddisascrollbar.Tobemorespecific,ScrollViewisabouttobeadded.
BackintheQt4era,youhadtowriteyourownScrollViewcomponenttogainthissmallyetverynicefeature.AlthoughyoucanutilizeKDEPlasmaComponents'ScrollAreaonX11Platforms,therearenoQtbundledmodulesforthispurpose,whichmeansyoucan'tusetheseonWindowsandMacOSX.ThankstotheopengovernanceoftheQtproject,alotofcommunitycodegetsmerged,especiallyfromtheKDEcommunity.FromQt5.1onwards,wehavetheQtQuick.Controlsmodule,whichhasmanybuilt-indesktopcomponents,includingScrollView.
It'saveryeasytouseelementthatprovidesscrollbarsandcontentframesforitschilditem.TherecanbeonlyonedirectItemchild,andthischildisimplicitlyanchoredtofilltheScrollViewcomponent.ThismeansthatweonlyneedtoanchortheScrollViewcomponent.
Therearetwowaystospecifythechilditem.ThefirstoneistodeclarethechilditeminsidetheScrollViewcomponent'sscope,andtheitemwhichisinsidewillimplicitlybecomethechildoftheScrollViewcomponent.AnotherwayistosetthecontentItemproperty,whichisanexplicitmethod.Inthischapter'sexample,bothwaysaredemonstratedforyou.Thecontentofmain.qmlisshownhere:
importQtQuick2.3
importQtQuick.Window2.2
importQtQuick.XmlListModel2.0
importQtQuick.Controls1.2
importQtQuick.Controls.Styles1.2
import"qrc:/"
Window{
id:mainWindow
visible:true
width:720
height:400
Feeds{
id:categoriesModel
}
ListView{
id:categories
orientation:ListView.Vertical
spacing:3
model:categoriesModel
delegate:CategoriesDelegate{
id:categoriesDelegate
width:categories.width
}
propertystringcurrentUrl:categoriesModel.get(0).url
}
ScrollView{
id:categoriesView
contentItem:categories
anchors{left:parent.left;top:parent.top;bottom:
parent.bottom;}
width:0.2*parent.width
style:ScrollViewStyle{
transientScrollBars:true
}
}
XmlListModel{
id:newsModel
source:categories.currentUrl
namespaceDeclarations:"declarenamespacemedia=
'http://search.yahoo.com/mrss/';declarenamespaceatom=
'http://www.w3.org/2005/Atom';"
query:"/rss/channel/item"
XmlRole{name:"title";query:"title/string()"}
XmlRole{name:"description";query:"description/string()"
}
XmlRole{name:"link";query:"link/string()"}
XmlRole{name:"pubDate";query:"pubDate/string()"}
//XPathstartsfrom1not0andthesecondthumbnailis
largerandmoreclear
XmlRole{name:"thumbnail";query:
"media:thumbnail[2]/@url/string()"}
}
ScrollView{
id:newsView
anchors{left:categoriesView.right;leftMargin:10;
right:parent.right;top:parent.top;bottom:parent.bottom}
style:ScrollViewStyle{
transientScrollBars:true
}
ListView{
id:newsList
model:newsModel
delegate:NewsDelegate{
width:newsList.width
}
}
}
}
Sincethechilditemisautomaticallyfilledwithanchors,somelinesinsideListViewaredeleted.MostofthemarejustmovedtoScrollViewthough.YoucanseethatweusetheexplicitwayforcategoriesandtheimplicitwayfornewsList.
LookingintoScrollView,wedefinedacustomstyleelementbyforcingtransientScrollBarstotrue.It'snotedthatthedefaultvalueoftransientScrollBarsisplatformdependent.Thetransientscrollbarsonlyappearwhenthecontentisscrolledandthendisappearwhentheyarenolongerneeded.Anyway,it'sfalsebydefaultonWindows,soweturnitonexplicitly,resultinginabettervisualstyleshownasfollows:
AddingBusyIndicatorTheabsenceofabusyindicatormakespeopleuncomfortableaswell.Nomatterhowshortorlongindicatoritis,ittakestimetodownloaddataandparseXML.I'mprettysureyou'dliketoaddsuchanindicator,whichtellsuserstocalmdownandwait.Luckily,BusyIndicator,whichissimplyarunningcircle,isanelementofQtQuick.Controls.Thisdoesexactlywhatwewant.
Whatyouneedtodoistoaddtheselinestomain.qmlinsidetheWindowitem:
BusyIndicator{
anchors.centerIn:newsView
running:newsModel.status==XmlListModel.Loading
}
Notethatwedon'tneedtochangethevisiblepropertyofBusyIndicator,becauseBusyIndicatorisonlyvisiblewhentherunningpropertyissettotrue.Inthiscase,wesetrunningtotruewhenthenewsModelstatusisLoading.
MakingaframelesswindowSimilartowhatwedidinthepreviouschapter,herewedon'twantthebordersofthesystemwindowtodecorateourQtQuickapplication.Thisispartlybecauseitlookslikeawebapplication,whichmakesitseemsoddwithnativewindowdecorations.ThisjobiseveneasierinQMLthaninC++.WecanaddthefollowinglinetoWindowinmain.qml:
flags:Qt.Window|Qt.FramelessWindowHint
AlthoughourRSSreaderrunsinaframelessstyle,thereisnowaytomoveitandit'sdifficulttocloseit,justlikethesituationinthepreviouschapter.SinceourmousehasmanydutiesforthecategoriesandnewsListViewalongwithScrollView,wecan'tsimplyuseanewMouseAreaelementtofilltheWindowroot.Therefore,whatwe'regoingtodoistodrawourowntitlebarand,ofcourse,theexitbutton.
Toaddtheexitbuttonimagetotheqrcfile,right-clickonqml.qrc,selectOpeninEditor,navigatetoAdd|AddFiles,andthenselectclose.png.
TipIt'dbebettertousedifferentresourcefiles(qrc)fordifferenttypesoffiles,whichmakeitmoreorganized.We'lltalkmoreaboutresourcefilesinChapter8,EnablingYourQtApplicationtoSupportOtherLanguages.
Now,addanewQMLTitleBar.qmlfilewhosecontentispastedhere:
importQtQuick2.3
Row{
id:titlebar
width:parent.width
height:22
layoutDirection:Qt.RightToLeft
propertypointmPos:Qt.point(0,0)
Image{
id:closebutton
width:22
height:22
fillMode:Image.PreserveAspectFit
source:"qrc:/close.png"
MouseArea{
anchors.fill:parent
onClicked:{
Qt.quit()
}
}
}
Rectangle{
width:titlebar.width-closebutton.width
height:titlebar.height
color:"#000"
MouseArea{
anchors.fill:parent
onPressed:{
mPos=Qt.point(mouseX,mouseY)
}
onPositionChanged:{
mainWindow.setX(mainWindow.x+mouseX-mPos.x)
mainWindow.setY(mainWindow.y+mouseY-mPos.y)
}
}
}
}
Here,weuseaQPointobject,mPos,tostorethepositionwhenthemousebuttonisclicked.
NoteAlthoughwemayhavedeclareditasvarorvariantinthepast,formaximumperformanceyoushouldavoidtheuseofvar.Alsonotethatvariantisdeprecatednow,soitshouldn'tbeusedunderanycircumstances.
TheMouseAreaelement,whichisusedformoving,islocatedinsidetheRectangleelement.TherearelotsofpredefinedsignalsandslotsforMouseArea.NotethatweusetheonPressedslotinsteadofonClickedheretogetthemouseposition.Thisisbecausetheclickedsignalisonlyemittedwhenthemousebuttonispressedandthenreleased,whichmakesitunsuitableformovingthewindow.
ThepositionChangedsignalisemittedwhenthemousebuttonispressedandthenmoved.Inadditiontothis,thereisapropertycalledhoverEnabled,whichisfalsebydefault.Ifyousetittotrue,allthemouseeventswillbehandledevenwhennomousebuttonisclicked.Inotherwords,thepositionChangedsignalwillbeemittedwhenthemouseismoving,regardlessofwhetherit'sclickedornot.Therefore,we
don'tsethoverEnabledtotrueinthisexample.
Nowlet'sgobackandchecktheImageitem.ThefillModeelementdetermineshowanimageshouldbeadjusted.Bydefault,it'llbestretcheddespitetheratio.Here,wesetittopreservetheratiowhilewefittheImage.Thesourcepropertyholdstheimagefilepath.Inthiscase,it'stheclose.pngfilethatisintheResourcesfile,qml.qrc.Herewego;thisisanotherMouseArea,whichsimplymakesImageintoaclosedbutton.
Atlast,it'stimetoaddTitleBartomain.qmlasfollows:
importQtQuick2.3
importQtQuick.Window2.2
importQtQuick.XmlListModel2.0
importQtQuick.Controls1.2
importQtQuick.Controls.Styles1.2
import"qrc:/"
Window{
id:mainWindow
visible:true
width:720
height:400
flags:Qt.Window|Qt.FramelessWindowHint
TitleBar{
id:titleBar
}
Text{
id:windowTitle
anchors{left:titleBar.left;leftMargin:10;
verticalCenter:titleBar.verticalCenter}
text:"BBCNewsReader"
color:"#FFF"
font.pointSize:10
}
Feeds{
id:categoriesModel
}
ListView{
id:categories
orientation:ListView.Vertical
spacing:3
model:categoriesModel
delegate:CategoriesDelegate{
id:categoriesDelegate
width:categories.width
}
propertystringcurrentUrl:categoriesModel.get(0).url
}
ScrollView{
id:categoriesView
contentItem:categories
anchors{left:parent.left;top:titleBar.bottom;bottom:
parent.bottom;}
width:0.2*parent.width
style:ScrollViewStyle{
transientScrollBars:true
}
}
XmlListModel{
id:newsModel
source:categories.currentUrl
namespaceDeclarations:"declarenamespacemedia=
'http://search.yahoo.com/mrss/';declarenamespaceatom=
'http://www.w3.org/2005/Atom';"
query:"/rss/channel/item"
XmlRole{name:"title";query:"title/string()"}
XmlRole{name:"description";query:
"description/string()"}
XmlRole{name:"link";query:"link/string()"}
XmlRole{name:"pubDate";query:"pubDate/string()"}
//XPathstartsfrom1not0andthesecondthumbnailis
largerandmoreclear
XmlRole{name:"thumbnail";query:
"media:thumbnail[2]/@url/string()"}
}
ScrollView{
id:newsView
anchors{left:categoriesView.right;leftMargin:10;
right:parent.right;top:titleBar.bottom;bottom:
parent.bottom}
style:ScrollViewStyle{
transientScrollBars:true
}
ListView{
id:newsList
model:newsModel
delegate:NewsDelegate{
width:newsList.width
}
}
}
BusyIndicator{
anchors.centerIn:newsView
running:newsModel.status==XmlListModel.Loading
}
}
WealsouseaTextelement,windowTitle,todisplaythewindowtitleintitleBar.SinceweretrievedatafromBBCNews,it'snotabadideatocallitBBCNewsReaderorjustnameitwhateveryoulike.
Apartfromtheadditionofthetitlebar,somecodeneedstobemodifiedtospareroomforit.BoththeScrollViewcomponent'sanchoredtopshouldbechangedtotitleBar.bottominsteadofparent.top,otherwisethetitlebarwillbeplacedpartiallyontopofthesetwoscrollviews.
Givetheapplicationarun;itshoulddeliveranewvisualstyle.Althoughitlooksmorelikeawebapplication,thewholeinterfaceiscleanandintegrated.AnotherbenefitofthischangeisaunifiedUIacrossallplatforms.
DebuggingQMLThemostcommonpracticetodebugQMListheuseoftheAPIconsole.JavaScriptdevelopersshouldbefamiliarwiththisbecauseoftheconsolesupportinQML.TherelationshipsbetweentheconsolefunctionsandtheQt/C++QDebugfunctionsaregivenasfollows:
QML Qt/C++
console.log() qDebug()
console.debug() qDebug()
console.info() qDebug()
console.warn() qWarning()
console.error() qCritical()
Withtheprecedingsupportspresent,QMLisjustlikeJavaScriptprogramming.Atthesametime,thefollowingfunctionsarealsointroducedinQML:
Functions Description
console.assert()
Thisfunctiontestswhethertheexpressionistrue.Ifnot,itwillwriteanoptionalmessagetotheconsoleandprintthestacktrace.
console.exception()ThisfunctionprintsanerrormessagetogetherwiththestacktraceoftheJavaScriptexecutionatthepointitiscalled.
console.trace()ThisfunctionprintsthestacktraceoftheJavaScriptexecutionatthepointitiscalled.
console.count()Thisfunctionprintsthecurrentnumberoftimesaparticularpieceofcodehasbeenexecuted,alongwithamessage.
console.time()
console.timeEnd()
Thispairoffunctionswillprintthetimethataparticularpieceofcodebetweenthemtakesinmilliseconds.
console.profile()
console.profileEnd()
ThispairoffunctionsprofilesboththestateofQDeclarativeEngineaswellastheV8callmethods.However,youneedtoattachtheQMLProfilertooltotheapplicationbeforeconsole.profileEnd()iscalled.
Inadditiontotheprecedingusefulfunctions,thecommonDebugmodeinQtCreatorisavailableforQMLaswell.TheoperationsarealmostidenticaltoC++debugging.Youcansetthebreakpoints,observevalues,andsoon.However,thereisonemorethingprovidedforQML.It'stheQML/JSConsole!QtCreatordoesn'tshowtheQML/JSConsolebydefault,youhavetoturnitonmanually.Justclickonthesmallbutton(theredcircleinthefollowingscreenshot),andthentickQML/JSConsole:
Tip
Whentheapplicationisinterruptedbyabreakpoint,youcanusetheQML/JSConsoletoexecutetheJavaScriptexpressionsinthecurrentcontext.Youcanchangethepropertyvaluestemporarily,withouteditingthesource,andviewtheresultsintherunningapplication.
TheQML/JSConsoletabshowsthedebugoutput,boththeQtdebugmessagesandJavaScriptconsolemessages,inanappealingway.Itprovidesabuttongrouptohelpyoufilterinformation,warnings,anderrors.Therefore,justusethisQML/JSConsoletabtoreplaceApplicationOutputwhenyoudebugQtQuickapplications.
SummaryInthischapter,wewentthroughathoroughintroductiontoQtQuick.Wealsocoveredmodel-viewprogramming,whichisavitalconceptinbothQt/C++andQtQuick/QML.YoumayalsofindthatQMLisinsomewayanextensibleversionofJavaScript.ThisisanadditionalbonusforJavaScriptdevelopers.However,it'snotdifficulttostartifyou'veneverwrittenascriptbefore.Onceyoustart,you'llgettoexplorethefascinatingqualitiesofQtQuick.We'regoingtoshowyouhowtoaccesscameradevicesusingQtinthenextchapter.
Chapter4.ControllingCameraandTakingPhotosThroughthischapter,you'llfindhoweasyitistoaccessandcontrolacamerawithQt.Theexampleinthischapteralsodemonstrateshowtoutilizethestatusbarandmenubar.InadditiontothetraditionalQtWidgetapplications,thereisaQMLcameraexample,whichdoesthesamethingasQt/C++butinamoreelegantway.Thefollowingtopics,whicharecoveredinthischapter,willextendyourapplication:
AccessingthecamerainQtControllingthecameraDisplayingerrorsinthestatusbarDisplayingthepermanentwidgetsinthestatusbarUtilizingthemenubarUsingQFileDialogUsingtheQMLCamera
AccessingthecamerainQtAlthoughwewon'ttalkaboutthetechnicaldetailsofhowacameraworks,theoverviewoftheimplementationofacamerainQtwillbecovered.SupportforacameraisincludedinQtMultimedia,whichisamodulethatprovidesarichsetofQMLtypesandC++classestohandlemultimediacontent.Thingssuchasaudioplayback,camera,andradiofunctionalityareshown.Tocomplementthis,theQtMultimediaWidgetsmoduleprovideswidget-basedmultimediaclassestomaketheworkeasier.
Therearesomeclassestohelpusdealwiththecamera.Forinstance,viewfinderletsauserlookthroughthecameratocompose,andinmanycasesfocus,thepicture.InQt/C++,youcanuseQGraphicsViewalongwithQGraphicsVideoItemtodothisjob.QGraphicsViewprovidesawidgettodisplaythecontentsofQGraphicsScene.Inthiscase,QGraphicsVideoItemisanitemofthescene.Thisview-scene-itemistheGraphicsViewFramework.Fordetailsonthisconcept,visithttp://doc.qt.io/qt-5/graphicsview.html.Inthisexample,weuseQCameraViewfinder,whichisthededicatedviewfinderclassandissimplerandmorestraightforward.
Tocaptureaphoto,weneedtousetheQCameraImageCaptureclass,whichrecordsthemediacontent,whilethefocusandzoomaremanagedbytheQCameraFocusclass.
Afterall,QCameraplaysacoreroleinthisprocess.TheQCameraclassprovidesaninterfacetoaccessthecameradevices,includingwebcamsandmobiledevicecameras.Thereisanotherclass,QCameraInfo,whichcanlistalltheavailablecameradevicesandchoosewhichonetouse.Thefollowingdiagramwillhelpyouunderstandthis:
Toseeademonstration,createanewQtWidgetApplicationprojectnamedCameraDemo.EdittheCameraDemo.profile.AddmultimediamultimediawidgetstoQTbyappendingaline,asshownhere,oraddtwomodulestothepredefinedQTline:
QT+=multimediamultimediawidgets
Afterthismodification,youneedsavethefileandnavigatetoBuild|Runqmaketoloadthesenewmodules.Let'seditthemainwindow.uifileofCameraDemotoaddsomewidgetstousethecamerabyperformingthefollowingsteps:
1. Removethestatusandmenubars.Theywillbere-addedinthenextsections.Fornow,they'reremovedforacleaneruserinterface.
2. DragWidgetintotheframe.3. Changeitsnametoviewfinder.
4. Right-clickonviewfinderandselectPromoteto….5. FillinQCameraViewfinderinthePromotedclassnamefield.Remembertotick
theGlobalincludecheckboxbecausethisisapredefinedQtclass.ClickonAdd,andthenonPromote.
6. SetMainWindowtoLayOutHorizontally.7. DragaVerticallayoutontheright-handsideofviewfinder.Followingthis,
componentswillbeaddedtothelayout.8. AddLabel,whichisusedtodisplaythecapturedimage.Notethat,herewedon't
useQGraphicsView,simplybecauseQLabelisgoodenoughforthispurposeandismucheasier.
9. RenameitaspreviewLabelandclearitstext.10. DragComboBoxjustbeneathpreviewLabel.11. RenameitascameraComboBoxsinceit'llbeusedtodisplayalltheavailable
cameradevices.12. AddaPushButtonnamedcaptureButtonbelowComboBoxintheVerticallayout
tolettheuserclicktotakeaphoto.ThisbuttonshouldhavethetextCaptureonit.
Itshouldlooklikethefollowingscreenshot:
Now,gobacktothemainwindow.hedit,asshownhere:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
#include<QCamera>
#include<QCameraInfo>
#include<QCameraImageCapture>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
QList<QCameraInfo>camList;
QCamera*camera;
QCameraImageCapture*imgCapture;
privateslots:
voidonCameraChanged(int);
voidonCaptureButtonClicked();
voidonImageCaptured(int,constQImage&);
};
#endif//MAINWINDOW_H
Asusual,inordertousetheclassesintheprecedingcode,wehavetoincludethemproperly.Inadditiontothis,weusecamList,whichisatypeofQList<QCameraInfo>,tostoretheavailablecameradevices.SinceQListisatemplateclass,wehavetopassthetypeoflistelement,whichisQCameraInfointhiscase,totheconstructor.
Theseprivateslotsareresponsibleforthecameracontrols,namely,changingthecameradeviceandclickingthecapturebutton.Meanwhile,onImageCapturedisusedtohandletheimageCapturedsignalofQCameraImageCapture.
Thecontentofthemaindow.cppfileisshownasfollows:
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
camera=NULL;
connect(ui->captureButton,&QPushButton::clicked,this,
&MainWindow::onCaptureButtonClicked);
connect(ui->cameraComboBox,static_cast<void(QComboBox::*)
(int)>(&QComboBox::currentIndexChanged),this,
&MainWindow::onCameraChanged);
camList=QCameraInfo::availableCameras();
for(QList<QCameraInfo>::iteratorit=camList.begin();it
!=camList.end();++it){
ui->cameraComboBox->addItem(it->description());
}
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::onCameraChanged(intidx)
{
if(camera!=NULL){
camera->stop();
}
camera=newQCamera(camList.at(idx),this);
camera->setViewfinder(ui->viewfinder);
camera->setCaptureMode(QCamera::CaptureStillImage);
camera->start();
}
voidMainWindow::onCaptureButtonClicked()
{
imgCapture=newQCameraImageCapture(camera,this);
connect(imgCapture,&QCameraImageCapture::imageCaptured,
this,&MainWindow::onImageCaptured);
camera->searchAndLock();
imgCapture-
>setCaptureDestination(QCameraImageCapture::CaptureToFile);
imgCapture->capture();
}
voidMainWindow::onImageCaptured(int,constQImage&img)
{
QPixmappix=QPixmap::fromImage(img).scaled(ui-
>previewLabel->size(),Qt::KeepAspectRatio);
ui->previewLabel->setPixmap(pix);
camera->unlock();
imgCapture->deleteLater();
}
Let'shavealookattheconstructorfirst.WegivecameraaNULLaddresstomarkthatthereisnocameraallocatedand/oractive.Thisisexplainedlater.
SincethereareoverloadedsignalfunctionsforQComboBox::currentIndexChanged,
youhavetospecifythesignalthatyouwantwithstatic_cast.Otherwise,thecompilerwouldcomplainandfailtocompile.Onlythenewsyntaxstatementofthesignalandslotareaffected,whichmeanstheoldsyntaxstatement,shownhere,won'tposeanyerrors:
connect(ui->cameraComboBox,SIGNAL(currentIndexChanged(int)),
this,SLOT(onCameraChanged(int)));
However,asmentionedpreviously,thenewsyntaxhasmanyadvantagesandit'shighlyrecommendedthatyoureplacetheoldone.
Aswecontinue,wecanfillincamListwiththeavailablecamerassinceavailableCamerasisastaticmemberfunctionofQCameraInfo,whichreturnsalistofallavailablecamerasonthesystem.Also,youcanpassanargumenttospecifythecameraposition,suchasthefrontorbackcamera,whichisprettyusefulonmobileplatforms.
Then,weaddalltheitemsincamListtoourcameracombobox.Here,it'stheiteratorthatwalksthroughthelistandoperateseachone.Usingiteratorsisveryfastwhendealingwithalist,array,andsoon.Qtsupportsthismethod,includingbothJava-styleandSTL-styleiterators.Inthiscase,wepreferanduseSTL-styleiterators.ThedescriptionfunctionofQCameraInforeturnsahuman-readabledescriptionofthecamera.
Now,let'sgoinsideonCameraChanged.Beforetheconstructionofthecamera,weneedtocheckwhetherthereisacamerapresentalready.Ifthereis,westoptheoldcamera.Then,wesetuptheviewfinderclassusingtheviewfinderwidget,whichwedidintheDesignmode.AfterspecifyingthecapturemodetoCaptureStillImage,wecanstartthecamera.
TipThecameracannotstartagainifit'snotdeallocated(stopped).
Consequently,itgoestotheonCaptureButtonClickedslot.Similarly,theimgCaptureobjectisconstructedbypassingthecameraandthisargumentsasitsQCameratargetandQObjectparentrespectively.Then,wehavetoconnecttheimageCapturedsignaltotheonImageCapturedslotofMainWindow.Now,letcamera->searchAndLock()lockallthecamerasettings.Thisfunctionisinresponsetotheshutterbuttonbeinghalfpressed.Beforetakingashot,wesetthecapturedestinationtothefile.AlthoughitcanbesettoabufferusingtheQCameraImageCapture::CaptureToBufferflagifneeded,bearinmindthatit'snotsupportedonallplatforms.
Ifeverythinggoeswell,animagewillbecapturedbycameraandtheimageCapturedsignalwillbeemitted.Then,theonImageCapturedslotfunctionwillbeexecuted.Insidethisfunction,wescalethecapturedimagetothesizeofourpreviewLabel.Then,justsetQPixmapforpreviewLabelandunlockcamera.Intheend,wecallthedeleteLater()functiontosafelydeletetheimgCaptureinstance.
NoteYoushouldexplicitlyindicateQt::KeepAspectRatio,otherwiseitusesthedefaultQt::IgnoreAspectRatioflag.
Now,runthedemoandseewhatyouget.
Justaswedidinthepreviouschapters,feelfreetochangethewindow'stitle,applicationfont,andsoon.Thesetrivialtweakswon'tbedetailedanymore.
ControllingthecameraTheQCameraFocusclassismentionedtocontrolthezoomandfocusofthecamera.Speakingofzoom,Qtsupportsbothopticalanddigitalzoom.Asweallknow,opticalzoomoffersabetterqualitythandigital.Hence,opticalzoomshouldtakepriorityoverdigital.
DragahorizontalsliderandalabeltoMainWindowpane'sverticalLayoutjustabovethecapturebutton.NamethemzoomSliderandzoomLabel,respectively.RemembertochangethetextofzoomLabeltoZoomandHorizontalinalignmenttoAlignHCenter.SinceQtdoesn'tprovideafloatingpointnumberslider,wesimplymultiply10togetanintegerintheslider.Hence,changetheminimumvalueofzoomSliderto10,whichmeanszoomby1.0.
IncludeQCameraFocusinmainwindow.handaddthesetwoprivatemembers:
QCameraFocus*cameraFocus;
qrealmaximumOptZoom;
TipNoteverycamerasupportszoom.Ifitdoesn't,themaximumzoomis1.0,whichappliestobothopticalanddigitalzoom.
Thereisatypenamedqreal,whichisbasicallyadoublevalue.ItwasfloatontheARMplatformsforperformanceconcernsanddoubleonothers.However,QthasuseddoubleonARMbydefaultsincetheQt5.2version.Anyway,usingqrealisrecommendediftheapplicationisdeployedondifferenthardwareplatforms.
Anewslotalsoneedstobedeclared:
voidonZoomChanged(int);
Now,connectzoomSliderintheMainWindowclass'constructor:
connect(ui->zoomSlider,&QSlider::valueChanged,this,
&MainWindow::onZoomChanged);
However,QCameraFocuscan'tbeconstructedexplicitly.Instead,itcanonlyberetrievedfromtheQCameraclass.So,wegetcameraFocusjustaftertheconstructionofthecameraargumentinsideonCameraChanged:
cameraFocus=camera->focus();
Then,setupmaximumOptZoomandthemaximumvalueofzoomSlider:
maximumOptZoom=cameraFocus->maximumOpticalZoom();
ui->zoomSlider->setMaximum(maximumOptZoom*cameraFocus-
>maximumDigitalZoom()*10);
Ifthecameradoesn'tsupportzoomatall,thesliderwon'tbeabletoslide.ThedefinitionoftheonZoomChangedslotisshowninthefollowinglines:
voidMainWindow::onZoomChanged(intz)
{
qrealzoom=qreal(z)/10.0;
if(zoom>maximumOptZoom){
cameraFocus->zoomTo(maximumOptZoom,zoom/
maximumOptZoom);
}
else{
cameraFocus->zoomTo(zoom,1.0);
}
}
Asyoucansee,thefirstparameterofzoomToistheopticalzoomfactorwhiletheotheristhedigitalzoomfactor.
DisplayingerrorsonthestatusbarFirstofall,therecouldbeerrorsduringthewholecameraprocessandit'sagoodpracticetomaketheuserawareofwhattheerroris.Itcanbedonebyapop-updialogand/orstatusbar.Youdon'twanttoalerttheusertoeverytrivialerror.Therefore,it'dbebettertouseapop-updialogonlyforcriticalerrors,whiledisplayingnon-criticalerrorsandwarningsonthestatusbar.
Qtbegansupportingthestatusbaralongtimeago.TheQStatusBarclassistheonethatprovidesahorizontalbarsuitableforpresentingstatusinformation.Thestatusofthecameracanbedisplayedaswellandit'llbeintroducedinlatertopics.
Tousethestatusbar,editmainwindow.ui,right-clickonMainWindow,andselectCreateStatusBarifitdoesn'texistorwaspreviouslyremoved.
Then,weshoulddeclaretwoslotstohandlethecameraandimagecaptureerrors,respectively.Addthesetwolinestoprivateslotsinmainwindow.h:
voidonCameraError();
voidonImageCaptureError(int,QCameraImageCapture::Error,
constQString&);
Thedefinitionsinmainwindow.cppareshownasfollows:
voidMainWindow::onCameraError()
{
ui->statusBar->showMessage(camera->errorString(),5000);
}
voidMainWindow::onImageCaptureError(int,
QCameraImageCapture::Error,constQString&err)
{
ui->statusBar->showMessage(err,5000);
imgCapture->deleteLater();
}
ThissimplymakesstatusBarshowatemporarymessageforfiveseconds.EvenifyoupasszerotoshowMessage,it'sstillatemporarymessage.Inlatercases,itwon'tdisappearafteragivenperiod;instead,it'lldisappearifthereisanewtemporarymessage.
SincethesignalerrorisdifferentinQCamerafromQCameraImageCapture,weusedifferentslotstohandleit.ForQCamera,theerrorsignalfunctionhasQCamera::Errorastheonlyargument.
Bycontrast,QCameraImageCapture::errorprovidesthreearguments:int,QCameraImageCapture::Error,andaconstQStringreference.Therefore,wecanmakeuseofthissignalbyusingitserrorstringdirectly.
Don'tforgettoconnectthesignalsandslots.Here,insidetheonCameraChangedfunction,justafterthecameraconstruction,connectthecameraerrorandtheonCameraErrorslot.
connect(camera,static_cast<void(QCamera::*)
(QCamera::Error)>(&QCamera::error),this,
&MainWindow::onCameraError);
AsthereisanotheroverloadedfunctioncallederrorintheQCameraclass,wehavetousestatic_casttospecifythesignalfunction,aswedidinQComboBox.
Similarly,addtheconnectstatementaftertheimgCaptureobject'sconstructionintheonCaptureButtonClickedfunction.
connect(imgCapture,static_cast<void(QCameraImageCapture::*)
(int,QCameraImageCapture::Error,constQString&)>
(&QCameraImageCapture::error),this,
&MainWindow::onImageCaptureError);
Itisanotheroverloadederrorsignalfunction.However,it'stediousbecauseofthreearguments.
PermanentwidgetsinthestatusbarSometimes,wewantasortofindicatorinsidethestatusbartodisplayreal-timestatus,suchasthecamerastatus.Thisisinappropriateifit'scoveredbytemporarymessages.Insuchacase,QStatusBarprovidestheinsertPermanentWidgetfunctiontoaddawidgettothestatusbarpermanently.Itmeansthatitwon'tbeobscuredbytemporarymessagesandislocatedonthefarrightofthestatusbar.
Firstly,let'smakeacamerastatuswidget.AddanewC++classnamedCameraStatusWidgetthatinheritsfromQWidget,butuseQLabelasthebaseclass.WeuseQLabelasthebaseclassbecausethestatusofthecameraisdisplayedintextandisbasicallyacustomizedlabelwidget.Thecamerastatuswidget.hcontentisshownasfollows:
#ifndefCAMERASTATUSWIDGET_H
#defineCAMERASTATUSWIDGET_H
#include<QLabel>
#include<QCamera>
classCameraStatusWidget:publicQLabel
{
Q_OBJECT
public:
explicitCameraStatusWidget(QWidget*parent=0);
publicslots:
voidonCameraStatusChanged(QCamera::Status);
};
#endif//CAMERASTATUSWIDGET_H
Besidesthe#include<QCamera>,weonlyaddadeclarationoftheonCameraStatusChangedslottothisheaderfile.Therelevantcamerastatuswidget.cppsourcefileispastedasfollows:
#include"camerastatuswidget.h"
CameraStatusWidget::CameraStatusWidget(QWidget*parent):
QLabel(parent)
{
}
voidCameraStatusWidget::onCameraStatusChanged(QCamera::Status
s)
{
QStringstatus;
switch(s){
caseQCamera::ActiveStatus:
status=QString("Active");
break;
caseQCamera::StartingStatus:
status=QString("Starting");
break;
caseQCamera::StoppingStatus:
status=QString("Stopping");
break;
caseQCamera::StandbyStatus:
status=QString("Standby");
break;
caseQCamera::LoadedStatus:
status=QString("Loaded");
break;
caseQCamera::LoadingStatus:
status=QString("Loading");
break;
caseQCamera::UnloadingStatus:
status=QString("Unloading");
break;
caseQCamera::UnloadedStatus:
status=QString("Unloaded");
break;
caseQCamera::UnavailableStatus:
status=QString("Unavailable");
break;
default:
status=QString("Unknown");
}
this->setText(status);
}
TipAlwayshandleexceptionsintheswitchstatements.
QCamera::Statusisanenumtype.That'swhywehavetouseaswitchstatementtotranslatethestatustostring.Sincewehaveourcamerastatuswidgetnow,it'stimetoaddittothestatusbar.Editmainwindow.handaddaCameraStatusWidgetpointerasfollows:
CameraStatusWidget*camStatusWid;
Don'tforgettoincludethecamerastatuswidget.hheaderfile.Then,setupcamStatusWidjustafterui->setupUi(this)byaddingthefollowinglines:
camStatusWid=newCameraStatusWidget(ui->statusBar);
ui->statusBar->addPermanentWidget(camStatusWid);
NavigatetotheonCameraChangedfunction;weneedtoconnecttheQCamera::statusChangedsignal.Justaddthefollowinglineafterconstructionofthecamera:
connect(camera,&QCamera::statusChanged,camStatusWid,
&CameraStatusWidget::onCameraStatusChanged);
Likewise,wecanaddcurrentzoomtothestatusbar.Infact,forthissmallandeasy-to-dowidget,wedon'tneedtocreateanewclass.Instead,we'llusetheexistingQLabelclasstoachievethisbydeclaringanewmember.Inmainwindow.h,addanewprivatemember:
QLabel*zoomStatus;
Meanwhile,constructandinsertthezoomStatusintostatusBarintheMainWindowclassconstructorinmainwindow.cpp:
zoomStatus=newQLabel(QString::number(qreal(ui->zoomSlider-
>value())/10.0),this);
ui->statusBar->addPermanentWidget(zoomStatus);
Here,weuseanumberfunction,whichisastaticpublicfunctionoftheQStringclasstoconvertanumber(itcanbedoubleorinteger)toQString.InordertoupdatezoomStatusintime,appendthislinetotheonZoomChangedfunction:
zoomStatus->setText(QString::number(zoom));
Afterthesemodifications,theapplicationwillrunasshowninthefollowingscreenshot:
UtilizingthemenubarNowthatyouhavefinishedthebaratthebottom,it'stimetobegintheoneontop—themenubar.Similartothestatusbar,right-clickonMainWindowintheDesignmode,andselectCreateMenuBarifitdoesn'texistorwaspreviouslyremoved.
Then,justfollowthehints.AddaFilemenucontainingtheExitaction.AnothermenucouldbeAbout,whichcontainstheAboutCameraDemoaction.YoushouldknowthattheseactionsareabletochangeintheActionEditorpanel,whichisinthesameplaceasSignals&SlotsEditor.
Asshowninthefollowingscreenshot,thenamesoftheseactionsarechangedtoactionAboutandactionExit,respectively.Inadditiontothis,thereisashortcut,Ctrl+Q,foractionExit.Justdouble-clickontheactionandaddshortcutsbypressingtheshortcutyouwant.Thisisshowninthefollowingscreenshot:
WealreadyusedQMenuandQActioninChapter2,BuildingaBeautifulCross-platformClock.ThedifferencehereisthatyouuseQMenuasthemenubar,andsetitupitinDesignmodeinsteadofwritingcode.ButwhyisitcalledQAction?Thisisbecausetheusercantriggeracommandonthemenu,toolbar,orkeyboardshortcut.Theyexpectthesamebehaviorregardlessofwhereitis.Therefore,itshouldbeabstractedintoanaction,whichcanbeinsertedintothemenuortoolbar.YoucansetittothecheckableQActionoptionanduseitasasimpleQCheckboxoption.
Let'sfinishactionExitfirst,sinceit'ssimplerthantheotherone.ForactionExit,weonlyneedoneconnectstatementtomakeitwork.AddthefollowingstatementtotheMainWindowclassconstructorinthemainwindow.cppfile:
connect(ui->actionExit,&QAction::triggered,this,
&MainWindow::close);
Thetriggeredsignalwillbeemittedbyeitheramouseclickorakeyboardshortcut(ifthereisashortcut).SinceweconnectittothecloseslotofMainWindow,it'llcloseMainWindow,whichresultsinexitingtheentireapplication.
Meanwhile,weneedtodeclareaslottofulfilltheconnectionwithactionAbout.Asusual,declareitinthemainwindow.hheaderfile.
voidonAboutTriggered();
Youmaythinkthatwe'regoingtocreateanewclassjusttoshowanAboutdialog.Well,wedon'thavetocooktheAboutdialogourselvesbecauseQthasalreadydonethisforus.It'sincludedinQMessageBox,soyoushouldincludeitwiththefollowingline:
#include<QMessageBox>
Thisisthedefinitionoftheslot:
voidMainWindow::onAboutTriggered()
{
QMessageBox::about(this,QString("About"),QString("Camera
DemonstrationofQt5"));
}
NoteTheQMessageBoxclassprovidesamodaldialogforinformingoraskingtheuseraquestionandreceivingananswer.
Almosteverykindofpop-updialogcanbefoundinQMessageBox.Here,weusethestaticAboutfunctiontocreateanAboutdialog.Ithasthreearguments.ThefirstoneistheparentQObjectpointer,followedbythetitleandcontext.RemembertoconnectthesignalandslotintheMainWindowclassconstructor.
connect(ui->actionAbout,&QAction::triggered,this,
&MainWindow::onAboutTriggered);
Ifyoucompileandruntheapplicationagain,trytotriggertheAboutdialog,whichwouldlooksimilartothefollowingscreenshot:
NoteInadditiontoAbout,thereareotherusefulstaticpublicmembersofQMessageBox.Mostcommonly,critical,information,question,and,warningareusedtopopupamessagebox.Sometimes,you'llseeanAboutQtentryinthemenubar,whichistocalltheaboutQtfunction.
Infact,theAboutdialogwilldisplayaniconifitexists.Thereisanemptyspacesinceitlacksanicon.Theorderofthesearchiconsisshownasfollows:
Thisfirsticonwillbeparent->icon(),ifitexists.Thesecondiconwillbethetop-levelwidget,whichcontainsparent.Thethirdiconwillbetheactivewindow.ThefourthiconwillbetheInformationicon.
UsingQFileDialogThelaststepoftakingaphotoistosaveittodisk.Atthispoint,theprogramsavesanimagetothefile,butthelocationisdeterminedbythecamerabackend.Wecansimplyuseadialog,lettingtheuserchoosethedirectoryandthefilenameofthephoto.ThereisaQFileDialogclasstohelpmaketheworkeasier.TheeasiestwaytocreateaQFileDialogclassistousethestaticfunctions.Therefore,edittheonCaptureButtonClickedfunctioninthemainwindow.cppfile.
voidMainWindow::onCaptureButtonClicked()
{
imgCapture=newQCameraImageCapture(camera,this);
connect(imgCapture,static_cast<void
(QCameraImageCapture::*)(int,QCameraImageCapture::Error,
constQString&)>(&QCameraImageCapture::error),this,
&MainWindow::onImageCaptureError);
connect(imgCapture,&QCameraImageCapture::imageCaptured,
this,&MainWindow::onImageCaptured);
camera->searchAndLock();
imgCapture-
>setCaptureDestination(QCameraImageCapture::CaptureToFile);
QStringlocation=QFileDialog::getSaveFileName(this,
QString("SavePhotoAs"),QString(),"JPEGImage(*.jpg)");
imgCapture->capture(location);
}
Here,we'reusingthegetSaveFileNamestaticfunctiontocreateafiledialogtoreturnthefilethattheuserselected.IftheuserclicksonCancel,thelocationtypewouldbeanemptyQStringreferenceandtheimagewillbestoredinadefaultlocation.Thefiledoesn'tneedtoexist.Infact,ifitexists,therewillbeanoverwritedialog.Thisfunction'sprototypeispastedasfollows:
QStringQFileDialog::getSaveFileName(QWidget*parent=0,
constQString&caption=QString(),constQString&dir=
QString(),constQString&filter=QString(),QString*
selectedFilter=0,Optionsoptions=0)
ThefirstargumentistheQObjectparent,asusual.Thesecondoneisthedialog'stitle,followedbythedefaultdirectory.Thefilterobjectisusedtorestrictthefiletypeandit'spossibletousemultiplefiltersthatareseparatedbytwosemicolons,;;.Hereisanexample:
"JPEG(*.jpeg*.jpg);;PNG(*.png);;BMP(*.bmp)"
SettingselectedFiltercanchangethedefaultfilter.Lastly,Optionsisusedtodefinethebehaviorsofthefiledialog.Formoredetails,refertotheQFileDialogdocumentation.
QMLcameraSofar,wetalkedabouthowtoaccessandcontrolthecamerainQt/C++.Nowit'stimetoseehowQMLdoesinthisarea.Althoughtherearesomelimitations,you'llfindit'smucheasierandmoreeleganttodothisinQtQuick/QMLbecauseofthemanypackagesthatQthastooffer.
CreateanewQtQuickapplicationproject.Themain.qmlcontentisshownasfollows:
importQtQuick2.3
importQtQuick.Controls1.2
importQtMultimedia5.3
import"qrc:/"
ApplicationWindow{
visible:true
width:640
height:480
title:qsTr("QMLCameraDemo")
Camera{
id:camera
imageCapture{
onImageCaptured:{
photoPreview.source=preview
photoPreview.visible=true;
previewTimer.running=true;
}
}
}
VideoOutput{
id:viewfinder
source:camera
anchors.fill:parent
}
Image{
id:photoPreview
anchors.fill:viewfinder
}
Timer{
id:previewTimer
interval:2000
onTriggered:photoPreview.visible=false;
}
CaptureButton{
anchors.right:parent.right
anchors.verticalCenter:parent.verticalCenter
diameter:50
}
}
Letmewalkyouthroughthisone.
CameraandVideoOutputareprovidedbytheQtMultimediamodule.SimilartotheQt/C++classes,theCameratypeisidenticaltotheQCameraclass.ThepreviewisdealtwithdifferentlywhenVideoOutputisusedasviewfinder.Animageobjectisusedtodisplaythecapturedphotoandit'sonlyvisiblefor2secondseachtimeapictureistaken.ThisphotoPreviewiscontrolledbythetimer,previewTimer.Inotherwords,the2secondsshowupofphotoPreviewdependsonthispreviewTimertimer.Atthesametime,thecameratype'simageCapturewillprovidethepreviewimagetophotoPreviewandturnonpreviewTimeronceitcapturesaphoto.
ThelastpieceisCaptureButton,whichisnotprovidedbyQtbutwritteninanotherfile,CaptureButton.qml.Itscontentisshowninthefollowingcode:
importQtQuick2.3
Rectangle{
propertyrealdiameter
width:diameter
height:diameter
color:"blue"
border.color:"grey"
border.width:diameter/5
radius:diameter*0.5
MouseArea{
anchors.fill:parent
onClicked:camera.imageCapture.capture()
}
}
SincethereisnocircularshapeprovidedbyQtQuick,weusethisRectangleobjectasaworkaroundtodisplayitasacircle.Justlikewhatwedidinthepreviouschapter,defineadiameterpropertytoholdbothheightandwidth.Thetrickistheradiusvalue.Bysettingittohalfthediameter,thisRectangleobjectbecomescircular.Lastbutnotleast,addMouseAreatorespondtoauser'sclick.It'sapitythatMouseAreacan'tbecircular,sojustleaveitandfillinthebutton.
Nowyoucanrunyourapplication,anditshouldbesomethingsimilartothis:
It'snotaspowerfulastheQt/C++demo.Thefirstthingyouprobablynoticeisthatyoucan'tchangethecameradevice.It'smissinginthecurrentversionofQt,butitshouldbesupportedinthefuture.Inthemeantime,theonlysolutiontothisistowriteaC++pluginwhilethemainpartisstillwritteninQML.SincedevelopingaC++pluginforQMLwillbecoveredinalaterchapter,we'llsimplyskipthisparthere.
WecanmakethefiledialoginQMLinanevenmoreelegantway.QtQuickprovidescommonly-useddialogsthroughtheQtQuick.Dialogsmodule.Therefore,firstimportthismodule:
importQtQuick.Dialogs1.2
What'reweinterestedinistheFileDialogtype,whichprovidesabasicfilechooser.Itallowstheusertoselectexistingfilesand/ordirectories,orcreatenewfilenames.Itusesthenativeplatformfiledialogswhereverpossible.Tousethistype,addFileDialoginsideApplicationWindowinthemain.qmlfile.
FileDialog{
id:saveDlg
propertystringlocation
title:"SavePhotoAs"
selectExisting:false
nameFilters:["JPEG(*.jpg)"]
onAccepted:{
location=saveDlg.fileUrl
camera.imageCapture.captureToLocation(location.slice(8))
}
}
ThestringtypeinQMLisanextendedversionoftheJavaScriptstringtype.Whereverpossible,youshouldavoidthevarkeywordandusetheexacttype,suchasint,double,andstring.AccordingtotheQMLdocumentation,thiswillimproveperformancesincethemachinedoesn'tneedtoguessthedatatype.Here,wedeclarelocation,whichisastringtype,whiletherestofitspropertiesaresimilartothedialogsettingsinQt/C++,itstitle(caption),andnameFilters.YoushouldsettheselectExistingpropertytofalse,asitistruebydefault.Otherwise,it'llbehavelikeanopenfiledialog.
InsidetheonAcceptedhandler,passthefileUrlvaluetolocationfirst.Thishandleristheresponsetotheacceptedsignal,whichisemittediftheuserselectsafilesuccessfully.ThefileUrlpropertywillthenbechanged.It'sinaURIformat,whichhasanextrafile:///prefix.Inadditiontothis,thereiscurrentlyanissueifweexecutesliceonfileUrldirectly.Soasaworkaround,weusetheexplicitlydeclaredstringlocationtoinvoketheslicefunction.ThisisaJavaScriptfunction,whichwillreturnanewstringtypethatcontainstheextractedpartsofastring.Theslicemethod'sprototypeisslice(start,end)whereendwillbetheendofthestringtypeifit'somitted.Also,notethatthecharacteratthestartpositionisincludedandtheindexstartsfromzero.Afterthat,wesimplycallthecaptureToLocationfunctionofimageCapturetostoretheimageattheselectedlocation.
Inordertomakeitwork,wehavetochangethebehaviorofCaptureButton.EdittheCaptureButton.qmlfileandchangeMouseArea,asshowninthefollowinglines:
MouseArea{
anchors.fill:parent
onClicked:saveDlg.open()
onPressed:parent.color="black"
onReleased:parent.color="blue"
}
Inadditiontothis,tochangetheonClickedhandler,wealsoaddonPressedandonReleasedtoletithavethepusheffect.Asyoucansee,theopen()functionwillexecuteourFileDialog.Onadesktopoperatingsystem,suchasWindows,theplatformfiledialogisusedasshownhere:
TheinnercircleofCaptureButtonwillbecomeblackonceit'spressed,andthengobacktobluewhenthemouseisreleased.Althoughit'sjustaminorvisualeffect,itdefinitelyimprovestheuserexperience.
"Donotfailtocommitanactofkindnessjustbecauseitissmallinscale."
TocompletethisQMLcameraapplication,weneedtoaddazoomcontrolaswedidfortheQt/C++camera.AddanewQMLfilenamedZoomControl.qml,whosecontentisshownasfollows:
importQtQuick2.3
Item{
propertyrealzoom:camera.opticalZoom*camera.digitalZoom
functionzoomControl(){
if(zoom>camera.maximumOpticalZoom){
camera.digitalZoom=zoom/camera.maximumOpticalZoom
camera.opticalZoom=camera.maximumOpticalZoom
}
else{
camera.digitalZoom=1.0
camera.opticalZoom=zoom
}
}
Text{
id:indicator
anchors.fill:parent
horizontalAlignment:Text.AlignHCenter
verticalAlignment:Text.AlignVCenter
color:"darkgrey"
font.bold:true
font.family:"Segoe"
font.pointSize:20
style:Text.Raised
styleColor:"black"
}
Timer{
id:indicatorTimer
interval:2000
onTriggered:indicator.visible=false
}
MouseArea{
anchors.fill:parent
onWheel:{
if(wheel.angleDelta.y>0){
zoom+=1.0
if(zoom>camera.maximumOpticalZoom*
camera.maximumOpticalZoom){
zoom-=1.0
}
else{
zoomControl()
}
}
else{
zoom-=1.0
if(zoom<camera.maximumOpticalZoom*
camera.maximumOpticalZoom){
zoom+=1.0
}
else{
zoomControl()
}
}
indicator.text="X"+zoom.toString()
indicator.visible=true
indicatorTimer.running=true
}
}
}
First,wedeclarepropertyoftherealtypetostorethecurrentzoom,whoseinitial
valueisthecamera'scurrentzoom,whichisitselfthemultiplicationofthecurrentdigitalandopticalzoom.ThisisfollowedbyaJavaScript-stylefunction,zoomControl.Asmentionedbefore,youcanuseJavaScriptinQMLanywhereseamlessly.ThisfunctionisidenticaltotheQt/C++slot,onZoomChanged,intheprevioustopic.
Then,thereisaTextelementusedtodisplaythecurrentzoomfunctiononscreen.ThesearejustsomevisualcustomizationsinsidetheTextelement,whichincludecenteringintheparentbysettingboththehorizontalandverticalalignments.
What'snextisaTimerelementthatcontrolsthevisibilityofText,similartothecontrollerofphotoPreview.
ThelastbutalsothetrickiestisMouseArea.Weusethemousewheeltocontrolthezoom,sothehandlerthatcangetthewheeleventisonWheel.Thewheel.angleDelta.yisthewheel,whichisrotatedtoaverticalorientation.Ifit'spositive,itgoesup;otherwise,itgoesdown.Itzoomsinwithapositivevalue,andzoomsoutwithnegativeone.YouhavetoensurethatthenewzoomiswithinthesupportedzoomrangeofcamerabeforeinvokingthezoomControlfunction.Afterthis,lettheTextindicatordisplayzoomandturnonTimersothatit'sonlyvisiblefor2seconds.Youcanalsoseethatthereisabuilt-infunctionfortherealelementtoconvertittostring,justliketheQString::numberfunctioninQt/C++.
Afterallthis,editmain.qmlandaddZoomControltotheapplication,asshowninthefollowingcode:
ZoomControl{
anchors.fill:viewfinder
}
BeawarethatZoomControlshouldfillinviewfinderinsteadofparent;otherwise,itmaygetoverlaidbyothercomponents,suchasviewfinder.
GivethisQMLcameraatestrunandcomparewhichoneisbetter.
SummaryBytheendofthischapter,youshouldbeabletowriteapplicationsthatcanaccesscameradevicesineitherQt/C++orQML.What'smoreisthatyoushouldbeabletoutilizethestatusandmenubarintraditionaldesktopapplications,whicharehistoricallyimportantandcontinuetoplayacrucialroleasinteractivefunctionalwidgets.Inadditiontothis,don'tforgetthefiledialogandmessageboxsincetheymakeyourcodingworkeasier.Inthenextchapter,we'regoingtotalkaboutanadvancedtopic,plugins,whichisapopularwaytoextendlargeapplications.
Chapter5.ExtendingPaintApplicationswithPluginsPluginsenableyoutomakeyourapplicationextendableandfriendlyforotherdevelopers.Therefore,inthischapter,we'llguideyouinhowtowritepluginsforQtapplications.ApaintapplicationdemonstratestherecipeforQt/C++.AsimpledemonstrationshowsyouhowtowriteaC++pluginforQML.Thetopicswewillcoverinthischapterarelistedasfollows:
DrawingviaQPainterWritingstaticpluginsWritingdynamicpluginsMergingpluginandmainprogramprojectsCreatingaC++pluginsforQMLapplications
DrawingviaQPainterBeforewegetstarted,letmeintroducetheQPainterclasstoyou.Thisclassperformslow-levelpaintingonwidgetsandotherpaintdevices.Infact,everythingdrawnonthescreeninaQtapplicationistheresultofQPainter.Itcandrawalmostanything,includingsimplelinesandalignedtext.Thankstothehigh-levelAPIsthatQthasprovided,it'sextremelyeasytousetheserichfeatures.
Qt'spaintsystemconsistsofQPainter,QPaintDevice,andQPaintEngine.Inthischapter,wewon'tneedtodealwiththelattertwo.Therelationsdiagramissketchedasfollows:
QPainterisusedtoperformdrawingoperations,whileQPaintDeviceisanabstractionofatwo-dimensionalspacethatcanbepaintedonbyusingQPainter.QPaintEngineprovidestheinterfacethatthepainterusestodrawontodifferenttypesofdevices.NotethattheQPaintEngineclassisusedinternallybyQPainterandQPaintDevice.It'salsodesignedtobehiddenfromprogrammersunlesstheycreatetheirowndevicetype.
Sobasically,whatweneedtoconcentrateonisQPainter.Let'screateanewproject
anddosomeexercisesinit.Thenewpainter_demoprojectisaQtWidgetapplication.QuicklycreateitandaddanewC++CanvasclassthatinheritsfromQWidget.Canvasisourcustomizedwidgetwhoseheaderfileisshownasfollows:
#ifndefCANVAS_H
#defineCANVAS_H
#include<QWidget>
classCanvas:publicQWidget
{
Q_OBJECT
public:
explicitCanvas(QWidget*parent=0);
private:
QVector<QPointF>m_points;
protected:
voidpaintEvent(QPaintEvent*);
voidmousePressEvent(QMouseEvent*);
voidmouseMoveEvent(QMouseEvent*);
voidmouseReleaseEvent(QMouseEvent*);
};
#endif//CANVAS_H
TheQVectorclassisatemplateclassthatprovidesafastanddynamicarray.It'sfastbecausetheitemsarestoredinadjacentmemorylocations,whichmeansthattheindexingtimeisconstant.Here,westoretheQPointFelementsinm_points,whereQPointFisaclassthatdefinesapointusingafloatingpointprecision.
Inaprotectedscope,therearefoureventfunctions.We'refamiliarwiththesemouseevents.Theleadingone,whichisalsothenewone,isthepaintEventfunction.Sincewe'repaintingonthewidget,QPaintershouldonlybeusedinsidethepaintEventfunction.
Thedefinitionsofthefunctionsincanvas.cppareshownasfollows:
#include<QStyleOption>
#include<QPainter>
#include<QPaintEvent>
#include<QMouseEvent>
#include"canvas.h"
Canvas::Canvas(QWidget*parent):
QWidget(parent)
{
}
voidCanvas::paintEvent(QPaintEvent*)
{
QPainterpainter(this);
QStyleOptionopt;
opt.initFrom(this);
this->style()->drawPrimitive(QStyle::PE_Widget,&opt,
&painter,this);
painter.setPen(QColor(Qt::black));
painter.setRenderHint(QPainter::Antialiasing);
painter.drawPolyline(m_points.data(),m_points.count());
}
voidCanvas::mousePressEvent(QMouseEvent*e)
{
m_points.clear();
m_points.append(e->localPos());
this->update();
}
voidCanvas::mouseMoveEvent(QMouseEvent*e)
{
m_points.append(e->localPos());
this->update();
}
voidCanvas::mouseReleaseEvent(QMouseEvent*e)
{
m_points.append(e->localPos());
this->update();
}
First,let'scheckwhat'sinsidethepaintEventfunction.ThefirstclauseistoinitializeaQPainterobject,whichusesthisasQPaintDevice.Well,thereisanalternatewaytoinitializeaQPainterclass,whichisdemonstratedhere:
QPainterpainter;
painter.begin(this);
painter.drawPolyline(m_points.data(),m_points.count());
painter.end();
Ifyouusethemethodshownintheprecedingcode,remembertocalltheend()functiontodestroypainter.Bycontrast,ifyouinitializeQPainterbyitsconstructor,thedestructorwillautomaticallycalltheend()function.However,theconstructorwon'treturnavalueindicatingwhetheritwasinitializedsuccessfullyornot.Thus,it'dbebettertochoosethelattermethodwhendealingwithanexternalQPaintDevicesuchasaprinter.
Aftertheinitialization,weuseQStyleOption,whichcontainsalltheinformationthat
theQStylefunctionsneedtodrawagraphicalelementandmakeourcustomizedwidgetstyle-aware.WesimplyusetheinitFromfunctiontogetthestyleinformation.Then,wegettheQStylefunctionofourwidgetanddrawQStyle::PE_Widgetwithpainterusingthestyleoptionsspecifiedbyopt.Ifwedon'twritethesethreelines,wecan'tchangethewidgetdisplaystyle,suchasthebackgroundcolor.
Then,weletthepainteruseablackpentodrawananti-aliasingpolylineonthewidget.Here,anoverloadedsetPenfunctionisused.Thepainter.setPen(QColor(Qt::black))functionwillsetasolid-linestylepenwithawidthof1andthecolorinblack.Thepainter.setRenderHint(QPainter::Antialiasing)functionwillmakethedrawingsmooth.
NoteAsecondargument,bool,controlstherenderhint.It'struebydefault,whichmeansthatyouneedtoturnontherenderhint.Youcanturnoffarenderhintbypassingafalsevalue,though.
Alistoftheavailablerenderhintsareshownasfollows:
QPainter::Antialiasing
QPainter::TextAntialiasing
QPainter::SmoothPixmapTransform
QPainter::Qt4CompatiblePainting
Therearealsotwoobsoletehints:QPainter::HighQualityAntialiasingandQPainter::NonCosmeticDefaultPen.ThefirstoneisreplacedbyQPainter::AntialiasingandthesecondisuselessbecauseQPenisnon-cosmeticbydefaultnow.
Finally,thedrawPolylinefunctionwilldrawapolyline,whichismadefromthemousemovements,ontheCanvaswidget.ThefirstargumentisthepointertoaQPointForQPointarray,whilethesecondoneisthenumberofitemsinsidethatarray.
Speakingofmousemovements,threemouseeventfunctionsareusedtotrackthemouse.Infact,they'reprettyself-explanatory.Whenamousepresseventoccurs,purgethepointsarraybecauseit'sobviouslyanewpolylinenow,andthenaddthemousepositionbyinvokingalocalPos()function.ThelocalPos()functionwillreturnthepositionofthemouserelativetothewidgetoritemthatreceivedtheevent.AlthoughyoucangetaglobalpositionbythescreenPos()andglobalPos()function,inmostcases,weonlyneedalocalposition.Attheendoftheseeventfunctions,call
update()torepaintthewidgettoshowthemousemovingpathasapolyline.
Now,editmainwindow.uiintheDesignmode.Removethestatusbarsincewewon'tuseitinthischapter,butkeepthemenubar.DragWidgettocentralWidgetandrenameitascanvas.Right-clickoncanvasandselectPromoteto…,andthenfillinCanvasinPromotedclassname.Now,clickonAdd,andthenonPromote.Youshouldn'tchecktheGlobalincludeboxbecausethecanvas.hheaderfileisinourprojectdirectoryinsteadoftheglobalincludedirectory.
InsideProperty,editstyleSheet,inputbackground-color:rgb(255,255,255);sothatthecanvashasawhitebackground.Then,changetheMainWindowclass'layouttoLayOutHorizontallyorLayOutVerticallysothatthecanvaswidgetcanfillthewholeframe.Giveyourapplicationarunnow;youshouldexpectasimplewhitepainterasfollows:
Thispainteristoosimpletoholdtheoldlines.WhileQtdoesn'tprovideanAPItopaintontheoldscene,QImagecangetusoutofthisdilemma.Inotherwords,whenthemousemoves,wepaintastrokeonaQImageobject,andthenpaintthisQImageobjectontoCanvas.
Thenewheaderfile,canvas.h,isasshownasfollows:
#ifndefCANVAS_H
#defineCANVAS_H
#include<QWidget>
classCanvas:publicQWidget
{
Q_OBJECT
public:
explicitCanvas(QWidget*parent=0);
private:
QVector<QPointF>m_points;
QImageimage;
voidupdateImage();
protected:
voidpaintEvent(QPaintEvent*);
voidmousePressEvent(QMouseEvent*);
voidmouseMoveEvent(QMouseEvent*);
voidmouseReleaseEvent(QMouseEvent*);
voidresizeEvent(QResizeEvent*);
};
#endif//CANVAS_H
ThedifferencesincludethedeclarationofaQImageobject,image;privatememberfunction,updateImage();andareimplementedfunction,resizeEvent(QResizeEvent*).ThepaintEvent(QPaintEvent*)functionisalsochangedtodrawtheimageobjectinstead,whereastherearemoremodificationsinthecanvas.cppsourcefilethantheheaderfile,whosecontentisshownhere:
#include<QStyleOption>
#include<QPainter>
#include<QPaintEvent>
#include<QMouseEvent>
#include<QResizeEvent>
#include"canvas.h"
Canvas::Canvas(QWidget*parent):
QWidget(parent)
{
}
voidCanvas::paintEvent(QPaintEvent*e)
{
QPainterpainter(this);
QStyleOptionopt;
opt.initFrom(this);
this->style()->drawPrimitive(QStyle::PE_Widget,&opt,
&painter,this);
painter.drawImage(e->rect().topLeft(),image);
}
voidCanvas::updateImage()
{
QPainterpainter(&image);
painter.setPen(QColor(Qt::black));
painter.setRenderHint(QPainter::Antialiasing);
painter.drawPolyline(m_points.data(),m_points.count());
this->update();
}
voidCanvas::mousePressEvent(QMouseEvent*e)
{
m_points.clear();
m_points.append(e->localPos());
updateImage();
}
voidCanvas::mouseMoveEvent(QMouseEvent*e)
{
m_points.append(e->localPos());
updateImage();
}
voidCanvas::mouseReleaseEvent(QMouseEvent*e)
{
m_points.append(e->localPos());
updateImage();
}
voidCanvas::resizeEvent(QResizeEvent*e)
{
QImagenewImage(e->size(),QImage::Format_RGB32);
newImage.fill(Qt::white);
QPainterpainter(&newImage);
painter.drawImage(0,0,image);
image=newImage;
QWidget::resizeEvent(e);
}
Let'slookintothemouseeventhandlers;aftertheoperationonm_points,theupdateImage()functioniscalledinsteadofupdate().InsidetheupdateImage()function,wecreateaQPainterobjectusingtheQImageobjectimageasQPaintDevicewhiletherestofthemarejustthesameasinpaintEvent.
Thereisanewmemberfunction,though,calledresizeEvent,whichisreimplementedfromQWidget.Asyoucanimagine,wechangetheunderlyingQImageobjectoncethewidgetsizechanges,whichcouldbeasaresultofwindowresizing.Therefore,wesimplypainttheoldimageontothenewone.Thismaycausethelossofapartoftheimageifthenewsizeissmallerthanthepreviousone.YoumaywishtoaddScrollAreatoMainWindowandmakeCanvasthechildwidgetofScrollArea.YoualreadyknowhowtodothatinQML,whileit'ssimilarinQt/C++.Therefore,justtakeitasanexerciseandimplementScrollAreaforthisapplication.
WritingstaticpluginsTherearetwotypesofplugins:staticanddynamic.Staticpluginsarestaticallylinkedtotheexecutables,whilethedynamicpluginsareloadedatruntime.Dynamicpluginsexistasthe.dllor.sofiles,dependingontheplatform.Althoughthestaticpluginswillbebuiltasthe.libor.afiles,they'llbeintegratedintoanexecutablefilewhenthemainprogramgetscompiled.
Inthistopic,we'llgettoknowhowtowriteastaticplugintoextendtheapplication.Servingasanexternalplugin,itgainstheflexibilitytochangeitsinternalcodewhileit'sonlyrequiredtokeeptheinterfacecompatible.It'suptoyoutodecidewhethertheinterfaceshouldbemaintainedinthemainprogramorindifferentplugins.Inthisexample,we'llputtheinterface.hfileinthemainprogram,painter_demo.Thecontentofinterface.hisasfollows:
#ifndefINTERFACE_H
#defineINTERFACE_H
#include<QtPlugin>
#include<QPainterPath>
classInsertInterface
{
public:
virtual~InsertInterface(){}
virtualQStringname()const=0;
virtualQPainterPathgetObject(QWidget*parent)=0;
};
#defineInsertInterface_iid"org.qt-
project.Qt.PainterDemo.InsertInterface"
Q_DECLARE_INTERFACE(InsertInterface,InsertInterface_iid)
#endif//INTERFACE_H
Asyoucansee,wedeclareapurevirtualclass,InsertInterface.Inordertoavoiderrors,youhavetodeclareavirtualdestructor.Otherwise,thecompilermaycomplainandabortthecompilation.TheQPainterPathclassprovidesacontainerforcommon2Dpaintingoperations,includingellipseandtext.Hence,thereturntypeofgetObjectisQPainterPath,whichcanbeuseddirectlywheretheargument,QWidget,couldbeusefulifthereisanewlycreateddialogtogetanyinputfromtheuser.
Attheendofthisfile,wedeclareInsertInterfaceasaninterfacebytheQ_DECLARE_INTERFACEmacro,whereInsertInterface_iidistheidentifierforthe
InsertInterfaceclass.Notethattheidentifiermustbeunique,soit'srecommendedthatyouuseaJava-stylenamingrule.
Now,weneedtocreateanewproject.NavigatetoLibraries|C++Library.Then,asshowninthefollowingscreenshot,selectQtPluginforTypeandkeepthisprojectinsidethemainprogramprojectfolderforthesakeofconvenienceoranyconcerns:
ClickonNextandchoosethesameQtkitsasthepainter_demoproject.Inthisexample,thebuilddirectoryissetinthesamedirectoryasthepainter_demoproject,whichisD:\Projects\build.Therefore,thebuilddirectoryofTextPluginisD:\Projects\build\TextPlugin-Qt_5_4_0_mingw491_32-DebugandD:\Projects\build\TextPlugin-Qt_5_4_0_mingw491_32-ReleaseforDebugandRelease,respectively.
NoteFurthermore,youcanchangeDefaultbuilddirectoryinTools|Options|Build&Run|General.Inthisbook,weuseD:/Projects/build/%{CurrentProject:Name}-%{CurrentKit:FileSystemName}-%{CurrentBuild:Name}
sothatallthebuildsareorganizedinoneplace.
Then,fillinTextPluginintheClassnamefield,asshowninthefollowingscreenshot:
WeneedtoapplysomemodificationstotheTextPlugin.proprojectfile,asdisplayedhere:
QT+=coreguiwidgets
TARGET=TextPlugin
TEMPLATE=lib
CONFIG+=pluginstatic
DESTDIR=../plugins
SOURCES+=textplugin.cpp
INCLUDEPATH+=../
HEADERS+=textplugin.h
OTHER_FILES+=TextPlugin.json
Byaddingwidgets,wecanusesomeusefulclassessuchasQMessageBox.WealsoneedtoaddstatictoCONFIGtodeclarethisastaticpluginproject.Then,changetheDESTDIRvariableto../pluginssothatthepluginisinstalledtothepluginsdirectoryoutsidethebuildfolder.Lastly,weaddtheupperdirectory../toINCLUDEPATHsothatwecanincludetheinterface.hheaderfileinthissubproject.Thetextplugin.hfileisshownasfollows:
#ifndefTEXTPLUGIN_H
#defineTEXTPLUGIN_H
#include"interface.h"
classTextPlugin:publicQObject,
publicInsertInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID"org.qt-
project.Qt.PainterDemo.InsertInterface"FILE
"TextPlugin.json")
Q_INTERFACES(InsertInterface)
public:
QStringname()const;
QPainterPathgetObject(QWidget*parent);
};
#endif//TEXTPLUGIN_H
WeusetheQ_PLUGIN_METADATAmacrotospecifytheuniqueIID,whichisthesameastheonewedeclaredininterface.h,whereFILE"TextPlugin.json"canbeusedtocontainthemetadataforthisplugin.Inthiscase,wejustkeeptheTextPlugin.jsonfileintact.Then,theQ_INTERFACESmacrotellsthecompilerthatthisisapluginforInsertInterface.Inthepublicscope,therearejusttworeimplementedfunctions.Theirdefinitionsarelocatedinthetextplugin.cppsourcefile,whosecontentispastedasfollows:
#include<QInputDialog>
#include"textplugin.h"
QStringTextPlugin::name()const
{
returnQString("Text");
}
QPainterPathTextPlugin::getObject(QWidget*parent)
{
QPainterPathppath;
QStringtext=QInputDialog::getText(parent,QString("Insert
Text"),QString("Text"));
if(!text.isEmpty()){
ppath.addText(10,80,QFont("Cambria",60),text);
}
returnppath;
}
Thename()functionsimplyreturnsthenameofthisplugin,whichisTextinthiscase.AsforgetObject,itconstructsaQPainterPathclassthatcontainsthetextgivenbytheuserviaapop-updialog,andthenreturnstheQPainterPathobjecttothemainprogram.TheaddTextfunctionwilldrawthetextasasetofclosedsubpathscreated
fromthefont,whilethefirsttwoargumentsdefinetheleftendofthebaselineforthistext.
Thisisitforthepluginproject.Now,justbuilditandyoushouldexpectalibTextPlugin.afiletobelocatedunderthepluginsdirectory,whilethepluginsdirectoryitselfshouldbelocatedintheparentdirectoryofyourproject'sbuildfolders,asshownhere:
Itdoesn'tmattermuchifyouputthefilesunderotherdirectories,althoughthismeansthatyouneedtodosomepathmodificationsrelevantlyafterwards.
Now,let'sgobacktothemainprogram'sproject,whichispainter_demointhisexample.Edititspainter_demo.proprojectfileandaddthefollowinglinetoit:
LIBS+=-L../plugins-lTextPlugin
TipTheworkingdirectoryduringcompilationisthebuilddirectoryinsteadoftheprojectsourcecodedirectory.
Then,editmainwindow.uiintheDesignmode;addamenunamedPluginstothemenubar,whoseobjectnameismenuPlugins.
Amongallthechangesmadeinthemainprogram,themodificationsfortheMainWindowclassaremaximum.Hereisthecodeofthenewmainwindow.hfile:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
voidloadPlugins();
voidgeneratePluginMenu(QObject*);
privateslots:
voidonInsertInterface();
};
#endif//MAINWINDOW_H
Stillnoclueaboutit?Well,itsmainwindow.cppsourcefileispastedhereaswell:
#include<QPluginLoader>
#include"mainwindow.h"
#include"ui_mainwindow.h"
#include"interface.h"
Q_IMPORT_PLUGIN(TextPlugin)
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
loadPlugins();
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::loadPlugins()
{
foreach(QObject*plugin,QPluginLoader::staticInstances()){
generatePluginMenu(plugin);
}
}
voidMainWindow::generatePluginMenu(QObject*plugin)
{
InsertInterface*insertInterfacePlugin=
qobject_cast<InsertInterface*>(plugin);
if(insertInterfacePlugin){
QAction*action=newQAction(insertInterfacePlugin-
>name(),plugin);
connect(action,&QAction::triggered,this,
&MainWindow::onInsertInterface);
ui->menuPlugins->addAction(action);
}
}
voidMainWindow::onInsertInterface()
{
QAction*action=qobject_cast<QAction*>(sender());
InsertInterface*insertInterfacePlugin=
qobject_cast<InsertInterface*>(action->parent());
constQPainterPathppath=insertInterfacePlugin-
>getObject(this);
if(!ppath.isEmpty()){
ui->canvas->insertPainterPath(ppath);
}
}
YoumayhavefiguredoutthattheQ_IMPORT_PLUGINmacroisusedtoimporttheplugin.Yes,itis,butonlyforstaticplugins.IntheloadPlugins()function,wewalkedthroughallthestaticplugininstancesandaddedthemtothemenubyinvokingthegeneratePluginMenufunction.
PluginsaretreatedasplainQOjbectobjectsatfirst,andthenyoucanuseqobject_casttoconvertthemtotheirownclasses.Theqobject_castclasswillreturnaNULLpointerifitfailed.Insidetheifstatement,thereisatricktousethepluginsuccessfullylater.InsteadofcallingasimplifiedandoverloadedaddActionfunction,wecanconstructQActionandaddittothemenu,becauseQActionwillhavethepluginasitsQObjectparent.Therefore,youcanseethatweconvertitsparenttotherelevantpluginclassintheonInsertInterfacefunction.Insidethisfunction,wecalltheinsertPainterPathfunctiontopainttheQPainterPathclassreturnedbythepluginoncanvas.Ofcourse,weneedtodeclareanddefinethisfunctionintheCanvasclass.Addthisstatementtothepublicdomainofthecanvas.hfile:
voidinsertPainterPath(constQPainterPath&);
Theprecedingcode'sdefinitionincanvas.cppisasfollows:
voidCanvas::insertPainterPath(constQPainterPath&ppath)
{
QPainterpainter(&image);
painter.drawPath(ppath);
this->update();
}
Theprecedingstatementsshouldbefamiliartoyouandthey'realsoself-explanatory.Now,buildandrunthisapplicationagain;don'tforgettochangethecurrentactiveprojectbacktopainter_demobyright-clickingonthepainter_demoprojectandselectingSet"painter_demo"asActiveProject.Whenitruns,clickonPlugins,selectText,inputPlugin!!inthepop-updialog,andconfirm.Then,you'llseethe
text,Plugin!!,paintedonthecanvasasexpected.
Theexecutable'ssizegrowsaswellbecausewestaticallylinkedourTextPluginprojectfiletoit.Inadditiontothis,youhavetorebuildthemainprogramifyouchangedtheplugin.Otherwise,thenewlygeneratedpluginwon'tbelinkedtotheexecutableasitshould.
WritingdynamicpluginsStaticpluginsprovideaconvenientwaytodistributeyourapplications.However,thisalwaysrequiresarebuildofthemainprogram.Bycontrast,dynamicpluginsaremuchmoreflexiblesincethey'relinkeddynamically.Thismeansthemainproject,whichispainter_demointhisexample,doesn'tneedtobebuiltwithdynamicpluginsnorisitrequiredtoreleaseitssourcecode.Instead,itonlyneedstoprovideaninterfaceandtheheaderfileofthatinterface,andthenscanthosedynamicpluginsatruntimesothattheycanbeloaded.
NoteDynamicpluginsarecommonlyseenincomplexapplications,especiallyincommercialsoftwaresuchasAdobeIllustrator.
Similartothestaticpluginwejustwrote,weneedtocreateanewQtPluginprojectandwe'llcallitEllipsePluginthistime.Althoughyoucanwriteanewinterfacealongwiththisplugin,herewewilljustfocusonplugin-relatedtopics.So,wejustreusetheInsertInterfaceclasswhiletheellipseplugin.proprojectfileisshownasfollows:
QT+=coreguiwidgets
TARGET=EllipsePlugin
TEMPLATE=lib
CONFIG+=plugin
DESTDIR=../plugins
SOURCES+=ellipseplugin.cpp\
ellipsedialog.cpp
HEADERS+=ellipseplugin.h\
ellipsedialog.h
OTHER_FILES+=EllipsePlugin.json
INCLUDEPATH+=../
FORMS+=ellipsedialog.ui
Don'tforgettochangetheDESTDIRandINCLUDEPATHvariablesintheellipseplugin.profilethough,they'rebasicallythesameasthepreviousTextPluginproject.
Ignoringthesourcefiles,forms,andsoon,it'sbasicallythesamethingwithonlythe
removalofstaticinCONFIG.Theellipseplugin.hheaderfileisshownasfollows:
#ifndefELLIPSEPLUGIN_H
#defineELLIPSEPLUGIN_H
#include"interface.h"
classEllipsePlugin:publicQObject,
publicInsertInterface
{
Q_OBJECT
Q_PLUGIN_METADATA(IID"org.qt-
project.Qt.PainterDemo.InsertInterface"FILE
"EllipsePlugin.json")
Q_INTERFACES(InsertInterface)
public:
QStringname()const;
QPainterPathgetObject(QWidget*parent);
publicslots:
voidonDialogAccepted(qrealx,qrealy,qrealwid,qreal
hgt);
private:
qrealm_x;
qrealm_y;
qrealwidth;
qrealheight;
};
#endif//ELLIPSEPLUGIN_H
Asyoucanseeintheprecedingcode,wedeclarethatthisisapluginusingInsertInterfaceasthesameinTextPlugin,whereasthedifferenceisthedeclarationofanonDialogAcceptedslotfunctionandseveralprivatevariables.Accordingly,theellipseplugin.cppfileisshownasfollows:
#include"ellipsedialog.h"
#include"ellipseplugin.h"
QStringEllipsePlugin::name()const
{
returnQString("Ellipse");
}
QPainterPathEllipsePlugin::getObject(QWidget*parent)
{
m_x=0;
m_y=0;
width=0;
height=0;
EllipseDialog*dlg=newEllipseDialog(parent);
connect(dlg,&EllipseDialog::accepted,this,
&EllipsePlugin::onDialogAccepted);
dlg->exec();
QPainterPathppath;
ppath.addEllipse(m_x,m_y,width,height);
returnppath;
}
voidEllipsePlugin::onDialogAccepted(qrealx,qrealy,qreal
wid,qrealhgt)
{
m_x=x;
m_y=y;
width=wid;
height=hgt;
}
Thereisnothingspecialaboutthename()function.Bycontrast,weusetheEllipseDialogcustomdialogtogetsomeinputsfromtheuser.Remembertoconnectallthesignalsandslotsassociatedwiththedialogbeforeexecutingtheexec()function;otherwise,theslotssimplywon'tbeconnected.Also,notethattheexec()functionwillblocktheeventloopandreturnonlyafterthedialogcloses,whichisprettyhandyforourpurposesbecausewecanusetheacceptedvalues,suchasm_xandm_y,toaddanellipsetoQPainterPath.
AsfortheEllipseDialogcustomdialogitself,itwascreatedbyaddinganewQtDesignerFormClassviaQtCreator.Sinceit'susedtoprovideaninterfacefortheusertospecifysomeparameters,weuseFormLayoutinthisdialog.AddQLabelandQDoubleSpinBox,assuggestedinthefollowingscreenshot:
Accordingly,theirobjectNamevaluesaretlXLabel,tlXDoubleSpinBox,tlYLabel,tlYDoubleSpinBox,widthLabel,widthDoubleSpinBox,heightLabel,andheightDoubleSpinBox.Youshouldalsochangethemaximumvalueto9999.99orsomethingbigenoughinthePropertypanelofQDoubleSpinBox.
Inadditiontothis,alsonotethatthereisaremovalofthedefaultsignalandslotinSignals&SlotsEditor.Simplydeletetheaccepted()signalpairofbuttonBoxbecauseweneedamoreadvancedhandler.Inthisformclassheaderfile,ellipsedialog.h,wedeclareanewsignalandanewslot:
#ifndefELLIPSEDIALOG_H
#defineELLIPSEDIALOG_H
#include<QDialog>
namespaceUi{
classEllipseDialog;
}
classEllipseDialog:publicQDialog
{
Q_OBJECT
public:
explicitEllipseDialog(QWidget*parent=0);
~EllipseDialog();
signals:
voidaccepted(qreal,qreal,qreal,qreal);
private:
Ui::EllipseDialog*ui;
privateslots:
voidonAccepted();
};
#endif//ELLIPSEDIALOG_H
Theaccepted(qreal,qreal,qreal,qreal)signalherepassesthesevaluesbacktotheplugin,whiletheonAccepted()slothandlestheaccepted()signalemittedfrombuttonBox.Theyaredefinedintheellipsedialog.cppsourcefile,asshowninthefollowingcode:
#include"ellipsedialog.h"
#include"ui_ellipsedialog.h"
EllipseDialog::EllipseDialog(QWidget*parent):
QDialog(parent),
ui(newUi::EllipseDialog)
{
ui->setupUi(this);
connect(ui->buttonBox,&QDialogButtonBox::accepted,this,
&EllipseDialog::onAccepted);
}
EllipseDialog::~EllipseDialog()
{
deleteui;
}
voidEllipseDialog::onAccepted()
{
emitaccepted(ui->tlXDoubleSpinBox->value(),ui-
>tlYDoubleSpinBox->value(),ui->widthDoubleSpinBox->value(),
ui->heightDoubleSpinBox->value());
this->accept();
}
Insidetheconstructor,connecttheaccepted()signalofbuttonBoxtotheonAccepted()advancedhandlerslot.Inthisslot,weemittheacceptedsignal,whichcontainsthevaluesthattheuserhasentered.Then,calltheaccept()functiontoclosethisdialog.
EllipsePluginisfinishedatthispoint.ClickontheBuildbuttoninthepaneltobuildthisproject.Youshouldexpecttheoutput,EllipsePlugin.dllonWindows,tobelocatedinthesamepluginsdirectoryasthepreviousTextPluginproject.
Tomakeuseofthisdynamicplugin,weneedafinalstep,whichistomakethemainprogramloadthedynamicplugin(s).WhatwehavetochangehereistheloadPlugins()functioninmainwindow.cpp:
voidMainWindow::loadPlugins()
{
foreach(QObject*plugin,QPluginLoader::staticInstances()){
generatePluginMenu(plugin);
}
//searchandloaddynamicplugins
QDirpluginDir=QDir(qApp->applicationDirPath());
#ifdefQ_OS_WIN
QStringdirName=pluginDir.dirName();
if(dirName.compare(QString("debug"),Qt::CaseInsensitive)
==0||dirName.compare(QString("release"),
Qt::CaseInsensitive)==0){
pluginDir.cdUp();
pluginDir.cdUp();
}
#endif
pluginDir.cd(QString("plugins"));
foreach(QStringfileName,pluginDir.entryList(QDir::Files))
{
QPluginLoader
loader(pluginDir.absoluteFilePath(fileName));
QObject*plugin=loader.instance();
if(plugin){
generatePluginMenu(plugin);
}
}
}
InordertousetheQDirclass,youmayalsoneedtoincludethis:
#include<QDir>
TheQDirclasswillprovideaccesstodirectorystructuresandtheircontents,whichweusetolocateourdynamicplugins.TheqAppmacroisaglobalpointer,referringtothisveryapplicationinstance.It'sequivalenttotheQCoreApplication::instance()functionandQApplication::instance()fornon-GUIandGUIapplications,respectively.OnWindowsplatforms,ourpluginsdirectoryislocatedinthesecondupperfolderofthebuildpath.
Then,wejusttesteachfileinthepluginsdirectory,loadit,andgenerateapropermenuentryifit'saloadableplugin.Runthisapplicationagain;you'llhaveanEllipseentryinsidethePluginsmenu.Itworksasexpected.
MergingpluginsandmainprogramprojectsItisatediousthingthatopensseveralprojectsandbuildstheminorder.Thisisnotabigdealgiventhatwehavejusttwopluginsandamainprogram.However,it'llbecomeaseriousinefficiencyissueoncethenumberofpluginsincrease.Therefore,itisabetterpracticetomergethepluginsintothemainprojectandgetthembuiltinaspecifiedordereverytimeweclickontheBuildbutton.It'stotallyfeasibleandiscommonlyseeninQtprojects.
Firstly,wemoveallthefilesinthepainter_demodirectory,exceptfortheEllipsePluginandTextPluginfolders,intoanewlycreatedmainfolder.
Then,renamethepainter_demo.protomain.prointhemainfolderwhilecreatinganewpainter_demo.proprojectfileoutsideinthepainter_demodirectory.Thisnewpainter_demo.proprojectfileneedstohavecontentsasshowninthefollowingcode:
TEMPLATE=subdirs
CONFIG+=ordered
SUBDIRS=TextPlugin\
EllipsePlugin\
main
Thesubdirsprojectisaspecialtemplate,whichmeansthatthisprojectfilewon'tgenerateanapplicationoralibrary.Instead,ittellsqmaketobuildsubdirectories.ByaddingorderedtoCONFIG,wecanensurethatthecompilingprocessfollowstheexactorderaccordingtoSUBDIRS.
Toaccomplishthis,weneedtomodifytheprojectfilesinthetwopluginsdirectories.ChangetheINCLUDEPATHvariabletothefollowingline:
INCLUDEPATH+=../main
Thischangeisobviousbecausewemovedallthesourcecodeintothemaindirectory.Ifwedon'tchangeINCLUDEPATH,thecompilerwillcomplainthatitcan'tfindtheinterface.hheaderfile.
CreatingaC++pluginforQMLapplicationsIt'snottoodifficulttowriteapluginforQt/C++applications,whereasit'ssomewhatmorecomplextocreateapluginfortheQMLapplications.Theideaisthesame,andherewewilluseaverybasicexampletodemonstratethistopic.Basically,thisapplicationwillencodethetextinputasBase64anddisplayit.TheBase64encodingpartisimplementedintheC++plugin.
Thistime,we'regoingtocreatethepluginprojectfirst,andthencompletetheQMLpart.CreatingapluginprojectforaQMLapplicationsharesthesameprocedure.NavigatetoLibraries|C++Library,andthenselectQtPluginwiththenameasBase64Plugin.Itsprojectfile,Base64Plugin.pro,ispastedhere:
QT+=coreqml
TARGET=qmlbase64Plugin
TEMPLATE=lib
CONFIG+=plugin
DESTDIR=../imports/Base64
SOURCES+=base64.cpp\
base64plugin.cpp
HEADERS+=base64.h\
base64plugin.h
OTHER_FILES+=\
qmldir
WesetDESTDIRto../imports/Base64forthesakeofconvenience.Youcanchangethistosomeotherpath,butyoumayneedtomakesomerelevantchangeslatertobeabletoimportthisplugin.
ThisprojectconsistsoftwoC++classes.TheBase64classwilllaterbeexportedtoQML,whereasBase64PluginregisterstheBase64class.Theformerclass'base64.hheaderfileisasfollows:
#ifndefBASE64_H
#defineBASE64_H
#include<QObject>
classBase64:publicQObject
{
Q_OBJECT
public:
explicitBase64(QObject*parent=0);
publicslots:
QStringget(QString);
};
#endif//BASE64_H
Thebase.cppcounterpartdefinesthegetfunction,asshowninthefollowingcode:
#include"base64.h"
Base64::Base64(QObject*parent):
QObject(parent)
{
}
QStringBase64::get(QStringin)
{
returnQString::fromLocal8Bit(in.toLocal8Bit().toBase64());
}
ThetrickypartisintheBase64Pluginclass,whichisnotidenticaltothepreviouspluginclass.Itsbase64plugin.hheaderfileisshownhere:
#ifndefBASE64PLUGIN_H
#defineBASE64PLUGIN_H
#include<QQmlExtensionPlugin>
classBase64Plugin:publicQQmlExtensionPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID"org.qt-
project.Qt.QmlExtensionInterface")
public:
voidregisterTypes(constchar*uri);
};
#endif//BASE64PLUGIN_H
WiththeQQmlExtensionPluginsubclass,we'reabletowriteourownQMLplugin.Infact,thisclassisusedtodeclaretheBase64classforQML.AlsonotethatsinceIIDinQ_PLUGIN_METADATAisfixed,youprobablydon'twanttochangeit.Asasubclass,it
hastoreimplementtheregisterTypesfunction,whichsimplyregisterstheclass(es).Thedetaileddefinitionislocatedinthebaseplugin.cppfilewhosecontentsareasshowninthefollowingcode:
#include<QtQml>
#include"base64plugin.h"
#include"base64.h"
voidBase64Plugin::registerTypes(constchar*uri)
{
Q_ASSERT(uri==QLatin1String("Base64"));
qmlRegisterType<Base64>(uri,1,0,"Base64");
}
TheQ_ASSERTmacrowillensurethatthepluginislocatedinsidetheBase64directory.Ifnot,it'llprintawarningmessagecontainingthesourcecode,filename,andlinenumber.Notethaturi,whichisexpectedtobeBase64inthiscase,isthemodulenameforQML.Belowthisline,qmlRegisterTypeisatemplatefunctionwhereyouneedtoputtheclassname,Base64,insidebrackets.TheseargumentswillregistertheclasswithBase64astheQMLnamewithVersion1.0.
Alastpieceisneededtodeclarealoadableplugin,whichistheqmldirfile.Notethatithasnoextensionname.Thisfiledefinesthemodulenameandrelevantfilesinthedirectory.Hereisthecontent:
moduleBase64
pluginqmlbase64Plugin
Weneedtoputthisfileinthe../imports/Base64directory,whichistheDESTDIRofBase64Plugin.AlongwithafewlinesintheQMLapplicationproject'smain.cppfile,QMLcanthenimportapluginasitimportsanyotherQtQuickmodules.
It'stimetocreateanewQtQuickapplicationprojectnow.TheprojectnameissimplyQML_PluginandwemovetheBase64PluginclassintotheQML_Plugindirectory,whichenablestheQtCreatorsyntaxtohighlighttheBase64Pluginclass.Hereisthecontentofmain.qml:
importQtQuick2.3
importQtQuick.Controls1.2
importBase641.0
ApplicationWindow{
visible:true
width:180
height:100
title:qsTr("QMLPlugin")
Base64{
id:b64
}
Column{
spacing:6
anchors{left:parent.left;right:parent.right;top:
parent.top;bottom:parent.bottom;leftMargin:6;rightMargin:
6;topMargin:6;bottomMargin:6}
Label{
text:"Input"
}
TextField{
id:input
width:parent.width
placeholderText:"Inputstringhere"
onEditingFinished:bt.text=b64.get(text)
}
Label{
text:"Base64Encoded"
}
TextField{
id:bt
readOnly:true
width:parent.width
}
}
}
RemembertostateimportBase641.0attheverybeginningofthecodesothatourplugincanbeloaded.Then,Base64isjustlikeotherQMLtypeswehaveusedbefore.IntheonEditingFinishedhandlerofinputTextField,weusethegetfunction,whichisintheBase64class,tosetbt.texttothecorrespondingBase64class-encodedstring.
YoumaywonderhowaQMLstringtypeisconvertedtoaQStringobject.Well,it'simplicitlyconvertedbetweenQMLandQt/C++.Thereareplentyoftheseconversionsforcommonly-seenQMLdatatypesandQtdataclasses.Fordetails,youcanlookattheQtdocumentationtoseethefulllist.
Anotherthingisthatweneedtochangemain.cpp,asmentionedbefore.SimilartotheQt/C++case,weusetheQDirclasstogetanapplicationdirectoryandchangeitto../imports.BeawarethatyoushoulduseaddImportPathinsteadofaddPluginPathtoadd../importstotheQMLengine'smodulesearchpath.ThisisbecauseweuseBase64asamodule,whichshouldbelocatedintheimportspath.Meanwhile,thepluginpathisfornativepluginsofimportedmodules,whicharestatedinqmldir.Thecontentofthemain.cppfileisasfollows:
#include<QApplication>
#include<QDir>
#include<QQmlApplicationEngine>
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
QQmlApplicationEngineengine;
QDirpluginDir=app.applicationDirPath();
pluginDir.cdUp();
pluginDir.cdUp();
pluginDir.cd("imports");
engine.addImportPath(pluginDir.absolutePath());
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
returnapp.exec();
}
Inordertorunthisapplication,performthefollowingsteps:
1. BuildBase64Plugin.2. Copytheqmldirfileintothe../imports/Base64directory(theimportsfolder
shouldbelocatedinthesameplaceasplugins).3. BuildandruntheQML_Pluginproject.
YoucantestthisapplicationbyinputtinganystringinthefirstinputfieldandjustpressingEnter.Onescenarioforthisapplicationistoencodeyoure-mailaddresstoavoidawebspider,asshownhere:
Ifthemoduleisn'twelllocated,theapplicationwon'tshowupandit'llcomplainthatBase64isnotinstalled.Ifthathappens,makesureyouaddthecorrectpathinmain.cppandthereisaqmldirfileinsidetheBase64folder.
SummaryItissomewhatdifficulttogetstartedonwritingplugins.However,aftersomebasicpractice,you'llfindthatit'sactuallyeasierthanitlooks.ForQtWidgetsapplications,pluginssimplyextendtheapplicationinaflexibleway.Meanwhile,theyenabledeveloperstodevisenewformsforQMLapplications.Wealsocoveredusingthesubdirsprojecttomanagemultiplesubprojects.Evenifyoudon'tplantowriteplugins,thischaptercoveredpainting-relatedstuffthatiscrucialforGUIapplicationdevelopment,includingQPainter,paintEvent,andresizeEvent.
Inthenextchapter,we'regoingtotalkaboutnetworkprogrammingandmultithreadinginQt.
Chapter6.GettingWiredandManagingDownloadsNetworkmoduleshavebecomecrucialnowadaysandarealsoamust-havefeaturefordevelopmentframeworks;therefore,QtdoesprovideAPIsfornetworkprogramming.Sittight,we'regoingtogetwiredanddownloadfilesfromthenetwork.Inadditiontothis,threadingisincludedinthischapter,whichisavitalprogrammingskilltoavoidblocking.Thischapter'stopicsarelistedasfollows:
IntroducingQtnetworkprogrammingUtilizingQNetworkAccessManagerMakinguseoftheprogressbarWritingmultithreadedapplicationsManagingasystemnetworksession
IntroducingQtnetworkprogrammingQtsupportsnetworkprogrammingandprovideslotsofhigh-levelAPIstoeaseyourwork.QNetworkRequest,QNetworkReply,andQNetworkAccessManagerusecommonprotocolstoperformnetworkoperations.Qtalsoofferslower-levelclassestorepresentlowlevelnetworkconcepts.
Inthischapter,we'regoingtoutilizethehigh-levelAPIsthatQthasofferedtowriteadownloadertoretrievetheInternetfilesandsavethemtoyourdisk.AsImentionedearlier,theapplicationwillneedtheQNetworkRequest,QNetworkReply,andQNetworkAccessManagerclasses.
Firstly,allnetworkrequestsarerepresentedbytheQNetworkRequestclass,whichisageneralcontainerforinformationassociatedwitharequest,includingtheheaderandencryption.Currently,HTTP,FTP,andlocalfileURLsaresupportedforuploadinganddownloading.
Oncearequesthasbeencreated,theQNetworkAccessManagerclassisusedtodispatchitandemitssignals,reportingtheprogress.Then,itcreatesthereplytoanetworkrequest,representedbytheQNetworkReplyclass.Atthesametime,thesignalsprovidedbyQNetworkReplycanbeusedtomonitoreachreplyindividually.SomedeveloperswilldiscardthereferencetothereplyandusetheQNetworkAccessManagerclass'ssignalsforthatpurpose,though.Allrepliescanbehandledsynchronouslyorasynchronously,becauseQNetworkReplyisasubclassofQIODevice,whichmeansthatit'spossibletoimplementnonblockingoperations.
Hereisadiagramthatdescribestherelationshipbetweentheseclasses:
Likewise,thenetwork-relatedstuffisofferedinthenetworkmodule.Tousethismodule,youneedtoedittheprojectfileandaddnetworktoQT.Now,createanewQtWidgetApplicationprojectandedittheprojectfile.InourDownloader_Demoexample,thedownloader_demo.proprojectfileisshownhere:
QT+=coreguinetwork
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Downloader_Demo
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp\
downloader.cpp\
downloaddialog.cpp
HEADERS+=mainwindow.h\
downloader.h\
downloaddialog.h
FORMS+=mainwindow.ui\
downloaddialog.ui
UtilizingQNetworkAccessManagerNow,we'regoingtodiscoverhowtowriteanapplicationthatisabletodownloadfilesfromotherlocations.Byotherlocations,wemeanthatyoucandownloadfilesfromalocalposition;itdoesn'thavetobeanInternetaddress,sincethelocalfileURLsaresupportedbyQtaswell.
Firstofall,let'screateaDownloaderclassthatwilluseQNetworkAccessManagertodothedownloadingworkforus.Thedownloader.hheaderfileispastedshownasfollows:
#ifndefDOWNLOADER_H
#defineDOWNLOADER_H
#include<QObject>
#include<QNetworkAccessManager>
#include<QNetworkRequest>
#include<QNetworkReply>
classDownloader:publicQObject
{
Q_OBJECT
public:
explicitDownloader(QObject*parent=0);
publicslots:
voiddownload(constQUrl&url,constQString&file);
signals:
voiderrorString(constQString&);
voidavailable(bool);
voidrunning(bool);
voiddownloadProgress(qint64,qint64);
private:
QNetworkAccessManager*naManager;
QStringsaveFile;
voidsaveToDisk(QNetworkReply*);
privateslots:
voidonDownloadFinished(QNetworkReply*);
};
#endif//DOWNLOADER_H
WeexposethedownloadslottogettheURLandthesavingtarget.Accordingly,saveFileisusedtostorethesavingtarget.Inadditiontothis,weuseannaManagerobjectoftheQNetworkAccessManagerclasstomanagethedownloadingprocess.
Let'scheckthedefinitionsofthesefunctionsinthedownloader.cppfile.Inthefollowingconstructor,weconnectthenaManagerobject'sfinishedsignaltotheonDownloadFinishedslot.Therefore,whenanetworkconnectionisfinished,arelevantQNetworkReplyreferencewillbepassedviathissignal.
Downloader::Downloader(QObject*parent):
QObject(parent)
{
naManager=newQNetworkAccessManager(this);
connect(naManager,&QNetworkAccessManager::finished,this,
&Downloader::onDownloadFinished);
}
Accordingly,intheonDownloadFinishedslot,wehavetohandleQNetworkReplywithcaution.Ifthereisanyerror,whichmeansthatthedownloadhasfailed,weexposetheerrorString()functionbytheerrorStringsignal.Otherwise,wecallthesaveToDiskfunctiontosavethefiletothedisk.Then,weusedeleteLater()toreleasetheQNetworkReplyobjectsafely.AsstatedintheQtdocumentation,it'snotsafetousethedeletestatementdirectly;sinceit'sfinished,weemittheavailableandrunningsignals.Thosesignalswilllaterbeusedtochangetheuserinterface.
voidDownloader::onDownloadFinished(QNetworkReply*reply)
{
if(reply->error()!=QNetworkReply::NoError){
emiterrorString(reply->errorString());
}
else{
saveToDisk(reply);
}
reply->deleteLater();
emitavailable(true);
emitrunning(false);
}
InthesaveToDiskfunction,wejustimplementQFiletosaveallthedownloadeddatatothedisk.ThisisfeasiblebecauseQNetworkReplyinheritsfromQIODevice.Therefore,inadditiontothenetworkingAPIs,youcantreatQNetworkReplyasanormalQIODeviceobject.Inthiscase,usethereadAll()functiontogetalldata:
voidDownloader::saveToDisk(QNetworkReply*reply)
{
QFilef(saveFile);
f.open(QIODevice::WriteOnly|QIODevice::Truncate);
f.write(reply->readAll());
f.close();
}
Finally,let'sseeinsidethedownloadfunctionthatwillbeusedbyMainWindowlater.Firstly,westorethesavedfiletosaveFile.Then,weconstructQNetworkRequestrequsingtheQUrlobject,url.Next,wesendreqtothenaManagerobjectofQNetworkAccessManager,whilesavingthereferencetothecreatedQNetworkManagerobjecttoreply.Afterthis,weconnectthetwodownloadProgresssignalstogether,whichissimplyexposingthedownloadProgresssignalofthereply.Atlast,weendupemittingtwosignals,indicatingtheavailabilityandrunningstatusrespectively.
voidDownloader::download(constQUrl&url,constQString
&file)
{
saveFile=file;
QNetworkRequestreq(url);
QNetworkReply*reply=naManager->get(req);
connect(reply,&QNetworkReply::downloadProgress,this,
&Downloader::downloadProgress);
emitavailable(false);
emitrunning(true);
}
WedescribedtheDownloaderclass.Now,we'regoingtoaddDownloadDialogbynavigatingtoQtDesigner|DialogwithButtonsBottom.ThisclassisusedtogettheURLandsavethepathfortheuser.Forthedesignofdownloaddialog.ui,weusethetwoQLineEditobjectstogettheURLandsavedpathrespectively.OneoftheobjectnamesisurlEdit,andtheotherissaveAsEdit.Inordertoopenafiledialogfortheusertochoosethesavinglocation,asaveAsButtonattributeofQPushButtonisaddedtotheright-handsideofsaveAsEdit.ThefollowingscreenshotshowsyouthelayoutofthisUIfile:
YouneedtochangethelayoutofthisdialogtoLayOutinaGrid.Inasimilarwayaswedidbefore,inordertopassthevaluestothemainwindow,weneedtodeletethedefaultacceptedsignalandslotconnectioninSignals&SlotsEditor.
Thecontentsofthisclass'sdownloaddialog.hheaderfileareshownhere:
#ifndefDOWNLOADDIALOG_H
#defineDOWNLOADDIALOG_H
#include<QDialog>
namespaceUi{
classDownloadDialog;
}
classDownloadDialog:publicQDialog
{
Q_OBJECT
public:
explicitDownloadDialog(QWidget*parent=0);
~DownloadDialog();
signals:
voidaccepted(constQUrl&,constQString&);
private:
Ui::DownloadDialog*ui;
privateslots:
voidonButtonAccepted();
voidonSaveAsButtonClicked();
};
#endif//DOWNLOADDIALOG_H
Asyoucansee,anewsignalnamedacceptedisaddedtopasstheURLandsavethelocation.Besides,thetwoprivateslotsareusedtohandletheacceptedeventofthebuttonboxandthesaveAsButtonClickedsignal,respectively.
Thedefinitionsareinthedownloaddialog.cppsourcefile,whichisshownhere:
#include<QFileDialog>
#include"downloaddialog.h"
#include"ui_downloaddialog.h"
DownloadDialog::DownloadDialog(QWidget*parent):
QDialog(parent),
ui(newUi::DownloadDialog)
{
ui->setupUi(this);
connect(ui->buttonBox,&QDialogButtonBox::accepted,this,
&DownloadDialog::onButtonAccepted);
connect(ui->saveAsButton,&QPushButton::clicked,this,
&DownloadDialog::onSaveAsButtonClicked);
}
DownloadDialog::~DownloadDialog()
{
deleteui;
}
voidDownloadDialog::onButtonAccepted()
{
emitaccepted(QUrl(ui->urlEdit->text()),ui->saveAsEdit-
>text());
this->accept();
}
voidDownloadDialog::onSaveAsButtonClicked()
{
QStringstr=QFileDialog::getSaveFileName(this,"SaveAs");
if(!str.isEmpty()){
ui->saveAsEdit->setText(str);
}
}
IntheconstructorofDownloadDialog,justconnectthesignalsandslots.IntheonButtonAcceptedslot,weemittheacceptedsignal,whichistopasstheURLandthesavingpath,whereatemporaryQUrlclassisconstructedusingthetextofurlEdit.Then,theacceptfunctionisinvokedtoclosethedialog.Meanwhile,intheonSaveAsButtonClickedslotfunction,weusethestaticfunctionprovidedbytheQFileDialogclasstoobtainthesavinglocation.DonothingiftheQStringreturnisempty;thismeansthattheusermayhaveclickedonCancelinthefiledialog.
MakinguseoftheprogressbarAnintuitivewaytoindicatethedownloadingprogressisbyusingaprogressbar.InQt,itistheQProgressBarclassthatprovidesahorizontalorverticalprogressbarwidget.Itusesminimum,value,andmaximumtodeterminethecompletedpercentage.Thepercentageiscalculatedbytheformula,(value–minimum)/(maximum–minimum).We'llusethisusefulwidgetinourexampleapplicationbyperformingthefollowingsteps:
1. GobacktotheMainWindowclass.2. Editthemainwindow.uifileintheDesignmode.3. DragPushButtonandrenameitasnewDownloadButtonwithNewDownloadas
itstext.4. DragProgressBarjustbeneathnewDownloadButton.5. ChangethelayouttoLayOutVertically.6. UnchecktextVisibleintheprogressBarwidget'sproperty.
Thepushbutton,newDownloadButton,isusedtopopupDownloadDialogtogetanewdownloadtask.Weneedtoapplysomemodificationstomainwindow.h,assuggestedhere:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
#include"downloader.h"
#include"downloaddialog.h"
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
Downloader*downloader;
DownloadDialog*ddlg;
privateslots:
voidonNewDownloadButtonPressed();
voidshowMessage(constQString&);
voidonDownloadProgress(qint64,qint64);
};
#endif//MAINWINDOW_H
InordertousetheDownloaderandDownloadDialogclasses,wehavetoincludethemintheheaderfile.Then,wecanincludethemastheprivatepointers.Fortheprivateslots,onNewDownloadButtonPressedisusedtohandlethenewDownloadButtonclickedsignal.WhileshowMessageisaslotfunctionthatdisplaysthemessageonstatusbar,thelastone,onDownloadProgress,isusedtoupdatetheprogressbar.
Similarly,forthemainwindow.cppsourcefile,weconnectthesignalsandslotsintheconstructor,shownasfollows:
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
ui->progressBar->setVisible(false);
downloader=newDownloader(this);
connect(ui->newDownloadButton,&QPushButton::clicked,this,
&MainWindow::onNewDownloadButtonPressed);
connect(downloader,&Downloader::errorString,this,
&MainWindow::showMessage);
connect(downloader,&Downloader::downloadProgress,this,
&MainWindow::onDownloadProgress);
connect(downloader,&Downloader::available,ui-
>newDownloadButton,&QPushButton::setEnabled);
connect(downloader,&Downloader::running,ui->progressBar,
&QProgressBar::setVisible);
}
Beforebeginningtocreatetheseconnections,weneedtohidetheprogressbarandcreateanewDownloaderclass,usingMainWindowastheQObjectparent.Meanwhile,intheseconnections,thefirstoneistoconnectthenewDownloadButtonclickedsignal.Then,weconnecttheerrorStringsignalofdownloadertoshowMessage,whichenablesthestatusbartoshowtheerrormessagedirectly.Next,weconnectthedownloadProgresssignaltoouronDownloadProgresshandler.Asfortheavailableandrunningsignals,they'reconnectedtocontroltheavailabilityandvisibilityofnewDownloadButtonandprogressBar,respectively.
InsidetheonNewDownloadButtonPressedslotfunction,weconstructaDownloadDialogobject,ddlg,thenconnecttheacceptedsignalofDownloadDialogtotheDownloaderclass'sdownloadslot.Then,useexectorunthedialogandblocktheeventloop.
Afterthis,wecalldeleteLatertosafelyreleasetheresourceallocatedforddlg.
voidMainWindow::onNewDownloadButtonPressed()
{
ddlg=newDownloadDialog(this);
connect(ddlg,&DownloadDialog::accepted,downloader,
&Downloader::download);
ddlg->exec();
ddlg->deleteLater();
}
AsfortheshowMessageslotfunction,itsimplycallstheshowMessagefunctionofstatusBarwithathreesecondtimeout,asshownhere:
voidMainWindow::showMessage(constQString&es)
{
ui->statusBar->showMessage(es,3000);
}
Atlast,wecanupdatetheprogressbarviatheonDownloadProgressfunction,whichisshowninthefollowingcode.Sincetheminimumvalueis0bydefault,wedon'tneedtochangeit.Instead,wechangethemaximumvaluetothetotalbytesofthedownload,andvaluetothecurrentdownloadedbytes.Notethatifthetotalsizeisunknown,thenthevalueofthetotalsizeis-1,whichwillhappentomaketheprogressbardisplayinabusystyle.
voidMainWindow::onDownloadProgress(qint64r,qint64t)
{
ui->progressBar->setMaximum(t);
ui->progressBar->setValue(r);
}
Now,givetheapplicationarunandclickontheNewDownloadbutton.TheAddNewDownloaddialogwillpopup,whereyoucanaddanewdownloadtaskasshownhere:
ClickonOK,ifthereisnoerror;aprogressbarisexpectedtoshowupanddisplaythecurrentdownloadprogress,shownasfollows:
Asyoucansee,theNewDownloadbuttonisnotenablednow,sinceitisassociatedwiththeavailablesignalofdownloader.Besides,theprogressbarwon'tevenshowifdownloaderisn'trunning.
Whilethisdownloaderdemostilllacksabasicfunction,whichistocanceldownloading,itis,infact,easytoimplement.ThereisaslotfunctioncalledabortintheQNetworkReplyclass.YoumayhavetostorethereferencetoQNetworkReplyandthencallabortifsomebuttoninMainWindowisclicked.Thiswon'tbedemonstratedhere.Ithasbeenleftuptoyoutopracticeonyourown.
WritingmultithreadedapplicationsIbetmultithreadorthreadingisn'tunfamiliartoyou.UsingotherthreadssavestheGUIapplicationfromfreezing.Iftheapplicationrunsonasinglethread,it'llgetstuckifthereit'sasynchronoustime-consumingoperation.Multiplethreadsmakeapplicationrunningmuchsmoother.AlthoughmostoftheQtNetworkAPIsarenonblocking,itisnotthatdifficulttopracticeonit.
QtprovidesaQThreadclasstoimplementthreadingonallsupportedplatforms.Inotherwords,wedon'tneedtowriteplatform-specificcodeutilizingPOSIXThreadsoraWin32API.Instead,QThreadprovidesaplatform-independentwaytomanagethreads.AQThreadobjectmanagesathreadwithintheprogram,whichbeginsexecutinginrun()andendswhencallingquit()orexit().
Forsomehistoricalreason,it'sstillpossibletosubclassQThreadandputtheblockingortime-consumingcodeinthereimplementedrun()function.However,it'sconsideredanincorrectpracticeandisnotrecommendedtodoso.TherightwayistouseQObject::moveToThread,whichwillbedemonstratedlater.
We'regoingtoputtheDownloader::downloadfunctionintoanewthread.Infact,it'stheQNetworkAccessManager::getfunctionthatwillbemovedontoanotherthread.Let'screateanewC++class,DownloadWorker,whosedownloadworker.hheaderfileispastedasfollows:
#ifndefDOWNLOADWORKER_H
#defineDOWNLOADWORKER_H
#include<QObject>
#include<QNetworkReply>
#include<QNetworkRequest>
#include<QNetworkAccessManager>
classDownloadWorker:publicQObject
{
Q_OBJECT
publicslots:
voiddoDownload(constQUrl&url,QNetworkAccessManager*nm);
signals:
voiddownloadProgress(qint64,qint64);
};
#endif//DOWNLOADWORKER_H
Theconstructorisremovedfromthecodebecausewecan'tmakeachildobjectthat
willbeinanotherthread.ThisisalmosttheonlylimitationofQThread.Incontrasttothis,youcanconnectsignalsandslotsbetweendifferentthreadswithoutanyproblems.
Don'tsplitparentandchildrenbetweenthreads.Parentobjectsandchildrenobjectscanonlybeinthesamethread.
WedeclarethedoDownloadslotfunctiontodotheQNetworkAccessManager::getfunctionworkforus.Ontheotherhand,thedownloadProgresssignalisusedtoexposethedownloadProgresssignalofQNetworkReplyaswedid.Thecontentsofdownloadworker.cppisshownasfollows:
#include"downloadworker.h"
voidDownloadWorker::doDownload(constQUrl&url,
QNetworkAccessManager*nm)
{
QNetworkRequestreq(url);
QNetworkReply*reply=nm->get(req);
connect(reply,&QNetworkReply::downloadProgress,this,
&DownloadWorker::downloadProgress);
}
Theprecedingcodeisanexampleofasimpleworkerclass.Now,wehavetochangetheDownloaderclasstousetheDownloadWorkerclass.TheheaderfileoftheDownloaderclass,downloader.h,needsafewmodifications,shownhere:
#ifndefDOWNLOADER_H
#defineDOWNLOADER_H
#include<QObject>
#include<QNetworkAccessManager>
#include<QNetworkRequest>
#include<QNetworkReply>
#include<QThread>
#include"downloadworker.h"
classDownloader:publicQObject
{
Q_OBJECT
public:
explicitDownloader(QObject*parent=0);
~Downloader();
publicslots:
voiddownload(constQUrl&url,constQString&file);
signals:
voiderrorString(constQString&);
voidavailable(bool);
voidrunning(bool);
voiddownloadProgress(qint64,qint64);
private:
QStringsaveFile;
QNetworkAccessManager*naManager;
DownloadWorker*worker;
QThreadworkerThread;
voidsaveToDisk(QNetworkReply*);
privateslots:
voidonDownloadFinished(QNetworkReply*);
};
#endif//DOWNLOADER_H
Asyoucansee,wehavedeclaredanewprivatemember,workerThread,whichisatypeofQThread.Also,aDownloadWorkerobjectworkerhasbeendeclaredaswell.Therearemorechangesinthedownloader.cppsourcefile,asdisplayedhere:
#include<QFile>
#include"downloader.h"
Downloader::Downloader(QObject*parent):
QObject(parent)
{
naManager=newQNetworkAccessManager(this);
worker=newDownloadWorker;
worker->moveToThread(&workerThread);
connect(naManager,&QNetworkAccessManager::finished,this,
&Downloader::onDownloadFinished);
connect(&workerThread,&QThread::finished,worker,
&DownloadWorker::deleteLater);
connect(worker,&DownloadWorker::downloadProgress,this,
&Downloader::downloadProgress);
workerThread.start();
}
Downloader::~Downloader()
{
workerThread.quit();
workerThread.wait();
}
voidDownloader::download(constQUrl&url,constQString
&file)
{
saveFile=file;
worker->doDownload(url,naManager);
emitavailable(false);
emitrunning(true);
}
voidDownloader::onDownloadFinished(QNetworkReply*reply)
{
if(reply->error()!=QNetworkReply::NoError){
emiterrorString(reply->errorString());
}
else{
saveToDisk(reply);
}
reply->deleteLater();
emitavailable(true);
emitrunning(false);
}
voidDownloader::saveToDisk(QNetworkReply*reply)
{
QFilef(saveFile);
f.open(QIODevice::WriteOnly|QIODevice::Truncate);
f.write(reply->readAll());
f.close();
}
Intheconstructor,wewillcreateanewDownloadWorkerclass,andmoveittoanotherthread,workerThread.ByconnectingthefinishedsignalofworkerThreadtothedeleteLaterfunctionofworker,theresourcesofworkercanbedeletedsafelyaftertheexitofworkerThread.Then,weneedtoexposedownloadProgressagain,sinceit'smovedintoworker.Atlast,wecallthestart()function,tostartworkerThread.
Asareverseoperation,wecallthequit()functiontoexitworkerThreadandthenusewait()toensureitquitssuccessfully.
SincealotofcodehasbeenmovedintothedoDownloadfunctionofworker,weonlyneedtocalldoDownloadofworkerhere.Infact,thefunctioncallingisinter-thread,whichmeansthatthemainthreadwon'tbeblockedbythatstatement.
Sincegetisnotblocking,youmaynotfeelthedifference.However,I'msureyouhavesomeapplicationsthathavefrozen,whichthereforeneedtobemodifiedtoadapttoQThread.Alwaysremembertoputonlythebackgroundblockingoperationsinanotherthread.ThisismainlybecausetheseoperationsareeasilyseparatedfromGUIintosingleobjectswithoutparentsorchildren.Duetothislimitation,almostalltheGUIobjectsmustbeinthesamethread,whichisthemainthreadinmostcases.
ManagingasystemnetworksessionInadditiontonetworkingapplications,Qtalsoprovidesyouwithcross-platformAPIstocontrolnetworkinterfacesandaccesspoints.Althoughit'snotverycommontocontrolthenetworkstate,therearesomecertainsituationswhereit'srequiredtodothis.
First,I'dliketointroduceQNetworkConfigurationManagertoyou.Thisclassmanagesthenetworkconfigurationsprovidedbythesystem.Itenablesyouaccesstothem,aswellastodetectthesystem'scapabilitiesduringruntime.ThenetworkconfigurationispresentedbytheQNetworkConfigurationclass,whichabstractsasetofconfigurationoptionsconcerninghowanetworkinterfacehastobeconfiguredinordertoconnecttothetargetnetwork.Tocontrolthenetworksession,youneedtousetheQNetworkSessionclass.Thisclassprovidesyouwithcontroloverthesystem'saccesspointsandenablessessionmanagement.ItalsoenablesyoutocontrolnetworkinterfacesthatarerepresentedbytheQNetworkInterfaceclass.Tohelpyoufigureoutthisrelationship,adiagramisshownhere:
Asyoucansee,thestructureissimilartoQNetworkAccessManager,QNetworkReply,andQNetworkRequest.Especially,thereisanothermanagerclass.Let'sseehowtodealwiththeseclassesinpractice.
CreateanewQtWidgetsApplicationprojectasusual.TheexampleregardingthistopiciscalledNetworkManager_Demo.RemembertoaddnetworktoQtinyourprojectfile,aswedidinthepreviousexample.Then,editmainwindow.uiintheDesignmodeandperformthefollowingsteps:
1. Removethestatusbar,menubar,andtoolbarsincewedon'tneedtheminthisapplication.
2. AddListView(undertheItemViews(Model-Based)category).
3. DragVerticalLayouttotherightoflistView.4. ChangeLayoutinMainWindowtoLayOutHorizontally.5. DragLabelintoverticalLayoutandrenameitasonlineStatus.6. DragProgressBarintoverticalLayout.Changeitsmaximumvalueto0and
unchecktextVisiblesothatitcanbeusedasabusyindicator.7. AddthreePushButtonbuttons;Refresh,Connect,andDisconnect;beneath
theprogressbar.TheirobjectnamesarerefreshButton,connectButton,anddisconnectButton,respectively.
8. Atlast,dragVerticalSpacerbetweenprogressBarandonlineStatustoseparatethem.
Asusual,weneedtodosomedeclarationsinmainwindow.hheaderfileasshownhere:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
#include<QNetworkConfigurationManager>
#include<QNetworkConfiguration>
#include<QNetworkSession>
#include<QStandardItemModel>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
QNetworkConfigurationManager*networkConfManager;
QStandardItemModel*confListModel;
privateslots:
voidonOnlineStateChanged(boolisOnline);
voidonConfigurationChanged(constQNetworkConfiguration
&config);
voidonRefreshClicked();
voidonRefreshCompleted();
voidonConnectClicked();
voidonDisconnectClicked();
};
#endif//MAINWINDOW_H
Inthiscase,weonlyutilizetheQNetworkConfigurationManager,QNetworkConfiguration,andQNetworkSessionclassestomanagethesystemnetworksessions.Therefore,weneedtoincludetheminanappropriatelocation.
NoteNotethatweonlyneedtodeclareaprivatemember,inthiscasenetworkConfManager,oftheQNetworkConfigurationManagerclass,becausetheQNetworkConfigurationcanberetrievedfromthismanager,whileQNetworkSessionisboundtoQNetworkConfiguration.
AsforQStandardItemModel,rememberthemodel/viewstuffinChapter3,CookinganRSSReaderwithQtQuick.TheonlydifferencebetweenthatchapterandthisoneisthatwewroteQMLintheformer.However,weareusingaC++applicationinthischapter.Theysharethesameconcept,though,andit'sjustthetoolthatchanges.QStandardItemModel*confListModelistheexactmodeloflistViewintheUIfile.
Last,butnotleast,isthedeclarationofsomeslots.Apartfromthebuttonclickhandlers,thefirsttwoareusedtomonitorthenetworksystem.Thisisexplainedlater.
Let'seditthemainwindow.cppfileandtakealookattheconstructorofMainWindow:
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
networkConfManager=newQNetworkConfigurationManager(this);
confListModel=newQStandardItemModel(0,1,this);
ui->listView->setModel(confListModel);
ui->progressBar->setVisible(false);
connect(networkConfManager,
&QNetworkConfigurationManager::onlineStateChanged,this,
&MainWindow::onOnlineStateChanged);
connect(networkConfManager,
&QNetworkConfigurationManager::configurationChanged,this,
&MainWindow::onConfigurationChanged);
connect(networkConfManager,
&QNetworkConfigurationManager::updateCompleted,this,
&MainWindow::onRefreshCompleted);
connect(ui->refreshButton,&QPushButton::clicked,this,
&MainWindow::onRefreshClicked);
connect(ui->connectButton,&QPushButton::clicked,this,
&MainWindow::onConnectClicked);
connect(ui->disconnectButton,&QPushButton::clicked,this,
&MainWindow::onDisconnectClicked);
onOnlineStateChanged(networkConfManager->isOnline());
onRefreshClicked();
}
WeconstructQNetworkConfigurationManagerwiththisobject,alsoknownasMainWindowasitsQObjectparent.Then,welookattheconstructionofconfListModel.Theargumentsarethecountofrow,thecountofcolumn,andtheQObjectparent,whichisthisasusual.WewilluseonlyonecolumnbecauseweuseListViewtodisplaythedata.IfyouuseTableView,youwillprobablyusemorecolumns.Then,webindthismodeltolistViewofui.Afterthis,wehideprogressBarbecauseit'sabusyindicator,whichonlyshowsupwhenthereisworkrunning.Therewillbeseveralconnectstatementsbeforewecalltwomemberfunctionsexplicitly.Amongthem,youmaywanttolookintothesignalsofQNetworkConfigurationManager.TheonlineStateChangedsignalisemittediftheonlinestatusofthesystemischanged,thatis,offlinefromonline.TheconfigurationChangedsignalisemittedwheneverthestateofQNetworkConfigurationischanged.OnceQNetworkConfigurationManagerfinishedupdateConfigurations,theupdateCompletedsignalwillbeemitted.Intheendoftheconstructor,wecallonOnlineStateChangeddirectlyinordertosetupthetextofonlineStatus.Similarly,callingonRefreshClickedenablesanapplicationtoscanforallthenetworkconfigurationsatthestart.
Asmentionedbefore,theonOnlineStateChangedfunctionisusedtosetuponlineStatus.It'lldisplayOnlineifthesystemisconsideredtobeconnectedtoanotherdeviceviaanactivenetworkinterface;otherwise,it'lldisplayOffline.Thisfunction'sdefinitionisshownasfollows:
voidMainWindow::onOnlineStateChanged(boolisOnline)
{
ui->onlineStatus->setText(isOnline?"Online":"Offline");
}
InsidetheonConfigurationChangedslotfunction,whichisshowninthefollowingcode,wechangetheitem'sbackgroundcolortoindicatewhetheraconfigurationisactiveornot.WeusethefindItemsfunctiontogetitemList,whichcontainsonlysomeQStandardItemthatmatchesconfig.name()exactly.However,theconfigurationnamemaynotbeunique.Thisiswhyweuseaforeachlooptocomparetheidentifierofconfig,whichisauniquestring,wherethedatafunctionisusedtoretrievethespecificdatawhosetypeisQVariant.Then,weusetoStringtocastitbacktoQString.QStandardItemenablesussetmultipledataintooneitem.
voidMainWindow::onConfigurationChanged(const
QNetworkConfiguration&config)
{
QList<QStandardItem*>itemList=confListModel-
>findItems(config.name());
foreach(QStandardItem*i,itemList){
if(i-
>data(Qt::UserRole).toString().compare(config.identifier())==
0){
if
(config.state().testFlag(QNetworkConfiguration::Active)){
i->setBackground(QBrush(Qt::green));
}
else{
i->setBackground(QBrush(Qt::NoBrush));
}
}
}
}
ThismeansthatwestoreidentifierasaQt::UserRoledata.Itwon'tbedisplayedonthescreen;instead,itservesasaspecificdatacarrier,whichturnsouttobeveryhelpfulinthiscase.Thus,afterthis,ifit'sactive,wesetthebackgroundcolortogreen;otherwise,usenobrush,whichmeansadefaultbackground.NotethatthestatefunctionofQNetworkConfigurationreturnsStateFlags,whichisactuallyaQFlagtemplateclass,wherethebestpracticeistocheckwhetherornotaflagissetistousethetestFlagfunction.
Let'schecktheonRefreshClickedfunction,whichisshowninthefollowingcodebeforeonRefreshCompleted.It'llcallupdateConfigurationsoftheQNetworkConfigurationManager*networkConfManager.Thisfunctionisatimeconsumingone,especiallyifitneedstoscanWLAN.Therefore,weshowprogressBartotelluserstobepatientanddisablerefreshButton,sinceit'srefreshing.
voidMainWindow::onRefreshClicked()
{
ui->progressBar->setVisible(true);
ui->refreshButton->setEnabled(false);
networkConfManager->updateConfigurations();
}
Whentheupdatehasbeencompleted,theupdateCompletedsignalisemittedandtheonRefreshCompletedboundslotisexecuted.Checkthefollowingfunctionshownhere,whereweneedtopurgethelist.However,insteadofcallingtheclearfunction,weuseremoveRows,whichwouldsparethecolumn.Ifyou'recallingclear,bewaretoaddthecolumnback;otherwise,thereisliterallynocolumn,whichmeansthatthereisnoplacetoputtheitem.Intheforeachloop,weaddalltheconfigurationsthat
networkConfManagerhasfoundtoconfListModel.AsImentionedpreviously,weusethenameasdisplayingtext,whilewesetitsidentifierasahiddenuserroledata.Aftertheloop,hideprogressBarastherefreshingisfinished,andthenenablerefreshButton.
voidMainWindow::onRefreshCompleted()
{
confListModel->removeRows(0,confListModel->rowCount());
foreach(QNetworkConfigurationc,networkConfManager-
>allConfigurations()){
QStandardItem*item=newQStandardItem(c.name());
item->setData(QVariant(c.identifier()),Qt::UserRole);
if(c.state().testFlag(QNetworkConfiguration::Active)){
item->setBackground(QBrush(Qt::green));
}
confListModel->appendRow(item);
}
ui->progressBar->setVisible(false);
ui->refreshButton->setEnabled(true);
}
Theremainingtwoarehandlerstotheconnectanddisconnectbuttons.ForconnectButton,weshowprogressBarbecauseitmaytakealongtimetogettheIPaddressfromtherouter.Then,wegetidentifierfromthedataofconfListModeldirectlyandsaveitasQStringident,wherethecurrentIndexfunctionoflistViewwillreturnthecurrentQModelIndexoftheview.Byusingthisindex,wecangetthecurrentlyselecteddatafromthemodel.Then,weconstructQNetworkConfigurationfromidentbycallingconfigurationFromIdentifierofnetworkConfManager.TheQNetworkSessionsessionisconstructedusingQNetworkConfiguration.Atlast,openthisnetworksessionandwaitfor1,000milliseconds.Then,calldeleteLatertosafelyreleasethesession.Also,hideprogressBarafteralltheseworksintheend.
voidMainWindow::onConnectClicked()
{
ui->progressBar->setVisible(true);
QStringident=confListModel->data(ui->listView-
>currentIndex(),Qt::UserRole).toString();
QNetworkConfigurationconf=networkConfManager-
>configurationFromIdentifier(ident);
QNetworkSession*session=newQNetworkSession(conf,this);
session->open();
session->waitForOpened(1000);
session->deleteLater();
ui->progressBar->setVisible(false);
}
voidMainWindow::onDisconnectClicked()
{
QStringident=confListModel->data(ui->listView-
>currentIndex(),Qt::UserRole).toString();
QNetworkConfigurationconf=networkConfManager-
>configurationFromIdentifier(ident);
QNetworkSession*session=newQNetworkSession(conf,this);
if(networkConfManager-
>capabilities().testFlag(QNetworkConfigurationManager::SystemS
essionSupport)){
session->close();
}
else{
session->stop();
}
session->deleteLater();
}
AsfordisconnectButton,theonDisconnectClickedhandlerwilldothereverse,whichistostopthenetworksession.ThefirstthreelinesareidenticaltothoseinonConnectClicked.However,wethenneedtotestwhethertheplatformsupportsout-of-processsessions.AsstatedintheQtdocumentation,theresultofcallingclosewillbeasfollows:
voidQNetworkSession::close()[slot]
Decreasesthesessioncounterontheassociatednetworkconfiguration.Ifthesessioncounterreacheszerotheactivenetworkinterfaceisshutdown.Thisalsomeansthatstate()willonlychangefromConnectedtoDisconnectedifthecurrent
sessionwasthelastopensession.
However,iftheplatformdoesn'tsupportout-of-processsessions,theclosefunctionwon'tstoptheinterface,inwhichcaseweneedtousestopinstead.
Therefore,wecallthecapabilitiesfunctionofnetworkConfManagertocheckwhetherithasSystemSessionSupport.Callcloseifitdoes,otherwisecallstop.Then,wejustcalldeleteLatertosafelyreleasethesession.
Now,runthisapplication,andyou'llexpectitworksasthefollowingscreenshot:
OnWindows,thenetworkarchitectureisdifferentfromthatoftheworldofUnix.So,youmayfindsomeoddconfigurationsinthelist,suchasTeredoTunnelingPseudo-Interfaceinthescreenshot.Don'tworryabouttheseconfigurationsandjustignorethem!Also,thereisnoQtAPItoallowyoutoconnecttoanewlydiscoveredencryptedWi-Fiaccesspoint.ThisisbecausethereisnoimplementationinplacetoaccesstheWLANsystempasswords.Inotherwords,itcanonlybeusedtocontrolthenetworksessionsthatarealreadyknowntothesystem.
SummaryInthischapter,youhavehadachancetopracticewhatyouhavelearnedinthepreviouschapterswhilepickingupnewskillsinQt.Sofar,you'llhavegainedaninsightintothearchitectureofQtthatiscommonlyseenandsharedbyitssubmodules.Afterall,networkingandthreadingwilldefinitelybringyourapplicationstoahigherlevel.
Inthenextchapter,besidesparsingXMLandJSONdocuments,we'regoingtorockAndroidwithQt!
Chapter7.ParsingJSONandXMLDocumentstoUseOnlineAPIsInthischapter,you'llfindthepowerfulapplication,Qt,runningonthepopularAndroiddevices.FollowingtheintroductionofQtapplicationdevelopmentforAndroid,italsoutilizesonlineAPIs,whichusuallyreturnJSONorXMLdocuments.Thetopicsthatarecoveredinthischapterareasfollows:
SettingupQtforAndroidParsingJSONresultsParsingXMLresultsBuildingQtapplicationsforAndroidParsingJSONinQML
SettingupQtforAndroidQtforAndroidrequiresatleastanAPIlevel10(forAndroid2.3.3platforms).MostQtmodulesaresupported,whichmeansyourQtapplicationcanbedeployedonAndroidwithlittleornomodification.Fordevelopment,bothQtWidget-basedapplicationsandQtQuickapplicationsinQtCreatoraresupportedonAndroid.However,settingupQtforAndroidonaWindowsPCisnotverystraightforward.Therefore,beforeweventuredeeperintoanything,let'ssetupthedevelopmentenvironmentforQtonAndroid.
First,youneedtoinstallQtforAndroid.Ifyou'reusinganonlineinstaller,remembertoselecttheAndroidcomponents,asshowninthefollowingscreenshot:
Here,weonlychoseAndroidarmv7,whichenablesustodeployapplicationsforARMv7Androiddevices.Ifyou'reusinganofflineinstaller,downloadQtfortheAndroidinstaller.
Now,let'sinstallaJavaDevelopmentKit(JDK).ThereisnowaytogetridofJava,sinceAndroidheavilydependsonit.Also,notethatyouneedtoinstallatleastVersion6ofJDK,accordingtohttp://doc.qt.io/qt-5/androidgs.html.YoucandownloadJDKfromhttp://www.oracle.com/technetwork/java/javase/downloads/index.html.YoualsoneedtosetaJAVA_HOMEenvironmentvariableintheJDKinstallationdirectory,D:\ProgramFiles\Java\jdk1.8.0_25.
Now,let'sinstalltwokitsfromGoogle,theAndroidSDKandAndroidNDK.Alwaysremembertodownloadthelatestversion;hereweuseAndroidSDKr24.0.2andAndroidNDKr10b.
AfteryouinstalltheAndroidSDK,runtheSDKManager.InstallorupdateAndroidSDKTools,AndroidSDKPlatform-tools,AndroidSDKBuild-tools,GoogleUSBDriver,atleastoneAPIlevel'sSDKPlatform,andARMEABIv7aSystemImageforthepurposeofourtask.Forthischapter,weinstalledAPI19'sSDKPlatformandARMEABIv7aSystemImage.Then,editthePATHenvironmentvariable.AddthepathoftheplatformandSDKtoolstoitwithasemicolonasaseparator.IfD:\ProgramFiles(x86)\Android\android-sdkisthepathofAndroidSDKTools,itwouldbeasfollows:
D:\ProgramFiles(x86)\Android\android-sdk\platform-tools;D:\ProgramFiles
(x86)\Android\android-sdk\tools
NoteAndroidSDKandNDKcanbeobtainedontheAndroiddeveloperwebsite,http://developer.android.com.
OnceyoudownloadtheNDK,extractthezipfiletoyourharddrive,D:\android-ndk.Then,addanenvironmentvariablenamedANDROID_NDK_ROOTwiththevalue,D:\android-ndk.
SimilarproceduresshouldbeappliedforApacheAnt.Youcandownloaditfromhttp://ant.apache.org/bindownload.cgi.WeuseApacheAnt1.9.4inthisbook.Thereisnoenvironmentvariablethatneedstobesethere.Now,rebootyourcomputerifyou'reusingWindowssothattheenvironmentvariablescanberefreshedandloadedcorrectly.
OpenAVDManagerandcreateanewvirtualdevice.You'dbetterchooseasmallervirtualdevicesuchasNexusSforthisexercise,asshowninthefollowingscreenshot.Feelfreetochangeitifyouwant,butremembertotickUseHostGPU,whichwillmakethevirtualdeviceuseGLEStoacceleratethegraphics.Ifyouhaven'tturnedthaton,you'llgetanextremelyslowvirtualdevicethatmightevenbetoosluggishtotestapplicationson.
Now,openQtCreator;navigatetoTools|Options.SeeifQtVersioninBuild&RunhasanAndroidentry.YouhavetomanuallyaddQtforAndroidifit'snotthere.Then,switchtotheAndroidoptions,setupJDK,AndroidSDK,AndroidNDK,andAnt,asshowninthefollowingscreenshot:
Thewarningformissingarchitecturescanbesafelyignoredbecausewewon'tdevelopapplicationsforMIPSandx86Androidinthischapter.However,payattentiontoitifyouneedtodeployyourapplicationsonthesehardwareplatforms.
ClickonApplyandswitchtotheDevicesoptions.ThereshouldbeaRunonAndroiditemintheDevicecombobox.Anauto-detectedAndroidforarmeabi-v7aisexpectedifyounavigatetoBuild&Run|Kitsnow.
Now,let'stestifwecanrunaQtapplicationonourvirtualAndroiddevice.OpenAVDManagerandstartthevirtualdevice.Westartitfirstbecauseitcouldtakealotoftime.Then,openQtCreatorandmakeasimpleapplication.
1. CreateanewQtWidget-basedapplicationproject.2. SelectAndroidforarmeabi-v7aKit.3. Editmainwindow.uianddragalabeltocentralWidget.4. ChangetheMainWindowpage'slayouttoLayOutVertically(orothers)sothat
thewidgetswillbestretchedautomatically.5. Changethelabel'stexttoHelloAndroid!orsomethingelse.
Waitforthetime-consumingvirtualAndroiddeviceuntilit'sfullystarted.Ifit'snot,clickonRunandwaitforafewminutes.You'llseethisapplicationrunningonourvirtualAndroiddevice.Asseeninthefollowingscreenshot,theQtforAndroiddevelopmentenvironmentissetupsuccessfully.So,wecanmoveonandwriteanapplicationthatcanuseacameratotakephotos:
TipTestinganapplicationonadesktopwhileit'sincomplete,andthentestingitonamobileplatformwouldsaveplentyoftimecomparedtotestingonthevirtualAndroiddeviceallthetime.Inadditiontothis,it'smuchfastertotestonareal
devicethanavirtualone.
Insteadoftoleratingaslowemulator,we'regoingtofirstdeveloptheapplicationonadesktop,thendeployitonanactualAndroiddeviceandseeifthereisanythingmismatchedorinappropriateformobiledevices.Makeanyrelevantchangesaccordingly.Thiscouldsaveyouplentyoftime.However,itstilltakesalongertime,eventhoughtheactualAndroiddeviceismuchmoreresponsivethanthevirtualone.
ParsingJSONresultsTherearetonsofcompaniesthatprovidedevelopersAPIstoaccesstotheirservices,includingthedictionary,weather,andsoon.Inthischapter,we'lluseYahoo!WeatherasanexampletoshowyouhowtouseitsonlineAPItogetweatherdata.FormoredetailsaboutYahoo!WeatherAPI,refertohttps://developer.yahoo.com/weather/.
Now,let'screateanewprojectnamedWeather_Demo,whichisaQtWidget-basedapplicationproject.Asusual,let'sfirstdesigntheUI.
We'veremovedthemenubar,toolbar,andstatusbaraswedidbefore.Then,weaddedaLabel,LineEdit,andPushButtonontopofcentralWidget.TheirobjectnamesarewoeidLabel,woeidEdit,andokButton,respectively.Afterthis,anotherlabelnamedlocationLabelisusedtodisplaythelocationreturnedfromtheAPI.TheredrectangleisHorizontalLayout,whichconsistsoftempLabelandwindLabel,whicharebothLabelandareseparatedbyHorizontalSpacer.AppendLabel,whoseobjectnameisattrLabel,andthenchangeitsalignmenttoAlignRightandAlignBottom.
WhereOnEarthID(WOEID)isa32-bitidentifierthatisuniqueandnonrepetitive.ByusingWOEID,wecanavoidduplicity.However,thisalsomeansthatweneedtofindoutwhatWOEIDisusedforourlocation.Luckily,thereareseveralwebsitesthatprovideyouwitheasy-to-useonlinetoolstogettheWOEID.OneofthemistheZourbuthproject,Yahoo!WOEIDLookup,whichcanbeaccessedathttp://zourbuth.com/tools/woeid/.
Now,let'smoveonandfocusontheparsingofAPIresults.WecreatedanewC++
class,Weather,todealwiththeYahoo!WeatherAPI.I'dliketointroduceyoutoparsingtheJSON(JavaScriptObjectNotation)resultsbeforeXML.However,beforewecooktheWeatherclass,remembertoaddnetworktoQTintheprojectfile.Inthiscase,theWeather_Demo.proprojectfilelookslikethis:
QT+=coreguinetwork
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Weather_Demo
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp\
weather.cpp
HEADERS+=mainwindow.h\
weather.h
FORMS+=mainwindow.ui
Now,wecanwritetheWeatherclass.Itsweather.hheaderfileispastedasfollows:
#ifndefWEATHER_H
#defineWEATHER_H
#include<QObject>
#include<QJsonDocument>
#include<QJsonObject>
#include<QNetworkAccessManager>
#include<QNetworkReply>
#include<QImage>
classWeather:publicQObject
{
Q_OBJECT
public:
explicitWeather(QObject*parent=0);
signals:
voidupdateFinished(constQString&location,constQString
&temp,constQString&wind);
voidimageDownloaded(constQImage&);
publicslots:
voidupdateData(constQString&woeid);
voidgetAttrImg();
private:
QNetworkAccessManager*naManager;
QNetworkReply*imgReply;
QImageattrImg;
privateslots:
voidonSSLErrors(QNetworkReply*);
voidonQueryFinished(QNetworkReply*);
};
#endif//WEATHER_H
Inadditiontotheweatherinformationquery,wealsousethisclasstogetanattributionimage,whichisstatedintheYahoo!documentation.ItiskindoftrivialintraditionalQt/C++thatwehavetouseQNetworkAccessManagertoaccessQUrl,becauseQJsonDocumentcannotloadfromQUrldirectly.Anyway,let'sseehowwegettheresultfromtheYahoo!WeatherAPIintheweather.cppfile.Theheaderpartincludesthefollowinglines:
#include<QDebug>
#include<QNetworkRequest>
#include<QJsonArray>
#include"weather.h"
Then,let'sseetheconstructorofWeather.Here,wesimplyconstructtheQNetworkAccessManagerobject,naManager,andconnectitssignals:
Weather::Weather(QObject*parent):
QObject(parent)
{
naManager=newQNetworkAccessManager(this);
connect(naManager,&QNetworkAccessManager::finished,this,
&Weather::onQueryFinished);
connect(naManager,&QNetworkAccessManager::sslErrors,
this,&Weather::onSSLErrors);
}
TheonSSLErrorsslotissimplytolettheQNetworkReplyobjectignorealltheSSLerrors.Thiswon'tcauseanyseriousproblemsinthiscase.However,ifyou'redealingwithasecurecommunicationoranythingelsethatneedstovalidatetheconnection,youmaywishtolookintotheerror.
voidWeather::onSSLErrors(QNetworkReply*re)
{
re->ignoreSslErrors();
}
Then,let'schecktheupdateDatafunctionbeforeonQueryFinished.Here,weconstructQUrl,whichistheYahoo!WeatherAPI'sexactaddress.Notethatyoudon'tneedtouseanHTMLcodeforQUrl.Infact,it'dbebettertouseaspacealongwith
theothersymbolsdirectly.Afterthis,similartothepreviouschapter,weuseQNetworkRequesttowrapthisQUrlanddispatchtherequestthroughQNetworkAccessManager.
voidWeather::updateData(constQString&woeid)
{
QUrlurl("https://query.yahooapis.com/v1/public/yql?
q=select*fromweather.forecastwherewoeid="+woeid+
"&format=json");
QNetworkRequestreq(url);
naManager->get(req);
}
AsforthegetAttrImgfunction,it'salmostthesame.Theonlydifferenceisthatthisfunctionisusedtogetanattributionimageinsteadofweatherinformation.WestorethereplyasimgReplysothatwecandistinguishtheimagefromtheweather.
voidWeather::getAttrImg()
{
QUrlurl("https://poweredby.yahoo.com/purple.png");
QNetworkRequestreq(url);
imgReply=naManager->get(req);
}
IfthecorrespondingQNetworkReplyobjectisfinished,theonQueryFinishedslotfunctionwillbeexecuted,whichisshowninthefollowingcode.Afterallthepavement,let'sseewhat'sinsidethisfunction.Wecancheckwhetherthereisanyerrorinthereplyattheverybeginning.Then,ifit'simgReply,wecookQImagefromthedataandemitasignaltosendthisimageout.Ifnoneofthesehappen,we'llparsetheweatherfromtheJSONreply.
voidWeather::onQueryFinished(QNetworkReply*re)
{
if(re->error()!=QNetworkReply::NoError){
qDebug()<<re->errorString();
re->deleteLater();
return;
}
if(re==imgReply){
attrImg=QImage::fromData(imgReply->readAll());
emitimageDownloaded(attrImg);
imgReply->deleteLater();
return;
}
QByteArrayresult=re->readAll();
re->deleteLater();
QJsonParseErrorerr;
QJsonDocumentdoc=QJsonDocument::fromJson(result,&err);
if(err.error!=QJsonParseError::NoError){
qDebug()<<err.errorString();
return;
}
QJsonObjectobj=doc.object();
QJsonObjectres=
obj.value("query").toObject().value("results").toObject().valu
e("channel").toObject();
QJsonObjectlocObj=res["location"].toObject();
QStringlocation;
for(QJsonObject::ConstIteratorit=locObj.constBegin();
it!=locObj.constEnd();++it){
location.append((*it).toString());
if((it+1)!=locObj.constEnd()){
location.append(",");
}
}
QStringtemperature=res["item"].toObject()
["condition"].toObject()["temp"].toString()+
res["units"].toObject()["temperature"].toString();
QJsonObjectwindObj=res["wind"].toObject();
QStringwind;
for(QJsonObject::ConstIteratorit=windObj.constBegin();
it!=windObj.constEnd();++it){
wind.append(it.key());
wind.append(":");
wind.append((*it).toString());
wind.append("\n");
}
emitupdateFinished(location,temperature,wind);
}
AsImentionedbefore,itistrivial.First,wereadtheresultfromQNetworkReply,andthenuseQJsonDocument::fromJsontoparsethebytearrayasaJSONdocument.Ifthereisanerrorduringtheprocess,wesimplyprinttheerrorstringandreturn.Then,weneedtogetQJsonObjectcontainedinQJsonDocument.Onlythencanweparsealltheinformationinsideit.Theformattedresultusing560743astheWOEIDisshownasfollows:
{
"query":{
"count":1,
"created":"2014-12-05T23:19:54Z",
"lang":"en-GB",
"results":{
"channel":{
"title":"Yahoo!Weather-Dublin,IE",
"link":"http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__I
E/*http://weather.yahoo.com/forecast/EIXX0014_f.html",
"description":"Yahoo!WeatherforDublin,IE",
"language":"en-us",
"lastBuildDate":"Fri,05Dec20149:59pmGMT",
"ttl":"60",
"location":{
"city":"Dublin",
"country":"Ireland",
"region":"DUB"
},
"units":{
"distance":"mi",
"pressure":"in",
"speed":"mph",
"temperature":"F"
},
"wind":{
"chill":"29",
"direction":"230",
"speed":"8"
},
"atmosphere":{
"humidity":"93",
"pressure":"30.36",
"rising":"1",
"visibility":"6.21"
},
"astronomy":{
"sunrise":"8:22am",
"sunset":"4:09pm"
},
"image":{
"title":"Yahoo!Weather",
"width":"142",
"height":"18",
"link":"http://weather.yahoo.com",
"url":"http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-
wea.gif"
},
"item":{
"title":"ConditionsforDublin,IEat9:59pmGMT",
"lat":"53.33",
"long":"-6.29",
"link":"http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__I
E/*http://weather.yahoo.com/forecast/EIXX0014_f.html",
"pubDate":"Fri,05Dec20149:59pmGMT",
"condition":{
"code":"29",
"date":"Fri,05Dec20149:59pmGMT",
"temp":"36",
"text":"PartlyCloudy"
},
"description":"\n<img
src=\"http://l.yimg.com/a/i/us/we/52/29.gif\"/><br
/>\n<b>CurrentConditions:</b><br/>\nPartlyCloudy,36F<BR
/>\n<BR/><b>Forecast:</b><BR/>\nFri-PartlyCloudy.High:
44Low:39<br/>\nSat-MostlyCloudy.High:48Low:41<br
/>\nSun-MostlySunny/Wind.High:43Low:37<br/>\nMon-
MostlySunny/Wind.High:43Low:37<br/>\nTue-PMLight
Rain/Wind.High:52Low:38<br/>\n<br/>\n<a
href=\"http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__IE
/*http://weather.yahoo.com/forecast/EIXX0014_f.html\">Full
ForecastatYahoo!Weather</a><BR/><BR/>\n(providedby<a
href=\"http://www.weather.com\">TheWeatherChannel</a>)
<br/>\n",
"forecast":[
{
"code":"29",
"date":"5Dec2014",
"day":"Fri",
"high":"44",
"low":"39",
"text":"PartlyCloudy"
},
{
"code":"28",
"date":"6Dec2014",
"day":"Sat",
"high":"48",
"low":"41",
"text":"MostlyCloudy"
},
{
"code":"24",
"date":"7Dec2014",
"day":"Sun",
"high":"43",
"low":"37",
"text":"MostlySunny/Wind"
},
{
"code":"24",
"date":"8Dec2014",
"day":"Mon",
"high":"43",
"low":"37",
"text":"MostlySunny/Wind"
},
{
"code":"11",
"date":"9Dec2014",
"day":"Tue",
"high":"52",
"low":"38",
"text":"PMLightRain/Wind"
}
],
"guid":{
"isPermaLink":"false",
"content":"EIXX0014_2014_12_09_7_00_GMT"
}
}
}
}
}
}
NoteFordetailsaboutJSON,visithttp://www.json.org.
Asyoucansee,alltheinformationisstoredinsidequery/results/channel.Therefore,weneedtoconvertittoQJsonObject,levelbylevel.Asyoucanseeinthecode,QJsonObjectresischannel.NotethatthevaluefunctionwillreturnaQJsonValueobjectandyouwillneedtocalltoObject()tomakeitQJsonObjectbeforeyoucanusethevaluefunctiontoparsethevalueagain.Afterthis,it'sprettystraightforward.ThelocObjobjectisthelocationwhereweuseaforlooptoputthevaluestogether,whereasQJsonObject::ConstIteratorisjustQt'swrapperofSTLconst_iterator.
Toobtainthecurrenttemperature,weneedtogothroughasimilarjourneytochannelbecausethetemperatureisinitem/condition/temp,whileitsunitisunits/temperature.
Asforthewindsection,weusealazywaytoretrievethedata.ThewindObjlineisnotasinglevaluestatement;instead,ithasseveralkeysandvalues.Therefore,weuseaforlooptowalkthroughthisarrayandretrievebothofitskeysalongwithitsvalue,andsimplyputthemtogether.
Now,let'sgobacktotheMainWindowclasstoseehowtointeractwiththeWeatherclass.TheheaderfileofMainWindow,whichismainwindow.h,ispastedhere:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
#include"weather.h"
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
Weather*w;
privateslots:
voidonOkButtonClicked();
voidonAttrImageDownloaded(constQImage&);
voidonWeatherUpdateFinished(constQString&location,const
QString&temp,constQString&wind);
};
#endif//MAINWINDOW_H
WedeclareaWeatherobjectpointer,w,astheMainWindowclass'sprivatemember.Meanwhile,onOkButtonClickedisthehandlerwhenokButtongetsclicked.TheonAttrImageDownloadedandonWeatherUpdateFinishedfunctionswillbecoupledwiththeWeatherclass'ssignals.Now,let'sseewhat'sinsidethesourcefile:
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
w=newWeather(this);
connect(ui->okButton,&QPushButton::clicked,this,
&MainWindow::onOkButtonClicked);
connect(w,&Weather::updateFinished,this,
&MainWindow::onWeatherUpdateFinished);
connect(w,&Weather::imageDownloaded,this,
&MainWindow::onAttrImageDownloaded);
w->getAttrImg();
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::onOkButtonClicked()
{
w->updateData(ui->woeidEdit->text());
}
voidMainWindow::onAttrImageDownloaded(constQImage&img)
{
ui->attrLabel->setPixmap(QPixmap::fromImage(img));
}
voidMainWindow::onWeatherUpdateFinished(constQString
&location,constQString&temp,constQString&wind)
{
ui->locationLabel->setText(location);
ui->tempLabel->setText(temp);
ui->windLabel->setText(wind);
}
Intheconstructor,apartfromthesignalsconnectionandthewobject'sconstruction,wecallgetAttrImgofwtoretrievetheattributionimage.Whentheimageisdownloaded,theonAttrImageDownloadedslotfunctionwillbeexecutedwheretheimagewillbedisplayedonattrLabel.
OncetheuserclicksonokButton,theonOkButtonClickedslotfunctiongetsexecuted,wherewecalltheupdateDatafunctionoftheWeatherclasstopasstheWOEID.Then,whentheupdateisfinished,theupdateFinishedsignalisemittedandonWeatherUpdateFinishedisexecuted.WejustusethesethreeQStringobjectstosetthecorrespondinglabel'stext.
Now,testyourapplicationtoseeifit'srunningasshowninthisscreenshot:
ParsingXMLresultsAlthoughalotofAPIsprovidebothXMLandJSONresults,youmaystillfindthatsomeofthemonlyofferoneformat.Besides,youmightfeelthatparsingJSONinC++/Qtisnotapleasantprocess.YoumayrememberhoweasyitistoparsetheXMLmodelinQML/QtQuick.Well,let'sseehowtodothisinC++/Qt.
Tomakeuseofanxmlmodule,wehavetoaddxmltoQTintheprojectfile,thesamewaywedidtonetwork.Thistime,QthasprovidedanXMLreaderclasscalledQXmlStreamReadertohelpusparsetheXMLdocuments.ThefirstthingweneedtodoistochangetheupdateDatafunctionintheWeatherclasstolettheYahoo!WeatherAPIreturnanXMLresult.
voidWeather::updateData(constQString&woeid)
{
QUrlurl("https://query.yahooapis.com/v1/public/yql?q=select
*fromweather.forecastwherewoeid="+woeid+
"&format=xml");
QNetworkRequestreq(url);
naManager->get(req);
}
Thechangingof&format=jsonto&format=xmlneedstobedonehere.Incontrasttothis,thereisalotofworktodointheonQueryFinishedslotfunction.TheoldJSONpartiscommentedoutsothatwecanwritetheXMLparsingcode.Themodifiedfunctionwithoutthecommentisshownasfollows:
voidWeather::onQueryFinished(QNetworkReply*re)
{
if(re->error()!=QNetworkReply::NoError){
qDebug()<<re->errorString();
re->deleteLater();
return;
}
if(re==imgReply){
attrImg=QImage::fromData(imgReply->readAll());
emitimageDownloaded(attrImg);
imgReply->deleteLater();
return;
}
QByteArrayresult=re->readAll();
re->deleteLater();
QXmlStreamReaderxmlReader(result);
while(!xmlReader.atEnd()&&!xmlReader.hasError()){
QXmlStreamReader::TokenTypetoken=xmlReader.readNext();
if(token==QXmlStreamReader::StartElement){
QStringRefname=xmlReader.name();
if(name=="channel"){
parseXMLChannel(xmlReader);
}
}
}
}
Here,parseXMLChannelisanewlycreatedmemberfunction.Wecanuseaseparatefunctiontomakeourcodeneatandtidy.
NoteRemembertodeclaretheparseXMLChannelfunctionintheheaderfile.
Itsdefinitionispastedasfollows:
voidWeather::parseXMLChannel(QXmlStreamReader&xml)
{
QStringlocation,temperature,wind;
QXmlStreamReader::TokenTypetoken=xml.readNext();
while(token!=QXmlStreamReader::EndDocument){
if(token==QXmlStreamReader::EndElement||
xml.name().isEmpty()){
token=xml.readNext();
continue;
}
QStringRefname=xml.name();
if(name=="location"){
QXmlStreamAttributeslocAttr=xml.attributes();
location=locAttr.value("city").toString()+","+
locAttr.value("country").toString()+","+
locAttr.value("region").toString();
}
elseif(name=="units"){
temperature=
xml.attributes().value("temperature").toString();
}
elseif(name=="wind"){
QXmlStreamAttributeswindAttr=xml.attributes();
for(QXmlStreamAttributes::ConstIteratorit=
windAttr.begin();it!=windAttr.end();++it){
wind.append(it->name().toString());
wind.append(":");
wind.append(it->value());
wind.append("\n");
}
}
elseif(name=="condition"){
temperature.prepend(xml.attributes().value("temp").toString())
;
break;//wegotallinformation,exittheloop
}
token=xml.readNext();
}
emitupdateFinished(location,temperature,wind);
}
BeforewewalkthroughparseXMLChannelfunction,I'dliketoshowyouwhattheXMLdocumentlookslike,shownasfollows:
<?xmlversion="1.0"?>
<queryxmlns:yahoo="http://www.yahooapis.com/v1/base.rng"
yahoo:count="1"yahoo:created="2014-12-06T22:50:22Z"
yahoo:lang="en-GB">
<results>
<channel>
<title>Yahoo!Weather-Dublin,IE</title>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__IE/
*http://weather.yahoo.com/forecast/EIXX0014_f.html</link>
<description>Yahoo!WeatherforDublin,IE</description>
<language>en-us</language>
<lastBuildDate>Sat,06Dec20149:59pm
GMT</lastBuildDate>
<ttl>60</ttl>
<yweather:location
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
city="Dublin"country="Ireland"region="DUB"/>
<yweather:units
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
distance="mi"pressure="in"speed="mph"temperature="F"/>
<yweather:wind
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
chill="41"direction="230"speed="22"/>
<yweather:atmosphere
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
humidity="93"pressure="30.03"rising="2"visibility="6.21"/>
<yweather:astronomy
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
sunrise="8:24am"sunset="4:07pm"/>
<image>
<title>Yahoo!Weather</title>
<width>142</width>
<height>18</height>
<link>http://weather.yahoo.com</link>
<url>http://l.yimg.com/a/i/brand/purplelogo//uh/us/news-
wea.gif</url>
</image>
<item>
<title>ConditionsforDublin,IEat9:59pm
GMT</title>
<geo:lat
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">53.33</ge
o:lat>
<geo:long
xmlns:geo="http://www.w3.org/2003/01/geo/wgs84_pos#">-6.29</ge
o:long>
<link>http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__IE/
*http://weather.yahoo.com/forecast/EIXX0014_f.html</link>
<pubDate>Sat,06Dec20149:59pmGMT</pubDate>
<yweather:condition
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="27"date="Sat,06Dec20149:59pmGMT"temp="48"
text="MostlyCloudy"/>
<description><![CDATA[<img
src="http://l.yimg.com/a/i/us/we/52/27.gif"/><br/><b>Current
Conditions:</b><br/>MostlyCloudy,48F<BR/><BR/>
<b>Forecast:</b><BR/>Sat-LightRain/WindLate.High:48
Low:42<br/>Sun-MostlySunny/Wind.High:44Low:37<br/>
Mon-Sunny.High:43Low:37<br/>Tue-Showers/Wind.High:
53Low:39<br/>Wed-PartlyCloudy/Wind.High:45Low:39<br
/><br/><a
href="http://us.rd.yahoo.com/dailynews/rss/weather/Dublin__IE/
*http://weather.yahoo.com/forecast/EIXX0014_f.html">Full
ForecastatYahoo!Weather</a><BR/><BR/>(providedby<a
href="http://www.weather.com">TheWeatherChannel</a>)
<br/>]]></description>
<yweather:forecast
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="11"date="6Dec2014"day="Sat"high="48"low="42"
text="LightRain/WindLate"/>
<yweather:forecast
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="24"date="7Dec2014"day="Sun"high="44"low="37"
text="MostlySunny/Wind"/>
<yweather:forecast
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="32"date="8Dec2014"day="Mon"high="43"low="37"
text="Sunny"/>
<yweather:forecast
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="11"date="9Dec2014"day="Tue"high="53"low="39"
text="Showers/Wind"/>
<yweather:forecast
xmlns:yweather="http://xml.weather.yahoo.com/ns/rss/1.0"
code="24"date="10Dec2014"day="Wed"high="45"low="39"
text="PartlyCloudy/Wind"/>
<guid
isPermaLink="false">EIXX0014_2014_12_10_7_00_GMT</guid>
</item>
</channel>
</results>
</query>
<!--total:27-->
<!--engine4.yql.bf1.yahoo.com-->
Asyoucandeduce,theXMLstructuresharesalotofsimilaritieswiththeJSONdocument.Forinstance,allthedataweneedisstillstoredinquery/results/channel.Thedifferenceis,however,moresignificantthanyoumayhaveexpected.
NoteIfyouwanttolearnXMLthoroughly,checktheXMLtutorialathttp://www.w3schools.com/xml/.
IntheonQueryFinishedslot,weuseawhilelooptoletxmlReaderkeepreadinguntiltheendoruntilanerror.ThereadNextfunctionoftheQXmlStreamReaderclasswillreadthenexttokenandreturnitstype.TokenTypeisanenum,whichdescribesthetypeoftokencurrentlybeingread.EachtimeyoucallreadNext,QXmlStreamReaderwillmoveforwardbyonetoken.Ifwewanttoreadallthedataofoneelement,wemayhavetoreaditfromthebeginning.Therefore,weuseanifstatementtoensurethatthetokenisatthestarting.Inadditiontothis,wetestifwe'rereadingthechannelnow.Then,wecallparseXMLChanneltoretrievealldatathatweneed.
IntheparseXMLChannelfunction,prettymuchthesamestrategyisused.Wetestthenameelementsothatweknowwhichstagewearein.Onethingworthyourattentionisthatallprefixessuchasyweather:areomitted.Hence,youshoulduselocationinsteadofyweather:location.OtherpartsaresimilartotheircounterpartsinJSON,whereQStringRefissimilartoQJsonValue.Lastbutnotleast,QXmlStreamReaderisastreamreader,whichmeansthatitreadsinorder.Inotherwords,wecanbreakthewhileloopafterwegettempinconditionsinceconditionisthelastelementthatwe'reinterestedin.
Afterthesechanges,youcanbuildandrunthisapplicationagainandyoushouldexpectittoruninthesamemanner.
BuildingQtapplicationsforAndroidYoumaywonderhowtobuildQtapplicationsforAndroiddevicessincethisapplicationisbuiltfordesktopPCs.Well,it'smucheasierthanyouthought.
1. SwitchtoProjectsmode.2. ClickonAddKitandselectAndroidforarmeabit-v7a(GCC4.9andQt5.3.2).
Notethatthetextmaydifferalittlebit.3. Pluginyourphoneifyou'reusingitasthetargetAndroiddevice.4. OpenCommandPromptandrunadbdevices.Makesureyourdeviceisonthe
list.
Now,clickonRunandQtwillpromptadialogaskingyoutoselecttheAndroiddevice,asshowninthefollowingscreenshot:
WechoosetorunourapplicationonanactualAndroiddevice,whichisanHTCOnephoneinthiscase.Ifyoudon'thaveanyavailableAndroiddevices,youmayhavetocreateavirtualdevice,asmentionedatthebeginningofthischapter.Forboththeoptions,choosethedeviceandclickontheOKbutton.
NoteOnanactualAndroiddevice,youneedtogotoSettingsandturnonUSBdebugginginDeveloperoptions.
Asyoucanseefromthefollowingscreenshot,thedemonstrationrunswell.ItdefinitelyneedsongoingimprovementsandUIoptimizationbeforesubmitting,though.However,rememberthatwedesignedandbuiltthisapplicationforadesktopPC!Wehavejustbuiltitforamobilephonewithoutanymodificationanditrunsasexpected.
Whenyoutesttheapplication,alltheinformationisprintedtotheApplicationOutputpanelinQtCreator.Thiscouldbeusefulwhenyourapplicationrunsunexpectedly.
ParsingJSONinQMLLet'srewritetheweatherdemoinQML.YouwillfindouthoweasyandelegantitistowritesuchanapplicationinQML.SincetheXMLpartisalreadycoveredinthepreviouschapter,we'llfocusonparsingJSONthistime.
First,createanewQtQuickapplicationprojectnamedWeather_QML.Keeptheothersettingsasdefault,whichmeansweuseQtQuickControls.RemembertotickthecheckboxoftheAndroidkit.
CreateanewQMLfilenamedWeather.qmltomimictheWeatherclassinthepreviousC++code.Thisfileispastedhere:
importQtQuick2.3
importQtQuick.Controls1.2
Rectangle{
Column{
anchors.fill:parent
spacing:6
Label{
id:location
width:parent.width
fontSizeMode:Text.Fit
minimumPointSize:9
font.pointSize:12
}
Row{
spacing:20
width:parent.width
height:parent.height
Label{
id:temp
width:parent.width/2
height:parent.height
fontSizeMode:Text.Fit
minimumPointSize:12
font.pointSize:72
font.bold:true
}
Label{
id:wind
width:temp.width-20
height:parent.height
fontSizeMode:Text.Fit
minimumPointSize:9
font.pointSize:24
}
}
}
Image{
id:attrImg
anchors{right:parent.right;bottom:parent.bottom}
fillMode:Image.PreserveAspectFit
source:'https://poweredby.yahoo.com/purple.png'
}
functionquery(woeid){
varurl='https://query.yahooapis.com/v1/public/yql?
q=select*fromweather.forecastwherewoeid='+woeid+
'&format=json'
varres
vardoc=newXMLHttpRequest()
doc.onreadystatechange=function(){
if(doc.readyState==XMLHttpRequest.DONE){
res=doc.responseText
parseJSON(res)
}
}
doc.open('GET',url,true)
doc.send()
}
functionparseJSON(data){
varobj=JSON.parse(data)
if(typeof(obj)=='object'){
if(obj.hasOwnProperty('query')){
varch=obj.query.results.channel
varloc='',win=''
for(varlkinch.location){
loc+=ch.location[lk]+','
}
for(varwkinch.wind){
win+=wk+':'+ch.wind[wk]+'\n'
}
location.text=loc
temp.text=ch.item.condition.temp+
ch.units.temperature
wind.text=win
}
}
}
}
ThefirstpartisjustaQMLversionUIofthepreviousapplication.YoumaywanttopayattentiontothefontSizeModeandminimumPointSizepropertyinLabel.ThesepropertiesarenewlyintroducedinQt5,andenablethetextscaletobedynamically
adjusted.BysettingText.FitasfontSizeMode,it'llshrinkthetextifheightorwidthisnotsufficientforthetext,whereminimumPointSizeistheminimumpointsize.Thetextwillgetelidedifitcan'tdisplayataminimumsize.Similartotheelideproperty,youhavetoexplicitlysetthewidthandheightpropertyofTextorLabeltomakethisdynamicmechanismwork.
TheattributionimageisdisplayedinaslightlydifferentwayfromC++.WeutilizetheflexibilityofQtQuicktofloatImageontopofthewholeitembysettingonlyanchors.Inadditiontothis,wedon'tneedtouseQNetworkAccessManagertodownloadtheimage.It'sallinone.
AftertheUIpart,wecreatethetwoJavaScriptfunctionstodothedirtywork.ThequeryfunctionisusedtosendanhttprequestandpassthereceiveddatatotheparseJSONfunctiononceit'sdone.Don'tgetconfusedbyXMLinXMLHttpRequest;it'sjustatraditionalnamingconvention.Then,wecreateahandlerfunctionforonreadystatechanged,whichistocallparseJSONwhentherequestisdone.Notethattheopenfunctionwon'tsendtherequest,onlythesendfunctiondoes.
It'sstillshortandcleanintheparseJSONfunction.JSON.parsewillreturnaJSONobjectifitisparsedsuccessfully.Therefore,weneedtotestwhetheritstypeisobjectbeforewegetintoparsing.Then,wejustdoonemoretesttoseewhetherithasqueryasitsproperty.Ifso,wecanstartextractingdatafromobj.UnlikeitsC++counterpart,wecantreatallitskeysasitspropertiesandusethedotoperationtoaccessthemdirectly.Toshortentheoperations,wefirstcreateachvariable,whichisquery/results/channel.Next,weextractthedatafromthechobject.Finally,wechangethetextdirectly.
NoteThech.locationandch.windobjectscanbetreatedasQVariantMapobjects.Thus,wecanusetheforlooptoeasilyextractthevalues.
Let'seditthemain.qmlfileasshownhere:
importQtQuick2.3
importQtQuick.Controls1.2
import"qrc:/"
ApplicationWindow{
visible:true
width:240
height:320
title:qsTr("WeatherQML")
Row{
id:inputField
anchors{top:parent.top;topMargin:10;left:
parent.left;leftMargin:10;right:parent.right;rightMargin:
10}
spacing:6
Label{
id:woeidLabel
text:"WOEID"
}
TextField{
width:inputField.width-woeidLabel.width
inputMethodHints:Qt.ImhDigitsOnly
onAccepted:weather.query(text)
}
}
Weather{
anchors{top:inputField.bottom;topMargin:10;left:
parent.left;leftMargin:10;right:parent.right;rightMargin:
10;bottom:parent.bottom;bottomMargin:10}
id:weather
}
}
RowisthesameWOEIDinputpanel,forwhichwedon'tcreateanOKbuttonthistime.Instead,wehandletheacceptedsignalinonAcceptedbycallingthequeryfunctioninweather,whichisaWeatherelement.WesettheinputMethodHintspropertytoQt.ImhDigitsOnly,whichisusefulonmobileplatforms.ThisapplicationshouldrunalmostthesameastheC++oneorshouldwesaybetter.
TheinputMethodHintspropertymayseemuselessonadesktop;indeed,youneedtouseinputMaskandvalidatortorestricttheacceptableinput.However,itshowsitspoweronmobiles,asfollows:
Asyoucansee,inputMethodHintsnotonlyrestrictstheinput,butitalsoprovidesabetterexperienceforusers.ThisisalsoviableinaC++/Qtdevelopment;youcanfindtherelevantfunctionstoachievethis.ThewholepointinQMListhatparsingtheJSONandXMLdocumentsiseasierandtidierthanC++.
SummaryAfterthischapter,you'reexpectedtohandlecommontasksandwritetypesofreal-worldapplications.You'llgetyourownunderstandingofQtQuickandtraditionalQt.It'salsoacurrenttrendtowritehybridapplications,whichmakefulluseofbothofthembywritingtheC++pluginstoenhanceQML.QMLhasanunbeatableadvantageofflexibleUIdesign,whichisevenmoreobviousonmobileplatforms.Whilethedevelopmentpartisnearingtheend,inthenextchapterwe'lltalkabouthowtosupportmultiplelanguages.
Chapter8.EnablingYourQtApplicationtoSupportOtherLanguagesInthiseraofglobalization,theinternationalizationandlocalizationofapplicationsisalmostinevitable.Fortunately,Qtprovidesrelevantclasses,alongwithsomehandytoolssuchasQtLinguisttoeasetheburdenofdevelopersandtranslators.Inthischapter,wewillusetwoexampleapplicationstodemonstratethefollowingtopics:
InternationalizationofQtapplicationsTranslatingQtWidgetsapplicationsDisambiguatingidenticaltextsChanginglanguagesdynamicallyTranslatingQtQuickapplications
InternationalizationofQtapplicationsInternationalizationandlocalizationaretheprocessesofadaptingtheapplicationtootherlocales,whichmightincludedifferentlanguagesandregionaldifferences.Insoftwaredevelopment,internationalizationreferstodesigninganapplicationinsuchawaythatitcanbeadaptedtovariouslanguagesandregionswithoutcodechanges.Ontheotherhand,localizationmeansadaptinginternationalizedsoftwareforaspecificlanguageorregion.Thisusuallyinvolveslocale-specificcomponentsandtranslatingtext.
Qthasdonealottofreedevelopersfromdifferentwritingsystems.Wedon'tneedtoworryabouthowdifferentlanguagesdisplayandinput,aslongasweuseQt'sinputanddisplaycontrolsortheirsubclasses.
Inmostcases,whatweneedtodoistoproducetranslationsandenablethemintheapplication.QtofferstheQTranslatorclass,whichloadsthetranslationfileanddisplaysthecorrespondinglanguageonthescreen.Theprocedureisconcludedinthefollowingdiagram:
Firstofall,Qtwon'tjustmakeallthestringstranslatable,becausethatwouldobviouslybeadisaster.Instead,youneedtoexplicitlysetwhetherthestringistranslatableincodeorintheDesignmode.IntheQt/C++code,usethetr()functiontoencloseallthestringsthatcanbetranslated.WeusetheqsTr()functiontodothisjobintheQtQuick/QMLcode.Letmeshowyouanexample.Hereisademonstrationofthenormalusageofastring:
qDebug()<<"HelloWorld";
ThiswilloutputHelloWorldtothestandardoutputstream,whichisyourcommandpromptorshellingeneralcases.IfwewanttomakeHelloWorldtranslatable,weneedtouseatr()functiontoenclosethestring,asfollows:
qDebug()<<tr("HelloWorld");
Sincetr()isastaticpublicmemberfunctionoftheQObjectclass,youcanstilluseitevenforanonQObjectclass.
qDebug()<<QObject::tr("HelloWorld");
Then,weneedtousethelupdatecommand,whichislocatedinTools|External|Linguist|UpdateTranslations(lupdate)inQtCreator.Thiswillupdate,orcreateifthetranslationsource(TS)filedoesn'texist.YoucanthenuseQtLinguisttotranslatethestrings.Beforeyoureleaseyourapplication,runthelreleasecommand,whichislocatedinTools|External|Linguist|ReleaseTranslations(lrelease),togeneratetheQtmessage(QM)filesthatcanbeloadedbyanapplicationdynamically.Don'tworryifitconfusesyou;we'llusetwoexamplestowalkyouthroughtheseprocedures.
TranslatingQtWidgetsapplicationsFirst,let'screateanewQtWidgetproject,whosenameisInternationalization.Then,editmainwindow.uiintheDesignmode.
1. Asusual,removethestatusbar,menubar,andtoolbar.2. AddLabelintocentralWidgetandchangeitsobjectnametononTransLabel.
Then,changeitstexttoThisisanon-translatablelabelandunchecktranslatableundertextinPropertyEditor.
3. DragaPushButtonjustbeneathnonTransLabelwithtransButtonasitsobjectname.ChangeitstexttoThisisatranslatablebutton.
4. ChangeLayouttoLayOutVerticallyinMainWindow.5. Resizetheframetoacomfortablesize.
GobacktoeditingtheInternationalization.proprojectfileintheEditmode.Addalineindicatingthetranslationsourcefile,whichisshownasfollows:
TRANSLATIONS=Internationalization_de.ts
The_desuffixisalocalecode,indicatingthatthisisaGermantranslationsourcefile.ThelocalecodesaredefinedbyInternetEngineeringTaskForceintheBCP47documentseries.Historically,QtfollowsthePOSIXdefinition,whichisslightlydifferentfromBCP47.Inthis,itusesunderscores(_)insteadofhyphens(-)toseparatesubtags.Inotherwords,BrazilianPortugueseisexpressedaspt_BRinsteadofpt-BR.Meanwhile,QthasprovidedsomeAPIstoconformthelocalenametoaBCP47definitionsincetheQt4.8version.
Toensurethischangeisvalid,savetheprojectfileandright-clickontheprojectandselectRunqmake.Afterthis,wecangeneratethetranslationsourcefile,whichisexactlyInternationalization_de.ts,byexecutingthelupdatecommand.TheresultswillbeprintedintheGeneralMessagespanel,whichcontainsthestringsaddedtotheTSfile,asshownhere:
Updating'Internationalization_de.ts'...
Found3sourcetext(s)(3newand0alreadyexisting)
Now,opentheInternationalization_de.tsfileinQtLinguist.TheoverviewUIofQtLinguistisdisplayedinthefollowingscreenshot:
Contextliststhesourcetextcontext,whichistheclassnameinmostcases,whileStringscontainsallthetranslatablestrings.SourcesandFormsdisplaysthecorrespondinglocationofthestring,eitherasapieceofcodeoraUIform.Beneaththemisthetranslationarea,whichletsyouinputthetranslationandcomments,ifthereareany.
Inadditiontotheoverview,theiconinfrontofeachentryisnoteworthy.Ayellowquestionmark(?)simplymeansthereisnotranslationcurrently,whileagreencheckmarkmeansaccepted/correct,andayellowcheckmarkstandsforaccepted/warnings.Youmayalsoencounteraredexclamationmark(!),whichindicateswarnings.Thesharpsymbol(#)infrontofabutton'stextintheSourcesandFormspaneindicatesuntranslated,andpossiblytranslatable,strings.QtLinguistchecksstringtranslationsautomaticallyaccordingtoitsownalgorithm,whichmeansthatitmaygiveafalsewarning.Inthiscase,simplyignorethewarningandacceptthetranslation.
You'llfindthatthelabeltextisn'tamongSourcetext.Thisisbecauseweuncheckedthetranslatableproperty.Now,inputGermantranslationsinthetranslationareaandclickontheDoneandNextbuttoninthetoolbar,thennavigatetoTranslation|DoneandNext.Or,evenquicker,pressCtrl+Entertoacceptthetranslation.Whenyou'vefinished,clickontheSavebutton,andthenexitQtLinguist.
Althoughit'srecommendedtouseQtLinguistfortranslationtasks,it'sviabletouseanormaltexteditortoedittheTSfiledirectly.TheTSfileisXML-formattedandshouldbesupportedwellbyothereditors.
Aftertranslating,returntoQtCreatorandrunthelreleasecommandtogeneratetheInternationalization_de.qmfile.Atthecurrentstage,yourprojectfoldershouldcontainboththeTSandQMfiles,asshowninthefollowingscreenshot:
NoteNotethatfileiconsmaydifferslightlyonyourcomputersbecauseofdifferentoperatingsystemand(or)softwareinstallations.
WealreadyproducedtheQMfile;it'snowtimetomodifythemain.cppfileinordertoloadthetranslationintothisapplication.
#include"mainwindow.h"
#include<QApplication>
#include<QTranslator>
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
QTranslatortranslator;
translator.load(QLocale::German,"Internationalization",
"_");
a.installTranslator(&translator);
MainWindoww;
w.show();
returna.exec();
}
Here,QTranslatorisusedtoloadtheGermantranslation.BeforeweinstalltranslatorintoQApplication,wehavetoloadaQMfilebycallingtheloadfunction.ThiswillloadthetranslationfilewhosefilenameconsistsofInternationalizationfollowedby_andtheUIlanguagename(whichisdeinthiscase)and.qm(thedefaultvalue).Thereisasimplifiedoverloadedloadfunction.Ourequivalentisasfollows:
translator.load("Internationalization_de");
Usually,itwouldbebettertocallthepreviousloadfunctionbecauseitusesQLocale::uiLanguages(),anditwillalsoformatdatesandnumbersifthey'renecessaryforthenewlocale.Whicheveryouchoose,alwaysrememberthatifyouloadthetranslationaftertheMainWindoww;line,MainWindowwon'tbeabletousethetranslationatall.
Ifyouruntheapplicationnow,theapplicationwon'tdisplayGermanyet.Why?ThisissimplybecauseQTranslatorcan'tfindtheInternationalization_de.qmfile.Therearelotsofwaystosolvethisproblem.Theneatestwayistochangetheworkingdirectory,whilerunningtheapplicationinQtCreator.
1. SwitchtotheProjectsmode.2. SwitchtoRunSettings.3. ChangeWorkingdirectorytoyourprojectsourcedirectorywhereyouputthe
Internationalization_de.qmfile.
Now,runitagain;you'llseeGermantextonthescreen,asfollows:
ThelabelisdisplayedinEnglishasweexpected,whereasthewindowtitleandbuttontextaredisplayedinGerman.
Youmaythinkthissolutionpointless,sincetheGermantranslationisloadeddespitethesystemlocalesetting.Well,theapplicationcanloadthetranslationaccordingtothesystemlocalewithonlyonemodification;thatis,changingthetranslatorloadlinetotheoneshownhere:
translator.load(QLocale::system().language(),
"Internationalization","_");
Here,system()isastaticmemberfunctionoftheQLocaleclass,whichreturnsaQLocaleobjectthatinitializedwiththesystemlocale.Wethencallthelanguage()functiontogetthelanguageofthecurrentlocale.
DisambiguatingidenticaltextsIfthereareidenticaltexts,thedefaultbehavioristotreatthemasthetextswiththesamemeaning.Thiscouldeffectivelysavetranslatorsfromtranslatingthesametexts.Meanwhile,thisdoesn'tholdtrueallthetime.Forinstance,thewordopencanbeusedasanounoranadjective,whichmaybedifferentwordsinotherlanguages.Thankfully,it'spossibleandeasytodisambiguateidenticaltextsinQt.
Now,let'saddaPushButtonandopenButtonbetweentransButtonandnonTransLabel.UseOpenasitstext,andtheneditmainwindow.h.AddanewprivateslotnamedonOpenButtonClicked(),whichisusedtohandletheeventwhenopenButtongetsclicked.Therelevantsourcefile,mainwindow.cpp,ispastedasfollows:
#include<QMessageBox>
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
connect(ui->openButton,&QPushButton::clicked,this,
&MainWindow::onOpenButtonClicked);
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::onOpenButtonClicked()
{
QMessageBox::information(this,tr("Dialog"),tr("Open"));
}
First,weconnecttheclickedsignalofopenButtontotheonOpenButtonClickedslotofMainWindowintheconstructorofMainWindow.Then,wesimplyusethestaticmemberfunction,information,ofQMessageBoxtopop-upaninformationdialog,usingDialogasthetitleandOpenasitscontext.Don'tforgettousethetr()functiontomakethesestringstranslatable.
Now,runlupdateandopentheTSfileinQtLinguist.ThereisonlyoneOpenstringintheStringspanel,asshownhere:
However,Openintheinformationdialogissupposedtohaveanadjective,whichshouldn'tbemixedupwiththetextinopenButton.It'sacommentthatweneedtoseparatethisOpenfromtheotherOpen.ModifytheonOpenButtonClickedfunctioninmainwindow.cpp:
voidMainWindow::onOpenButtonClicked()
{
QMessageBox::information(this,tr("Dialog"),tr("Open",
"adj."));
}
Here,thesecondargumentofthetr()functionisthecomment.Differentcommentsstandfordifferenttexts.Inthisway,lupdatewilltreatthemasnonidenticaltexts.Rerunlupdate,andyou'reabletotranslatetwoOpenstringsinQtLinguist.TheDevelopercommentscolumninthetranslationareaisshownhere.QtLinguistwillalsoshowtwotranslatableOpenstrings.
TheequivalentpropertyintheDesignmodeforopenButtonisdisambiguationunder
thetextproperty.Aftertranslation,executelrelease,andthenreruntheapplicationandthetwoOpenstringsshouldhavetwodifferenttranslations,whichisdemonstratedhere:
ChanginglanguagesdynamicallySometimes,peoplewanttouselanguagesotherthantheonespecifiedbythesystemlocale.Thisisamatterofapplicationofthecustomizedsettings.Thisusuallymeansrestartingtheapplicationinordertoloadthecorrespondingtranslationfile.Thisispartlybecausechangingthelanguagedynamicallyrequiresadditionalwork.However,it'sfeasibleandcanbedonewithsomelines.What'smoreimportantisthatitdeliversabetteruserexperience!
Let'saddanewpushbuttontoMainWindow.NameitloadButtonandchangeitstexttoLoad/UnloadTranslation.Then,editthemain.cppfileintheEditmode.RemoveallQTranslatorrelatedlines,aswe'llbeimplementingthisdynamiclanguageswitchintheMainWindowclass.Themain.cppfileshouldlookliketheoriginallygeneratedoneasfollows:
#include"mainwindow.h"
#include<QApplication>
intmain(intargc,char*argv[])
{
QApplicationa(argc,argv);
MainWindoww;
w.show();
returna.exec();
}
Now,editmainwindow.h,asweneedtodeclaresomemembershere:
#ifndefMAINWINDOW_H
#defineMAINWINDOW_H
#include<QMainWindow>
#include<QTranslator>
namespaceUi{
classMainWindow;
}
classMainWindow:publicQMainWindow
{
Q_OBJECT
public:
explicitMainWindow(QWidget*parent=0);
~MainWindow();
private:
Ui::MainWindow*ui;
QTranslator*deTranslator;
booldeLoaded;
privateslots:
voidonOpenButtonClicked();
voidonLoadButtonClicked();
protected:
voidchangeEvent(QEvent*);
};
#endif//MAINWINDOW_H
Asyoucantell,wemovedQTranslatorhere,nameditdeTranslator,anduseditasapointerwiththedeLoadedvariabletosuggestwhetherornotwe'vealreadyloadedtheGermantranslation.ThefollowingonLoadButtonClickedisaprivateslotfunction,whichwillbeconnectedtotheclickedsignalofloadButton.Lastbutnotleast,wereimplementchangeEvent,sothatwecantranslatetheentireuserinterfaceonthefly.It'llbeclearinthemainwindow.cppsourcefile,whereitispastedasfollows:
#include<QMessageBox>
#include"mainwindow.h"
#include"ui_mainwindow.h"
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
deTranslator=newQTranslator(this);
deTranslator->load(QLocale::German,"Internationalization",
"_");
deLoaded=false;
connect(ui->openButton,&QPushButton::clicked,this,
&MainWindow::onOpenButtonClicked);
connect(ui->loadButton,&QPushButton::clicked,this,
&MainWindow::onLoadButtonClicked);
}
MainWindow::~MainWindow()
{
deleteui;
}
voidMainWindow::onOpenButtonClicked()
{
QMessageBox::information(this,tr("Dialog"),tr("Open",
"adj."));
}
voidMainWindow::onLoadButtonClicked()
{
if(deLoaded){
deLoaded=false;
qApp->removeTranslator(deTranslator);
}
else{
deLoaded=true;
qApp->installTranslator(deTranslator);
}
}
voidMainWindow::changeEvent(QEvent*e)
{
if(e->type()==QEvent::LanguageChange){
ui->retranslateUi(this);
}
else{
QMainWindow::changeEvent(e);
}
}
Intheconstructor,weinitializedeTranslatorandloadtheGermantranslation,whichisalmostidenticaltowhatwedidinmain.cppbefore.Then,wesetdeLoadedtofalse,indicatingthattheGermantranslationisnotinstalledyet.Next,thisisfollowedbyaconnectstatement.
Now,let'slookintotheonLoadButtonClickedfunctiontoseewhatwillhappeniftheloadButtongetsclicked.WesetdeLoadedtofalseandremovedeTranslatorifit'salreadyloaded.Otherwise,weinstalldeTranslatorandsetdeLoadedtotrue.RememberthatqAppisapredefinedmacrothatsimplyreferstothecurrentinstanceofQCoreApplication.BothinstallTranslatorandremoveTranslatorwillpropagatetheeventtoallthetop-levelwindows,thatistosay,changeEventofMainWindowwillbetriggeredinthiscase.
Inordertoupdateallthetextaccordingtothetranslator,wehavetoreimplementchangeEvent.Inthisreimplementedfunction,wecalltheretranslateUifunctiontoretranslateMainWindowiftheeventislanguageChange.Otherwise,wesimplycalltheinheritedanddefaultQMainWindow::changeEventfunction.
Whenyoufirstlystarttheapplication,it'lldisplayEnglishtext.
OnceyouclickontheLoad/UnloadTranslationbutton,alltranslatableandtranslatedtextwillshowinGerman.
It'lldisplayinEnglishifyouclickthebuttonagain.Inadditiontoanontranslatablelabel,loadButtonwillnotbenottranslatedeither.Thisisbecausewedidn'ttranslatethebuttonatall.However,asyoucansee,thelackofsometranslationswon'tpreventtheapplicationfromloadingothertranslatedtexts.
TranslatingQtQuickapplicationsTheprocedureoftranslatingaQtQuickapplicationissimilartoaQtWidgetsapplication.We'llwalkthroughtheprocesswithanotherexampleapplication.
CreateanewQtQuickapplicationprojectandnameitInternationalization_QML.Thegeneratedmain.qmlfilehasalreadyaddedaqsTr()functionforus.ThecontentsmaydifferslightlyinalaterversionofQtCreatorand(or)QtLibrary.However,itshouldlooksimilartothisone:
importQtQuick2.3
importQtQuick.Controls1.2
ApplicationWindow{
visible:true
width:640
height:480
title:qsTr("HelloWorld")
menuBar:MenuBar{
Menu{
title:qsTr("File")
MenuItem{
text:qsTr("&Open")
onTriggered:console.log("Openactiontriggered");
}
MenuItem{
text:qsTr("Exit")
onTriggered:Qt.quit();
}
}
}
Text{
text:qsTr("HelloWorld")
anchors.centerIn:parent
}
}
Now,let'sedittheInternationalization_QML.proprojectfile,whosemodifiedversionispastedasfollows:
TEMPLATE=app
QT+=qmlquickwidgets
SOURCES+=main.cpp
RESOURCES+=qml.qrc
lupdate_only{
SOURCES+=main.qml
}
TRANSLATIONS=Internationalization_QML_de.ts
#AdditionalimportpathusedtoresolveQMLmodulesinQt
#Creator'scodemodel
QML_IMPORT_PATH=
#Defaultrulesfordeployment.
include(deployment.pri)
InadditiontotheTRANSLATIONSline,wealsoaddalupdate_onlyblock.Itiscrucialinthiscase.
NoteWeprobablydon'tneedthisblockintheQt/C++projectsbecausethelupdatetoolextractsthetranslatablestringsfromSOURCES,HEADERS,andFORMS.
However,thismeansthatallthestringslocatedelsewherewon'tbefound,notevensayingtranslating.Ontheotherhand,theqmlfilesarenottheC++sourcefilesthataregoingtobecompiledbytheC++compiler.Inthiscase,weuselupdate_onlytorestrictthoseSOURCES,whichareonlyavailableforlupdate.
Now,executinglupdatecangeneratethetranslationsourcefileforus.Similarly,weuseQtLinguisttotranslatetheInternationalization_QML_de.tsfile.Then,executelreleasetogeneratetheQMfile.
Toloadthetranslation,weneedtomodifymain.cppintotheoneshownhere:
#include<QApplication>
#include<QQmlApplicationEngine>
#include<QTranslator>
intmain(intargc,char*argv[])
{
QApplicationapp(argc,argv);
QTranslatortranslator;
translator.load(QLocale::German,"Internationalization_QML",
"_");
app.installTranslator(&translator);
QQmlApplicationEngineengine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
returnapp.exec();
}
Also,weneedtochangeWorkingdirectorytothisproject'sdirectoryinRunSettingsintheProjectsmode.Now,runtheapplicationagain;weshouldbeabletoseeGermantextonthescreen,aswecaninthefollowingscreenshot:
Thereisanalternativewaytoloadthetranslationsfile,whichdoesn'tneedtochangeWorkingdirectory.Firstly,changethetranslator.loadlineinmain.cpptothefollowingone:
translator.load(QLocale::German,"Internationalization_QML",
"_",":/");
Wespecifythedirectorythatthetranslatorshouldsearch.Inthiscase,it's":/",whichisthetopdirectoryinsideResources.Pleasedon'tprependqrctothedirectorystring;thiswillcausetranslatortobeunabletofindtheQMfile.Acolon(:)issufficientheretoindicatethatthereisaqrcpathinsideResources.
Youcaneithercreateanewqrcfile,orsimilartowhatwedo,addInternationalization_QML_de.qmtothecurrentqml.qrcfile.
1. Right-clickontheqml.qrcfileunderResourcesinProjectsEditor.2. SelectOpeninEditor.3. NavigatetoAdd|AddFilesonthelower-rightpanel.4. SelecttheInternationalization_QML_de.qmfileandclickonOpen.
Now,theInternationalization_QML_de.qmfileshoulddisplayonbothEditorandtheProjectstreelikethefollowingscreenshot:
GototheProjectsmodeandresetWorkingdirectoryinRunSettings.Then,runtheapplicationagain;theGermantranslationshouldstillloadsuccessfully.
Sofar,thereisnohugedifferencebetweenQtandQtQuick.However,it'stedioustoachievedynamictranslationinstallationandremovalinQtQuick.YouhavetowriteaC++classthatinstallsandremovethetranslator,whichthenemitsasignalindicatingthatthereisachangetothetext.Therefore,thebestpracticefortheQtQuickapplicationistomakelanguageasetting.Theusercanthenloaddifferenttranslations.Itneedsarestartoftheapplication,though.
SummaryYou'renowabletomakeyourapplicationmorecompetitivebyaddingsupportforotherlanguagesnow.Besides,thesupereasytouseQtLinguist,whichisalsoacross-platformtoolprovidedbyQt,isalsocoveredinthischapter.Inadditiontotheskillsyoulearnt,youcanalsotellthatQt/C++stillholdsagreatadvantageoverQtQuick/QMLintermsofAPIsandfeatures.
Inthenextchapter,we'regoingtomakeourQtapplicationsredistributableanddeploythemonotherdevices.
Chapter9.DeployingApplicationsonOtherDevicesAfterdevelopment,it'stimetodistributeyourapplication.We'lluseanexampleapplication,Internationalization,fromthepreviouschaptertodemonstratehowtospreadyourQtapplicationtoWindows,Linux,andAndroid.Thefollowingtopicswillbecoveredinthischapter:
ReleasingQtapplicationsonWindowsCreatinganinstallerPackagingQtapplicationsonLinuxDeployingQtapplicationsonAndroid
ReleasingQtapplicationsonWindowsAfterthedevelopmentstage,youcanbuildyourapplicationusingreleaseasthebuildconfiguration.Inthereleaseconfiguration,yourcompilerwilloptimizethecodeandwon'tproducedebugsymbols,whichinturnreducesthesize.Pleaseensurethattheprojectisinthereleaseconfiguration.
Beforewejumpintothepackagingprocedure,I'dliketotalkaboutthedifferencebetweenstaticanddynamiclinking.YouhaveprobablybeenusingdynamiclinkingofQtlibrariesthroughoutthisbook.ThiscanbeconfirmedifyoudownloadtheCommunityEditionfromtheQtwebsite.
So,whatdoesdynamiclinkingmean?Well,itmeansthatwhenanexecutablefilegetsexecuted,theoperatingsystemwillloadandlinkthenecessarysharedlibrariesatruntime.Inotherwords,you'llseealotof.dllfilesonWindowsand.sofilesontheUnixplatforms.Thistechniqueallowsdeveloperstoupdatethesesharedlibrariesandtheexecutableseparately,whichmeansthatyoudon'tneedtorebuildtheexecutablefileifyouchangesharedlibraries,solongastheirABIsarecompatible.Althoughthismethodismoreflexible,developersarewarnedtotakecaretoavoidDLLHell.
ThemostcommonlyusedsolutiontoDLLHellonWindowsistochoosestaticlinkinginstead.Bycontrast,staticlinkingwillresolveallthefunctioncallsandvariablesatcompiletimeandcopythemintothetargettoproduceastandaloneexecutable.Theadvantagesareobvious.Firstly,youdon'tneedtoshipallnecessaryandsharedlibraries.Therewon'tbeDLLHellinthissituation.OnWindows,staticlibrariesmayget.libor.aasextensionsdependingonthecompileryouuse,whereasthey
usuallyget.aontheUnixplatforms.
Tomakeaclearcomparison,atableismadeforyoutoseethedifferencesbetweenthedynamicandstaticlinking:
DynamicLinking StaticLinking
Librarytypes Sharedlibraries Staticlibraries
Executablesize Considerablysmaller Greaterthandynamicallylinked
Libraryupdates Onlylibrariesthemselves Executablefileneedstoberebuilt
Incompatiblelibraries
Needtotakecaretoavoidthis Won'thappen
However,ifthesharedlibrariesshippedwithdynamicallylinkedexecutablefilesarecountedaspartofthepackage,thedynamicstylepackagewillbelargerthanthestaticallylinkedstandaloneexecutablefiles.
Now,backtothetopic!SincethereisnostandardQtruntimelibraryinstallerforWindows,thebestroutineistoproduceastaticallylinkedtargetbecausethepackagetobereleasedwillbesmaller,andtheexecutableisimmunetoDLLHell.
However,asmentionedpreviously,theQtlibrariesyoudownloadedcanonlybeusedfordynamiclinkingapplicationsbecausetheyaresharedlibraries.ItisviabletocompileQtasstaticlibraries.However,beforeyouproceed,youneedtoknowthelicensesofQt.
Currently,inadditiontotheQtOpenSourceLicense,thereisalsotheQtCommercialLicense.Foropensourcelicenses,mostoftheQtlibrariesarelicensedunderTheGNULesserGeneralPublicLicense(LPGL).Inthiscase,ifyoubuildyourapplicationstaticallylinkedwiththeQtlibraries,yourapplicationissubjecttoprovideusersthesourcecodeofyourapplicationunderLGPL.Yourapplicationmaystayproprietaryandclosedsourceifit'sdynamicallylinkedwiththeQtlibraries.Inother
words,ifyouwanttolinkanapplicationstaticallyandkeepitproprietary,youhavetopurchasetheQtcommerciallicense.FordetailsaboutQtlicensing,refertohttp://www.qt.io/licensing/.
Ifyoudecidetousestaticlinking,youmighthavetocompiletheQtlibrariesstaticallybeforebuildingyourapplication.Inthiscase,theexecutabletargetistheonlythingthatneedstobepackagedandreleased.Don'tforgettheQMfilesifyourapplicationhasmulti-languagesupport,asmentionedpreviously.
Ontheotherhand,ifyouwanttogothedynamicway,it'dneedsomeextraeffort.Firstly,therearesomecoreDLLsthathavetoexistandthelistisdifferentdependingonthecompiler.ThefollowingtableincludesbothMSVCandMinGW/GCCscenarios:
MSVC2013 MinGW/GCC
msvcp120.dll libgcc_s_dw2-1.dll
msvcr120.dll libstdc++-6.dll
libwinpthread-1.dll
TherearecommonDLLsthatneedtobeincluded,suchasicudt53.dll,icuin53.dll,andicuuc53.dll.YoucanfindthesefilesintheQtlibrariesdirectory.TakeMinGW/GCCasanexample;they'relocatedinQT_DIR\5.4\mingw491_32\binwhereQT_DIRistheQtinstallationpath,suchasD:\Qt.NotethatthelaterversionsofQtmayhaveslightlydifferentfilenames.
Besides,thereisnoneedtoshipmsvcp120.dllandmsvcr120.dllifthetargetusershaveinstalledVisualC++RedistributablePackagesforVisualStudio2013,whichcanbedownloadedfromhttp://www.microsoft.com/en-ie/download/details.aspx?id=40784.
Afterthis,youmaywanttocheckotherDLLsyou'llneedbylookingintotheprojectfile.TaketheInternationalizationprojectasanexample.Itsprojectfile,Internationalization.pro,givesusaclue.TherearetwolinesrelatedtotheQTconfiguration,shownasfollows:
QT+=coregui
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TheQTvariableincludesthecoreguiwidgets.Infact,alltheQtapplicationswillincludecoreatleast,whileothersaredependent.Inthiscase,wehavetoshipQt5Core.dll,Qt5Gui.dll,andQt5Widgets.dllalongwiththeexecutabletarget.
Now,buildtheInternationalizationprojectwithMinGW/GCC.Theexecutabletarget,Internationalization.exe,shouldbelocatedinsidethereleasefolderofthebuilddirectory,whichcanbereadfromtheProjectsmode.Next,wecreateanewfoldernamedpackageandcopytheexecutablefilethere.Then,wecopytheneededDLLstopackageaswell.Now,thisfoldershouldhaveallthenecessaryDLLsasshownhere:
Inmostcases,ifarequiredlibraryismissing,theapplicationwon'trunwhiletheoperatingsystemwillpromptthemissinglibraryname.Forinstance,ifQt5Widgets.dllismissing,thefollowingsystemerrordialogwillshowupwhenyoutrytorunInternationalizationi.exe:
Basically,theroutineistocopythemissinglibrariestothesamefolderthattheapplicationisin.Besides,youcanusesometoolssuchasDependencyWalkertogetthelibrarydependencies.
Pleasedon'tuseDLLsfromtheQtEditorfolder.ThisversionisoftendifferentfromQtLibrariesyou'veused.Inadditiontotheselibraries,youmayhavetoincludealltheresourcesthatyourapplicationisgoingtouse.Forexample,theQMfilesusedfortranslation,thatis,tocopytheInternationalization_de.qmfileinordertoloadtheGermantranslation.
Thefilelistisasfollows:
icudt53.dll
icuin53.dll
icuuc53.dll
Internationalization.exe
Internationalization_de.qm
libgcc_s_dw2-1.dll
libstdc++-6.dll
libwinpthread-1.dll
Qt5Core.dll
Qt5Gui.dll
Qt5Widgets.dll
Don'tforget,thisisthecaseforMinGW/GCCinQt5.4.0,whiledifferentversionsandcompilersmighthaveaslightlydifferentlist,aswediscussedbefore.
Afterthisfirst-timepreparation,tosomeextentthislistisfixed.YouonlyneedtochangetheexecutabletargetandtheQMfileifit'schanged.Aneasywaytodothisistocompressallofthemintarball.
CreatinganinstallerAlthoughit'squicktouseanarchivefiletodistributeyourapplication,itseemsmoreprofessionalifyouprovideuserswithaninstaller.QtoffersQtInstallerFrameworkwhoselatestopensourceversion,1.5.0fornow,canbeobtainedfromhttp://download.qt.io/official_releases/qt-installer-framework/1.5.0/.
Forthesakeofconvenience,let'screateafoldernameddistundertheQtInstallerFrameworkinstallationpath,D:\Qt\QtIFW-1.5.0.Thisfolderisusedtostorealltheapplicationprojectsthatneedtobepackaged.
Then,createafoldernamedinternationalizationunderdist.Insideinternationalization,createtwofolders,configandpackages.
Thenameofthedirectoryinsidethepackagesdirectoryactsasadomain-like,orsayJava-style,identifier.Inthisexample,wehavetwopackages,oneistheapplicationwhiletheotheroneisatranslation.Therefore,itaddstothetwofoldersinthepackagesdirectory,com.demo.internationalization,andcom.demo.internationalization.translation,respectively.Therewillbemetaanddatadirectoriespresentinsideeachofthem,sotheoveralldirectorystructureissketchedasfollows:
Let'sedittheglobalconfigurationfile,config.xml,whichisfirstinsidetheconfigdirectory.Youneedtocreateonefilenamedconfig.xml.
NoteAlwaysremembernottousetheWindowsbuilt-inNotepadtoeditthisfile,orinfactanyfile.YoumayeitheruseQtCreatororotheradvancededitors,suchasNotepad++,toeditit.ThisissimplybecauseNotepadlacksofalotoffeaturesasacodeeditor.
Inthisexample,theconfig.xmlfile'scontentispastedhere:
<?xmlversion="1.0"encoding="UTF-8"?>
<Installer>
<Name>Internationalization</Name>
<Version>1.0.0</Version>
<Title>InternationalizationInstaller</Title>
<Publisher>Packt</Publisher>
<TargetDir>@homeDir@/Internationalization</TargetDir>
<AdminTargetDir>@rootDir@/Internationalization</AdminTargetDir
>
</Installer>
Foraminimumconfig.xmlfile,theelements<Name>and<Version>mustexistin<Installer>.Allotherelementsareoptional,butyoushouldspecifythemifthereisaneed.Meanwhile,<TargetDir>and<AdminTargetDir>maybeabitconfusing.Theybothspecifythedefaultinstallationpath,where<AdminTargetDir>istospecifytheinstallationpathwhenitgainedadministrativerights.Theotherelementsareprettymuchself-explanatory.Thereareotherelementsthatyoucansettocustomizetheinstaller.Formoredetails,refertohttp://doc.qt.io/qtinstallerframework/ifw-globalconfig.html.
Let'snavigateintothemetafolderinsidecom.demo.internationalization.Thisdirectorycontainsthefilesthatspecifythesettingsfordeploymentandinstallation.Allthefilesinthisdirectory,exceptforlicenses,won'tbeextractedbytheinstaller,andneitherwilltheybeinstalled.Theremustbeatleastapackageinformationfile,suchaspackage.xml.Thefollowingexample,package.xml,incom.demo.internationalization/metaisshownhere:
<?xmlversion="1.0"encoding="UTF-8"?>
<Package>
<DisplayName>CoreApplication</DisplayName>
<Description>Essentialpartof
Internationalization</Description>
<Version>1.0.0</Version>
<ReleaseDate>2014-12-27</ReleaseDate>
<Name>com.demo.internationalization</Name>
<Licenses>
<Licensename="LicenseAgreement"file="license.txt"/>
</Licenses>
<Default>true</Default>
<ForcedInstallation>true</ForcedInstallation>
</Package>
The<Default>elementspecifieswhetherthispackageshouldbeselectedbydefault.Atthesametime,weset<ForcedInstallation>totrue,indicatingthattheenduserscan'tdeselectthispackage.Whilethe<Licenses>elementcanhavemultiplechildren<License>,inthiscaseweonlyhaveone.Wehavetoprovidethelicense.txtfile,whosecontentisjustasinglelinedemonstration,asshownhere:
Thisisthecontentoflicense.txt.
Thefollowingpackage.xmlfile,whichislocatedincom.demo.internationalization.translation/meta,hasfewerlines:
<?xmlversion="1.0"encoding="UTF-8"?>
<Package>
<DisplayName>GermanTranslation</DisplayName>
<Description>Germantranslationfile</Description>
<Version>1.0.0</Version>
<ReleaseDate>2014-12-27</ReleaseDate>
<Name>com.demo.internationalization.translation</Name>
<Default>false</Default>
</Package>
Thedifferencebetween<DisplayName>and<Description>isdemonstratedbythefollowingscreenshot:
The<Description>elementisthetextthatdisplaysontheright-handsidewhenthepackagegetsselected.It'salsothetextthatpopsupasthetooltipwhenthemousehoversovertheentry.Youcanalsoseetherelationshipbetweenthesetwopackages.Asthenamecom.demo.internationalization.translationsuggests,itisasubpackageofcom.demo.internationalization.
Thelicenseswillbedisplayedafterthisstepandareshowninthefollowingscreenshot.Ifyousetmultiplelicenses,thedialogwillhaveapaneltoviewthoselicensesseparately,similartotheoneyouseewhenyouinstallQtitself.
Formoresettingsinthepackage.xmlfile,refertohttp://doc.qt.io/qtinstallerframework/ifw-component-description.html#package-information-file-syntax.
Bycontrast,thedatadirectoriesstoreallthefilesthatneedtobeinstalled.Inthisexample,wekeepallfilespreparedpreviouslyinthedatafolderofcom.demo.internationalization,exceptfortheQMfile.TheQMfile,Internationalization_de.qm,iskeptinthedatafolderinsidecom.demo.internationalization.translation.
Afteralltheinitialpreparation,wecometothefinalsteptogeneratetheinstallerapplicationofthisproject.Dependingonyouroperatingsystem,openCommandPromptorTerminal,changingthecurrentdirectorytodist/internationalization.Inthiscase,it'sD:\Qt\QtIFW-1.5.0\dist\internationalization.Then,executethefollowingcommandtogeneratetheinternationalization_installer.exeinstallerfile:
..\..\bin\binarycreator.exe-cconfig\config.xml-ppackages
internationalization_installer.exe
NoteOnUnixplatforms,includingLinuxandMacOSX,you'llhavetouseaslash(/)insteadofanti-slash(\),anddropthe.exesuffix,whichmakesthecommandslightlydifferent,asshownhere:
../../bin/binarycreator-cconfig/config.xml-ppackages
internationalization_installer
Youneedtowaitforawhilebecausethebinarycreatortoolwillpackagefilesinthedatadirectoriesintothe7ziparchives,whichisatimeconsumingprocess.Afterthis,youshouldexpecttoseeinternationalization_installer.exe(orwithout.exe)inthecurrentdirectory.
Theinstallerismuchmoreconvenient,especiallyforabigapplicationprojectthathasseveraloptionalpackages.Besides,it'llregisterandlettheendusersuninstallthroughControlPanel.
PackagingQtapplicationsonLinuxThingsaremorecomplicatedonLinuxthanonWindows.Therearetwopopularpackageformats:RPMPackageManager(RPM)andDebianBinaryPackage(DEB).RPMwasoriginallydevelopedforRedHatLinuxandit'sthebaselinepackageformatofLinuxStandardBase.It'smainlyusedonFedora,OpenSUSE,RedHatEnterpriseLinux,anditsderivatives;whilethelatterisfamousforbeingusedinDebiananditswell-knownandpopularderivative,Ubuntu.
Inadditiontotheseformats,thereareotherLinuxdistributionsusingdifferentpackageformats,suchasArchLinuxandGentoo.ItwilltakeextratimetopackageyourapplicationsfordifferentLinuxdistributions.
However,itwon'tbetootimeconsuming,especiallyforopen-sourceapplications.Ifyourapplicationisopensource,youcanrefertothedocumentationtowriteaformattedscripttocompileandpackageyourapplication.FordetailsoncreatinganRPMpackage,refertohttps://fedoraproject.org/wiki/How_to_create_an_RPM_package,whereasforDEBpackaging,refertohttps://www.debian.org/doc/manuals/maint-guide/index.en.html.ThereisanexamplelaterthatdemonstrateshowtopackageDEB.
Althoughit'sfeasibletopackproprietaryapplications,suchastheRPMandDEBpackages,theywon'tgetintotheofficialrepository.Inthiscase,youmaywanttosetuparepositoryonyourserverorjustreleasethepackagesviaafilehost.
Alternatively,youcanarchiveyourapplications,similartowhatwedoonWindows,andwriteashellscriptforinstallationanduninstallation.Inthisway,youcanuseonetarballorQtInstallerFrameworktocookaninstallerforvariousdistributions.But,don'teverforgettoaddressthedependenciesappropriately.TheincompatiblesharedlibraryissueisevenworseonLinux,becausealmostallthelibrariesandapplicationsarelinkeddynamically.Theworstpartistheincompatibilitybetweendifferentdistributions,sincetheymayusedifferentlibraryversions.Therefore,eithertakecareofthesepitfalls,orgothestaticlinkingway.
Aswementionedpreviously,staticallylinkedsoftwaremustbeopensourceunlessyouhavepurchasedtheQtcommerciallicense.Thisdilemmamakesthestaticallylinkedopensourceapplicationpointless.Thisisnotonlybecausedynamiclinkingisthestandardway,butalsobecausestaticallylinkedQtapplicationswon'tbeabletousethesystemthemeandcan'tbenefitfromsystemupgrades,whichisnotokaywhensecurityupdatesareinvolved.Anyway,youcancompileyourapplicationusingstaticlinkingifyourapplicationisproprietaryandyougetacommerciallicense.Inthiscase,justlikestaticlinkingonWindows,youonlyneedtoreleasethetarget
executablefileswiththenecessaryresources,suchasiconsandtranslations.It'snoteworthythatevenifyoubuildstaticallylinkedQtapplications,it'sstillimpossibletorunthemonanyLinuxdistributions.
Therefore,therecommendedwayistoinstallseveralmainstreamLinuxdistributionsonvirtualmachines,andthenusethesevirtualmachinestopackageyourdynamicallylinkedapplicationastheirownpackageformats.Thebinarypackagedoesn'tcontainsourcecode,andit'salsoacommonpracticetostripthesymbolsfromthebinarypackage.Inthisway,yoursourcecodeforproprietarysoftwarewon'tbeleakedthroughthesepackages.
WestilluseInternationalizationasanexamplehere.Let'sseehowtocreateaDEBpackage.ThefollowingoperationsweretestedonthelatestDebianWheezy;laterversionsordifferentLinuxdistributionsmightbeslightlydifferent.
Beforewepackagetheapplication,wehavetoedittheprojectfile,Internationalization.pro,tomakeitinstallableasfollows:
QT+=coregui
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Internationalization
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp
HEADERS+=mainwindow.h
FORMS+=mainwindow.ui
TRANSLATIONS=Internationalization_de.ts
unix:{
target.path=/opt/internationalization_demo
qmfile.path=$$target.path
qmfile.files=Internationalization_de.qm
INSTALLS+=target\
qmfile
}
Thereisaconceptinqmakecalledinstallset.Eachinstallsethasthreemembers:path,files,andextra.Thepathmemberdefinesthedestinationlocation,whilefilestellsqmakewhatfilesshouldbecopied.Youcanspecifysomecommandsthatneedtobeexecutedbeforeotherinstructionsinextra.
TARGETisabitspecial.Firstly,it'sthetargetexecutable(orlibrary),whileontheotherhand,italsoimpliestarget.files.Therefore,weonlyneedtospecifythepathoftarget.Wealsousethesamepathforqmfile,whichincludestheQMfile.Don'tforgettouseadoubledollarsign,$$,touseavariable.Lastly,wesettheINSTALLSvariable,whichdefineswhatistobeinstalledwhenmakeinstalliscalled.TheunixbracketsareusedtolimitthelinesonlyreadbyqmakeontheUnixplatforms.
Now,wecangetintotheDEBpackagingpartbyperformingthefollowingsteps:
1. Changeyourworkingdirectory(currentdirectory)totherootoftheproject,thatis,~/Internationalization.
2. Createanewfoldernameddebian.3. Createthefourrequiredfilesinthedebianfolder:control,copyright,
changelog,andrules,respectively.Then,createanoptionalcompatfileinthedebianfolderaswell.
Thecontrolfiledefinesthemostbasicyetmostcriticalthings.Thisfileisallaboutthesourcepackageandthebinarypackage(s).Thecontrolfileofourexampleispastedhere:
Source:internationalization
Section:misc
Priority:extra
Maintainer:SymeonHuang<[email protected]>
Build-Depends:debhelper(>=9),
qt5-qmake,
qtbase5-dev,
qtbase5-private-dev
Standards-Version:3.9.6
Package:internationalization
Architecture:any
Depends:${shlibs:Depends},${misc:Depends}
Description:AnexampleofQt5Blueprints
Thefirstparagraphistocontrolinformationforasource,whereaseachofthefollowingsetsdescribeabinarypackagethatthesourcetreebuilds.Inotherwords,onesourcepackagemaybuildseveralbinarypackages.Inthiscase,webuildonlyonebinarypackagewhosenameisthesameasSourceandinternationalization.
IntheSourceparagraph,SourceandMaintaineraremandatorywhileSection,Priority,andStandards-Versionarerecommended.Sourceidentifiesthesourcepackagename,whichcan'tincludeuppercaseletters.Meanwhile,Maintainercontainsthemaintainerpackage'snameandthee-mailaddressintheRFC822format.TheSectionfieldspecifiesanapplicationareainwhichthepackagehasbeenclassified.Priorityisaself-explanatoryfield,indicatinghowimportantthispackage
is.Lastly,Standards-Versiondescribesthemostrecentversionofthestandardswithwhichthepackagecomplies.Inmostcases,youshouldusethelateststandardversion,3.9.6fornow.Thereareotherfieldsthatmaybeusefulbutoptional.Formoredetails,refertohttps://www.debian.org/doc/debian-policy/ch-controlfields.html.
YoucanspecifycertainpackagesneededforbuildinginBuild-Depends,similartoqt5-qmakeandqtbase5-devinourexample.They'reonlydefinedforbuildingprocessesandwon'tbeincludedinthedependenciesofbinarypackages.
ThebinaryparagraphsaresimilartothesourceexceptthatthereisnoMaintainer,butArchitectureandDescriptionaremandatorynow.Forbinarypackages,Architecturecanbeanyparticulararchitectureorsimplyanyorall.Specifyinganyindicatesthatthesourcepackageisn'tdependentonanyparticulararchitectureandhencecanbebuiltonanyarchitecture.Incontrasttothis,allmeansthatthesourcepackagewillproduceonlyarchitecture-independentpackages,suchasdocumentationsandscripts.
InDependsofthebinaryparagraph,weput${shlibs:Depends},${misc:Depends}insteadofparticularpackages.The${shlibs:Depends}linecanbeusedtoletdpkg-shlibdepsgeneratesharedlibrarydependenciesautomatically.Ontheotherhand,accordingtodebhepler,you'reencouragedtoput${misc:Depends}inthefieldtosupplement${shlibs:Depends}.Inthisway,wedon'tneedtospecifythedependenciesmanually,whichisareliefforpackagers.
Thesecondrequiredfile,copyright,istodescribethelicensesofthesourceaswellastheDEBpackages.Inthecopyrightfile,theformatfieldisrequiredwhiletheothersareoptional.Formoredetailsabouttheformatsofcopyright,refertohttps://www.debian.org/doc/packaging-manuals/copyright-format/1.0/.Thecopyrightfileinthisexampleisshownasfollows:
Format:http://www.debian.org/doc/packaging-manuals/copyright-
format/1.0/
Upstream-Contact:SymeonHuang<[email protected]>
File:*
Copyright:2014,2015SymeonHuang
License:Packt
License:Packt
ThispackageisreleasedunderPacktlicense.
ThefirstparagraphiscalledHeaderparagraph,whichisneededonceandonlyonce.TheFormatlineistheonlymandatoryfieldinthisparagraph,andinmostcases,thislineisthesame.ThesyntaxoftheUpstream-ContactfieldisthesameasMaintainerinthecontrolfile.
ThesecondparagraphinthisfileisFilesparagraph,whichismandatoryandrepeatable.Intheseparagraphs,File,Copyright,andLicensearerequired.Weuseanasterisksign(*)indicatingthatthisparagraphappliestoallfiles.TheCopyrightfieldmaycontaintheoriginalstatementcopiedfromfilesorashortenedtext.TheLicensefieldinaFilesparagraphdescribesthelicensingtermsforthefilesdefinedbyFile.
FollowingtheFilesparagraph,theStand-alonelicenseparagraphisoptionalandrepeatable.WehavetoprovidethefulllicensetextifthelicenseisnotprovidedbyDebian.Generallyspeaking,onlycommonly-seenopen-sourcelicensesareprovided.Thefirstlinemustbeasinglelicenseshortname,whichisthenfollowedbyalicensetext.Foralicensetext,theremustbeatwospaceindentationineachline'shead.
Don'tbemisledbythechangelogfilename.Thisfilealsohasaspecialformatandisusedbydpkgtoobtaintheversionnumber,revision,distribution,andurgencyofyourpackage.It'sagoodpracticetodocumentallthechangesyouhavemadeinthisfile.However,youcanjustlistthemostimportantonesifyouhaveaversioncontrolsystem.Thechangelogfileinourexamplehasthefollowingcontents:
internationalization(1.0.0-1)unstable;urgency=low
*Initialrelease
--SymeonHuang<[email protected]>Mon,29Dec2014
18:45:31+0000
Thefirstlineisthepackagename,version,distribution,andurgency.Thenamemustmatchthesourcepackagename.Inthisexample,internationalizationisthename,1.0.0-1istheversion,unstablestandsforthedistribution,andurgencyislow.Then,useanemptylinetoseparatethefirstlineandlogentries.Inthelogentries,allthechangesthatyouwanttodocumentshouldbelisted.Foreachentry,therearetwospacesandanasterisksign(*)intheheader.Thelastpartofaparagraphisamaintainerlinethatbeginswithaspace.Formoredetailsaboutthisfileanditsformat,refertohttps://www.debian.org/doc/debian-policy/ch-source.html#s-dpkgchangelog.
Now,weneedtotakealookatwhatdpkg-buildpackagewilldotocreatethepackage.Thisprocessiscontrolledbytherulesfile;theexampleispastedhere:
#!/usr/bin/make-f
exportQT_SELECT:=qt5
%:
dh$@
override_dh_auto_configure:
qmake
Thisfile,similartoMakefile,consistsofseveralrules.Also,eachrulebeginswithitstargetdeclaration,whiletherecipesarethefollowinglinesbeginningwiththeTABcode(notfourspaces).WeexplicitlysetQt5astheQtversion,whichcanavoidsomeissueswhenQt5coexistswithQt4.Thepercentagesign(%)isaspecialtargetandmeansanytargets,whichjustcallsthedhprogramwiththetargetname,whiledhisjustawrapperscript,whichrunsappropriateprogramsdependingonitsargument,therealtarget.
Therestofthelinesarecustomizationsforthedhcommand.Forinstance,dh_auto_configurewillcall./configurebydefault.Inourcase,weuseqmaketogenerateMakefileinsteadofaconfigurescript.Therefore,weoverridedh_auto_configurebyaddingtheoverride_dh_auto_configuretargetwithqmakeastherecipe.
Althoughthecompatfileisoptional,you'llgetbombardedwithwarningsifyoudon'tspecifyit.Currently,youshouldsetitscontentto9,whichcanbedonebythefollowingsingle-linecommand:
echo9>debian/compat
WecangeneratethebinaryDEBpackagenow.The-ucargumentstandsforuncheckwhile-usstandsforunsign.IfyouhaveaPKGkey,youmayneedtosignthepackagesothatuserscantrustthepackagesyou'vereleased.Wedon'tneedsourcepackages,sothelastargument,-b,indicatesthatonlythebinarypackageswillbebuilt.
dpkg-buildpackage-uc-us-b
Theautomaticallydetecteddependenciescanbeviewedinthedebian/file,internationalization.substvars.Thisfile'scontentsarepastedhere:
shlibs:Depends=libc6(>=2.13-28),libc6(>=2.4),libgcc1(>=
1:4.4.0),libqt5core5a(>=5.0.2),libqt5gui5(>=5.0.2),
libqt5widgets5(>=5.0.2),libstdc++6(>=4.3.0)
misc:Depends=
Aswediscussedearlier,thedependenciesaregeneratedbyshlibsandmisc.Thebiggestadvantageisthatthesegeneratedversionnumberstendtobethesmallest,whichmeansthemaximumbackwardscompatibility.Asyoucansee,ourInternationalizationexamplecanrunonQt5.0.2.
Ifeverythinggoeswell,you'dexpectaDEBfileinanupper-leveldirectory.However,youcanonlybuildthecurrentarchitecture'sbinarypackage,amd64.Ifyouwanttobuildfori386natively,youneedtoinstalla32-bitx86Debian.Forcross-compilation,refertohttps://wiki.debian.org/CrossBuildPackagingGuidelinesandhttps://wiki.ubuntu.com/CrossBuilding.
InstallingalocalDEBfileiseasilydonewiththefollowingsingle-linecommand:
sudodpkg-iinternationalization_1.0.0-1_amd64.deb
Afterinstallation,wecanrunourapplicationbyrunning/opt/internationalization_demo/Internationalization.ItshouldrunasexpectedandbehaveexactlythesameasonWindows,asshowninthefollowingscreenshot:
DeployingQtapplicationsonAndroidTheinternationalizationapplicationrequiresaQMfiletobeloadedcorrectly.OnWindowsandLinux,wechoosetoinstallthemalongsidethetargetexecutable.However,thisisnotalwaysagoodapproach,especiallyonAndroid.Thepathismorecomplicatedthanthedesktopoperatingsystems.Besides,we'rebuildingaQtapplicationinsteadoftheJavaapplication.LocalizationisdefinitelydifferentfromaplainJavaapplication,asstatedintheAndroiddocumentation.Hence,we'regoingtobundlealltheresourcesintotheqrcfile,whichwillbebuiltintothebinarytarget:
1. Addanewfiletoprojectbyright-clickingontheproject,andthenselectAddNew….
2. NavigatetoQt|QtResourceFileintheNewFiledialog.3. NameitresandclickonOK;QtCreatorwillredirectyoutoeditres.qrc.4. NavigatetoAdd|AddPrefixandchangePrefixto/.5. NavigatetoAdd|AddFilesandselectthe.Internationalization_de.qmfilein
thedialog.
Now,weneedtoeditmainwindow.cpptomakeitloadthetranslationfilefromResources.WeonlyneedtochangetheconstructorofMainWindowwhereweloadthetranslation,asshownhere:
MainWindow::MainWindow(QWidget*parent):
QMainWindow(parent),
ui(newUi::MainWindow)
{
ui->setupUi(this);
deTranslator=newQTranslator(this);
deTranslator->load(QLocale::German,"Internationalization",
"_",":/");
deLoaded=false;
connect(ui->openButton,&QPushButton::clicked,this,
&MainWindow::onOpenButtonClicked);
connect(ui->loadButton,&QPushButton::clicked,this,
&MainWindow::onLoadButtonClicked);
}
TheprecedingcodeistospecifythedirectoryfortheQTranslator::loadfunction.Aswementionedinthepreviouschapter,:/indicatesthatit'saqrcpath.Don'taddaqrcprefixunlessit'saQUrlobject.
Wecanremovetheqmfileinstallsetfromtheprojectfilenow,becausewe'vealreadybundledtheQMfile.Inotherwords,afterthischange,youdon'tneedtoship
theQMfileonWindowsorLinuxanymore.Edittheprojectfile,Internationalization.pro,asshowninthefollowingcode:
QT+=coregui
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Internationalization
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp
HEADERS+=mainwindow.h
FORMS+=mainwindow.ui
TRANSLATIONS=Internationalization_de.ts
unix:{
target.path=/opt/internationalization_demo
INSTALLS+=target
}
RESOURCES+=\
res.qrc
Now,switchtoProjectsmodeandaddtheAndroidkit.Don'tforgettoswitchthebuildtorelease.InProjectsmode,youcanmodifyhowQtCreatorshouldbuildtheAndroidAPKpackage.ThereisanentryinBuildStepscalledBuildAndroidAPK,asshowninthefollowingscreenshot:
Here,youcanspecifytheAndroidAPIlevelandyourcertificate.Bydefault,QtDeploymentissettoBundleQtlibrariesinAPK,whichcreatesaredistributableAPKfile.Let'sclickontheCreateTemplatesbuttontogenerateamanifestfile,AndroidManifest.xml.Normally,youjustclickontheFinishbuttononthepop-updialog,andthenQtCreatorwillredirectyoubacktotheEditmodewithAndroidManifest.xmlopenintheeditingarea,asshownhere:
Let'smakeafewchangestothismanifestfilebyperformingthefollowingsteps:
1. ChangePackagenametocom.demo.internationalization.2. ChangeMinimumrequiredSDKtoAPI14:Android4.0,4.0.1,4.0.2.3. ChangeTargetSDKtoAPI19:Android4.4.4. Savethechanges.
DifferentAPIlevelshaveanimpactoncompatibilityandtheUI;youhavetodecidethelevelscarefully.Inthiscase,werequireatleastAndroid4.0torunthisapplication,whichwe'regoingtoitforAndroid4.4.Generallyspeaking,thehighertheAPIlevel,thebettertheoverallperformanceis.TheInternationalization.proprojectfileisautomaticallychangedaswell.
QT+=coregui
greaterThan(QT_MAJOR_VERSION,4):QT+=widgets
TARGET=Internationalization
TEMPLATE=app
SOURCES+=main.cpp\
mainwindow.cpp
HEADERS+=mainwindow.h
FORMS+=mainwindow.ui
TRANSLATIONS=Internationalization_de.ts
unix:{
target.path=/opt/internationalization_demo
INSTALLS+=target
}
RESOURCES+=\
res.qrc
DISTFILES+=\
android/gradle/wrapper/gradle-wrapper.jar\
android/AndroidManifest.xml\
android/res/values/libs.xml\
android/build.gradle\
android/gradle/wrapper/gradle-wrapper.properties\
android/gradlew\
android/gradlew.bat
ANDROID_PACKAGE_SOURCE_DIR=$$PWD/android
Now,buildareleasebuild.TheAPKfileiscreatedinandroid-build/bininsidetheprojectbuilddirectory.TheAPKfilenameisQtApp-release.apkorQtApp-debug.apkifyoudon'tsetyourcertificate.Ifyou'regoingtosubmityourapplicationtoGooglePlayoranyotherAndroidmarkets,youhavetosetyourcertificateanduploadQtApp-release.apkinsteadofQtApp-debug.apk.Meanwhile,QtApp-debug.apkcanbeusedonyourowndevicestotestthefunctionalityofyourapplication.
ThescreenshotofInternationalizationrunningonHTCOneisshownasfollows:
Asyoucansee,theGermantranslationisloadedasexpected,whilethepop-updialoghasanativelookandfeel.
SummaryInthischapter,wecomparedtheadvantagesanddisadvantagesofstaticanddynamiclinking.Lateron,weusedanexampleapplication,showingyouhowtocreateaninstalleronWindowsandhowtopackageitasaDEBpackageonDebianLinux.Lastbutnotleast,wealsolearnedhowtocreatearedistributableAPKfileforAndroid.Theslogan,codeless,createmore,deployeverywhereisnowfulfilled.
Inthenextchapter,whichisalsothelastchapterofthisbook,inadditiontohowtodebugapplications,we'realsogoingtolookatsomecommonissuesandsolutionstothem.
Chapter10.Don'tPanicWhenYouEncounterTheseIssuesDuringapplicationdevelopment,youmaygetstuckwithsomeissues.Qtisamazing,asalways,sinceQtCreatorhasanexcellentDebugmodethatcansaveyoutimewhendebugging.You'lllearnhowtodebugeitherQt/C++orQtQuick/QMLapplications.Thefollowingtopicswillbecoveredinthischapter:
CommonlyencounteredissuesDebuggingQtapplicationsDebuggingQtQuickapplicationsUsefulresources
CommonlyencounteredissuesErrors,ormoreappropriately,unexpectedresults,aredefinitelyunavoidableduringapplicationdevelopment.Besides,therecouldalsobecompilererrors,orevenapplicationcrashes.Pleasedon'tpanicwhenyouencounterthesekindsofissues.Toeaseyourpainandhelpyoulocatetheproblem,wehavecollectedsomecommonlyencounteredandreproducibleunexpectedresultsandcategorizedthem,asshowninthenextsections.
C++syntaxmistakesForprogrammingbeginners,ordeveloperswhoarenotfamiliarwithCandC++,thesyntaxofC++isnoteasytoremember.Ifthereareanysyntaxmistakes,thecompilerwillabortwitherrormessages.Infact,theeditorwilldisplaytildesbelowproblematicstatements,asshownhere:
AmongallC++syntaxmistakes,themostcommononeisamissingsemicolon(;).C++needsasemicolontomarktheendofastatement.Therefore,line7andline8areequivalenttothefollowingline:
MainWindowww.show();
This,inC++,isobviouslywrittenincorrectly.Notonlywilltheeditorhighlighttheerror,thecompilerwillalsogiveyouathorougherrormessage.Inthiscase,it'lldisplaythefollowingmessage:
C:\Users\Symeon\OneDrive\Book_Dev\4615OS\4615OS_07\project\Weather_Demo\main.cpp:8:
error:C2146:syntaxerror:missing';'beforeidentifier'w'
Asyoucantell,thecompilerwon'ttellyouthatyoushouldaddasemicolonattheendofline7.Instead,itreadsmissing;beforethewidentifier,whichisinline8.Anyway,inmostcasestheC++syntaxerrorscanbedetectedbythecompiler,whilemostofthemwillfirstbedetectedbytheeditor.ThankstothehighlightingfeatureofQtCreator,thesetypesofmistakesshouldbeavoidedeffectively.
It'srecommendedasagoodhabitthatyouaddasemicolonbeforeyoupressEnter.ThisisbecauseinsomecasesthesyntaxmayseemcorrectforcompilersandQtCreator,butit'sdefinitelywronglycodedandwillcauseunexpectedbehavior.
PointerandmemoryAnyonefamiliarwithCanditswildpointersunderstandshoweasyitistomakeamistakeregardingmemorymanagement.Aswementionedbefore,Qthasasuperiormemorymanagementmechanism,whichwillreleaseitschildobjectsoncetheparentisdeleted.This,unfortunately,mayleadtoacrashifthedeveloperexplicitlyusesdeletetoreleaseachildobject.
Theprimaryreasonbehindthisisthatdeleteisnotathread-safeoperation.Itmaycauseadoubledelete,resultinginasegmentfault.Therefore,toreleasememoryinathread-safeway,weusethedeleteLater()functiondefinedintheQObjectclass,whichmeansthatthismethodisavailableforallclassesinheritedfromQObject.Asstatedinthedocumentation,deleteLater()willscheduletheobjectfordeletionbutthedeletionwon'thappenimmediately.
NoteIt'scompletelysafetocalldeleteLater()multipletimes.Oncethefirstdeferreddeletioniscompleted,anypendingdeletionsareremovedfromtheeventqueue.Therewon'tbeanydoubledeletes.
ThereisanotherclassdealingwithmemorymanagementinQt,QObjectCleanupHandler.ThisclasswatchesthelifetimeofmultipleQObjects.YoucantreatitasasimpleQtgarbagecollector.Forinstance,therearealotofQTcpSocketobjectsthatneedtobewatchedanddeletedproperly.Thesekindsofcasesarenotuncommon,especiallyfornetworkingprograms.AneasytrickistoaddalltheseobjectstoQObjectCleanupHandler.ThefollowingpieceofcodeisasimpledemonstrationthataddsQObjecttoQObjectCleanupHandlerch:
QTcpSocket*t=newQTcpSocket(this);
QObjectCleanupHandlerch;
ch.add(t);
Addingthetobjecttochwon'tchangetheparentobjectoftfromthisto&ch.QObjectCleanupHandlerismorelikeQListinthisway.Iftisdeletedsomewhereelse,it'llgetremovedfromthelistofchautomatically.Ifthereisnoobjectleft,theisEmpty()functionwillreturntrue.AllobjectsinQObjectCleanupHandlerwillbedeletedwhenit'sdestroyed.Youcanalsoexplicitlycallclear()todeleteallobjectsinQObjectCleanupHandlermanually.
IncompatiblesharedlibrariesThistypeoferrorsaretheso-calledDLLHell,whichwediscussedinthepreviouschapter.Itresultsfromincompatiblesharedlibraries,whichmayleadtostrangebehaviororcrashes.
Inmostcases,Qtlibrariesarebackwardscompatible,whichmeansthatyoumayreplaceallDLLswithneweronesandnotneedtorecompileexecutables.SomecertainmodulesorAPIsmaybedeprecatedandbedeletedfromalaterversionof
Qt.Forexample,theQGLWidgetclassisreplacedbyanewlyintroducedQOpenGLWidgetclassinQt5.4.QGLWidgetisstillprovidedfornowthough.
Inthereversedirection,thingsaregettingprettybad.IfyourapplicationcallsanAPIthatisintroducedsince,forexample,Qt5.4,theapplicationdefinitelywillmalfunctionwithanolderversionofQt,suchasQt5.2.
ThefollowingisasimpleprogramthatmakesuseofQSysInfo,whichisintroducedinQt5.4.Themain.cppfileofthissimpleincompat_demoprojectisshownhere:
#include<QDebug>
#include<QSysInfo>
#include<QCoreApplication>
intmain(intargc,char*argv[])
{
QCoreApplicationa(argc,argv);
qDebug()<<"CPU:"<<QSysInfo::currentCpuArchitecture();
returna.exec();
}
QSysInfo::currentCpuArchitecture()returnsthearchitectureoftheCPUthattheapplicationisrunningonasaQStringobject.IftheversionofQtishighenough(greaterthanorequalto5.4),it'llrunasexpected,asshowninthefollowingscreenshot:
Asyoucansee,itsayswe'rerunningthisapplicationona64-bitx86CPUmachine.However,ifweputthecompiledexecutablewithDLLsfromQt5.2,it'llgiveanerrorasshownhereandcrash:
Thissituationisrare,ofcourse.However,ifthishappens,you'llgetanideaaboutwhatgoeswrong.Fromtheerrordialog,wecanseetheerrorisbecauseofthemissingQSysInfo::currentCpuArchitecturelineinthedynamiclinklibrary.
AnotherDLLHellismorecomplexandmaybeignoredbybeginners.Alllibrariesmustbebuiltbythesamecompiler.Youcan'tusetheMSVClibrarieswithGCC,whichholdstrueforothercompilers,suchasICCandClang.Differentcompilerversionsmightcauseincompatibilityaswell.Youprobablydon'twanttousealibrarycompiledbyGCC4.3inyourdevelopmentenvironmentwheretheGCCversionis4.9.However,librariescompiledbyGCC4.9.1shouldbecompatiblewiththosecompiledbyGCC4.9.2.
Inadditiontocompilers,differentarchitecturesareoftenincompatible.Forexample,64-bitlibrarieswon'tworkon32-bitplatforms.Similarly,x86librariesandbinariescan'tbeusedonthenon-x86devices,suchasARMandMIPS.
Doesn'trunonAndroid!QtwasportedtoAndroidnottoolongago.Hence,thereisapossibilitythatitrunswellonadesktopPCbutnotonAndroid.Ononehand,Androidhardwarevaries,notevenspeakingofthousandsofcustomizedROMs.Therefore,itisreasonablethatsomeAndroiddevicesmayencountercompatibilityissues.Ontheotherhand,theQtapplicationrunningonAndroidisanativeC++applicationwithaJavawrapper,whilebinaryexecutablesarenaturallymorevulnerabletocompatibilityissuesthanscripts.
Anyway,here'stherecipe:
1. TrytorunyourapplicationonanotherAndroidhandsetorvirtualAndroiddevice.2. Ifitstilldoesn'twork,itcanbeapotentialbugofQtonAndroid.We'lltalkabout
howtoreportabugtoQtattheendofthischapter.
DebuggingQtapplicationsTodebuganyQtapplication,youneedtoensurethatyouhaveinstalledthedebugsymbolsoftheQtlibraries.OnWindows,theyareinstalledtogetherwithreleaseversionDLLs.Meanwhile,onLinux,youmayneedtoinstalldebugsymbolsbythedistribution'spackagemanager.
Somedeveloperstendtouseafunctionsimilartoprintftodebugtheapplication.Qtprovidesfourglobalfunctions,whichareshowninthefollowingtable,toprintoutdebug,warnings,anderrortext:
Function Usage
qDebug() Thisfunctionisusedforwritingcustomdebugoutput.
qWarning() Thisfunctionisusedforreportingwarningsandrecoverableerrors.
qCritical()Thisfunctionisusedforwritingcriticalerrormessagesandreportingsystemerrors.
qFatal()Thisfunctionisusedforprintingfatalerrormessagesshortlybeforeexiting.
Normally,youcanjustuseaC-stylemethodsimilartoprintf.
qDebug("Hello%s","World!");
However,inmostcases,we'llincludethe<QtDebug>headerfilesothatwecanusethestreamoperator(<<)asamoreconvenientway.
qDebug()<<"HelloWorld!"
Themostpowerfulplaceofthesefunctionsisthattheycanoutputthecontentsofsomecomplexclasses',QListandQMap.It'snotedthatthesecomplexdatatypescan
onlybeprintedthroughastreamoperator(<<).
BothqDebug()andqWarning()aredebuggingtools,whichmeanthattheycanbedisabledatcompiletimebydefiningQT_NO_DEBUG_OUTPUTandQT_NO_WARNING_OUTPUT,respectively.
Inadditiontothesefunctions,QtalsoprovidestheQObject::dumpObjectTree()andQObject::dumpObjectInfo()functionswhichareoftenuseful,especiallywhenanapplicationlooksstrange.QObject::dumpObjectTree()dumpsinformationaboutsignalconnections,whichisreallyusefulifyouthinktheremaybeaprobleminsignalslotconnections.Meanwhile,thelatterdumpsatreeofchildrentothedebugoutput.Don'tforgettobuildtheapplicationinDebugmode,otherwiseneitherofthemwillprintanything.
Apartfromtheseusefuldebuggingfunctions,QtCreatorhasofferedanintuitivewaytodebugyourapplication.Ensurethatyou'veinstalledMicrosoftConsoledebugger(CDB)ifyou'reusinganMSVCcompiler.Inothercases,theGDBdebuggerisbundledinaMinGWversion.
NoteCDBisnowapartofWindowsDriverKit(WDK);visithttp://msdn.microsoft.com/en-us/windows/hardware/hh852365todownloadit.Don'tforgettocheckDebuggingToolsforWindowsduringtheinstallation.
ConsiderFancy_ClockfromChapter2,BuildingaBeautifulCross-platformClock,asanexample.IntheMainWindow::setColour()function,movethecursortoline97,whichisswitch(i){.Then,navigatetoDebug|ToggleBreakpointorjustpressF9onthekeyboard.Thiswilladdabreakpointonline97,whichwilladdabreakpointmarker(aredpauseiconinfrontofalinenumber)asshownhere:
NowclickontheStartDebuggingbuttononthepane,whichhasabugonit,ornavigatetoDebug|StartDebugging|StartDebuggingonthemenubar,orpressF5onthekeyboard.Thiswillrecompiletheapplication,ifneeded,andstartitinDebugmode.Atthesametime,QtCreatorwillautomaticallyswitchtoDebugmode.
Theapplicationisinterruptedbecauseofthebreakpointweset.Youcanseeayellowarrowindicatingwhichlinetheapplicationiscurrentlyon,asshownintheprecedingscreenshot.Bydefault,ontherightpane,youcanseeLocalsandExpressionswhereallthelocalvariablesalongwiththeirvaluesandtypesareshown.Tochangethedefaultsettings,navigatetoWindow|Views,andthenchoosewhattodisplayorhide.
ThepanesintheDebugmodearemarkedinbluetextinthisscreenshot:
Brieflysaid,youcanmonitorthevariablesinLocalsandexpressionsinExpressions.StackdisplaysthecurrentstackandallbreakpointscanbemanagedintheBreakpointspane.
Onthebottompane,thereareaseriesofbuttonstocontrolthedebuggingprocess.ThefirstsixbuttonsareContinue,StopDebugger,StepOver,StepInto,StepOut,andRestartthedebuggingsession,respectively.StepOveristoexecutealineofcodeasawhole.StepIntowillstepintoafunctionorasubfunction,whileStepOutcanleavethecurrentfunctionorsubfunction.
Breakpointsplaysacrucialroleindebugging,asyoucantellwhetherabreakpointrepresentsapositionorsetofpositionsinthecodethatinterruptstheapplicationfrombeingdebuggedandgrantsyoucontrol.Onceitisinterrupted,youcanexaminethestateoftheprogramorcontinuetheexecution,eitherline-by-lineorcontinuously.QtCreatorshowsbreakpointsintheBreakpointsview,whichislocatedatthelower-right-handsidebydefault.YoucanaddordeletebreakpointsintheBreakpointsview.Toaddabreakpoint,right-clickontheBreakpointsviewandselectAddBreakpoint…;therewillbeanAddBreakpointdialogasshownhere:
IntheBreakpointtypefield,selectthelocationintheprogramcodewhereyouwanttheapplicationtobeinterrupted.Otheroptionsaredependentontheselectedtype.
Tomovethebreakpoint,simplydragthebreakpointmarkeranddropitonthedestination.It'snotanoftenneededfunction,though.
There'remanywaystodeleteabreakpoint.
Byclickingonthebreakpointmarkerintheeditor,movingthecursortothecorrespondingline,andnavigatingtoDebug|ToggleBreakpoint,orbypressingF9Byright-clickingonthebreakpointintheBreakpointsviewandselectingDeleteBreakpointByselectingthebreakpointintheBreakpointsviewandpressingtheDeletebuttononthekeyboard
ThemostpowerfulplaceisthepreviouslyintroducedLocalsandExpressionsview.Everytimetheprogramstopsunderthecontrolofthedebugger,itretrievesinformationanddisplaysitintheLocalsandExpressionsview.TheLocalspaneshowsfunctionparametersandlocalvariables.Thereisacomprehensivedisplayof
databelongingtoQt'sbasicobjects.Inthiscase,whentheprogramisinterruptedinMainWindow::setColour(),thereisapointerwhoseValueis"MainWindow".Insteadofjustmemoryaddressofthispointer,itcanshowyouallthedataandchildrenthatbelongtothisobject:
Asyoucanseefromprecedingscreenshot,thisisaMainWindowinstance,whichisinheritedfromQMainWindow.Ithasthreechildrenitems:_layout,qt_rubberband,andcentralWidget.It'snotedthatonlyslotfunctionsaredisplayedin[methods].Nowyou'llunderstandwhytheLocalspaneisthemostimportantandcommonlyusedviewintheDebugmode.
Ontheotherhand,theExpressionspaneisevenmorepowerfulandcancomputethevaluesofarithmeticexpressionsorfunctioncalls.Right-clickontheLocalsandExpressionsviewandselectAddNewExpressionEvaluator…inthecontextmenu.
Notethatthecontextmenuentriesareavailableonlywhentheprogramisinterrupted.Inthiscase,Fancy_ClockisinterruptedintheMainWindow::setColour()functionwherethelocalvariable,i,canbeusedtoperformsomearithmeticoperations.Forexample,wefilli*5intheNewEvaluatedExpressionpop-updialog.
Inadditiontoarithmeticoperations,youcancallafunctiontoevaluatethereturnvalue.However,thisfunctionmustbeaccessibletothedebugger,whichmeansit'seithercompiledintotheexecutableorcanbeinvokedfromalibrary.
Theexpressionvaluewillbere-evaluatedaftereachstep.AfteryouclickontheOKbutton,theexpressioni*5,isshownintheExpressionspaneasshownhere:
Thevalueofiisnow3.Therefore,theexpressioni*5isevaluatedas15.
"Expressionevaluatorsarepowerful,butslowdowndebuggeroperationsignificantly.Itisadvisablenottousethemexcessively,andtoremoveunneeded
expressionevaluatorsassoonaspossible."
Eveniffunctionsusedintheexpressionshavesideeffects,theywillbecalledeachtimethecurrentframechanges.Afterall,theexpressionevaluatorispowerfulbutbadfordebuggingspeed.
DebuggingQtQuickapplicationsWewillusetheWeather_QMLprojectfromChapter7,ParsingJSONandXMLDocumentstoUseOnlineAPIs,asademonstrationprogramtoshowhowtodebugaQtQuickapplication.
First,weneedtoensurethatQMLdebuggingisenabled.OpentheWeather_QMLprojectinQtCreator.Then,performthefollowingsteps:
1. SwitchtotheProjectsmode.2. ExpandtheqmakestepinBuildSteps.3. CheckEnableQMLdebuggingifit'snotchecked.
TipDebuggingQMLwillopenasocketatawell-knownport,whichposesasecurityrisk.AnyoneonyournetworkcouldconnecttothedebuggingapplicationandexecuteanyJavaScriptfunction.Therefore,youhavetomakesurethereareappropriatefirewallrules.
ThesameprocedureisusedtostartQMLdebugging,whichistonavigatetoDebug|StartDebugging|StartDebugging,orclicktheDebugbutton,orjustpressF5.ItmaytriggeraWindowsSecurityAlert,showninthefollowingscreenshot.Don'tforgettoclickontheAllowaccessbutton.
Oncetheapplicationstartsrunning,itbehavesandperformsasusual.However,youcanperformsomeusefultasksindebuggingmode.YoucanseealltheelementsandtheirpropertiesintheLocalspaneaswedidfortheQt/C++applications.
Inadditiontojustwatchingthesevariables,youcanchangethemtemporarilyandseethechangesatruntimeimmediately.Tochangeavalue,youcaneitherdirectlychangeitintheLocalspaneorchangeitinQML/JSConsole.
Forexample,tochangethetitlepropertyofApplicationWindow,performthefollowingsteps:
1. ExpandApplicationWindow|PropertiesintheLocalspane.2. Double-clickonthetitleentry.3. ChangethevaluefromWeatherQMLtoYahoo!Weather.4. PresstheEnterorReturnkeyonthekeyboardtoconfirm.
Alternatively,youcanchangeitinQML/JSConsole.ThereisnoneedtoexpandApplicationWindow;justclickonApplicationWindowintheLocalspane.You'llnoticeContextontheQML/JSConsolepanelwillbecomeApplicationWindow,asshowninthefollowingscreenshot.Then,justinputthetitle="Yahoo!Weather"commandtochangethetitle.
You'llnoticethetitleintheapplicationwindowischangedtoYahoo!Weatherimmediately,asshownhere:
Meanwhile,thesourcecodeisleftintact.Thisfeatureisreallyhandywhenyouwanttotestabettervalueforaproperty.Insteadofchangingitinthecodeandrerunning,youcanchangeandtestitonthefly.Infact,youcanalsoexecutetheJavaScriptexpressionsinQML/JSConsole,notjustchangetheirvalues.
UsefulresourcesStillgettingstuckwithanissue?Inadditiontoonlinesearchengines,therearetwoonlineforumsthatcouldalsobeusefulforyou.ThefirstoneistheforumintheQtProject,whoseURLishttp://qt-project.org/forums.Theotheroneismaintainedbyacommunitysite,QtCentre,anditsURLishttp://www.qtcentre.org/forum.php.
Inmostcases,youshouldbeabletofindsimilarorevenidenticalproblemsonthesewebsites.Ifnot,youcanpostanewthreadaskingforhelp.Describetheproblemasthoroughlyaspossiblesothatotheruserscangetanideaofwhat'sgoingwrong.
Thereisapossibilitythatyoudideverythingcorrectlybutstillmightbegettingunexpectedresults,compilererrors,orcrashes.Inthiscase,itmaybeaQtbug.Ifyoubelievethatyou'veencounteredaQtbug,youareencouragedtoreportit.It'seasytoreportabugsinceQthasabugtracker,whoseURLishttps://bugreports.qt.io.
TipThequalityofthebugreportdramaticallyimpactshowsoonthebugwillbefixed.
Toproduceahigh-qualitybugreport,hereisasimplestep-by-stepmanual:
1. VisittheQtbugtrackerwebsite.2. Login.Ifit'syourfirsttime,youneedtocreateanewaccount.Rememberto
supplyavalide-mailaddressasthisistheonlywayfortheQtdeveloperstocontactyou.
3. UsetheSearchfieldontheupper-rightsidetofindanysimilar,orevenidenticalbugs.
4. Ifyoufindone,youcanleaveacommentwithanyadditionalinformationthatyouhave.Besides,youcanclickonVotetovoteforthatbug.Lastly,youcouldaddyourselfasawatcherifyouwanttotracktheprogress.
5. Ifnot,clickonCreateNewIssuesandfillinthefields.
YoushouldenterabriefdescriptivetextinSummary.Thisisnotonlyforahigherchancetogetitfixed,butalsogoodforotherpeoplesearchingforexistingbugs.Forotherfields,you'realwaysencouragedtoprovideasmuchinformationasyoucan.
SummaryAfterhavingareadthroughthischapter,youcansortoutthemajorityofQt-basedissuesonyourown.Westartedoffwithafewcommonlyencounteredproblems,followedbyhowtodebugQtandQtQuickapplications.Attheend,therewereafewusefullinkstohelpyoucrackdownonthevariedissuesanderrors.IfyouencounteranyproblemwithaparticularQtbug,don'tpanic,justgotothebugtrackerandreportit.