【C#】UWPでプリンタに直接印刷する方法

# UWPではプリンタに直接印刷できない！
初めてUWPの開発を行って、何もかもが初めてで四苦八苦しながら作っていたところ、どうもUWPは特定のプリンタに直接印刷することができないらしい。

最初から難しそうだなーなんて思ってた。
なんかお店とかだとタブレットからレシートに印刷をかけてるようなイメージがあったので、なんかやり方あるんだろとも思ってたけど、調べまくっても、ユーザーに印刷を促すコードばかりが出てくる。

結論、UWPではセキュリティの関係上、印刷をユーザーに促すことはできても、天地がひっくり返っても直接特定のプリンタに印刷をすることはできない。
しかもカメラデバイスの利用許可のように一度だけ出る分にはまぁまぁとも思うけど、印刷の場合は常に促される形になるらしく、話にならんかった。

コードで何も指定できなくて、どのプリンタに印刷するの？何ページ目を印刷するの？みたく聞いてくるならそりゃ毎度毎度聞くわなって感じ。

# 実現するには
UWPから直接実行する考えは切り捨て、UWPから.NET Frameworkで作られたexeを実行することで実現する。

参考にしたのは下記。
[UWP – Print PDF Files Silently (Without Print Dialog)](http://zawayasoft.com/2018/03/13/uwp-print-pdf-files-silently-without-print-dialog/)

流れの概要は下記の通り。
情報の受け渡しにオブジェクトは利用できないので、[Newtonsoft Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/)とかを利用して、JSON文字列とかで受け渡しすればいい感じになる。

1. UWP側で、印刷に必要な情報を渡す。
2. UWP側で、.NET Frameworkのexeを実行する。
3. .NET Frameworkのexeで、UWP側から渡された情報を受け取る。
4. .NET Frameworkのexeで、印刷する。

# 実装の流れの補足
記事の便宜上

- UWPアプリケーションを UwpApp とする。
- .NET Frameworkアプリケーションを NetApp とする。

### NetAppを作成する

1. NetAppを.NET Frameworkでコンソールアプリケーションを作成する。
2. 出力の種類をWindowsアプリケーションにする。これはコンソール画面を表示させないため。
   ![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/05b5d37c-1acd-7e38-36f9-363a628c9876.png)

3. 参照設定から、『C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.17763.0\Windows.winmd』を選択する。
   パスの『10.0.17763.0』は、UwpAppで選択している最小バージョンに合わせたパスにある Windows.winmd を選択する。
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/4d504e90-8a24-75e2-aecb-24b2e13f8d17.png)

4. App.configを設定する。

    ```xml:App.config
    <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
      <startup>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" />
      </startup>
    
      <appSettings>
        <add key="PrinterName" value="FinePrint" />
      </appSettings>
    </configuration>
    ```

5. それらしいコードを書く。
   Windows.Storage.ApplicationData.Current.LocalSettings.Values["xxx"]で、UWP側から渡された値を取得できるようになる。

    ```CS:NetApp-Example.cs
    class Example
    {
        private string printingText;
        private Font printFont;
    
        public void Execute()
        {
            // 印刷するプリンタ名を取得
            var printerName = ConfigurationManager.AppSettings["PrinterName"];
    
            // UWPから渡ってきた値を取得する
            var text = Windows.Storage.ApplicationData.Current.LocalSettings.Values["Key"].ToString();
    
            // 印刷
            printingText = text;
            printFont = new Font("ＭＳ Ｐゴシック", 10);
            System.Drawing.Printing.PrintDocument pd = new System.Drawing.Printing.PrintDocument();
            pd.PrintPage += new System.Drawing.Printing.PrintPageEventHandler(pd_PrintPage);
            pd.Print();
        }
    
        private void pd_PrintPage(object sender, System.Drawing.Printing.PrintPageEventArgs e)
        {
            // 都合いい感じに印刷処理を書く
            e.Graphics.DrawString(printingText, printFont, Brushes.Black, 0, 0);
        }
    }
    ```

