Windows 8.1用のWindowsストア・アプリでは、PDFファイルを表示できる新機能が追加された。その機能を使ってPDFファイルを画面に表示する方法を解説する。
powered by Insider.NET
待ち望まれた機能、といってよいだろう。Windows 8.1(以降、Win 8.1)用のWindowsストア・アプリ(以降、Win 8.1アプリ)では、PDFファイルを表示できるようになったのだ。本稿では、新設されたWindows.Data.Pdf名前空間の機能を使ってPDFファイルを画面に表示する方法を解説する。本稿のサンプルは「Windows Store app samples:MetroTips #55(Windows 8.1版)」からダウンロードできる。
Win 8.1アプリを開発するには、Win 8.1とVisual Studio 2013(以降、VS 2013)が必要である。本稿ではOracle VM VirtualBox上で64bit版Windows 8.1 Pro PreviewとVisual Studio Express 2013 Preview for Windowsを使用している。これらを準備する方法や注意事項は、「WinRT/Metro TIPS:Win8用のソース・コードをWin8.1用に変換するには?[Windows 8.1ストア・アプリ開発]」の記事をご参照いただきたい。
なお、製品版のWin 8.1とVS 2013でも構わない。本稿で作成したコードは、Visual Studio Express 2013 for Windows*1でもビルドできて正常に動作することを確認している。ただし、操作の手順や自動生成されるコードなどで、Preview版と製品版では細部が異なる可能性がある。あらかじめご承知おきいただきたい。
*1マイクロソフト公式ダウンロード・センターの「Microsoft Visual Studio Express 2013 for Windows」から入手できる。
Win 8.1アプリ用に新設されたWindows.Data.Pdf名前空間の機能は、さほど多くない。端的に言えば、PDFファイルをページ単位でビットマップに変換できるだけである(次の図)。そのビットマップを画面に表示したり印刷したりする機能は、アプリ側で個別に実装することになる。
PDFを表示するために、PdfDocumentオブジェクトや表示しているページ番号などをメンバ変数として維持しなければならない。画面のソース・コードの中でそれをやると煩雑になってしまってよくないので、PDFを表示する部分だけを別のクラスに独立させよう。今回はそのためにユーザー・コントロールを使う。
VS 2013のプロジェクトに新しい項目を追加する。ソリューション・エクスプローラでそのプロジェクトを右クリックし[追加]−[新しい項目]を選択すると表示される[新しい項目の追加]ダイアログで[ユーザー コントロール]を選び、「PdfView.xaml」という名前を付ける。
「PdfView.xaml」ファイルを開いて、次のコードのようにScrollViewerコントロールとImageコントロールを配置する。
<UserControl
x:Class="MetroTips055CS.PdfView"
……省略……
d:DesignHeight="300"
d:DesignWidth="400">
<Grid>
<ScrollViewer HorizontalScrollBarVisibility="Auto" >
<Image x:Name="image1" />
</ScrollViewer>
</Grid>
</UserControl>
プロジェクトをいったんビルドしてから、アプリのメインとなる画面に今作ったユーザー・コントロールを配置する(次のコード)。ここでは、メイン画面にはGridコントロールがあって、それを3段(=3つのRowDefinitionオブジェクト)に分けてあるものとし、ユーザー・コントロールを3段目に置いた。
<Page
x:Name="pageRoot"
x:Class="MetroTips055CS.MainPage"
……省略……
<Grid Grid.Row="2" Background="LightGray">
<local:PdfView x:Name="pdfView1" />
</Grid>
</Grid>
</Page>
メイン画面にはこのほかに、ページを指定するUIなどを適宜構築してもらいたい。
アプリでPDFファイルを開いて、そのStorageFileオブジェクトをPdfDocumentクラスのLoadPdfDocumentAsyncメソッドに渡せばよい。
まず、ユーザー・コントロールの側に次のコードのようなメソッドを作る。このメソッドは、渡されたStorageFileオブジェクトを使ってPdfDocumentオブジェクトを生成し、それをメンバ変数に保持するものだ。なお、PdfDocumentオブジェクトが読み込んだPDFファイルの総ページ数がPageCountプロパティで取得できる。
Windows.Data.Pdf.PdfDocument _pdfDoc;
public uint PageCount { get { return _pdfDoc.PageCount; } }
public async Task<uint> LoadPdfDocumentAsync(Windows.Storage.StorageFile pdfFile)
{
_pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(pdfFile);
return this.PageCount;
}
Private _pdfDoc As Windows.Data.Pdf.PdfDocument
Public ReadOnly Property PageCount As UInteger
Get
Return _pdfDoc.PageCount
End Get
End Property
Public Async Function LoadPdfDocumentAsync(pdfFile As Windows.Storage.StorageFile) As Task(Of UInteger)
_pdfDoc = Await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(pdfFile)
Return Me.PageCount
End Function
メイン画面の側には、ユーザー・コントロールのLoadPdfDocumentAsyncメソッド(=上記のメソッド)にPDFファイルを渡すメソッドを作る(次のコード)。ここでは、PDFファイルを「sample.pdf」という名前でプロジェクトのルート・フォルダに置いた*2。
const string PdfFileName = "sample.pdf";
private async void LoadPdfDocument()
{
Windows.Storage.StorageFile pdfFile
= await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(PdfFileName);
await this.pdfView1.LoadPdfDocumentAsync(pdfFile);
}
Const PdfFileName As String = "sample.pdf"
Private Async Sub LoadPdfDocument()
Dim pdfFile As Windows.Storage.StorageFile _
= Await Windows.ApplicationModel.Package.Current.InstalledLocation.GetFileAsync(PdfFileName)
Await Me.pdfView1.LoadPdfDocumentAsync(pdfFile)
End Sub
*2PDFファイルをコピーした後、プロジェクトに追加し、そのプロパティの[ビルド アクション]を[コンテンツ]にしておく必要がある。このとき、ソリューション・エクスプローラでPDFファイルをクリックすると、テキスト・ファイルとして編集画面が開いてしまうので、うっかり上書き保存しないように注意してほしい。
上記のメソッドをメイン画面の中で呼び出す。画面のインスタンスが作られて表示されるまでの間のどこかで呼び出せばよいが、ここではコンストラクタの最後に入れた。
public MainPage()
{
this.InitializeComponent();
……省略……
this.LoadPdfDocument();
}
Public Sub New()
InitializeComponent()
……省略……
Me.LoadPdfDocument()
End Sub
以上で、PDFファイルからPdfDocumentオブジェクトを作成するコードが出来上がった。
PdfDocumentオブジェクトのGetPageメソッドで1つのPdfPageオブジェクトが得られる。別途、描画データを書き込むためのストリームを用意しておき、PdfPageオブジェクトのRenderToStreamAsyncメソッドでストリームに書き込ませればよい。
書き込むためのストリームは、あらかじめ画像ファイルを作っておいてそれを開いてもよいし、InMemoryRandomAccessStreamオブジェクト(メモリ上のストリーム)を使ってもよい。今回は後者を使おう。
ユーザー・コントロールに、表示するページのインデックスを表すメンバ変数と、上記の処理を行うRenderPageBitmapAsyncメソッドを追加する(次のコード)。このメソッドから返されたInMemoryRandomAccessStreamオブジェクトには、PDFファイルの1ページが描き込まれていることになる。
uint _currentPageIndex; // 0が1ページ目
private async Task<Windows.Storage.Streams.InMemoryRandomAccessStream> RenderPageBitmapAsync()
{
using (Windows.Data.Pdf.PdfPage pdfPage = _pdfDoc.GetPage(_currentPageIndex))
{
var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
await pdfPage.RenderToStreamAsync(memStream);
return memStream;
}
}
Private _currentPageIndex As UInteger ' 0が1ページ目
Private Async Function RenderPageBitmapAsync() As Task(Of Windows.Storage.Streams.InMemoryRandomAccessStream)
Using pdfPage As Windows.Data.Pdf.PdfPage = _pdfDoc.GetPage(_currentPageIndex)
Dim memStream = New Windows.Storage.Streams.InMemoryRandomAccessStream()
Await pdfPage.RenderToStreamAsync(memStream)
Return memStream
End Using
End Function
PDFファイルの1ページが描き込まれたInMemoryRandomAccessStreamオブジェクトを使ってBitmapImageオブジェクトを作り、それをImageコントロールに表示させればよい。
ユーザー・コントロールに、上記の処理を行うShowImageAsyncメソッドを追加する(次のコード)。
private async Task ShowImageAsync(Windows.Storage.Streams.InMemoryRandomAccessStream bitmapStream)
{
var bitmap = new Windows.UI.Xaml.Media.Imaging.BitmapImage();
await bitmap.SetSourceAsync(bitmapStream);
this.image1.Width = bitmap.PixelWidth;
this.image1.Height = bitmap.PixelHeight;
this.image1.Source = bitmap;
}
Private Async Function ShowImageAsync(bitmapStream As Windows.Storage.Streams.InMemoryRandomAccessStream) As Task
Dim bitmap = New Windows.UI.Xaml.Media.Imaging.BitmapImage()
Await bitmap.SetSourceAsync(bitmapStream)
Me.image1.Width = bitmap.PixelWidth
Me.image1.Height = bitmap.PixelHeight
Me.image1.Source = bitmap
End Function
最後に、前述したRenderPageBitmapAsyncメソッドとこのShowImageAsyncメソッドを組み合わせて、指定したページを表示するためのRenderPageメソッドを仕立てる。このRenderPageメソッドは、メイン画面から呼び出せるようにpublicなメソッドにする。
public uint RenderPage(uint pageIndex)
{
if (_currentPageIndex == pageIndex)
return _currentPageIndex;
if (_pdfDoc.PageCount <= pageIndex)
return _currentPageIndex;
_currentPageIndex = pageIndex;
RenderPageAsync();
return _currentPageIndex;
}
private async void RenderPageAsync()
{
if (_pdfDoc == null || _pdfDoc.PageCount == 0)
return;
using (Windows.Storage.Streams.InMemoryRandomAccessStream bitmapStream
= await RenderPageBitmapAsync())
{
await ShowImageAsync(bitmapStream);
}
}
Public Function RenderPage(pageIndex As UInteger) As UInteger
If (_currentPageIndex = pageIndex) Then
Return _currentPageIndex
End If
If (_pdfDoc.PageCount <= pageIndex) Then
Return _currentPageIndex
End If
_currentPageIndex = pageIndex
RenderPageAsync()
Return _currentPageIndex
End Function
Private Async Sub RenderPageAsync()
If (_pdfDoc Is Nothing OrElse _pdfDoc.PageCount = 0) Then
Return
End If
Using bitmapStream As Windows.Storage.Streams.InMemoryRandomAccessStream _
= Await RenderPageBitmapAsync()
Await ShowImageAsync(bitmapStream)
End Using
End Sub
メイン画面に適当なUIを配置し、そのイベント・ハンドラで上記のRenderPageメソッドを呼び出せば、指定したページが画面に表示されるはずだ。ただし、このままではファイルをロードした直後に何も表示されないので、前述のLoadPdfDocumentAsyncメソッドの中にRenderPageAsyncメソッドの呼び出しを追加しておく(次のコード)。
public async Task<uint> LoadPdfDocumentAsync(Windows.Storage.StorageFile pdfFile)
{
_pdfDoc = await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(pdfFile);
_currentPageIndex = 0;
RenderPageAsync();
return this.PageCount;
}
Public Async Function LoadPdfDocumentAsync(pdfFile As Windows.Storage.StorageFile) As Task(Of UInteger)
_pdfDoc = Await Windows.Data.Pdf.PdfDocument.LoadFromFileAsync(pdfFile)
_currentPageIndex = 0
RenderPageAsync()
Return Me.PageCount
End Function
PdfPageオブジェクトに描画してもらうときには、PdfPageRenderOptionsオブジェクトを渡せる。このPdfPageRenderOptionsオブジェクトで、描画される画像のサイズを指定すればよい。
ほかにも描画するときの背景色などを指定できる。画面の回転はサポートされていないので、回転させる必要があるならそのような処理を別途記述しなければならない(本稿では説明しない)。
画像の拡大率を表すメンバ変数を追加し、RenderPageBitmapAsyncメソッドを変更してみよう(次のコード)。
double _zoom = 1.0;
private async Task<Windows.Storage.Streams.InMemoryRandomAccessStream> RenderPageBitmapAsync()
{
using (Windows.Data.Pdf.PdfPage pdfPage = _pdfDoc.GetPage(_currentPageIndex))
{
var options = new Windows.Data.Pdf.PdfPageRenderOptions();
options.DestinationHeight = (uint)(pdfPage.Size.Height * _zoom);
var memStream = new Windows.Storage.Streams.InMemoryRandomAccessStream();
await pdfPage.RenderToStreamAsync(memStream, options);
return memStream;
}
}
Private _zoom As Double = 1.0
Private Async Function RenderPageBitmapAsync() As Task(Of Windows.Storage.Streams.InMemoryRandomAccessStream)
Using pdfPage As Windows.Data.Pdf.PdfPage = _pdfDoc.GetPage(_currentPageIndex)
Dim options = New Windows.Data.Pdf.PdfPageRenderOptions()
options.DestinationHeight = pdfPage.Size.Height * _zoom
Dim memStream = New Windows.Storage.Streams.InMemoryRandomAccessStream()
Await pdfPage.RenderToStreamAsync(memStream, options)
Return memStream
End Using
End Function
メイン画面にページ送りと倍率変更のUIを配置してアプリに仕立ててみた*3。実行すると次の画像のようになる。
*3実際のコードは、別途公開のサンプル・コードを見てほしい。また、そちらには90°回転させる機能も実装してあるので参考にしてほしい。
Windows.Data.Pdf名前空間は画面への表示まではサポートしていないため、PDFを表示するにはストリームやBitmapImageオブジェクトを扱うスキルが必要だ。ちょっと面倒だが、自分が必要とする機能をまとめたユーザー・コントロールに仕立てておけば、アプリからは簡単に利用できるようになる。
Copyright© Digital Advantage Corp. All Rights Reserved.