製造業 スマートDX推進課 スマート工場DIY日記

製造業で働いているごく普通の社内SEです。日々の活動で生まれた成果物を共有します。

【C# .NET8.0】XAMLで印刷レイアウトを作成し、印刷する ※QRコード埋込可

Windowsアプリでラベル印刷のアプリを作りたいが、レイアウト作成が面倒。
と、億劫になっていたところ、XAMLで簡単に印刷レイアウトを作成し印刷できるみたいので
簡潔にまとめてみました。

WPFライブラリ を使用していますが WPFアプリ のみならず、Windows Forms でも使用可能です。

 

印刷サイズの変更は可能なので、A4で帳票作成なども出来ます。
設定方法も記載しますので、適宜変更してください。

ユーザーコントロールライブラリの作成

1、WPFアプリ もしくは Windowsフォームアプリ を作成

2、ユーザーコントロールライブラリを作成
[ソリューション で右クリック]→[追加]→[新しいプロジェクト]→[WPF ユーザーコントロールライブラリ]

3、プロジェクト名は『LabelPrint』とします。

4、名前の変更。『UserContol1.xml』を『Layout.xml』に変更

5、プロジェクト参照を追加
[本体アプリプロジェクト で右クリック]→[追加]→[プロジェクト参照]→[LabelPrint にチェック]→[OK]

ソースコード

レイアウトの作成 (Layout.xaml)

<?xml version="1.0" encoding="UTF-8"?>
<UserControl x:Class="LabelPrint.Layout"
             x:Name="Layout1"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:LabelPrint"
             mc:Ignorable="d" 
             Height="151" Width="227">

    <FixedPage Name="fixedPage">
        <Canvas Background="White" Height="151" Width="227" VerticalAlignment="Center" HorizontalAlignment="Center">
            <!--枠線-->
            <Border BorderBrush="#FF000000" BorderThickness="1" Background="#FFFFFFFF" Margin="0.2cm" Height="3.6cm" Width="5.6cm" CornerRadius="5" />
            <StackPanel Orientation="Horizontal" Margin="0.35cm">
                <!--文字列-->
                <StackPanel Width="1.8cm">
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Right" Text="メーカー品番:"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Right" Text="メーカー名:"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Right" Text="保管場所:"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Right" Text="内容量:"/>
                </StackPanel>
                <!--設定値-->
                <StackPanel Margin="0.2cm, 0" Width="3cm">
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Left" Text="{Binding メーカー品番, ElementName=Layout1}"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Left" Text="{Binding メーカー名, ElementName=Layout1}"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Left" Text="{Binding 保管場所, ElementName=Layout1}"/>
                    <TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Left" Text="{Binding 内容量, ElementName=Layout1}"/>
                </StackPanel>
            </StackPanel>
            <!--会社名-->
            <TextBlock Margin="3.5cm, 3.4cm, 0, 0" Height="0.5cm" FontSize="0.2cm" TextAlignment="Left" Text="株式会社****"/>
            <!--Code39-->
            <StackPanel Margin="0.6cm, 3.1cm, 0, 0">
                <Path Width="3cm" Height="0.4cm" x:Name="Code128Image" Data="" Fill="Black" Stretch="Fill" />
                <TextBlock Height="0.25cm" x:Name="Code128Text" FontSize="0.15cm" TextAlignment="Center" Text="" />
            </StackPanel>
            <!--QRCode-->
            <Path Margin="4.1cm, 1.7cm, 0, 0" Width="1.5cm" Height="1.5cm" x:Name="QRCodeImage" Data="" Fill="Black" Stretch="Fill" />
        </Canvas>
    </FixedPage>
</UserControl>

 

データバインディング (Layout.xaml.cs)

[NuGetパッケージ]よりZXing.Net(Ver.0.16.6以上)をインストールしてください。
※今回はVer.0.16.9を使用

using System.Printing;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Xml.Linq;
using ZXing;
using ZXing.QrCode;
using ZXing.QrCode.Internal;
using ZXing.Rendering;

namespace LabelPrint
{
    public partial class Layout : UserControl
    {
        private static readonly DependencyProperty TextProperty1 = DependencyProperty.Register(nameof(メーカー品番), typeof(string), typeof(Layout));
        private static readonly DependencyProperty TextProperty2 = DependencyProperty.Register(nameof(メーカー名), typeof(string), typeof(Layout));
        private static readonly DependencyProperty TextProperty3 = DependencyProperty.Register(nameof(保管場所), typeof(string), typeof(Layout));
        private static readonly DependencyProperty TextProperty4 = DependencyProperty.Register(nameof(内容量), typeof(string), typeof(Layout));
        private string? _QRCode;
        private string? _Code128;

        public Layout()
        {
            InitializeComponent();
        }

