<おことわり>
- @FKbelm さんの書かれた『XAML内でDataContextのプロパティにIntelliSenseが効くようにする』を適用して加筆修正しました。@FKbelm さん、ありがとうございます。
- MainWindow.xamlについて重要な書き換えをしました。詳しくは『編集履歴』をご参照ください。
はじめに
この記事は、Visual Basic Advent Calendar 2021に投稿した『Visual Basic で簡単レポート作成』のC#版です。若干加筆修正しています。
また、以下の続編もありますので、あわせてご覧ください。
『WPFで簡単レポート作成(2)複数ページのレポート』
『WPFで簡単レポート作成(3)ページ指定印刷』
帳票は無くならない
請求書、依頼書、出荷明細書など、企業で使う帳票は、ペーパーレスが徐々に浸透しつつある今でも無くなる気配がない。ペーパーレスになったところで、見たいものを簡潔にまとめ上げるレイアウト情報としての帳票は、まだまだ存在し続けるだろう。
そんな帳票を作るのに、AccessのレポートやRDLCなどを使っている人も多いと思うが、
WPF + C# でやってみたら意外に楽しかった
FixedDocumentを使う
WPFでレポート作成するのなら、FixedDocumentクラスがお勧めだ。
レポートの大枠が、以下のようにでき上がっている。
この枠の中に、Xamlで帳票を組み立てていく。
Xamlで帳票を組み立てる
せっかくなので、最新のVisual Studio 2022を使い、.NET6で作ってみよう。
まずはWPFプロジェクトを作成する。
次にUserControlを追加。
追加したUserControlにXamlをゴリゴリ書いていく。Xamlデザイナーに慣れていない人は、デザインビューでの編集はしないことだ。思い通りにコントロールを配置できず、イライラが増して嫌になる。デザインビューは仕上がりを視覚的に確認するものと割り切った方がいい。
XamlビューでXamlのテキストを直接書き込んでいく。書くと言っても大部分はコピぺで済む。
まずはGrid
をCanvas
に変える。Canvas
は、レポート内の文字や罫線が固定されるので帳票作成に向いている。Canvas
内はセンチメートルでサイズを書ける。
以下、単純な帳票を書いてみた。
<UserControl x:Class="ShukkaReport.ShukkaUserControl"
-- 省略 --
DataContext="{StaticResource ShukkaVM}"
Height="1122.51" Width="793.7">
<UserControl.Resources>
<Style x:Key="Line1" TargetType="Line">
<Setter Property="StrokeThickness" Value="1"/>
<Setter Property="Stroke" Value="Black"/>
</Style>
</UserControl.Resources>
<Canvas Background="White">
<!--横線-->
<Line X1="6cm" X2="15cm" Y1="3.5cm" Y2="3.5cm" Style="{StaticResource Line1}"/>
<Line X1="1cm" X2="20cm" Y1="5cm" Y2="5cm" Style="{StaticResource Line1}"/>
<Line X1="1cm" X2="20cm" Y1="7cm" Y2="7cm" Style="{StaticResource Line1}"/>
<Line X1="1cm" X2="20cm" Y1="15cm" Y2="15cm" Style="{StaticResource Line1}"/>
<Line X1="1cm" X2="20cm" Y1="17cm" Y2="17cm" Style="{StaticResource Line1}"/>
<Line X1="1cm" X2="20cm" Y1="19cm" Y2="19cm" Style="{StaticResource Line1}"/>
<!--縦線-->
<Line X1="1cm" X2="1cm" Y1="5cm" Y2="19cm" Style="{StaticResource Line1}"/>
<Line X1="10.5cm" X2="10.5cm" Y1="7cm" Y2="19cm" Style="{StaticResource Line1}"/>
<Line X1="20cm" X2="20cm" Y1="5cm" Y2="19cm" Style="{StaticResource Line1}"/>
<!--文字列-->
<TextBlock Text="出 荷 指 図 書" Margin="6.65cm,2cm" Width="8cm" Height="1.5cm" FontSize="46"/>
<TextBlock Text="出荷指図№" Margin="1.25cm,5.25cm" Height="1.5cm" FontSize="36" FontWeight="Bold"/>
<TextBlock Text="納品先" Margin="1.25cm,7.25cm" Height="1cm" FontSize="26"/>
<TextBlock Text="住所" Margin="10.75cm,7.25cm" Height="1cm" FontSize="26"/>
<TextBlock Text="出荷日" Margin="1.25cm,15.25cm" Height="1cm" FontSize="26"/>
<TextBlock Text="納品日" Margin="1.25cm,17.25cm" Height="1cm" FontSize="26"/>
<TextBlock Text="個数" Margin="10.75cm,15.25cm" Height="1cm" FontSize="26"/>
<TextBlock Text="運送便" Margin="10.75cm,17.25cm" Height="1cm" FontSize="26"/>
<!--設定値-->
<TextBlock Text="{Binding 出荷指図NO}" Margin="7cm,5.25cm" Height="1.5cm" Width="10cm" FontSize="36"/>
<TextBlock Text="{Binding 納品先}" Margin="1.25cm,9cm" Height="5cm" FontSize="20"/>
<TextBlock Text="{Binding 住所}" Margin="10.75cm,9cm" Height="5cm" FontSize="20"/>
<TextBlock Text="{Binding 出荷日}" Margin="4cm,15.5cm" Height="1cm" FontSize="20"/>
<TextBlock Text="{Binding 納品日}" Margin="4cm,17.5cm" Height="1cm" FontSize="20"/>
<TextBlock Text="{Binding 個数}" Margin="13cm,15.5cm" Height="1cm" FontSize="20"/>
<TextBlock Text="{Binding 運送便}" Margin="14cm,17.5cm" Height="1cm" FontSize="20"/>
</Canvas>
</UserControl>
クラス名に名前空間が付加されるのは、VBと異なる。
ページサイズはA4にしている。ここはピクセルでの設定となる。
このレベルならAccessのレポートで十分作れるのだが、Xamlなら更に複雑なことができるのは想像がつくだろう。
コントロールをマウスでドラッグして線が斜めになったとか、部分的に表示位置をずらしたくなったとか、Accessレポートなどではうんざりする修正作業も、Xamlならストレス無く、各コントロールの座標数字を書き換えるだけで済む。
ここで、3行目の、
DataContext="{StaticResource ShukkaVM}
は、本来ならViewModelを作成してから書く。(これを書かない方法を後述しています)
ViewModelの作成
以下は追加したユーザーコントロールのViewModelである。コードビハインドは一切汚さない。ふざけたコードだが、わかりやすさ重視だ
namespace ShukkaReport.ViewModels
{
public class ShukkaViewModel
{
public string 出荷指図NO { get; }
public string 納品先 { get; }
public string 住所 { get; }
public string 出荷日 { get; }
public string 納品日 { get; }
public string 個数 { get; }
public string 運送便 { get; }
public ShukkaViewModel()
{
this.出荷指図NO = "SK20211224-001";
this.納品先 = "株式会社ほげ商事";
this.住所 = "東京都中央区〇〇町〇丁目-〇-〇\nレポートビル1階";
this.出荷日 = "12月24日";
this.納品日 = "12月25日";
this.個数 = "10個";
this.運送便 = "佐山運輸";
}
}
}
以下はウインドのViewModelである。こちらもコードビハインドは一切汚さない。
using System.Windows.Documents;
namespace ShukkaReport.ViewModels
{
public class MainWindowViewModel
{
public FixedDocument ReportViewer { get; }
public MainWindowViewModel()
{
var report = new ShukkaUserControl();
var page = new FixedPage() { Width = 793.7, Height = 1122.51 };
var pageContent = new PageContent();
var doc = new FixedDocument();
page.Children.Add(report);
pageContent.Child = page;
doc.Pages.Add(pageContent);
ReportViewer = doc;
}
}
}
ここでFixedDocumentをこしらえてReportViewer
プロパティにセットしている。
今回は単一ページだが、複数ページも実装可能。これを見れば自力で作れると思う。
2ページ目以降は書式が変わってアタッチシート、なんてこともできるだろう。
FixedDocumentの構造は、
親)FixedDocument
子)PageContent
孫)FixedPage
で、孫のFixedPageの子供としてUserControlをChildren.Add
する。
App.xamlの編集(これを書かない方法を後述)
アプリで二つのViewModelを利用できるようにリソースとして登録する。
<Application x:Class="ShukkaReport.App"
-- 省略 --
xmlns:local="clr-namespace:ShukkaReport.ViewModels"
StartupUri="MainWindow.xaml">
<Application.Resources>
<local:MainWindowViewModel x:Key="MainVM"/>
<local:ShukkaViewModel x:Key="ShukkaVM"/>
</Application.Resources>
</Application>
ここのx:Key
は自分の好みで名付ける。ウインドウとユーザーコントロールのDataContextに、これらの名前を各々設定する。
インテリセンスで各ViewModelが現れない場合は、xmlns:local の記載を各ViewModelのCSファイルの名前空間と一致させる。
MainWindow.xamlの編集
<Window x:Class="ShukkaReport.MainWindow"
-- 省略 --
DataContext="{StaticResource MainVM}"
Title="MainWindow" Height="600" Width="600">
<Grid>
<DocumentViewer Document="{Binding ReportViewer}"/>
</Grid>
</Window>
クラス名に名前空間が付加されるのは、VBと異なる。
3行目でDataContext
にApp.xamlで設定した MainWindowViewModel のリソースをセットしている。(これを書かない方法を次節に示しています)
また、Grid
内でDocumentViewer
のDocument
にReportViewer
をバインドしている。
App.xaml に書かない方法
@FKbelm さんの書かれた『XAML内でDataContextのプロパティにIntelliSenseが効くようにする』の前半部分を適用すると、App.xaml には何も書かずに済みます。これは見習いたい。@FKbelm さん、ありがとうございます。
<Window x:Class="MainWindow"
-- 省略 -- >
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<UserControl x:Class="ShukkaUserControl"
-- 省略 -- >
<UserControl.DataContext>
<local:ShukkaViewModel/>
</UserControl.DataContext>
そして、
DataContext="{StaticResource MainVM}"
と
DataContext="{StaticResource ShukkaVM}"
は消します。
実行結果
印刷や拡大縮小などの機能が既に組み込まれていて、それらを実装する必要はない。あー何て楽なんだろう。UXの時代に古臭い見た目だが、そんなことは気にしない
(ボタン・アイコンの変更は、『WPFで簡単レポート作成(3)ページ指定印刷』をご参照ください)
WPFによるデスクトップ・アプリは、まだまだ事務処理系の現場で使い倒せるのだ