rksoftware

Visual Studio とか C# とかが好きです

.NET Core デスクトップアプリケーションで DioDocs で作った PDF 帳票を印刷する

.NET Core 3.0 で Windows デスクトップアプリ(WPF/WinForms)が .NET Core で作れるようになりました。
しかし、実際にデスクトップアプリを作る際には様々なライブラリを導入することが多いものです。その中でもいわゆる帳票出力コンポーネントは日本では非常に人気の高い要件でしょう。これがなければどんなに WPF の .NET Core 対応が完璧でも採用できないケースも多いはずです。

そこで DioDocs です、という記事を以前に書きました。

※DioDocs について

詳しくは次の素晴らしい記事を読んでください。

■ 前回までのあらすじ

  1. テンプレートとなる Excel ファイルを読み込んでセルに値を設定したシートを PDF ファイルとして出力し、Edge で表示しました。
  2. PDF をファイルに保存せず Stream に Save -> Image に変換 -> 画面に表示しました。

■ 今回のあらすじ

PDF ファイルに保存せず、プリンター印刷します(手元にプリンターがないので、XPS ファイルにしました)。

■ 試してみる

試してみましょう。

  • プロジェクトの作成
  • NuGet パッケージのインストール
  • テンプレートの Excel
  • PDF を Image に変換

というところまでは前回までと同様です。

□ 前回との違い

前回は System.Runtime.WindowsRuntime.dll.NETPortable\v4.6 のものを参照しましたが、今回は .NETCore\v4.5 のものを参照しています。
AsRandomAccessStream() メソッドのためにこちらの DLL を参照しましたが、MemoryStream は不要であることに気が付きました。.NETCore\v4.5 に含まれる機能だけで充分です。

今回参照へ追加したもの

ファイル パス
System.Runtime.WindowsRuntime.dll C:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\System.Runtime.WindowsRuntime.dll
Windows.winmd C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd

■ エラー発生

.NET Core で動作する WPF アプリケーションで作っているので、一旦次のようなコードを書いたのですがエラーで動作しませんでした。
※ .NET Framework での WPF では問題なく動作するコードです。

エラー

System.DllNotFoundException
Message=Unable to load DLL 'PresentationNative_v0400.dll' or one of its dependencies: 指定されたモジュールが見つかりません。

コード

using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace WpfApp1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var image = await CreateImageFromExcel();

            var printDialog = new System.Windows.Controls.PrintDialog();
            var dialogResult = printDialog.ShowDialog();
            if (dialogResult != true) return;

            var id = new ImageDrawing
            {
                ImageSource = image,
            };
            var dv = new DrawingVisual();
            var dc = dv.RenderOpen();
            dc.DrawImage(image, new Rect(0, 0, printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight));
            dc.Close();

            printDialog.PrintVisual(dv, "print");
        }

        async Task<ImageSource> CreateImageFromExcel()
        {
            // 入力文字列を数値にパース
            if (!int.TryParse(priceText.Text, out var price)) return null;

            var image = new BitmapImage();

            // テンプレート Excel を読み込む
            var workbook = new GrapeCity.Documents.Excel.Workbook();
            workbook.Open("template.xlsx");
            var activesheet = workbook.ActiveSheet;

            // データを書き込む
            foreach (var row in Enumerable.Range(4, 7))
            {
                activesheet.Range[row, 2].Value = menuText.Text;
                activesheet.Range[row, 3].Value = price;
            }

            // PDF に変換して表示
            using (var randomAccesStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            using (var inMemoryRandomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            using (var stream = inMemoryRandomAccessStream.AsStream())
            {
                // PDF ファイルをストリームに保存
                workbook.Save(randomAccesStream.AsStream(), GrapeCity.Documents.Excel.SaveFileFormat.Pdf);

                // PDF を画像に変換
                var pdfDocument = await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(randomAccesStream);
                using (var page = pdfDocument.GetPage(0))
                {
                    await page.RenderToStreamAsync(inMemoryRandomAccessStream);
                }

                image.BeginInit();
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.StreamSource = stream;
                image.EndInit();
            }

            return image;
        }
    }
}

