🖨️

WebView2を使ってHTML帳票をプリンタから直接印刷する

2023/03/27に公開

はじめに

「え、今の時代、紙帳票が必要ですか!?」
んー、どうしてもということなら、何とかしてみましょう。

ところがあまり需要がないのか、帳票や印刷に関するOSSの情報が少ない。
帳票で使えそうな無料ライブラリってあまりないんですね。
そこで思いついたのがHTMLで帳票を作って印刷する方法です。
帳票で頭を悩ましている誰かの参考になれば幸いです。

ざっくり概要

Webブラウザから印刷すると印刷ダイアログが開いてしまうので煩わしい。
ワンクリックでプリンタから直接印刷したいというのが、今回の要望です。

WebView2を使用して、ネイティブアプリにWebブラウザを組み込んでしまおうという発想で検証プログラムを作成しました。
以下が今回作成したプログラムの流れです。

  1. HTMLで帳票テンプレートを定義
  2. HTMLに値を埋め込む
  3. WebView2で表示
  4. プリンタへ直接印刷

それでは早速作ってみましょう。

作ってみよう

Visual Studioを使用します。C#(.NET6)で開発していきます。
Windows環境を前提にしています。

プロジェクト作成

Visual Studioを起動し、「新しいプロジェクトの作成」を選択します。
WebView2を使用するので、画面が必要です。「Windows フォーム アプリ」を選択します。

プロジェクト名を「PrintTest」とし「次へ」

追加情報は「.NET 6.0」を選択し、「作成」するとひな形ができます。

HTMLを格納するフォルダを作っておきましょう。
プロジェクトを右クリック → 追加 → 新しいフォルダー
フォルダ名は「Resource」という名前にしました。

HTMLテンプレート作成

帳票の見た目をHTMLで作っていきます。
今回はこんな感じに作ってみました。
変数部分は${変数名}にして後で置換します。

お気に入りのエディタで好みのHTMLを作成してください。

print.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style type="text/css">
      @page {
        size: A4;
      }
      .sheet {
        width: 190mm;
        height: 276mm;
        page-break-after: always;
      }
      .title {
        text-align: center;
        font-size: x-large;
      }
      .kaisha {
        text-decoration: underline;
        font-size: larger;
      }
      .waku {
        display: grid;
        width: 190mm;
        height: 55mm;
        grid-template-columns: 7fr 2fr 3fr;
        grid-template-rows: 6mm repeat(5, 1fr);
        border: solid 1px;
        border-radius: 10px;
        overflow: hidden;
      }
      .head {
        text-align: center;
        background-color: lightgray;
        border-bottom: solid 1px;
      }
      .right-txt {
        text-align: right;
      }
    </style>
  </head>
  <body>
    <section class="sheet">
      <div class="title">帳票名</div>
      <div class="kaisha">${会社名} 御中</div>
      <div class="right-txt">${日付}</div>
      <div class="waku">
        <div class="head">品名</div>
        <div class="head">数量</div>
        <div class="head">金額</div>
        <div>${品名1}</div>
        <div class="right-txt">${数量1}</div>
        <div class="right-txt">${金額1}</div>
        <div>${品名2}</div>
        <div class="right-txt">${数量2}</div>
        <div class="right-txt">${金額2}</div>
        <div>${品名3}</div>
        <div class="right-txt">${数量3}</div>
        <div class="right-txt">${金額3}</div>
        <div>${品名4}</div>
        <div class="right-txt">${数量4}</div>
        <div class="right-txt">${金額4}</div>
        <div>${品名5}</div>
        <div class="right-txt">${数量5}</div>
        <div class="right-txt">${金額5}</div>
      </div>
    </section>
  </body>
</html>

作成したHTMLを先ほど作成した「Resource」フォルダに格納します。
プロパティのビルドアクションを「埋め込みリソース」にしておきます。

値の埋め込み

テンプレートHTMLの${変数名}を埋める処理を作成します。
テンプレートHTMLと同じ変数名を持つクラスを作成し、プロパティ名を値で置き換えます。

private static string ReplaceHtmlVal()
{
    // リソースからHTMLを取得
    var stream = Assembly.GetExecutingAssembly()
        .GetManifestResourceStream(
        "PrintTest.Resource.print.html");
    var streamReader = new StreamReader(stream);
    var htmlStr = streamReader.ReadToEnd();

    // データを取得
    var dt = GetData();

    // プロパティ名を値で置換
    foreach (var prop in dt.GetType().GetProperties())
    {
        // 置換前 プロパティ名(${会社名}など)
        var oldVal = $"${{{prop.Name}}}";
        // 置換後 値(株式会社○○○○など)
        var newVal = prop.GetValue(dt) as string;

        // HTML文字を置換
        htmlStr = htmlStr.Replace(oldVal, newVal);
    }

    return htmlStr;
}