### UwpAppを修正する
1. 参照設定で、Universal Windows -> 拡張 から、『Windows Desktop Extensions for the UWP』を選択する。
   バージョンはUwpAppの最小バージョンに合わせたものを選択する。
![1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/1e29597c-2595-1305-fe88-7a45f695e31e.png)

2. ビルドしたNetApp.exe、NetApp.exe.configをプロジェクトに含める。
   （NetApp.exe.configは、アプリケーション構成ファイルを操作したい場合）。
   ![4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/da0fdbce-08f2-72c2-1baa-520b0f3a08ba.png)

    |プロパティ|値|
    |---|---|
    |ビルドアクション|コンテンツ|
    |出力ディレクトリにコピー|コピーしない|

3. NetApp.exe.config の中身が色々な都合で変わっちゃっていたら、元の形に変える。（.NET Core のDLLを参照してる時は変えないとダメそう）

4. プロジェクトのプロパティ→ビルド から、『.NETネイティブツールチェーンでコンパイルする』のチェックを外す。
   Debugビルドだと初めから外れているけど、Releaseビルドだと既定値としてチェックがついている。
   よくわからんけどチェックがついてると、.NET Native を使ってネイティブコンパイルするようになるようだ。
   NetApp.exeが.NET Frameworkを利用するものである以上、ネイティブコンパイルはNGなので、チェックを外す。
   というのが正解のようだ。
   ![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/0c67ec15-874e-ec7b-0316-0537d08586f7.png)

5. Package.appxmanifest をコード表示して Extensions、Capabilities 部分を加えて、NetApp.exeを実行できるようにする。

    ```xml:Package.appxmanifest
    <?xml version="1.0" encoding="utf-8"?>
    <Package>
      ・・・
      <Applications>
        <Application>
          ・・・
          <Extensions xmlns:desktop="http://schemas.microsoft.com/appx/manifest/desktop/windows10">
            <desktop:Extension Category="windows.fullTrustProcess" Executable="NetApp.exe" />
          </Extensions>
        </Application>
      </Applications>
      ・・・
      <Capabilities>
        <rescap:Capability xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" Name="runFullTrust" />
      </Capabilities>
      ・・・
    </Package>
    ```

6. NetApp.exe に渡したい値を設定する。

    ```cs
    Windows.Storage.ApplicationData.Current.LocalSettings.Values["Key"] = "Example";
    ```

7. NetApp.exe を実行する。

    ```cs
    if (Windows.Foundation.Metadata.ApiInformation.IsApiContractPresent("Windows.ApplicationModel.FullTrustAppContract", 1, 0))
    {
        await Windows.ApplicationModel.FullTrustProcessLauncher.LaunchFullTrustProcessForCurrentAppAsync();
    }
    ```

# 実際の動き
UWPアプリケーションの上に、キャンセルボタンが押せるダイアログが表示される。
印刷が終われば消えるし、UWPで画面が切り替わるわけでもないので、まぁまぁ求めた動作はする。
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/13972/19e6eeb8-39c4-a35a-7f52-787705a81304.png)

# 悩むこと
1. NetApp.exeの変更が適用されない。  
   ビルドしても反映されない時があるみたいで、よくわからんので、UwpAppをリビルドした方が良さそう。
2. NetAppでは、Windows.Storage.ApplicationData.Current.LocalSettings.Values[] によって値を取得しているため、入口がUWP側から実行してないと下記例外が発生してしまう。

    ```bat
    System.InvalidOperationException:
    プロセスにパッケージ ID がありません。 (HRESULT からの例外:0x80073D54)
    ```

    前段としてどうやるとそこがうまいこと通るようになるのか分からなかったので、当該部分だけ望む値が渡ってきたという想定のコードに変えてデバッグしました。