<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE html> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <!-- photoviewer.qdoc --> <title>Qt Quick Demo - Photo Viewer | Qt 5.12</title> <link rel="stylesheet" type="text/css" href="style/offline-simple.css" /> <script type="text/javascript"> document.getElementsByTagName("link").item(0).setAttribute("href", "style/offline.css"); // loading style sheet breaks anchors that were jumped to before // so force jumping to anchor again setTimeout(function() { var anchor = location.hash; // need to jump to different anchor first (e.g. none) location.hash = "#"; setTimeout(function() { location.hash = anchor; }, 0); }, 0); </script> </head> <body> <div class="header" id="qtdocheader"> <div class="main"> <div class="main-rounded"> <div class="navigationbar"> <table><tr> <td ><a href="index.html">Qt 5.12</a></td><td >Qt Quick Demo - Photo Viewer</td></tr></table><table class="buildversion"><tr> <td id="buildversion" width="100%" align="right">Qt 5.12.6 Reference Documentation</td> </tr></table> </div> </div> <div class="content"> <div class="line"> <div class="content mainContent"> <div class="sidebar"> <div class="toc"> <h3><a name="toc">Contents</a></h3> <ul> <li class="level1"><a href="#running-the-example">Running the Example</a></li> <li class="level1"><a href="#using-custom-types">Using Custom Types</a></li> <li class="level1"><a href="#creating-the-main-window">Creating the Main Window</a></li> <li class="level1"><a href="#displaying-photos">Displaying Photos</a></li> <li class="level1"><a href="#downloading-flickr-feeds">Downloading Flickr Feeds</a></li> <li class="level1"><a href="#creating-flipable-labels">Creating Flipable Labels</a></li> <li class="level1"><a href="#laying-out-photos-on-a-path">Laying Out Photos on a Path</a></li> <li class="level1"><a href="#providing-feedback-to-users">Providing Feedback to Users</a></li> <li class="level1"><a href="#localizing-applications">Localizing Applications</a></li> </ul> </div> <div class="sidebar-content" id="sidebar-content"></div></div> <h1 class="title">Qt Quick Demo - Photo Viewer</h1> <span class="subtitle"></span> <!-- $$$demos/photoviewer-brief --> <p>A QML photo viewer that that uses XmlListModel and XmlRole to download Flickr feeds, and Package to display the photos in different views.</p> <!-- @@@demos/photoviewer --> <!-- $$$demos/photoviewer-description --> <div class="descr"> <a name="details"></a> <p class="centerAlign"><img src="images/qtquick-demo-photoviewer-small.png" alt="" /></p><p><i>Photo Viewer</i> demonstrates the following Qt Quick features:</p> <ul> <li>Using custom types to create screens and screen controls.</li> <li>Using Qt Quick Controls 1 to create an application window.</li> <li>Using the Package type with a DelegateModel to provide delegates with a shared context to multiple views.</li> <li>Using XML list models to download Flickr feeds.</li> <li>Using the Flipable type to create labels with different text on the front and back.</li> <li>Using the PathView, Path, PathAttribute, and PathLine types to lay out photos on a path.</li> <li>Providing feedback to users while data is loading.</li> <li>Localizing applications.</li> </ul> <a name="running-the-example"></a> <h2 id="running-the-example">Running the Example</h2> <p>To run the example from <a href="http://doc.qt.io/qtcreator/index.html">Qt Creator</a>, open the <b>Welcome</b> mode and select the example from <b>Examples</b>. For more information, visit <a href="http://doc.qt.io/qtcreator/creator-build-example-application.html">Building and Running an Example</a>.</p> <a name="using-custom-types"></a> <h2 id="using-custom-types">Using Custom Types</h2> <p>In the Photo Viewer app, we use the following custom types that are each defined in a separate .qml file:</p> <ul> <li><code>AlbumDelegate.qml</code></li> <li><code>BusyIndicator.qml</code></li> <li><code>Button.qml</code></li> <li><code>EditableButton.qml</code></li> <li><code>PhotoDelegate.qml</code></li> <li><code>ProgressBar.qml</code></li> <li><code>RssModel.qml</code></li> <li><code>Tag.qml</code></li> </ul> <p>To use the custom types, we add an import statement to the main QML file, main.qml, that imports the folder called <code>PhotoViewerCore</code> where the types are located:</p> <pre class="cpp"> import "PhotoViewerCore" </pre> <a name="creating-the-main-window"></a> <h2 id="creating-the-main-window">Creating the Main Window</h2> <p>In main.qml, we use the ApplicationWindow Qt Quick Control to create the app main window:</p> <pre class="cpp"> ApplicationWindow { id: mainWindow visible: true </pre> <p>We use a ListModel type with ListElement types to display photo albums:</p> <pre class="cpp"> ListModel { id: photosModel ListElement { tag: "Flowers" } ListElement { tag: "Wildlife" } ListElement { tag: "Prague" } } </pre> <p>List elements are defined like other QML types except that they contain a collection of <i>role</i> definitions instead of properties. Roles both define how the data is accessed and include the data itself. For each list element, we use the <code>tag</code> role to specify the photos to download.</p> <p>A DelegateModel type is used together with the Package type to provide delegates to multiple views. The <code>model</code> property holds the model providing data for the delegate model and the <code>delegate</code> property specifies the template defining each item instantiated by a view:</p> <pre class="cpp"> DelegateModel { id: albumVisualModel; model: photosModel; delegate: AlbumDelegate {} } </pre> <p>We use a GridView type to lay out the albums as a grid:</p> <pre class="cpp"> GridView { id: albumView; width: parent.width; height: parent.height; cellWidth: 210; cellHeight: 220 model: albumVisualModel.parts.album; visible: albumsShade.opacity != 1.0 } </pre> <p>The <code>model</code> property references the package name <code>album</code> that we specify in AlbumDelegate.qml. We use the Package type to allow the photos to move between different views. The Package contains the named items <code>browser</code>, <code>fullscreen</code>, and <code>album</code>:</p> <pre class="cpp"> Package { Item { Package.name: 'browser' GridView { id: photosGridView; model: visualModel.parts.grid; width: mainWindow.width; height: mainWindow.height - 21 x: 0; y: 21; cellWidth: 160; cellHeight: 153; interactive: false onCurrentIndexChanged: photosListView.positionViewAtIndex(currentIndex, ListView.Contain) } } Item { Package.name: 'fullscreen' ListView { id: photosListView; model: visualModel.parts.list; orientation: Qt.Horizontal width: mainWindow.width; height: mainWindow.height; interactive: false onCurrentIndexChanged: photosGridView.positionViewAtIndex(currentIndex, GridView.Contain) highlightRangeMode: ListView.StrictlyEnforceRange; snapMode: ListView.SnapOneItem } } Item { Package.name: 'album' id: albumWrapper; width: 210; height: 220 </pre> <p>The named items are used as the delegates by the views that reference the special DelegateModel::parts property to select the model that provides the chosen delegate.</p> <p>We use a ListView type to lay out albums in other views:</p> <pre class="cpp"> ListView { anchors.fill: parent; model: albumVisualModel.parts.browser; interactive: false } ListView { anchors.fill: parent; model: albumVisualModel.parts.fullscreen; interactive: false } </pre> <a name="displaying-photos"></a> <h2 id="displaying-photos">Displaying Photos</h2> <p>We use the PhotoDelegate custom type that is specified in PhotoDelegate.qml to display photos. We use a Package type to lay out the photos either in a stack, list, or a grid:</p> <pre class="cpp"> Package { Item { id: stackItem; Package.name: 'stack'; width: 160; height: 153; z: stackItem.PathView.z } Item { id: listItem; Package.name: 'list'; width: mainWindow.width + 40; height: 153 } Item { id: gridItem; Package.name: 'grid'; width: 160; height: 153 } </pre> <p>The photos are rotated at random angles by using the <code>Math.random()</code> JavaScript method:</p> <pre class="cpp"> Item { width: 160; height: 153 Item { id: photoWrapper property double randomAngle: Math.random() * (2 * 6 + 1) - 6 property double randomAngle2: Math.random() * (2 * 6 + 1) - 6 x: 0; y: 0; width: 140; height: 133 z: stackItem.PathView.z; rotation: photoWrapper.randomAngle </pre> <p>We use a BorderImage type to create borders for the images:</p> <pre class="cpp"> BorderImage { anchors { fill: originalImage.status == Image.Ready ? border : placeHolder leftMargin: -6; topMargin: -6; rightMargin: -8; bottomMargin: -8 } source: 'images/box-shadow.png' border.left: 10; border.top: 10; border.right: 10; border.bottom: 10 } </pre> <a name="downloading-flickr-feeds"></a> <h2 id="downloading-flickr-feeds">Downloading Flickr Feeds</h2> <p>In AlbumDelegate.qml, we use the DelegateModel to provide the PhotoDelegate delegate to the RssModel model:</p> <pre class="cpp"> DelegateModel { id: visualModel; delegate: PhotoDelegate { } model: RssModel { id: rssModel; tags: tag } } </pre> <p>In RssModel.qml, we use an XmlListModel type as a data source for Package objects to download photos from the selected feeds:</p> <pre class="cpp"> import QtQuick.XmlListModel 2.0 XmlListModel { property string tags : "" function encodeTags(x) { return encodeURIComponent(x.replace(' ',',')); } </pre> <p>We use the <code>tags</code> custom property to specify which photos to download. The <code>encodeTags</code> custom function uses the <code>encodeURIComponent</code> JavaScript method to ensure that the requests to the server are correctly formatted.</p> <p>We use the <code>source</code> property to fetch photos that have the specified tags attached from public Flickr feeds:</p> <pre class="cpp"> source: "http://api.flickr.com/services/feeds/photos_public.gne?"+(tags ? "tags="+encodeTags(tags)+"&" : "") query: "/feed/entry" namespaceDeclarations: "declare default element namespace 'http://www.w3.org/2005/Atom';" </pre> <p>The <code>query</code> property specifies that the XmlListModel generates a model item for each feed entry.</p> <p>The <code>namespaceDeclarations</code> property specifies that the requested document uses the namespace <code>http://www.w3.org/2005/Atom</code>, which is declared as the default namespace.</p> <p>We use the XmlRole type to specify the model item attributes. Each model item has the <code>title</code>, <code>content</code>, and <code>hq</code> attributes that match the values of the corresponding feed entry:</p> <pre class="cpp"> XmlRole { name: "title"; query: "title/string()" } XmlRole { name: "content"; query: "content/string()" } XmlRole { name: "hq"; query: "link[@rel='enclosure']/@href/string()" } </pre> <a name="creating-flipable-labels"></a> <h2 id="creating-flipable-labels">Creating Flipable Labels</h2> <p>When users select the <b>Edit</b> button, the album labels are flipped from their front side to their back side and the text on them changes from album name to <b>Remove</b>.</p> <p>In AlbumDelegate.qml, we use the Tag custom type to specify the text to display on the front and back sides of album labels:</p> <pre class="cpp"> Tag { anchors { horizontalCenter: parent.horizontalCenter; bottom: parent.bottom; bottomMargin: 10 } frontLabel: tag; backLabel: qsTr("Remove"); flipped: mainWindow.editMode onTagChanged: rssModel.tags = tag onBackClicked: if (mainWindow.editMode) photosModel.remove(index); } </pre> <p>The <code>onTagChanged</code> signal handler is used to change the tag based on which the model is populated. The <code>onBackClicked</code> signal handler is used to remove the album.</p> <p>In Tag.qml, we use a Flipable type with custom properties and signals to create the labels:</p> <pre class="cpp"> Flipable { id: flipable property alias frontLabel: frontButton.label property alias backLabel: backButton.label property int angle: 0 property int randomAngle: Math.random() * (2 * 6 + 1) - 6 property bool flipped: false signal frontClicked signal backClicked signal tagChanged(string tag) </pre> <p>The <code>front</code> property holds the EditableButton custom type that enables users to edit the label text:</p> <pre class="cpp"> front: EditableButton { id: frontButton; rotation: flipable.randomAngle anchors { centerIn: parent; verticalCenterOffset: -20 } onClicked: flipable.frontClicked() onLabelChanged: flipable.tagChanged(label) } </pre> <p>The <code>back</code> property holds the <code>Button</code> custom type that is used to remove the album:</p> <pre class="cpp"> back: Button { id: backButton; tint: "red"; rotation: flipable.randomAngle anchors { centerIn: parent; verticalCenterOffset: -20 } onClicked: flipable.backClicked() } </pre> <a name="laying-out-photos-on-a-path"></a> <h2 id="laying-out-photos-on-a-path">Laying Out Photos on a Path</h2> <p>In AlbumDelegate.qml, we use a PathView type to lay out the photos provided by the <code>visualModel.parts.stack</code> model on a path that has the form of a stack:</p> <pre class="cpp"> PathView { id: photosPathView; model: visualModel.parts.stack; pathItemCount: 5 visible: !busyIndicator.visible anchors.centerIn: parent; anchors.verticalCenterOffset: -30 path: Path { PathAttribute { name: 'z'; value: 9999.0 } PathLine { x: 1; y: 1 } PathAttribute { name: 'z'; value: 0.0 } } } </pre> <p>The <code>path</code> property holds the Path type that defines the path used by the PathView. The PathAttribute types are used to set a range of <code>0</code> to <code>9999</code> for the <code>z</code> attribute. This way, the path creates a stack of album photos. Because each PhotoDelegate is slightly rotated at a random angle, this results in a realistic-looking stack of photos.</p> <a name="providing-feedback-to-users"></a> <h2 id="providing-feedback-to-users">Providing Feedback to Users</h2> <p>We use a busy indicator and a progress bar to indicate activity while Flickr feeds and photos are being loaded.</p> <p>In AlbumDelegate.qml, we use the <code>BusyIndicator</code> custom type and the <code>on</code> custom property to display a rotating image while the Flickr feed is being loaded:</p> <pre class="cpp"> BusyIndicator { id: busyIndicator anchors { centerIn: parent; verticalCenterOffset: -20 } on: rssModel.status != XmlListModel.Ready } </pre> <p>In PhotoDelegate.qml, we use them to indicate activity while a photo is being loaded:</p> <pre class="cpp"> BusyIndicator { anchors.centerIn: parent; on: originalImage.status != Image.Ready } </pre> <p>We define the <code>BusyIndicator</code> type in <code>BusyIndicator.qml</code>. We use an Image type to display an image and apply a NumberAnimation to its <code>rotation</code> property to rotate the image in an infinite loop:</p> <pre class="cpp"> Image { id: container property bool on: false source: "images/busy.png"; visible: container.on NumberAnimation on rotation { running: container.on; from: 0; to: 360; loops: Animation.Infinite; duration: 1200 } } </pre> <p>In your apps, you can also use the BusyIndicator type from the Qt Quick Controls module.</p> <p>In main.qml, we use the <code>ProgressBar</code> custom type to indicate progress while a high quality version of a photo is being opened on full screen:</p> <pre class="cpp"> ProgressBar { progress: mainWindow.downloadProgress; width: parent.width; height: 4 anchors.bottom: parent.bottom; opacity: mainWindow.imageLoading; visible: opacity != 0.0 } </pre> <p>We define the <code>ProgressBar</code> type in <code>ProgressBar.qml</code>. We use a Rectangle type to create the progress bar and apply a NumberAnimation to its <code>opacity</code> property to change the color of the bar from black to white as data loading proceeds:</p> <pre class="cpp"> Item { id: container property real progress: 0 Behavior on opacity { NumberAnimation { duration: 600 } } Rectangle { anchors.fill: parent; color: "black"; opacity: 0.5 } Rectangle { id: fill; color: "white"; height: container.height width: container.width * container.progress } } </pre> <p>In your apps, you can also use the ProgressBar type from the Qt Quick Controls module.</p> <a name="localizing-applications"></a> <h2 id="localizing-applications">Localizing Applications</h2> <p>The example application is translated into German and French. The translated strings are loaded at runtime according to the current locale.</p> <p>We use a Column type in main.qml to position buttons for adding and editing albums and exiting the application:</p> <pre class="cpp"> Column { spacing: 20; anchors { bottom: parent.bottom; right: parent.right; rightMargin: 20; bottomMargin: 20 } Button { id: newButton; label: qsTr("Add"); rotation: 3 anchors.horizontalCenter: parent.horizontalCenter onClicked: { mainWindow.editMode = false photosModel.append( { tag: "" } ) albumView.positionViewAtIndex(albumView.count - 1, GridView.Contain) } } Button { id: deleteButton; label: qsTr("Edit"); rotation: -2; onClicked: mainWindow.editMode = !mainWindow.editMode anchors.horizontalCenter: parent.horizontalCenter } Button { id: quitButton; label: qsTr("Quit"); rotation: -2; onClicked: Qt.quit() anchors.horizontalCenter: parent.horizontalCenter } } </pre> <p>We use the qsTr() command to mark the button labels translatable.</p> <p>We use the <code>lupdate()</code> tool to generate the translation source files and the <code>lrelease()</code> tool to convert the translated strings to the QM files used by the application at runtime. These files are stored in the <code>i18n</code> directory.</p> <p>To make the application aware of the translations, we add code to the <code>main()</code> function in the main.cpp file. The code creates a QTranslator object, loads a translation according to the current locale at runtime, and installs the translator object into the application:</p> <pre class="cpp"> <span class="type">int</span> main(<span class="type">int</span> argc<span class="operator">,</span> <span class="type">char</span> <span class="operator">*</span>argv<span class="operator">[</span><span class="operator">]</span>) { <span class="type">QGuiApplication</span> app(argc<span class="operator">,</span> argv); <span class="type">QTranslator</span> qtTranslator; qtTranslator<span class="operator">.</span>load(<span class="type">QLocale</span>()<span class="operator">,</span> <span class="string">"qml"</span><span class="operator">,</span> <span class="string">"_"</span><span class="operator">,</span> <span class="string">":/i18n/"</span>); app<span class="operator">.</span>installTranslator(<span class="operator">&</span>qtTranslator); </pre> <p>Files:</p> <ul> <li><a href="qtdoc-demos-photoviewer-photoviewercore-albumdelegate-qml.html">demos/photoviewer/PhotoViewerCore/AlbumDelegate.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-busyindicator-qml.html">demos/photoviewer/PhotoViewerCore/BusyIndicator.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-button-qml.html">demos/photoviewer/PhotoViewerCore/Button.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-editablebutton-qml.html">demos/photoviewer/PhotoViewerCore/EditableButton.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-photodelegate-qml.html">demos/photoviewer/PhotoViewerCore/PhotoDelegate.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-progressbar-qml.html">demos/photoviewer/PhotoViewerCore/ProgressBar.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-rssmodel-qml.html">demos/photoviewer/PhotoViewerCore/RssModel.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewercore-tag-qml.html">demos/photoviewer/PhotoViewerCore/Tag.qml</a></li> <li><a href="qtdoc-demos-photoviewer-main-cpp.html">demos/photoviewer/main.cpp</a></li> <li><a href="qtdoc-demos-photoviewer-main-qml.html">demos/photoviewer/main.qml</a></li> <li><a href="qtdoc-demos-photoviewer-photoviewer-pro.html">demos/photoviewer/photoviewer.pro</a></li> </ul> <p>Images:</p> <ul> <li><a href="images/used-in-examples/demos/photoviewer/PhotoViewerCore/images/box-shadow.png">demos/photoviewer/PhotoViewerCore/images/box-shadow.png</a></li> <li><a href="images/used-in-examples/demos/photoviewer/PhotoViewerCore/images/busy.png">demos/photoviewer/PhotoViewerCore/images/busy.png</a></li> <li><a href="images/used-in-examples/demos/photoviewer/PhotoViewerCore/images/cardboard.png">demos/photoviewer/PhotoViewerCore/images/cardboard.png</a></li> </ul> </div> <p><b>See also </b><a href="qmlapplications.html">QML Applications</a>.</p> <!-- @@@demos/photoviewer --> </div> </div> </div> </div> </div> <div class="footer"> <p> <acronym title="Copyright">©</acronym> 2019 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners.<br/> The documentation provided herein is licensed under the terms of the <a href="http://www.gnu.org/licenses/fdl.html">GNU Free Documentation License version 1.3</a> as published by the Free Software Foundation.<br/> Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners. </p> </div> </body> </html>