實作Print列印功能在Universal Windows App!
今天要介紹的是Windows 10的列印文件(Print API)在UWP上如何實作!
從8.1開始就已經支援的Print API到底有甚麼變化呢?在8.1上只能在平板或是PC上使用Print,但在Windows 10的UWP上也延伸到了Mobile!
那...到底要怎樣實作Print API呢?先看以下的圖片展示Print的框架
這次範例使用的是Print API銜接 XAML的Render部分!
Windows Mobile的列印功能又與Desktop版本不同!需要採用的是Network的連線方式,因此Printer Driver的實際驅動安裝是採用Install on demand!
先說明一下目前8.1以及10的Print差異,之前在使用8.1的Store的流程會從Charms Bar呼叫Device(裝置)接者按下Print(列印)才會顯示印表的預覽畫面。
第二點就是PrintManagerShowPrintUIAsync需要放在Try-Catch的程式碼區塊內!如果裝置安裝Driver或是不支援的狀況導致無法只用至少在Print的Dialog會優雅的關閉!(Graceful fail)
接者開始實作Print的Code啦,這邊我採用的一樣是MVVM的方式來進行APP的開發。
先是實作Model的方式
public class SampleModel : INotifyPropertyChanged
{
private string _Title;
public string Title
{
get { return _Title; }
set { _Title = value; NotifyChange(); }
}
private string _Description;
public string Description
{
get { return _Description; }
set { _Description = value; }
}
private string _ImageSrcUri;
public string ImageSrcUri
{
get { return _ImageSrcUri; }
set { _ImageSrcUri = value; NotifyChange(); }
}
private ImageSource _ImageSrc;
public ImageSource ImageSrc
{
get { return _ImageSrc; }
set { _ImageSrc = value; NotifyChange(); }
}
public async Task LoadImageAsync()
{
if(!string.IsNullOrEmpty(ImageSrcUri) && !string.IsNullOrWhiteSpace(ImageSrcUri))
{
var storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri(ImageSrcUri));
using (var stream = await storageFile.OpenReadAsync())
{
var bitmapImg = new BitmapImage();
await bitmapImg.SetSourceAsync(stream);
_ImageSrc = bitmapImg;
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyChange([CallerMemberName]string propertyName = "")
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
中間大家可以看到有個LoadImage的Method,後續的Code會解釋為什麼要在Model做這個功能!
接者是實作ViewModel的部分,請看以下程式碼
namespace PrintUAP.ViewModel
{
public class MainPageViewModel
{
private ObservableCollection<SampleModel> _SampleList;
public ObservableCollection<SampleModel> SampleList
{
get { return _SampleList; }
}
private PrintDocument printDoc;
private PrintManager printMgr;
public MainPageViewModel()
{
_SampleList = new ObservableCollection<SampleModel>(Enumerable.Range(0, 10).Select(i => new SampleModel()
{
Title = string.Format("Title - {0}", i),
Description = string.Format("ABCDEFGHIJKLMNOPQRSTUVWXYZ,This is description of page {0}", i),
ImageSrcUri = string.Format("ms-appx:///Assets/CKONE.jpg")
}));
}
public void RegistrationPrintManager()
{
printMgr = PrintManager.GetForCurrentView();
printMgr.PrintTaskRequested += MainPage_PrintTaskRequested;
}
private void MainPage_PrintTaskRequested(PrintManager sender, PrintTaskRequestedEventArgs args)
{
var deferral = args.Request.GetDeferral();
var printTask = args.Request.CreatePrintTask("SamplePrintTitle", new PrintTaskSourceRequestedHandler(PrintTaskHandler));
deferral.Complete();
}
private async void PrintTaskHandler(PrintTaskSourceRequestedArgs args)
{
var deferral = args.GetDeferral();
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
printDoc = new PrintDocument();
printDoc.AddPages += PrintDoc_AddPages;
printDoc.Paginate += PrintDoc_Paginate;
printDoc.GetPreviewPage += PrintDoc_GetPreviewPage;
args.SetSource(printDoc.DocumentSource);
});
deferral.Complete();
}
private async void PrintDoc_GetPreviewPage(object sender, GetPreviewPageEventArgs e)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
printDoc.SetPreviewPage(e.PageNumber, await BuildVisual(e.PageNumber));
});
}
private void PrintDoc_Paginate(object sender, PaginateEventArgs e)
{
printDoc.SetPreviewPageCount(SampleList.Count, PreviewPageCountType.Final);
}
private async void PrintDoc_AddPages(object sender, AddPagesEventArgs e)
{
await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, async () =>
{
printDoc.AddPage(await BuildVisual(0));
printDoc.AddPagesComplete();
});
}
private async Task<UIElement> BuildVisual(int pageNumber)
{
var pageVisual = new Grid();
var mainPage = (Window.Current.Content as Frame).Content as MainPage;
var contentGrid = mainPage.Content as Grid;
var richTb = contentGrid.Children.FirstOrDefault() as RichTextBlock;
var UIContainer = (richTb.Blocks.FirstOrDefault() as Paragraph).Inlines.FirstOrDefault() as InlineUIContainer;
var renderCtrl = UIContainer.Child as ItemsControl;
if ((pageNumber - 1) >= 0)
{
var dataContext = renderCtrl.Items[pageNumber - 1] as SampleModel;
await dataContext.LoadImageAsync();
var elementItem = renderCtrl.ContainerFromItem(dataContext) as ListViewItem;
var presenter = new ContentPresenter();
presenter.ContentTemplate = elementItem.ContentTemplate;
var uiElement = presenter.ContentTemplate.LoadContent() as UIElement;
(uiElement as FrameworkElement).DataContext = dataContext;
((uiElement as Grid).Children.FirstOrDefault() as Image).Source = dataContext.ImageSrc;
pageVisual.Children.Add(uiElement);
}
return pageVisual;
}
public void UnRegistrationPrintManager()
{
if (printMgr != null)
{
printMgr.PrintTaskRequested -= MainPage_PrintTaskRequested;
}
}
}
}
這邊就是把SampleList建立起來,然後藉由Registration和UnRegistration將Printer的Pipeline的TaskRequest指派給EventHandler或是沒有。
然後CreatePrintTask設定顯示Print task的Name以及後續處裡的Handler,然後再PrintTaskHandler裡面開始建立PrintDocument!這個物件就是建立Print的內容(輸出給印表機的主要物件),並且委派三個重要的Event(AddPages,Paginate,GetPreviewPage)
AddPages->是加入多個Page在列印的文件中,每次的列印流程都是有一個PrintDocument但該Document可以含有多個Page!
Paginate->是設定預覽畫面的數量並且顯示在預覽視窗的UI上。
GetPreviewPage->顯示單一預覽頁面時候會觸發的事件。
以上三個事件除了Paginate以外都需要用到UI Thread!但由於採用的是MVVM的開發方式所以要用MainView.CoreWindow.Dispatcher來取代在Page.cs可以呼叫的Dispatcher。
接者到App.xaml上面先定義MainPageViewModel的Resource來Binding MainPage的DataContext。
<Application
x:Class="PrintUAP.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PrintUAP"
xmlns:vm="using:PrintUAP.ViewModel">
<Application.Resources>
<vm:MainPageViewModel x:Key="MainPageVM"/>
</Application.Resources>
</Application>
接者在MainPage.xaml將變更DataContext以及Content,如下Code。當然如果要使用Compile binding一樣可以將Binding換掉,請參考前幾篇的UWP文章
<Page
x:Class="PrintUAP.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:PrintUAP"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
DataContext="{Binding Source={StaticResource MainPageVM}}">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<RichTextBlock Margin="20">
<Paragraph>
<InlineUIContainer>
<ListView ItemsSource="{Binding SampleList}" SelectionMode="None" IsItemClickEnabled="False">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Image Grid.Row="0" Grid.Column="0" Source="{Binding ImageSrcUri}"/>
<TextBlock Grid.Row="0" Grid.Column="1" Style="{ThemeResource HeaderTextBlockStyle}" Text="{Binding Title}" Foreground="Green" TextTrimming="WordEllipsis" TextWrapping="Wrap"/>
<TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Style="{ThemeResource SubheaderTextBlockStyle}" Text="{Binding Description}" Foreground="Red" TextTrimming="WordEllipsis" TextWrapping="Wrap"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListViewItem">
<ListViewItemPresenter SelectionCheckMarkVisualEnabled="False"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</InlineUIContainer>
</Paragraph>
</RichTextBlock>
</Grid>
<Page.BottomAppBar>
<CommandBar>
<CommandBar.PrimaryCommands>
<AppBarButton Icon="Add" Label="print" Click="AppBarButton_Click"/>
</CommandBar.PrimaryCommands>
</CommandBar>
</Page.BottomAppBar>
</Page>
這邊主要是將ListView的UI長出來,並且使用AppBarButton呼叫PrintManager 顯示列印UI的部分。
接下來在MainPage.xaml.cs加入以下的Code
public MainPageViewModel MainPageVM;
public MainPage()
{
this.InitializeComponent();
MainPageVM = this.DataContext as MainPageViewModel;
this.Loaded += MainPage_Loaded;
this.Unloaded += MainPage_Unloaded;
}
private void MainPage_Loaded(object sender, RoutedEventArgs e)
{
MainPageVM.RegistrationPrintManager();
}
private void MainPage_Unloaded(object sender, RoutedEventArgs e)
{
MainPageVM.UnRegistrationPrintManager();
}
private async void AppBarButton_Click(object sender, RoutedEventArgs e)
{
await PrintManager.ShowPrintUIAsync();
}
在Win8.1顯示Preview的畫面
Win10的顯示列印預覽模式
***以上Code以及說明都有可能隨著Windows 10 的版本以及Visual Studio 2015版本有所調整!***
參考資料 MSDN, Printing: Build 2015 Developing Apps That Print in Windows 10 ( 2-94 )
下次再分享Windows 10 的新技術拉~