この記事は公開から4年以上経過しています。
WinForms開発で1枚ものの簡単なレポートを印刷したいときなどにNuGetライブラリなど専用ライブラリを使わずGDIでゴリゴリと印刷ロジックを書かずに、お手軽にイメージ通りの帳票が出力できる方法を紹介します。
ここで紹介するコードはあくまで目的の機能を分かりやすく説明/実現するための最小限コードとなっています。このコードや説明を参考にされる場合は適切なエラーチェックや、充分な品質/動作検証を行って頂くようにお願い致します。
2022.4.17追記:
もしDPI設定周りで問題が発生する場合は高DPI環境でWinFormsのレイアウトが崩れる問題の解決方法をご参照ください。
2021.2.2追記:
WPFのFlowDocumentのPaginatorは、Tableをページ跨ぎで自動改ページさせるとコンテンツによっては意図しない改ページや罫線が部分的に消えるといった難解なFrameworkバグ(仕様?)でドツボにハマるので、複数ページで自動改ページさせるような利用には不向きです。
対応手順
- WPFのユーザーコントロールで帳票のレイアウトテンプレートを作成。
- 印刷データを上記で作成したユーザーコントロールにバインディング。
- ユーザーコントロールをドキュメント化してプリンタへ出力。
たったのこれだけです。
動作イメージ
左側にデータを入力するためのテキストボックス、その下に印刷実行ボタン、右側には実際の印刷イメージに近いプレビューをリアルタイムで表示します。
プレビューするかしないかは印刷には関係なく任意です。
PG本体
出力帳票
上図のとおり、おおよそ見た目通りにPDFに印字できているのがお分かり頂けると思います。
サンプルソースコード
ソースコードは以下の3部構成です。
- 本体プログラム(MainForm.cs)
- 印刷プログラム(PrintClass.cs)
- 帳票レイアウト定義(ReportControl.xaml/ReportControl.xaml.cs)
ここで紹介するサンプルコード一式はこちら(github)からダウンロード可能ですので、興味があればご利用ください(VisualStudio2017/Framework4.5/C#)。
本体プログラム(MainForm.cs)
メインフォームです。フォーム上にはTextBox/Button/ReportControl(WPFをWinFormsに表示するElementHostコントロール)が貼り付けられており、ReportDataクラス(印刷データクラス)がTextBoxとReportControlにバインディング(画面とデータが連携)しています。Print
ボタンを押下すると印刷プログラムが呼び出されます。
// MainForm.cs
using System.ComponentModel;
using System.Windows.Forms;
namespace PrintReportWithWPFUC
{
// メインフォーム
public partial class MainForm : Form
{
// 印字データ
private ReportData _model = new ReportData();
// コンストラクタ
public MainForm()
{
InitializeComponent();
// テキストボックスと印字データをバインディング
tbxMessage.DataBindings.Add(new Binding(nameof(TextBox.Text), _model, nameof(ReportData.Message), false, DataSourceUpdateMode.OnPropertyChanged));
// プレビューのReportControlと印字データをバインディング
reportControl.DataContext = _model;
}
// 印刷ボタン処理
private void btnPrint_Click(object sender, System.EventArgs e)
{
// 印字開始
new PrintClass().Print(_model);
}
}
// 印刷データクラス
public class ReportData : INotifyPropertyChanged
{
private string _message = string.Empty;
public event PropertyChangedEventHandler PropertyChanged;
// メッセージプロパティ
public string Message
{
get => _message;
set
{
if (_message != value)
{
// 値が変更されたら値を更新して変更を通知
_message = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Message)));
}
}
}
}
}
印刷プログラム(PrintClass.cs)
WPFの印刷処理本体です。
メインフォームのPrint
ボタン押下により渡される印刷データクラスとReportControl(印刷レイアウト情報)を用いてFixedDocumentに印字イメージをレンダリング、その後プリンタへ出力します。
// PrintClass.cs
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
namespace PrintReportWithWPFUC
{
internal class PrintClass
{
// 規定の用紙サイズ定義 = A4(8.27inch * 96dpi, 11.69inch * 96dpi)
private const double DEFAULT_PAPER_SIZE_A4_S = 8.27 * 96;
private const double DEFAULT_PAPER_SIZE_A4_L = 11.69 * 96;
// 印刷処理
public void Print(ReportData data)
{
// 印刷ダイアログを表示してプリンタや用紙を選択
var printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
// 選択されたプリンタの能力を取得
var pcaps = printDialog.PrintQueue.GetPrintCapabilities();
var pticket = printDialog.PrintTicket;
// とりあえず印字の向きはポートレート/ランドスケープのみサポート
double reportWidth = 0, reportHeight = 0;
switch (pticket.PageOrientation)
{
case System.Printing.PageOrientation.Portrait:
reportWidth = pticket.PageMediaSize.Width ?? DEFAULT_PAPER_SIZE_A4_S;
reportHeight = pticket.PageMediaSize.Height ?? DEFAULT_PAPER_SIZE_A4_L;
break;
case System.Printing.PageOrientation.Landscape:
reportWidth = pticket.PageMediaSize.Height ?? DEFAULT_PAPER_SIZE_A4_L;
reportHeight = pticket.PageMediaSize.Width ?? DEFAULT_PAPER_SIZE_A4_S;
break;
}
// 固定ページを作成し選択用紙サイズと合わせる
var page = new FixedPage()
{
Width = reportWidth,
Height = reportHeight,
// プリンタの余白設定を反映
Margin = new Thickness(
pcaps.PageImageableArea.OriginWidth,
pcaps.PageImageableArea.OriginHeight,
0,
0),
};
// ReportControlを作成して用紙サイズに合わせる
var vb = new Viewbox
{
Width = reportWidth,
Height = reportHeight,
StretchDirection = StretchDirection.Both,
Stretch = Stretch.Uniform,
Child = new ReportControl { Width = DEFAULT_PAPER_SIZE_A4_S, Height = DEFAULT_PAPER_SIZE_A4_L, DataContext = data }
};
// 固定ページドキュメントを作成して固定ページを追加
page.Children.Add(vb);
var content = new PageContent();
((IAddChild)content).AddChild(page);
var fixedDoc = new FixedDocument();
fixedDoc.Pages.Add(content);
// 選択したプリンタで印字
printDialog.PrintDocument(fixedDoc.DocumentPaginator, "Report print sample");
}
}
}
}
帳票レイアウト定義(ReportControl.xaml)
レポート構造の定義です。UserControl内のRichTextBoxにFlowDocument(XAML)でレイアウトを定義しています。
Tableを使っているので少し長いですが、基本的にはVisualStudioのXAMLエディタがあればWYSIWYGで直感的に編集可能です。
RichTextで表現出来る範囲であればWordで作成したドキュメントを転用(変換)することも可能で、このサンプルは後者の方法で作成しています。
(そのため少し冗長になっています)
2020.10.15更新:
本題から逸れてしまうため、このあたりは別の機会にブログ記事で紹介させて頂きたいと思います。
こちらで即席変換ツールを紹介しています。
<!-- ReportControl.xaml -->
<UserControl x:Class="PrintReportWithWPFUC.ReportControl"
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"
mc:Ignorable="d"
d:DesignWidth="794"
d:DesignHeight="1122">
<!-- 帳票フォーマットサンプル(A4: 8.27 * 96dpi 11.69 * 96dpi) -->
<RichTextBox IsReadOnly="True">
<FlowDocument Background="White">
<Paragraph TextAlignment="Justify" FontFamily="Times New Roman" FontWeight="Bold" FontSize="16" Margin="0,0,0,0">
<Span TextDecorations="Underline">
<Run>Details:</Run>
</Span>
</Paragraph>
<Table CellSpacing="0" Margin="0.67,0,0,0">
<Table.Columns>
<TableColumn Width="72" />
<TableColumn Width="72" />
<TableColumn Width="72" />
<TableColumn Width="72" />
</Table.Columns>
<TableRowGroup>
<TableRow>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="1.33,1.33,1.33,1.33" BorderBrush="#FF000000" Background="#FFD9D9D9">
<Paragraph TextAlignment="Center" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>#</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,1.33,1.33,1.33" BorderBrush="#FF000000" Background="#FFD9D9D9">
<Paragraph TextAlignment="Center" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>name</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,1.33,1.33,1.33" BorderBrush="#FF000000" Background="#FFD9D9D9">
<Paragraph TextAlignment="Center" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>quantity</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,1.33,1.33,1.33" BorderBrush="#FF000000" Background="#FFD9D9D9">
<Paragraph TextAlignment="Center" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>price</Run>
</Span>
</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="1.33,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>1</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>Item1</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>5</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>1</Run>
</Span>
<Span>
<Run>,</Run>
</Span>
<Span>
<Run>250</Run>
</Span>
</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="1.33,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>2</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>Item2</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>1</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>150</Run>
</Span>
</Paragraph>
</TableCell>
</TableRow>
<TableRow>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="1.33,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>3</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>Item3</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>200</Run>
</Span>
</Paragraph>
</TableCell>
<TableCell Padding="6.6,0,6.6,0" BorderThickness="0,0,1.33,1.33" BorderBrush="#FF000000">
<Paragraph TextAlignment="Right" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0">
<Span>
<Run>2</Run>
</Span>
<Span>
<Run>,</Run>
</Span>
<Span>
<Run>500</Run>
</Span>
</Paragraph>
</TableCell>
</TableRow>
</TableRowGroup>
</Table>
<Paragraph TextAlignment="Justify" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0"></Paragraph>
<Paragraph TextAlignment="Justify" FontFamily="Times New Roman" FontWeight="Bold" FontSize="16" Margin="0,0,0,0" Padding="1.33,1.33,1.33,1.33" BorderThickness="0,0,0,1" BorderBrush="#FF000000">
<Span>
<Run>Message:</Run>
<!-- メッセージプロパティにバインド -->
<Run Text="{Binding Message, Mode=OneWay}" />
</Span>
</Paragraph>
<Paragraph TextAlignment="Justify" FontFamily="Times New Roman" FontSize="16" Margin="0,0,0,0"></Paragraph>
</FlowDocument>
</RichTextBox>
</UserControl>
帳票レイアウト定義(ReportControl.xaml.cs)
ここでは特に何もしていません。コントロール追加時のままとなっています。
// ReportControl.xaml.cs
using System.Windows.Controls;
namespace PrintReportWithWPFUC
{
// 帳票コントロールクラス
public partial class ReportControl : UserControl
{
// コンストラクタ
public ReportControl()
{
InitializeComponent();
}
}
}
注意点
WinFormsプロジェクトでWPFを使う場合、WinFormsのクラスとWPFのクラスが混在すると躓く原因になるため、特にimports
に気を付けて下さい。
本例はC#/Framework3.5で作成しましたが、VB.NETや他のFrameworkバージョンでもWPFを利用できれば基本的な考え方は同様です。
今回は部分的にWPF機能を利用しましたが、一度慣れてしまうとWinFormsには後戻りできない(したくない)、とても便利で優れたFrameworkだと思いますので、利用経験がない方はこれを機に一度触れてみては如何でしょうか。
以上です。