        public string メーカー品番
        {
            get => (string)GetValue(TextProperty1);
            set => SetValue(TextProperty1, value);
        }
        public string メーカー名
        {
            get => (string)GetValue(TextProperty2);
            set => SetValue(TextProperty2, value);
        }
        public string 保管場所
        {
            get => (string)GetValue(TextProperty3);
            set => SetValue(TextProperty3, value);
        }
        public string 内容量
        {
            get => (string)GetValue(TextProperty4);
            set => SetValue(TextProperty4, value);
        }
        public string QRCode
        {
            get => this._QRCode ?? "";
            set
            {
                this._QRCode = value;
                CreateBarcode(QRCodeImage, this._QRCode, BarcodeFormat.QR_CODE);
            }
        }
        public string Code128
        {
            get => this._Code128 ?? "";
            set
            {
                this._Code128 = value;
                CreateBarcode(Code128Image, this._Code128, BarcodeFormat.CODE_128);
            }
        }

        private void CreateBarcode(Path path, string text, BarcodeFormat barcodeFormat)
        {
            string data = "F1 ";
            foreach (
                XElement el in XElement.Parse(
                    new BarcodeWriter<SvgRenderer.SvgImage>
                    {
                        Format = barcodeFormat,
                        Options = new QrCodeEncodingOptions
                        {
                            ErrorCorrection = ErrorCorrectionLevel.M,
                            CharacterSet = "UTF-8",
                            NoPadding = true,
                        },
                        Renderer = new SvgRenderer(),
                    }.Write(text).Content
                ).Elements())
            {
                if (el.Name.LocalName == "rect")
                {
                    string x = el.Attribute("x").Value;
                    string y = el.Attribute("y").Value;
                    string width = el.Attribute("width").Value;
                    string height = el.Attribute("height").Value;
                    //data += $"M{x},{y} v{height} h{width} v-{height} Z";
                    data += $"M{x},{y} h{width} v{height} h-{width} Z";
                    path.Data = (Geometry?)new GeometryConverter().ConvertFromInvariantString(data);
                }
                else if (el.Name.LocalName == "text")
                {
                    if (path == Code128Image) Code128Text.Text = el.Value;
                }
            }
        }

        public void Print(string printer, int qty)
        {
            new PrintDialog
            {
                PrintTicket = new PrintTicket
                {
                    PageMediaSize = new PageMediaSize(this.Width, this.Height),
                    CopyCount = qty,
                    PageResolution = new PageResolution(96, 96),
                    PageBorderless = PageBorderless.None,
                    PageScalingFactor = 1,
                },
                PrintQueue = new PrintQueue(new PrintServer(), printer),
            }.PrintVisual(fixedPage, "LabelPrint");
        }
    }
}

最後に リビルド を行ってください。

印刷メソッドの読み出し方法 (使用方法)

            new LabelPrint.Layout
            {
                メーカー品番 = "HOGE-001",
                メーカー名 = "××株式会社",
                保管場所 = "A棚",
                内容量 = "200g",
                Code128 = "HOGE-001",
                QRCode = "HOGE-001",
            }.Print("Microsoft Print to PDF", 1);

プロパティに 印刷内容 を入力し
.Printメソッド で プリンタ名部数 を入力するだけで使用できます。

解説

レイアウト (Layout.xaml)

<UserControl ...
             x:Name="Layout1"
             Height="151" Width="227">

x:Name="Layout1" はデータバインディングで必要な名前です。

Height="151" Width="227" でサイズの設定をしています。ここではpxを使用してください。
WPFのユーザーコントロールは96dpiで設定されているので、サイズを変更したい場合は mm ⇒ px 変換を行ってください。
A4サイズの場合は Height="1123" Width="794"
今回は高さ40mm×幅60mm設定です。

 

    <FixedPage Name="fixedPage">
...
    </FixedPage>

印刷ページを作成する際に必須です。

FixedPageタグ 内が1ページとなります。

Name は印刷メソッドで使用します。

 

                <!--文字列-->
<StackPanel Width="1.8cm">
<TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Right" Text="メーカー品番:"/>
...
               </StackPanel>
<!--設定値-->
<StackPanel Margin="0.2cm, 0" Width="3cm">
<TextBlock Margin="-3" Height="0.5cm" FontSize="0.3cm" TextAlignment="Left" Text="{Binding メーカー品番, ElementName=Layout1}"/>
...
               </StackPanel>

StackPanel Canvas などを使用し、レイアウトを整えていきます。

MarginHeightWidth 等の寸法設定はここでは『cm』での調整が可能です。

