PDF Multipage Viewer Example

A PDF viewer that allows scrolling through the pages.

PDF Multipage Viewer demonstrates how to use the PdfMultiPageView component to render PDF documents and search for text in them.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

Creating the Main Window

Instantiate an ApplicationWindow, bind its title to the title of the PDF document, and create a toolbar:

 ApplicationWindow {
     id: root
     width: 800
     height: 1024
     color: "lightgrey"
     title: document.title
     visible: true
     property string source // for main.cpp

     header: ToolBar {
         RowLayout {
             anchors.fill: parent
             anchors.rightMargin: 6

The toolbar has buttons for most of the common actions:

             ToolButton {
                 action: Action {
                     shortcut: StandardKey.Open
                     icon.source: "qrc:/pdfviewer/resources/document-open.svg"
                     onTriggered: fileDialog.open()
                 }
             }
             ToolButton {
                 action: Action {
                     shortcut: StandardKey.ZoomIn
                     enabled: view.renderScale < 10
                     icon.source: "qrc:/pdfviewer/resources/zoom-in.svg"
                     onTriggered: view.renderScale *= Math.sqrt(2)
                 }
             }
             ToolButton {
                 action: Action {
                     shortcut: StandardKey.ZoomOut

Declare a PdfDocument and bind the status property and passwordRequired signal to inform the user when an error occurs or a password is required:

     Dialog {
         id: passwordDialog
         title: "Password"
         standardButtons: Dialog.Ok | Dialog.Cancel
         modal: true
         closePolicy: Popup.CloseOnEscape
         anchors.centerIn: parent
         width: 300

         TextField {
             id: passwordField
             placeholderText: qsTr("Please provide the password")
             echoMode: TextInput.Password
             width: parent.width
             onAccepted: passwordDialog.accept()
         }
         onAccepted: document.password = passwordField.text
     }

     Dialog {
         id: errorDialog
         title: "Error loading " + document.source
         standardButtons: Dialog.Ok
         modal: true
         closePolicy: Popup.CloseOnEscape
         anchors.centerIn: parent
         width: 300

         Label {
             id: errorField
             text: document.error
         }
     }

     PdfDocument {
         id: document
         source: Qt.resolvedUrl(root.source)
         onStatusChanged: {
             if (status === PdfDocument.Error) errorDialog.open()
             view.document = (status === PdfDocument.Ready ? document : undefined)
         }
         onPasswordRequired: {
             passwordDialog.open()
             passwordField.forceActiveFocus()
         }
     }

Add the main component, PdfMultiPageView:

     PdfMultiPageView {
         id: view
         anchors.fill: parent
         anchors.leftMargin: searchDrawer.position * searchDrawer.width
         document: root.document
         searchString: searchField.text
         onCurrentPageChanged: currentPageSB.value = view.currentPage + 1
     }

A Drawer holds a ListView to show search results from the searchModel:

     Drawer {
         id: searchDrawer
         edge: Qt.LeftEdge
         modal: false
         width: 300
         y: root.header.height
         height: view.height
         dim: false
         clip: true
         ListView {
             id: searchResultsList
             anchors.fill: parent
             anchors.margins: 2
             model: view.searchModel
             ScrollBar.vertical: ScrollBar { }
             delegate: ItemDelegate {
                 width: parent ? parent.width : 0
                 RowLayout {
                     anchors.fill: parent
                     spacing: 0
                     Label {
                         text: "Page " + (page + 1) + ": "
                     }
                     Label {
                         text: contextBefore
                         elide: Text.ElideLeft
                         horizontalAlignment: Text.AlignRight
                         Layout.fillWidth: true
                         Layout.preferredWidth: parent.width / 2
                     }
                     Label {
                         font.bold: true
                         text: view.searchString
                         width: implicitWidth
                     }
                     Label {
                         text: contextAfter
                         elide: Text.ElideRight
                         Layout.fillWidth: true
                         Layout.preferredWidth: parent.width / 2
                     }
                 }
                 highlighted: ListView.isCurrentItem
                 onClicked: {
                     searchResultsList.currentIndex = index
                     view.goToLocation(page, location, 0)
                     view.searchModel.currentResult = indexOnPage
                 }
             }
         }
     }

Finally, add a second toolbar as a footer, to hold the search field, search up/down buttons and some status information:

     footer: ToolBar {
         height: footerRow.implicitHeight
         RowLayout {
             id: footerRow
             anchors.fill: parent
             ToolButton {
                 action: Action {
                     icon.source: "qrc:/pdfviewer/resources/go-up-search.svg"
                     shortcut: StandardKey.FindPrevious
                     onTriggered: view.searchBack()
                 }
                 ToolTip.visible: enabled && hovered
                 ToolTip.delay: 2000
                 ToolTip.text: "find previous"
             }
             TextField {
                 id: searchField
                 placeholderText: "search"
                 Layout.minimumWidth: 150
                 Layout.fillWidth: true
                 onAccepted: searchDrawer.open()
                 Image {
                     visible: searchField.text !== ""
                     source: "qrc:/pdfviewer/resources/edit-clear.svg"
                     anchors {
                         right: parent.right
                         top: parent.top
                         bottom: parent.bottom
                         margins: 3
                         rightMargin: 5
                     }
                     TapHandler {
                         onTapped: searchField.clear()
                     }
                 }
             }
             ToolButton {
                 action: Action {
                     icon.source: "qrc:/pdfviewer/resources/go-down-search.svg"
                     shortcut: StandardKey.FindNext
                     onTriggered: view.searchForward()
                 }
                 ToolTip.visible: enabled && hovered
                 ToolTip.delay: 2000
                 ToolTip.text: "find next"
             }
             Label {
                 id: statusLabel
                 property size implicitPointSize: document.pagePointSize(view.currentPage)
                 text: "page " + (currentPageSB.value) + " of " + document.pageCount +
                       " scale " + view.renderScale.toFixed(2) +
                       " original " + implicitPointSize.width.toFixed(1) + "x" + implicitPointSize.height.toFixed(1) + " pt"
                 visible: document.pageCount > 0
             }
         }
     }
 }

Files and Attributions

Files: