Qt Quick PDF 查看器允许卷动浏览页面。
PDF Multipage Viewer 演示如何使用 PdfMultiPageView component to render PDF documents and search for text in them.
要运行范例从 Qt Creator ,打开 Welcome 模式,然后选择范例从 Examples 。更多信息,拜访 构建和运行范例 .
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 < 10
icon.source: "qrc:/pdfviewer/resources/zoom-in.svg"
onTriggered: view.renderScale *= Math.sqrt(2)
}
}
ToolButton {
action: Action {
shortcut: StandardKey.ZoomOut
声明
PdfDocument
并绑定
status
特性和
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()
}
添加 main 组件 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) => {
drag.accepted = (drag.proposedAction === Qt.MoveAction || drag.proposedAction === Qt.CopyAction) &&
drag.hasUrls && drag.urls[0].endsWith("pdf")
}
onDropped: (drop) => {
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 > 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 && 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 && 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(1)
}
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 && 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 > 0
}
}
}
}