Visual Basic で簡単レポート作成

<おことわり>
---

* @FKbelm さんの書かれた『[XAML内でDataContextのプロパティにIntelliSenseが効くようにする](https://qiita.com/FKbelm/items/10f9276e217b5bf83cb0)』を適用して加筆修正しました。@FKbelm さん、ありがとうございます。
* MainWindow.xamlについて重要な書き換えをしました。詳しくは『[編集履歴](https://qiita.com/cyberCatvb/items/3db7aeea414424f6776b/revisions)』をご参照ください。


帳票は無くならない
---
請求書、依頼書、出荷明細書など、企業で使う帳票は、ペーパーレスが徐々に浸透しつつある今でも無くなる気配がない。ペーパーレスになったところで、見たいものを簡潔にまとめ上げるレイアウト情報としての帳票は、まだまだ存在し続けるだろう。
そんな帳票を作るのに、AccessのレポートやRDLCなどを使っている人も多いと思うが、

**WPF + Visual Basic** でやってみたら意外に楽しかった:laughing:
FixedDocumentを使う
---
WPFでレポート作成するのなら、FixedDocumentクラスがお勧めだ。
レポートの大枠が、以下のようにでき上がっている。
![大枠.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/c94e3d5e-0689-f7ef-f022-1a0446cc0495.png)
この枠の中に、Xamlで帳票を組み立てていく。

Xamlで帳票を組み立てる
---
せっかくなので、最新のVisual Studio 2022を使い、.NET6で作ってみよう。
まずはWPFプロジェクトを作成する。
![WPFプロジェクト作成.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/f669c371-8e03-2ec5-0495-8fe0fc82531a.png)
次にUserControlを追加。
![UserControl追加.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/480e74fc-4cfe-1fe9-3c5f-b457355027a8.png)
追加したUserControlにXamlをゴリゴリ書いていく。Xamlデザイナーに慣れていない人は、デザインビューでの編集はしないことだ。思い通りにコントロールを配置できず、イライラが増して嫌になる。デザインビューは仕上がりを視覚的に確認するものと割り切った方がいい。
XamlビューでXamlのテキストを直接書き込んでいく。書くと言っても大部分はコピぺで済む。
まずは`Grid`を`Canvas`に変える。`Canvas`は、レポート内の文字や罫線が固定されるので帳票作成に向いている。`Canvas`内はセンチメートルでサイズを書ける。
以下、単純な帳票を書いてみた。

```xml:ShukkaUserControl.xaml
<UserControl x:Class="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>
```

でき上りは以下のとおり。
![出荷指図書.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/28e14156-e67f-f5cb-1fb3-316a7c1ff934.png)
このレベルならAccessのレポートで十分作れるのだが、Xamlなら更に複雑なことができるのは想像がつくだろう。
コントロールをマウスでドラッグして線が斜めになったとか、部分的に表示位置をずらしたくなったとか、Accessレポートなどではうんざりする修正作業も、Xamlならストレス無く、各コントロールの座標数字を書き換えるだけで済む。

ここで、3行目の、
>DataContext="{StaticResource ShukkaVM}

は、本来ならViewModelを作成してから書く。（:warning:これを書かない方法を後述しています）


ViewModelの作成
---
以下は追加したユーザーコントロールのViewModelである。コードビハインドは一切汚さない。ふざけたコードだが、わかりやすさ重視だ:grin:

```vb:ShukkaViewModel.vb
Public Class ShukkaViewModel
    ' 以下バインドするプロパティたち
    ' バインドするならプロパティでなければならない
    Public Property 出荷指図NO As String
    Public Property 納品先 As String
    Public Property 住所 As String
    Public Property 出荷日 As String
    Public Property 納品日 As String
    Public Property 個数 As String
    Public Property 運送便 As String
    ' コンストラクタ
    Public Sub New()
        ' 実際はEntityFrameworkCoreなどでデータをはめ込むが、
        ' テーマから反れるので、ここでは省略
        出荷指図NO = "SK20211224-001"
        納品先 = "株式会社ほげ商事"
        住所 = "東京都中央区〇〇町〇丁目-〇-〇" & vbCr & "レポートビル１階"
        出荷日 = "12月24日"
        納品日 = "12月25日"
        個数 = "10個"
        運送便 = "佐山運輸"
    End Sub
End Class
```
以下はウインドのViewModelである。こちらもコードビハインドは一切汚さない。

```vb:MainWindowViewModel.vb
Public Class MainWindowViewModel 
    Public Property ReportViewer As FixedDocument
    Public Sub New()
        Dim report = New ShukkaUserControl
        Dim page = New FixedPage With {.Width = 793.7, .Height = 1122.51}
        Dim pageContent = New PageContent
        Dim doc = New FixedDocument
        page.Children.Add(report)
        pageContent.Child = page
        doc.Pages.Add(pageContent)
        ReportViewer = doc
    End Sub
End Class
```
ここでFixedDocumentをこしらえて`ReportViewer`プロパティにセットしている。
今回は単一ページだが、複数ページも実装可能。これを見れば自力で作れると思う。
2ページ目以降は書式が変わってアタッチシート、なんてこともできるだろう。
FixedDocumentの構造は、

親）FixedDocument
子）PageContent
孫）FixedPage

で、孫のFixedPageの子供としてUserControlを`Children.Add`する。

Application.xamlの編集（:warning:これを書かない方法を後述）
---
アプリで二つのViewModelを利用できるようにリソースとして登録する。

```xml:Application.xaml
<Application x:Class="Application"
    -- 省略 --
    StartupUri="MainWindow.xaml">
    <Application.Resources>
        <local:MainWindowViewModel x:Key="MainVM"/>
        <local:ShukkaViewModel x:Key="ShukkaVM"/>
    </Application.Resources>
</Application>
```

ここの`x:Key`は自分の好みで名付ける。ウインドウとユーザーコントロールのDataContextに、これらの名前を各々設定する。

MainWindow.xamlの編集
---

```xml:MainWindow.xaml
<Window x:Class="MainWindow"
    -- 省略 --
    DataContext="{StaticResource MainVM}"
    Title="MainWindow" Height="600" Width="600">
    <Grid>
        <DocumentViewer Document="{Binding ReportViewer}"/>
    </Grid>
</Window>
```
3行目で`DataContext`にApplication.xamlで設定した MainWindowViewModel のリソースをセットしている。（:warning:これを書かない方法を次節に示しています）
また、`Grid`内で`DocumentViewer`の`Document`に`ReportViewer`をバインドしている。
:warning:Application.xaml に書かない方法
---
@FKbelm さんの書かれた『[XAML内でDataContextのプロパティにIntelliSenseが効くようにする](https://qiita.com/FKbelm/items/10f9276e217b5bf83cb0)』の前半部分を適用すると、Application.xaml には何も書かずに済みます。これは見習いたい。@FKbelm さん、ありがとうございます。 

```xml:MainWindow.xaml
<Window x:Class="MainWindow"
    -- 省略 -- >
    <Window.DataContext>
        <local:MainWindowViewModel/>
    </Window.DataContext>
```

```xml:ShukkaUserControl.xaml
<UserControl x:Class="ShukkaUserControl"
    -- 省略 -- >
    <UserControl.DataContext>
        <local:ShukkaViewModel/>
    </UserControl.DataContext>
```
そして、
>DataContext="{StaticResource MainVM}"

と

>DataContext="{StaticResource ShukkaVM}"

は消します。

実行結果
---
![実行結果.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294209/c27730c0-6fc0-5a6f-9784-df25ed7503af.png)
印刷や拡大縮小などの機能が既に組み込まれていて、それらを実装する必要はない。あー何て楽なんだろう。UXの時代に古臭い見た目だが、そんなことは気にしない:laughing:

**WPF + Visual Basic** は、まだまだ事務処理系の現場で使い倒せるのだ:bangbang: