PDF Multipage Viewer Example▲
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
:
doc.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 &
lt; 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
contentItem
:
TextField {
id
:
passwordField
placeholderText
:
qsTr("Please provide the password"
)
echoMode
:
TextInput.Password
width
:
parent.width
onAccepted
:
passwordDialog.accept()
}
onOpened
:
passwordField.forceActiveFocus()
onAccepted
:
doc.password =
passwordField.text
}
Dialog {
id
:
errorDialog
title
:
"Error loading "
+
doc.source
standardButtons
:
Dialog.Close
modal
:
true
closePolicy
:
Popup.CloseOnEscape
anchors.centerIn: parent
width
:
300
visible
:
doc.status ===
PdfDocument.Error
contentItem
:
Label {
id
:
errorField
text
:
doc.error
}
}
PdfDocument {
id
:
doc
source
:
Qt.resolvedUrl(root.source)
onPasswordRequired
:
passwordDialog.open()
}
Add the main component, PdfMultiPageView:
PdfMultiPageView {
id
:
view
anchors.fill: parent
anchors.leftMargin: sidebar.position *
sidebar.width
document
:
doc
searchString
:
searchField.text
onCurrentPageChanged
:
currentPageSB.value =
view.currentPage +
1
}
DropArea {
anchors.fill: parent
keys
:
["text/uri-list"
]
onEntered
:
(drag) =&
gt; {
drag.accepted =
(drag.proposedAction ===
Qt.MoveAction ||
drag.proposedAction ===
Qt.CopyAction) &
amp;&
amp;
drag.hasUrls &
amp;&
amp; drag.urls[0
].endsWith("pdf"
)
}
onDropped
:
(drop) =&
gt; {
doc.source =
drop.urls[0
]
drop.acceptProposedAction()
}
}
A Drawer holds a ListView to show search results from the searchModel:
Drawer {
id
:
sidebar
edge
:
Qt.LeftEdge
modal
:
false
width
:
300
y
:
root.header.height
height
:
view.height
dim
:
false
clip
:
true
TabBar {
id
:
sidebarTabs
x
:
-
width
rotation
:
-
90
transformOrigin
:
Item.TopRight
currentIndex
:
2
// bookmarks by default
TabButton {
text
:
qsTr("Info"
)
}
TabButton {
text
:
qsTr("Search Results"
)
}
TabButton {
text
:
qsTr("Bookmarks"
)
}
TabButton {
text
:
qsTr("Pages"
)
}
}
GroupBox {
anchors.fill: parent
anchors.leftMargin: sidebarTabs.height
StackLayout {
anchors.fill: parent
currentIndex
:
sidebarTabs.currentIndex
component InfoField: TextInput {
width
:
parent.width
selectByMouse
:
true
readOnly
:
true
wrapMode
:
Text.WordWrap
}
Column {
spacing
:
6
width
:
parent.width -
6
Label {
font.bold: true
; text: qsTr("Title"
) }
InfoField {
text: doc.title }
Label {
font.bold: true
; text: qsTr("Author"
) }
InfoField {
text: doc.author }
Label {
font.bold: true
; text: qsTr("Subject"
) }
InfoField {
text: doc.subject }
Label {
font.bold: true
; text: qsTr("Keywords"
) }
InfoField {
text: doc.keywords }
Label {
font.bold: true
; text: qsTr("Producer"
) }
InfoField {
text: doc.producer }
Label {
font.bold: true
; text: qsTr("Creator"
) }
InfoField {
text: doc.creator }
Label {
font.bold: true
; text: qsTr("Creation date"
) }
InfoField {
text: doc.creationDate }
Label {
font.bold: true
; text: qsTr("Modification date"
) }
InfoField {
text: doc.modificationDate }
}
ListView {
id
:
searchResultsList
implicitHeight
:
parent.height
model
:
view.searchModel
currentIndex
:
view.searchModel.currentResult
ScrollBar.vertical: ScrollBar {
}
delegate
:
ItemDelegate {
id
:
resultDelegate
required property int
index
required property int
page
required property string contextBefore
required property string contextAfter
width
:
parent ? parent.width : 0
RowLayout {
anchors.fill: parent
spacing
:
0
Label {
text
:
"Page "
+
(resultDelegate.page +
1
) +
": "
}
Label {
text
:
resultDelegate.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
:
resultDelegate.contextAfter
elide
:
Text.ElideRight
Layout.fillWidth: true
Layout.preferredWidth: parent.width /
2
}
}
highlighted
:
ListView.isCurrentItem
onClicked
:
view.searchModel.currentResult =
resultDelegate.index
}
}
TreeView {
id
:
bookmarksTree
implicitHeight
:
parent.height
implicitWidth
:
parent.width
columnWidthProvider
:
function() {
return
width }
delegate
:
TreeViewDelegate {
required property int
page
required property point location
required property real zoom
onClicked
:
view.goToLocation(page, location, zoom)
}
model
:
PdfBookmarkModel {
document
:
doc
}
ScrollBar.vertical: ScrollBar {
}
}
GridView {
id
:
thumbnailsView
implicitWidth
:
parent.width
implicitHeight
:
parent.height
model
:
doc.pageModel
cellWidth
:
width /
2
cellHeight
:
cellWidth +
10
delegate
:
Item {
required property int
index
required property string label
required property size pointSize
width
:
thumbnailsView.cellWidth
height
:
thumbnailsView.cellHeight
Rectangle {
id
:
paper
width
:
image.width
height
:
image.height
x
:
(parent.width -
width) /
2
y
:
(parent.height -
height -
pageNumber.height) /
2
PdfPageImage {
id
:
image
document
:
doc
currentFrame
:
index
asynchronous
:
true
fillMode
:
Image.PreserveAspectFit
property bool
landscape: pointSize.width &
gt; pointSize.height
width
:
landscape ? thumbnailsView.cellWidth -
6
:
height *
pointSize.width /
pointSize.height
height
:
landscape ? width *
pointSize.height /
pointSize.width
:
thumbnailsView.cellHeight -
14
sourceSize.width: width
sourceSize.height: height
}
}
Text {
id
:
pageNumber
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
text
:
label
}
TapHandler {
onTapped
:
view.goToPage(index)
}
}
}
}
}
}
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 +
6
RowLayout {
id
:
footerRow
anchors.fill: parent
ToolButton {
action
:
Action {
id
:
sidebarOpenAction
checkable
:
true
checked
:
sidebar.opened
icon.source: checked ? "qrc:/pdfviewer/resources/sidebar-collapse-left.svg"
: "qrc:/pdfviewer/resources/sidebar-expand-left.svg"
onTriggered
:
sidebar.open()
}
ToolTip.visible: enabled &
amp;&
amp; hovered
ToolTip.delay: 2000
ToolTip.text: "open sidebar"
}
ToolButton {
action
:
Action {
icon.source: "qrc:/pdfviewer/resources/go-up-search.svg"
shortcut
:
StandardKey.FindPrevious
onTriggered
:
view.searchBack()
}
ToolTip.visible: enabled &
amp;&
amp; hovered
ToolTip.delay: 2000
ToolTip.text: "find previous"
}
TextField {
id
:
searchField
placeholderText
:
"search"
Layout.minimumWidth: 150
Layout.fillWidth: true
Layout.bottomMargin: 3
onAccepted
:
{
sidebar.open()
sidebarTabs.setCurrentIndex(0
)
}
Image {
visible
:
searchField.text !==
""
source
:
"qrc:/pdfviewer/resources/edit-clear.svg"
sourceSize.height: searchField.height -
6
anchors {
right
:
parent.right
verticalCenter
:
parent.verticalCenter
margins
:
3
}
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 &
amp;&
amp; hovered
ToolTip.delay: 2000
ToolTip.text: "find next"
}
Label {
id
:
statusLabel
property size implicitPointSize: doc.pagePointSize(view.currentPage)
text
:
"page "
+
(currentPageSB.value) +
" of "
+
doc.pageCount +
" scale "
+
view.renderScale.toFixed(2
) +
" original "
+
implicitPointSize.width.toFixed(1
) +
"x"
+
implicitPointSize.height.toFixed(1
) +
" pt"
visible
:
doc.pageCount &
gt; 0
}
}
}
}