Text="{Binding メーカー品番, ElementName=Layout1}" でデータバインディングを行ってます。
Binding ***は任意の名前、ElementNameは先ほど設定したx:Nameを書きます。

 

            <!--Code39-->
            <StackPanel Margin="0.6cm, 3.1cm, 0, 0">
                <Path Width="3cm" Height="0.4cm" x:Name="Code128Image" Data="" Fill="Black" Stretch="Fill" />
                <TextBlock Height="0.25cm" x:Name="Code128Text" FontSize="0.15cm" TextAlignment="Center" Text="" />
            </StackPanel>

バーコード描画用の Path を用意します。

詳細はこちら
【C# .NET8.0】XAMLにQRコードをSVGで表示【ZXing.NET】 - 製造業 スマートDX推進課 スマート工場DIY日記

 

データバインディング (Layout.xaml.cs)

        private static readonly DependencyProperty TextProperty1 = DependencyProperty.Register(nameof(メーカー品番), typeof(string), typeof(Layout));
public string メーカー品番 { get => (string)GetValue(TextProperty1); set => SetValue(TextProperty1, value); }
...

private string? _QRCode;
public string QRCode { get => this._QRCode ?? ""; set { this._QRCode = value; CreateBarcode(QRCodeImage, this._QRCode, BarcodeFormat.QR_CODE); } }

データバインディング用のプロパティを宣言します。

nameof(****) には<!--設定値-->で設定した Binding **** を入力

QRコードの set にはバーコード作成メソッドを書き、プロパティセットと同時に描画します。

 

        private void CreateBarcode(Path path, string text, BarcodeFormat barcodeFormat)
        {
            string data = "F1 ";
            foreach (
                XElement el in XElement.Parse(
                    new BarcodeWriter<SvgRenderer.SvgImage>
                    {
                        Format = barcodeFormat,
                        Options = new QrCodeEncodingOptions
                        {
                            ErrorCorrection = ErrorCorrectionLevel.M,
                            CharacterSet = "UTF-8",
                            NoPadding = true,
                        },
                        Renderer = new SvgRenderer(),
                    }.Write(text).Content
                ).Elements())
            {
                if (el.Name.LocalName == "rect")
                {
                    string x = el.Attribute("x").Value;
                    string y = el.Attribute("y").Value;
                    string width = el.Attribute("width").Value;
                    string height = el.Attribute("height").Value;
                    //data += $"M{x},{y} v{height} h{width} v-{height} Z";
                    data += $"M{x},{y} h{width} v{height} h-{width} Z";
                    path.Data = (Geometry?)new GeometryConverter().ConvertFromInvariantString(data);
                }
                else if (el.Name.LocalName == "text")
                {
                    if (path == Code128Image) Code128Text.Text = el.Value;
                }
            }
        }

バーコード描画用のメソッドです。QRコードとCODE128共通で使用しています。

詳細はこちら
【C# .NET8.0】XAMLにQRコードをSVGで表示【ZXing.NET】 - 製造業 スマートDX推進課 スマート工場DIY日記

 

        public void Print(string printer, int qty)
        {
            new PrintDialog
            {
                PrintTicket = new PrintTicket
                {
                    PageMediaSize = new PageMediaSize(this.Width, this.Height),
                    CopyCount = qty,
                    PageResolution = new PageResolution(96, 96),
                    PageBorderless = PageBorderless.None,
                    PageScalingFactor = 1,
                },
                PrintQueue = new PrintQueue(new PrintServer(), printer),
            }.PrintVisual(fixedPage, "LabelPrint");
        }

印刷メソッドです。PrintDialogクラスを使用しています。

PageMediaSize 印刷サイズ設定 (Layout.xaml の Height、Widthを直接参照)

PageResolution プリンタへ送る解像度 (96×96dpi固定です)

印刷サイズ解像度 をプリンタに送っておけば、プリンタ側で解像度処理します。
文字やバーコードは全て Path なので、拡大縮小しても文字がぼやける事がないです。

.PrintVisualメソッド 印刷するFixedPageキュー名 を指定しています。

 

最後に リビルド を行ってください。

あとは、元アプリから 印刷メソッドの読み出し方法 を使用すれば簡単に印刷が出来ます。

あとがき

部材管理に使用するラベル印刷を内製化したいと依頼があり
本来であればPDFを作って、位置をpx単位で調整して、レイアウト確認して、修正して...という流れで超面倒くさい。

しかし、調べてみると WPFユーザーコントロール を直接印刷できるらしい。
これならレイアウトもすぐに確認出来て、位置もcm単位で調整できるので作業がかなり楽になった。

 

だが、問題は WPF という事だ。

WPF の VMMV がどうも理解できず、どうにか Windows Formsアプリ から出来ないかと考えていたら
Windows Formsアプリ でも普通にWPFライブラリが使用できてしまった。

印刷アプリは Windows Forms で、印刷レイアウトは WPF でという形で作ってみました。