将XAML转换为PDF并为Xamarin.Forms UWP项目分页



直到最近,我一直被困在如何实现"导出";在我不知怎么从Dev Limbo中提取的一个项目中,将StackLayout的报告转换为PDF。

--背景——

以前,我曾尝试继续使用已放置(在项目中(的PDFSharp包,将XAML中显示的数据转换为客户端的PDF。长话短说,我无法让PDFSharp做我需要它做的事情,于是转向Syncfusion。它们似乎具备了实现这一目标所需的功能。根据他们的代码样本,我能够接近我的目标,但并不完全。它们有捕获部分和分页部分,但不是两者的组合。我基本上需要对CaptureAsync((保存的屏幕截图进行分页,以生成整个报告的pdf。

-如何解决此问题?--

经过一些挖掘,我在这篇文章中找到了一个答案(我永远感激(,并用它伪造了一个解决方案

以下是我的XAML内容页示例,用于上下文:

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:controls="clr-namespace:ReportTool.Controls"
x:Class="ReportTool.ReportViewer">
<ContentPage.Content>
<StackLayout Style="{StaticResource TopLevelStackLayout}">
<!-- Body Block -->
<Grid x:Name="MainGrid"  Style="{StaticResource MainContainingGrid}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<ScrollView x:Name="MainScrollLayout" VerticalOptions="Fill" HorizontalOptions="Fill" Grid.Row="0" Grid.Column="0" BackgroundColor="#FFFFFF" MinimumWidthRequest="700">
<StackLayout x:Name="MainStackLayout" Style="{StaticResource MainBkg}">

<Button x:Name="DownloadPdfBtn"  Text="Export to PDF" Clicked="DownloadPdfBtn_OnClicked" TextColor="White" BackgroundColor="DodgerBlue" VerticalOptions="Start" HorizontalOptions="Start" />
<Image Source="~..AssetsLogosCompanyLogo.png" Margin="0,60,0,10" HorizontalOptions="Center" />
<Label x:Name="TitlePageTitleText" Style="{StaticResource ReportViewerTitleTextMain}" Text="{StaticResource CompanyAnalysisReport}" />
<Label x:Name="TitlePagePreparedFor" Style="{StaticResource ReportViewerTitleTextMiddle}" Text="{StaticResource PreparedFor}" />
<Label x:Name="TitlePageOrganizationName" Style="{StaticResource ReportViewerTitleTextMiddle}" />
<Label x:Name="TitlePageOrganizationAddress1" Style="{StaticResource ReportViewerTitleTextMiddle}" />
<Label x:Name="TitlePageOrganizationAddress2" Style="{StaticResource ReportViewerTitleTextMiddle}" />
<Label x:Name="TitlePageOrganizationCityStateZip" Style="{StaticResource ReportViewerTitleTextLast}" />
<Grid x:Name="ReportGrid" Style="{StaticResource ReportGridBody}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="90"/>
<ColumnDefinition Width="1*"/>
<ColumnDefinition Width="125"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
</Grid>
</StackLayout>
</ScrollView>
</Grid>
</StackLayout>
</ContentPage.Content>
</ContentPage>

这是该节目的明星ExportToPdf按钮的代码:

using Syncfusion.Drawing;
using Syncfusion.Pdf;
using Syncfusion.Pdf.Graphics;
private async void DownloadPdfBtn_OnClicked(object sender, EventArgs e)
{
try
{
var filename = "SurveyReport_" + ((App)Application.Current).CurrentUser.UserName + "_" + DateTime.UtcNow.ToString("MMddyy") + ".pdf";

// Init Memory Stream.
var stream = new MemoryStream();

//Create a new PDF document
using (var document = new PdfDocument())
{
// Add page to the PDF document.
var page = document.Pages.Add();
// Get the scroll view height.
var xamlPageHeight = MainScrollLayout.ContentSize.Height;
// Get the page dimensions.
var pageWidth = page.GetClientSize().Width;
var pageHeight = page.GetClientSize().Height;
// Capture the number of pages.
var numberOfPages = (int)Math.Ceiling(xamlPageHeight / pageHeight);

for (var i = 0; i < numberOfPages; i++)
{
// Find beginning of page.
await MainScrollLayout.ScrollToAsync(0, i * pageHeight, false).ConfigureAwait(false);
// Capture the XAML page as an image and returns the image in memory stream.
var byteData = await DependencyService.Get<IExportPdf>().CaptureAsync();
var imageStream = new MemoryStream(byteData);

// Load the image in PdfBitmap.
var pdfBitmapImage = new PdfBitmap(imageStream);
// Set the pdf page settings.
document.PageSettings.Margins.All = 0;
document.PageSettings.Orientation = PdfPageOrientation.Portrait;
document.PageSettings.Size = new SizeF(pageWidth, pageHeight);

// Add new page for graphics (otherwise graphics won't know where to draw the rest of the image)
page = document.Pages.Add();
// Graphics for drawing image to pdf.
var graphics = page.Graphics;
// Draw the image to the page.
graphics.DrawImage(pdfBitmapImage,0,0, pageWidth, pageHeight);
// Insert page at i position.
document.Pages.Insert(i, page);
// Save the document into memory stream.
document.Save(stream);
}
}

stream.Position = 0;
// Save the stream as a file in the device and invoke it for viewing.
await Xamarin.Forms.DependencyService.Get<IExportPdf>().Save(filename, "application/pdf", stream);
}
catch (Exception ex)
{
DisplayErrorAlert("DownloadPdfBtn_OnClicked", ex.StackTrace);
}
}

需要注意的是,为了保存本地内存以外的任何位置,您都需要一个依赖项。值得庆幸的是,Syncfusion提供了一个片段供您使用。为了您的时间,我将分享这些片段。您需要为应用程序添加两个.cs文件,一个具有捕获/保存功能的类文件和一个接口文件。

捕获/保存类:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Display;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Media.Imaging;
using Xamarin.Forms;
public class ExportPdf : IExportPdf
{
public async Task<byte[]> CaptureAsync()
{
var renderTargetBitmap = new RenderTargetBitmap();
await renderTargetBitmap.RenderAsync(Window.Current.Content);

var pixelBuffer = await renderTargetBitmap.GetPixelsAsync();
var pixels = pixelBuffer.ToArray();

var displayInformation = DisplayInformation.GetForCurrentView().LogicalDpi;

var stream = new InMemoryRandomAccessStream();

var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
encoder.SetPixelData(
BitmapPixelFormat.Bgra8, 
BitmapAlphaMode.Ignore, 
(uint)renderTargetBitmap.PixelWidth, 
(uint)renderTargetBitmap.PixelHeight, 
displayInformation,
displayInformation, 
pixels);
await encoder.FlushAsync();

stream.Seek(0);
var readStream = stream.AsStreamForRead();
var bytes = new byte[readStream.Length];
await readStream.ReadAsync(bytes, 0, bytes.Length);
return bytes;
}
public async Task Save(string filename, string contentType, MemoryStream stream)
{
if (Device.Idiom != TargetIdiom.Desktop)
{
var local = ApplicationData.Current.LocalFolder;
var outFile = await local.CreateFileAsync(filename, CreationCollisionOption.ReplaceExisting);
using (var outStream = await outFile.OpenStreamForWriteAsync()) { await outStream.WriteAsync(stream.ToArray(), 0, (int)stream.Length); }
if (contentType != "application/html") await Windows.System.Launcher.LaunchFileAsync(outFile);
}
else
{
StorageFile storageFile = null;
var savePicker = new FileSavePicker
{
SuggestedStartLocation = PickerLocationId.Desktop,
SuggestedFileName = filename
};
switch (contentType)
{
case "application/vnd.openxmlformats-officedocument.presentationml.presentation":
savePicker.FileTypeChoices.Add("PowerPoint Presentation", new List<string> { ".pptx" });
break;
case "application/msexcel":
savePicker.FileTypeChoices.Add("Excel Files", new List<string> { ".xlsx" });
break;
case "application/msword":
savePicker.FileTypeChoices.Add("Word Document", new List<string> { ".docx" });
break;
case "application/pdf":
savePicker.FileTypeChoices.Add("Adobe PDF Document", new List<string> { ".pdf" });
break;
case "application/html":
savePicker.FileTypeChoices.Add("HTML Files", new List<string> { ".html" });
break;
}
storageFile = await savePicker.PickSaveFileAsync();
using (var outStream = await storageFile.OpenStreamForWriteAsync())
{
await outStream.WriteAsync(stream.ToArray(), 0, (int)stream.Length);
await outStream.FlushAsync();
outStream.Dispose();
}
stream.Flush();
stream.Dispose();
await Windows.System.Launcher.LaunchFileAsync(storageFile);
}
}
}

接口:

using System.IO;
using System.Threading.Tasks;
public interface IExportPdf
{
Task Save(string filename, string contentType, MemoryStream stream);
Task<byte[]> CaptureAsync();
}

这样就可以了!我希望这能帮助任何被赋予类似任务的人!

最新更新