次のような最低限のコードでも実行時にエラーとなるので回避することにしました。

var printDialog = new System.Windows.Controls.PrintDialog();
var r = printDialog.ShowDialog();
if (r != true) return;

var dv = new DrawingVisual();
printDialog.PrintVisual(dv, "print");       

■ 回避策

原因はまだ不明ですが今回はいったん回避策として、System.Windows.Forms 名前空間のクラスを使ってみました。

コード

<Window x:Class="dotnetcorediodocs.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:dotnetcorediodocs"
        mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="500">
    <Grid HorizontalAlignment="Stretch" Margin="20">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Image HorizontalAlignment="Center" Width="900" Height="1000" x:Name="imgPdf" Margin="20,-60,-100,0" Grid.Row="1"/>
        <StackPanel HorizontalAlignment="Stretch" Background="White">

            <Label Content="メニュー" FontSize="20"/>
            <TextBox HorizontalAlignment="Stretch" x:Name="menuText"/>

            <Label Content="価格"  FontSize="20"/>
            <TextBox HorizontalAlignment="Stretch" x:Name="priceText"/>

            <Button Content="印刷" Grid.Row="1" FontSize="20" Click="Button_Click"/>
        </StackPanel>
    </Grid>
</Window>
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;

namespace dotnetcorediodocs
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private async void Button_Click(object sender, RoutedEventArgs e)
        {
            var printDocument = new System.Drawing.Printing.PrintDocument();

            var printDialog = new System.Windows.Forms.PrintDialog();
            printDialog.Document = printDocument;
            var dialogResult = printDialog.ShowDialog();
            if (dialogResult != System.Windows.Forms.DialogResult.OK) return;

            var image = await CreateImageFromExcel();
            printDocument.PrintPage += (ppSender, ppE) =>
            {
                var wigth = (int)ppE.PageSettings.PrintableArea.Width;
                var height = (int)ppE.PageSettings.PrintableArea.Height;
                ppE.Graphics.DrawImage(image, 0, 0, wigth, height);
                ppE.HasMorePages = false;
            };
            printDocument.EndPrint += (ppSender, ppE) =>
            {
                image.Dispose();
            };

            printDocument.Print();
        }

        async Task<System.Drawing.Image> CreateImageFromExcel()
        {
            // 入力文字列を数値にパース
            if (!int.TryParse(priceText.Text, out var price)) return null;

            // テンプレート Excel を読み込む
            var workbook = new GrapeCity.Documents.Excel.Workbook();
            workbook.Open("template.xlsx");
            var activesheet = workbook.ActiveSheet;

            // データを書き込む
            foreach (var row in Enumerable.Range(4, 7))
            {
                activesheet.Range[row, 2].Value = menuText.Text;
                activesheet.Range[row, 3].Value = price;
            }

            // PDF に変換して表示
            using (var randomAccesStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            using (var inMemoryRandomAccessStream = new Windows.Storage.Streams.InMemoryRandomAccessStream())
            using (var stream = inMemoryRandomAccessStream.AsStream())
            {
                // PDF ファイルをストリームに保存
                workbook.Save(randomAccesStream.AsStream(), GrapeCity.Documents.Excel.SaveFileFormat.Pdf);

                // PDF を画像に変換
                var pdfDocument = await Windows.Data.Pdf.PdfDocument.LoadFromStreamAsync(randomAccesStream);
                using (var page = pdfDocument.GetPage(0))
                {
                    await page.RenderToStreamAsync(inMemoryRandomAccessStream);
                }

                var image = System.Drawing.Image.FromStream(stream);
                return image;
            }
        }
    }
}

■ 実行

印刷設定ダイアログで xps を選択して、XPS ファイルに印刷してみます。

この様な入力をして。
f:id:rksoftware:20190203202142j:plain

完璧な XPS ができました!
f:id:rksoftware:20190203202123j:plain