データ取得部分GetData()はAPIやDBから取得することを想定していますが、今回はテストデータを設定しておきます。

private static PrintClass GetData()
{
    // APIやDBから値を取得したとする
    return new PrintClass
    {
        会社名 = "株式会社○○○○",
        日付 = DateTime.Now.ToString("yyyy/MM/dd"),
        品名1 = "商品名□□□□□",
        数量1 = "100",
        金額1 = "1,000",
        品名2 = "商品名△△△△△",
        数量2 = "200",
        金額2 = "2,000",
    };
}

値クラスはHTMLの変数名と一致させます。
string.Emptyを初期値にしておくと、値がセットされなかった場合、空白で置換されます。

PrintClass.cs
namespace PrintTest
{
    internal class PrintClass
    {
        public string 会社名 { get; set; } = string.Empty;
        public string 日付 { get; set; } = string.Empty;
        public string 品名1 { get; set; } = string.Empty;
        public string 数量1 { get; set; } = string.Empty;
        public string 金額1 { get; set; } = string.Empty;
        public string 品名2 { get; set; } = string.Empty;
        public string 数量2 { get; set; } = string.Empty;
        public string 金額2 { get; set; } = string.Empty;
        public string 品名3 { get; set; } = string.Empty;
        public string 数量3 { get; set; } = string.Empty;
        public string 金額3 { get; set; } = string.Empty;
        public string 品名4 { get; set; } = string.Empty;
        public string 数量4 { get; set; } = string.Empty;
        public string 金額4 { get; set; } = string.Empty;
        public string 品名5 { get; set; } = string.Empty;
        public string 数量5 { get; set; } = string.Empty;
        public string 金額5 { get; set; } = string.Empty;
    }
}

WebView2の配置

nugetでWebView2をインストールします。

すると、ツールボックスにWebView2が現れます。

フォーム画面上にWebView2のコントロールを配置します。
また、印刷イベント用のボタンも配置しておきます。

ここでのWebView2コントロール名は「webView21」になっています。

印刷処理の作成

ここからが本題です。これまで作った部品をつなぎ合わせて印刷していきます。
まず、WebView2にHTMLを表示する処理です。
ボタンのクリックイベントでNavigateToStringを使い、HTML文字列を表示します。
値の埋め込みで作成した処理ReplaceHtmlValを呼び出しています。

private async void button1_Click(object sender, EventArgs e)
{
    await webView21.EnsureCoreWebView2Async();
    webView21.NavigateToString(ReplaceHtmlVal());
}

次に、WebView2の「NavigationCompleted」イベントに印刷処理を設定します。
「Print」という名前で登録しました。

メソッドの中はこんな感じです。
印刷設定を作成し、PrintAsyncを実行します。

private async void Print(object sender, CoreWebView2NavigationCompletedEventArgs e)
{
    // プリンタ設定を作成
    var setting = webView21.CoreWebView2.Environment.CreatePrintSettings();
    // 塗りつぶしを印刷する
    setting.ShouldPrintBackgrounds = true;
    // ヘッダ、フッタを印刷しない
    setting.ShouldPrintHeaderAndFooter = false;
    // プリンタ名を指定(仮でPDFを設定しています)
    setting.PrinterName = "Microsoft Print to PDF";

    try
    {
        var printStatus = await webView21.CoreWebView2.PrintAsync(setting);

        if (printStatus == CoreWebView2PrintStatus.Succeeded)
        {
            MessageBox.Show(this, "印刷完了");
        }
        else
        {
            MessageBox.Show(this, "印刷失敗");
        }
    }
    catch (Exception)
    {
        MessageBox.Show(this, "エラー");
    }
}

実行してみる

画面を開いて、印刷ボタンをクリック

PDFを保存し、中身を確認します。印刷成功っぽいです。

まとめ

今回はHTMLを埋め込みで作成しましたが、Webアプリから画面を取得してそのまま印刷するような応用もできそうです。
WebView2を使う上で考えられるデメリットもいくつかあります。

  • Microsoft Edgeに依存している(Edgeがインストールされていない場合、WebViewランタイムの配布等を考えないといけない)
  • Edgeバージョンによってレイアウト崩れが発生する可能性がある(Edgeのバージョンアップで突然振る舞いが変わる可能性)
  • 実行クライアントのインストールフォントによって見た目が変わる可能性がある

これらをコントロールできる環境では一つの答えになるのではないでしょうか。
WebView2の利用方法など参考にして頂けると幸いです。

Discussion