Windows 8 Store Apps 透過 Windows.Graphics.Printing 命名空間提供完整的列印支援。核心流程:註冊列印合約 → 準備列印內容(分頁佈局) → 回應系統預覽請求 → 發送最終內容至印表機。本文詳細解析 PrintDocument、PrintManager、分頁邏輯與自訂選項的完整實作。
| 類別 | 職責 | 關鍵事件/方法 |
|---|---|---|
| PrintManager | 管理列印合約,顯示印表機選擇 UI | GetForCurrentView()、ShowPrintUIAsync()、PrintTaskRequested |
| PrintTask | 代表單次列印作業 | Options、Completed、SourceRequested |
| PrintDocument | 提供列印內容來源 | Paginate、GetPreviewPage、AddPages、SetPreviewPageCount、InvalidatePreview |
| PrintTaskOptionDetails | 管理列印預覽選項(份數、方向、紙張等) | DisplayedOptions、CreateItemListOption、OptionChanged |
包含頁首(僅首頁顯示)、內容區(RichTextBlock + RichTextBlockOverflow 鏈式流動)、頁尾(僅末頁顯示):
<Grid x:Name="printableArea">
<Grid.RowDefinitions>
<RowDefinition Height="60"/> <!-- 頁首 -->
<RowDefinition Height="3*"/> <!-- 內容區 1 -->
<RowDefinition Height="3*"/> <!-- 內容區 2 (含圖片) -->
<RowDefinition Height="4*"/> <!-- 內容區 3 -->
<RowDefinition Height="60"/> <!-- 頁尾 -->
</Grid.RowDefinitions>
<!-- 頁首:Logo + 標題 -->
<StackPanel x:Name="header" Visibility="Collapsed" ...>...</StackPanel>
<!-- 主內容:RichTextBlock 連結 Overflow 鏈 -->
<RichTextBlock x:Name="textContent" OverflowContentTarget="{Binding ElementName=imageLeftText}" ...>...</RichTextBlock>
<RichTextBlockOverflow x:Name="imageLeftText" OverflowContentTarget="{Binding ElementName=textOverflow}" Grid.Row="2" Grid.Column="0"/>
<Image Source="ms-appx:///Assets/Logo.png" Grid.Row="2" Grid.Column="1"/>
<RichTextBlockOverflow x:Name="textOverflow" Grid.Row="3" Grid.ColumnSpan="2"/>
<!-- 頁尾:版權資訊 -->
<StackPanel x:Name="footer" Visibility="Collapsed" ...>...</StackPanel>
</Grid>
僅包含 RichTextBlockOverflow 接收上一頁流動內容,以及條件性頁尾:
<Grid x:Name="printableArea">
<RichTextBlockOverflow x:Name="textOverflow" />
<StackPanel x:Name="footer" Visibility="Collapsed" ...>...</StackPanel>
</Grid>
protected override void OnNavigatedTo(NavigationEventArgs e) {
RegisterForPrinting();
}
protected override void OnNavigatedFrom(NavigationEventArgs e) {
UnregisterForPrinting();
}
private void RegisterForPrinting() {
_printDocument = new PrintDocument();
_printDocument.Paginate += OnPaginate;
_printDocument.GetPreviewPage += OnGetPreviewPage;
_printDocument.AddPages += OnAddPages;
PrintManager.GetForCurrentView().PrintTaskRequested += OnPrintTaskRequested;
PreparePrintContent(); // 建立首頁視覺樹
}
private void UnregisterForPrinting() {
_printDocument.Paginate -= OnPaginate;
_printDocument.GetPreviewPage -= OnGetPreviewPage;
_printDocument.AddPages -= OnAddPages;
PrintManager.GetForCurrentView().PrintTaskRequested -= OnPrintTaskRequested;
_printingRoot.Children.Clear();
}
系統請求預覽頁數與佈局時觸發,需建立所有預覽頁視覺樹:
private void OnPaginate(object sender, PaginateEventArgs e) {
_previewPages.Clear();
_printingRoot.Children.Clear();
var printOptions = (PrintTaskOptions)e.PrintTaskOptions;
var pageDescription = printOptions.GetPageDescription(0);
// 建立首頁
var lastOverflow = AddPreviewPageAndPrintPage(null, pageDescription);
// 循環建立溢出頁,直到無溢出內容
while (lastOverflow.HasOverflowContent && lastOverflow.Visibility == Visibility.Visible) {
lastOverflow = AddPreviewPageAndPrintPage(lastOverflow, pageDescription);
}
_printDocument.SetPreviewPageCount(_previewPages.Count, PreviewPageCountType.Intermediate);
}
private RichTextBlockOverflow AddPreviewPageAndPrintPage(RichTextBlockOverflow lastOverflow, PrintPageDescription pageDesc) {
FrameworkElement previewPage;
if (lastOverflow == null) {
previewPage = _firstPage; // 首頁模板
((StackPanel)previewPage.FindName("footer")).Visibility = Visibility.Collapsed; // 首頁無頁尾
} else {
previewPage = new ContinuationPage(lastOverflow); // 溢出頁模板
}
// 設定頁面尺寸與邊距
previewPage.Width = pageDesc.PageSize.Width;
previewPage.Height = pageDesc.PageSize.Height;
var printableArea = (Grid)previewPage.FindName("printableArea");
double marginWidth = Math.Max(pageDesc.PageSize.Width - pageDesc.ImageableRect.Width, pageDesc.PageSize.Width * 0.15);
double marginHeight = Math.Max(pageDesc.PageSize.Height - pageDesc.ImageableRect.Height, pageDesc.PageSize.Height * 0.06);
printableArea.Width = previewPage.Width - marginWidth;
printableArea.Height = previewPage.Height - marginHeight;
// 加入預覽集合與發送列印視覺樹
_previewPages.Add(previewPage);
_printingRoot.Children.Add(previewPage);
_printingRoot.InvalidateMeasure();
_printingRoot.UpdateLayout();
var textOverflow = (RichTextBlockOverflow)previewPage.FindName("textOverflow");
if (!textOverflow.HasOverflowContent && textOverflow.Visibility == Visibility.Visible) {
((StackPanel)previewPage.FindName("footer")).Visibility = Visibility.Visible; // 末頁顯示頁尾
}
return textOverflow;
}
private void OnGetPreviewPage(object sender, GetPreviewPageEventArgs e) {
_printDocument.SetPreviewPage(e.PageNumber, _previewPages[e.PageNumber - 1]);
}
private void OnAddPages(object sender, AddPagesEventArgs e) {
foreach (var page in _previewPages) {
_printDocument.AddPage(page);
}
_printDocument.AddPagesComplete();
}
private async void OnPrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs e) {
var printTask = e.Request.CreatePrintTask("列印標題", async sourceRequested => {
// 設定預覽選項
var optionDetails = PrintTaskOptionDetails.GetFromPrintTaskOptions(printTask.Options);
var displayedOptions = optionDetails.DisplayedOptions;
displayedOptions.Clear();
displayedOptions.Add(StandardPrintTaskOptions.Copies);
displayedOptions.Add(StandardPrintTaskOptions.Orientation);
displayedOptions.Add(StandardPrintTaskOptions.MediaSize);
displayedOptions.Add(StandardPrintTaskOptions.Collation);
displayedOptions.Add(StandardPrintTaskOptions.Duplex);
printTask.Options.MediaSize = PrintMediaSize.NorthAmericaLegal;
// 自訂選項
var customOption = optionDetails.CreateItemListOption("CustomOption", "自訂選項");
customOption.AddItem("item1", "選項 1");
customOption.AddItem("item2", "選項 2");
customOption.AddItem("item3", "選項 3");
displayedOptions.Add("CustomOption");
optionDetails.OptionChanged += OnOptionChanged;
// 提供列印來源
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
sourceRequested.SetSource(_printDocument.DocumentSource);
});
printTask.Completed += (s, args) => {
if (args.Completion == PrintTaskCompletion.Failed) {
// 處理失敗
}
};
});
}
private async void OnOptionChanged(PrintTaskOptionDetails sender, PrintTaskOptionChangedEventArgs args) {
var optionId = args.OptionId as string;
if (string.IsNullOrEmpty(optionId)) return;
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, async () => {
var itemId = sender.Options[optionId].Value as string;
// 根據選項調整列印內容...
_printDocument.InvalidatePreview(); // 重新觸發 Paginate
});
}
_previewPages 供預覽,_printingRoot 供實際列印,兩者同步佈局但獨立管理。PageSize - ImageableRect(印表機硬體邊距)與 PageSize * 比例(應用程式邊距)的較大值,確保內容不被切邊。OverflowContentTarget 串聯多個 Overflow 元素,實現自動分頁流動。await Dispatcher.RunAsync。| 問題 | 原因 | 解決 |
|---|---|---|
| 預覽頁數為 0 | Paginate 未呼叫 SetPreviewPageCount | 確保分頁邏輯正確添加頁面並呼叫 SetPreviewPageCount |
| 內容被切邊 | 邊距計算錯誤 | 使用 Math.Max(硬體邊距, 應用邊距) 取較大值 |
| 頁首頁尾顯示錯誤 | Visibility 邏輯錯誤 | 首頁 Header Visible、Footer Collapsed;末頁相反;中間頁皆 Collapsed |
| 選項變更不生效 | 未呼叫 InvalidatePreview | OptionChanged 中必須呼叫 _printDocument.InvalidatePreview() |
掌握列印合約的完整流程與分頁佈局技巧,能為 Windows 8 Store Apps 提供專業級的文件列印體驗。