プログラム初心者には、以外に難しいと思われる印刷レポートのループの組み方を例にクラスの継承、オーバーライド等の使いの方を記載します。

レポートの仕様

レポートヘッダー(ReportHeader)

・印刷帳票で最初に1度印刷します。

ページヘッダー(PageHeader)

・ページ毎に最初に印刷します。(見出し等)

詳細部(Detail)

・実際の項目の内容等を印刷します。

ページフッター(PageFooter)

・ページ毎に最後に印刷します。

レポートフッター(ReportFooter)

・印刷帳票で最後に印刷します。

基本となるクラス

Public Class ReportClass

#Region "ReportControlクラス"
    ''' <summary>
    ''' レポートの管理クラス
    ''' </summary>
    Public Class ReportControl
        'ページの最大行数
        Public PageMaxRows As Short

        'レポートヘッダ行数
        Public ReportHeaderRows As Short
        'レポートフッタ行数
        Public ReportFooterRows As Short

        'ページヘッダ行数
        Public PageHeaderRows As Short
        'ページフッタ行数
        Public PageFooterRows As Short
    End Class
#End Region

#Region "列挙体"
    ''' <summary>
    ''' 行を取得する列挙体
    ''' </summary>
    Public Enum DetailsRowType
        Middle = 0
        First
        Final
    End Enum

#End Region

#Region "クラス内のみで使用する変数"

    '1ページの最大の行数
    Private _RowMax As Integer

    '現在の印刷行位置
    Private _RowPoint As Integer

#End Region

    'ページ番号
    Private _PageNo As Integer

    '行番号(最初からの位置)
    Private _LineNo As Integer

    'レポート印刷制御データ
    Private _ReportCtrl As New ReportControl

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    Sub New()
        _PageNo = 1
        _LineNo = 1
    End Sub

    ''' <summary>
    ''' ページ番号を取得する(1-)
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property PageNo() As Integer
        Get
            Return _PageNo
        End Get
    End Property

    ''' <summary>
    ''' 先頭行からの行番号を取得する(1-)
    ''' </summary>
    ''' <returns></returns>
    Public ReadOnly Property LineNo() As Integer
        Get
            Return _LineNo
        End Get
    End Property

    ''' <summary>
    ''' レポートの情報等を設定または取得します。
    ''' </summary>
    ''' <returns></returns>
    Public Property ReportCtrl() As ReportControl
        Get
            Return _ReportCtrl
        End Get
        Set(ByVal value As ReportControl)
            _ReportCtrl = value
        End Set
    End Property

    ''' <summary>
    ''' ページ行数を取得する
    ''' </summary>
    ''' <param name="mode">0:通常ページ 1:開始ページ 2:終了ページ</param>
    ''' <returns></returns>
    Protected Function setDetailRows(Optional mode As DetailsRowType = DetailsRowType.Middle)
        If mode = DetailsRowType.First Then        '開始ページ
            _RowMax = ReportCtrl.PageMaxRows - (ReportCtrl.PageHeaderRows + ReportCtrl.PageFooterRows + ReportCtrl.ReportHeaderRows)
            _RowPoint = 0
        ElseIf mode = DetailsRowType.Final Then    '最終ページ
            _RowMax = ReportCtrl.PageMaxRows
            _RowPoint = 0
        Else
            _RowMax = ReportCtrl.PageMaxRows - (ReportCtrl.PageHeaderRows + ReportCtrl.PageFooterRows)
            _RowPoint = 0
        End If

        Return True
    End Function

    ''' <summary>
    ''' 改ページをする 
    ''' </summary>
    Protected Sub Pr_NextPage()
        _PageNo += 1
        Print_NextPage()
        setDetailRows()
    End Sub

    ''' <summary>
    ''' ページヘッダが必要なら出力する
    ''' </summary>
    Protected Sub Pr_NeedPageHeader()
        If _RowPoint = 0 Then
            Pr_PageHeader()
        End If
    End Sub

    ''' <summary>
    ''' 次の行に進めページフッターが必要なら出力し改ページする
    ''' </summary>
    Protected Sub Pr_NextLine()
        _RowPoint += 1
        _LineNo += 1
        If _RowMax <= _RowPoint Then        '改ページ
            Pr_PageFooter()
            Pr_NextPage()
        End If
    End Sub

    ''' <summary>
    ''' レポートヘッダを印刷する
    ''' </summary>
    Protected Sub Pr_ReportHeader()
        If ReportCtrl.ReportHeaderRows <= 0 Then Return
        Print_ReportHeader()
        _LineNo += ReportCtrl.ReportHeaderRows
    End Sub

    ''' <summary>
    ''' レポートフッターを印刷する
    ''' </summary>
    Protected Sub Pr_ReportFooter()
        If ReportCtrl.ReportFooterRows <= 0 Then Return

        Print_ReportFooter()
        _LineNo += ReportCtrl.ReportFooterRows

        Dim n As Integer = _RowMax - _RowPoint
        If n < ReportCtrl.ReportFooterRows Then
            Pr_NextPage()
        End If
    End Sub

    ''' <summary>
    ''' ページヘッダを印刷する
    ''' </summary>
    Protected Sub Pr_PageHeader()
        If ReportCtrl.PageHeaderRows <= 0 Then Return
        Print_PageHeader()
        _LineNo += ReportCtrl.PageHeaderRows
    End Sub

    ''' <summary>
    ''' ページフッターを印刷する
    ''' </summary>
    Protected Sub Pr_PageFooter()
        If ReportCtrl.PageFooterRows <= 0 Then Return
        Print_PageFooter()
        _LineNo += ReportCtrl.PageFooterRows
    End Sub

    ''' <summary>
    ''' 1行印刷する
    ''' </summary>
    ''' <param name="data"></param>
    Protected Sub Pr_LinePrint(data As Object)
        Print_LinePrint(data)
    End Sub

    ''' <summary>
    ''' 詳細内容を印刷する
    ''' </summary>
    Protected Sub Pr_Detail(datas As Object)
        Print_Detail(datas)
    End Sub

    Protected Sub Pr_Detail(table As DataTable)
        Print_Detail(table)
    End Sub

    ''' <summary>
    ''' レポート印刷
    ''' </summary>
    Public Sub Pr_Report(datas As Object)
        Print_Report(datas)
    End Sub

#Region "継承側で実際に出力するコードを書く関数"

    Protected Overridable Sub Print_NextPage()
    End Sub
    Protected Overridable Sub Print_ReportHeader()
    End Sub
    Protected Overridable Sub Print_ReportFooter()
    End Sub
    Protected Overridable Sub Print_PageHeader()
    End Sub
    Protected Overridable Sub Print_PageFooter()
    End Sub
    Protected Overridable Sub Print_LinePrint(data As Object)
    End Sub

    Protected Overridable Sub Print_Detail(datas As Object)
        Dim dataList As List(Of Object) = DirectCast(datas, List(Of Object))

        If dataList.Count = 0 Then Return

        For Each o As Object In dataList
            Pr_NeedPageHeader()
            Pr_LinePrint(o)
            Pr_NextLine()
        Next

        If 0 < _RowPoint Then        '改ページ
            Pr_PageFooter()
        End If
    End Sub

    Protected Overridable Sub Print_Detail(table As DataTable)
        If table.Rows.Count = 0 Then Return

        For Each row As DataRowCollection In table.Rows
            Pr_NeedPageHeader()
            Pr_LinePrint(row)
            Pr_NextLine()
        Next

        If 0 < _RowPoint Then        '改ページ
            Pr_PageFooter()
        End If
    End Sub

    Protected Overridable Sub Print_Report(datas As Object)
        Pr_ReportHeader()
        Pr_Detail(datas)
        Pr_ReportFooter()
    End Sub

#End Region

End Class

継承をしたクラス

Public Class MyReport
    Inherits ReportClass

    Dim _list As ListBox

    Sub New(list As ListBox)
        _list = list

        Dim RCtrl As ReportControl = New ReportControl()

        RCtrl.ReportHeaderRows = 1
        RCtrl.ReportFooterRows = 1
        RCtrl.PageHeaderRows = 1
        RCtrl.PageFooterRows = 1
        RCtrl.PageMaxRows = 10

        ReportCtrl = RCtrl
        setDetailRows(DetailsRowType.First)
    End Sub

    Protected Overrides Sub Finalize()
    End Sub

    Protected Overrides Sub Print_NextPage()
        _list.Items.Add("--------------")
    End Sub

    Protected Overrides Sub Print_ReportHeader()
        _list.Items.Add("ReportHeader")
    End Sub

    Protected Overrides Sub Print_ReportFooter()
        _list.Items.Add("ReportFooter")
    End Sub

    Protected Overrides Sub Print_PageHeader()
        _list.Items.Add("PageHeader")
    End Sub

    Protected Overrides Sub Print_PageFooter()
        _list.Items.Add("PageFooter")
    End Sub

    Protected Overrides Sub Print_LinePrint(data As Object)
        Dim s As String = DirectCast(data, String)
        _list.Items.Add(s)
    End Sub

End Class

呼び出し側

Private Sub Button1_Click_1(sender As Object, e As EventArgs) Handles Button1.Click
    Dim dtList As List(Of Object) = New List(Of Object)

    For i As Integer = 0 To 100
        Dim s As String = ""
        s = i.ToString()
        dtList.Add(s)
    Next

    Dim myrep As New MyReport(ListBox1)
    myrep.Pr_Report(dtList)
End Sub

上のサンプルは、リストボックスに表示するようにしています。実際は、継承を行ったクラスで印刷部分を書きます。


 

継承してEXCELに出力するサンプル

Imports Microsoft.Office.Interop

Public Class MyExcelReport
    Inherits ReportClass
    Implements IDisposable

    Dim xlApp As Excel.Application
    Dim xlBook As Excel.Workbook
    Dim xlSheet As Excel.Worksheet
    Dim olSheet As Excel.Worksheet

    Dim ReportHeader_Range As Excel.Range
    Dim ReportFooter_Range As Excel.Range
    Dim PageHeader_Range As Excel.Range
    Dim PageFooter_Range As Excel.Range
    Dim Detail_Range As Excel.Range

    Dim wRange As Excel.Range

    Dim _DataTable As DataTable


#Region "Dispose"

    Private disposedValue As Boolean = False        '重複する呼び出しの制御

    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: 他の状態を解放します (マネージ オブジェクト)。
            End If

            ComObjectFree(wRange, True)

            ComObjectFree(Detail_Range, True)
            ComObjectFree(ReportHeader_Range, True)
            ComObjectFree(ReportFooter_Range, True)
            ComObjectFree(PageHeader_Range, True)
            ComObjectFree(PageFooter_Range, True)

            ComObjectFree(olSheet, True)
            ComObjectFree(xlSheet, True)
            ComObjectFree(xlBook, True)
            ComObjectFree(xlApp, True)
        End If
        Me.disposedValue = True
    End Sub

#Region "IDisposable Support"

    Public Sub Dispose() Implements IDisposable.Dispose
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub

#End Region

#End Region

    Protected Overrides Sub Finalize()
        MyBase.Finalize()

        ' Disposeが呼ばれてなかったら呼び出す
        If disposedValue = False Then
            Me.Dispose()
        End If

    End Sub

    ''' <summary>
    ''' コンストラクタ
    ''' </summary>
    ''' <param name="filtpath"></param>
    Sub New(filtpath As String)
        MyBase.New()

        xlApp = New Excel.Application()
        xlApp.Workbooks.Open(filtpath)

        Dim FileName As String = System.IO.Path.GetFileName(filtpath)

        xlBook = xlApp.Workbooks(FileName)
        xlSheet = xlBook.Worksheets("SheetOrg")
        olSheet = xlBook.Worksheets("Sheet2")

        Dim RCtrl As ReportControl = New ReportControl()

        ReportHeader_Range = xlSheet.Range("ReportHeader")
        RCtrl.ReportHeaderRows = ReportHeader_Range.Rows.Count

        ReportFooter_Range = xlSheet.Range("ReportFooter")
        RCtrl.ReportFooterRows = ReportFooter_Range.Rows.Count

        PageHeader_Range = xlSheet.Range("PageHeader")
        RCtrl.PageHeaderRows = PageHeader_Range.Rows.Count

        PageFooter_Range = xlSheet.Range("PageFooter")
        RCtrl.PageFooterRows = PageFooter_Range.Rows.Count

        Detail_Range = xlSheet.Range("Detail")

        Dim xRange As Excel.Range = xlSheet.Range("PageRows")
        RCtrl.PageMaxRows = xRange.Rows.Count
        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xRange)

        ReportCtrl = RCtrl
        setDetailRows(DetailsRowType.First)
    End Sub

    ''' <summary>
    ''' COMの解放
    ''' </summary>
    ''' <typeparam name="T"></typeparam>
    ''' <param name="objCom"></param>
    ''' <param name="force"></param>
    Public Shared Sub ComObjectFree(Of T As Class)(ByRef objCom As T, Optional ByVal force As Boolean = False)
        If objCom Is Nothing Then
            Return
        End If

        If Not System.Runtime.InteropServices.Marshal.IsComObject(objCom) Then
            Return
        End If

        Try
            If force Then
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(objCom)
                objCom = Nothing
            Else
                System.Runtime.InteropServices.Marshal.ReleaseComObject(objCom)
            End If
        Finally
        End Try
    End Sub

    Protected Overrides Sub Print_ReportHeader()
        wRange = olSheet.Cells(LineNo, ReportHeader_Range.Column)
        '  SyncLock wRange
        ReportHeader_Range.Copy(wRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(wRange)
        ' End SyncLock
    End Sub

    Protected Overrides Sub Print_ReportFooter()
        wRange = olSheet.Cells(LineNo, ReportFooter_Range.Column)
        ReportFooter_Range.Copy(wRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(wRange)
    End Sub

    Protected Overrides Sub Print_PageHeader()
        wRange = olSheet.Cells(LineNo, PageHeader_Range.Column)
        PageHeader_Range.Copy(wRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(wRange)
    End Sub

    Protected Overrides Sub Print_PageFooter()
        wRange = olSheet.Cells(LineNo, PageFooter_Range.Column)
        PageFooter_Range.Copy(wRange)
        System.Runtime.InteropServices.Marshal.ReleaseComObject(wRange)
    End Sub

    Protected Overrides Sub Print_LinePrint(data As Object)
        Dim s As String = DirectCast(data, String)

        xlSheet.Range("item1").Value = s

        wRange = olSheet.Cells(LineNo, Detail_Range.Column)
        Detail_Range.Copy(wRange)

        System.Runtime.InteropServices.Marshal.FinalReleaseComObject(wRange)
    End Sub

    Protected Overrides Sub Print_Report(datas As Object)
        MyBase.Print_Report(datas)

        xlApp.Visible = True
        System.Threading.Thread.Sleep(10000)
        xlApp.Quit()

        Clipboard.Clear()
    End Sub

End Class

呼び出し側

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim dtList As List(Of Object) = New List(Of Object)

    For i As Integer = 0 To 100
        Dim s As String = ""
        s = i.ToString()
        dtList.Add(s)
    Next

    Using myrep As New MyExcelReport("C:\work\ExcelTemplate.xls")
        myrep.Pr_Report(dtList)
    End Using
End Sub

上記のサンプルはEXCELファイルをテンプレートとして読み込みテンプレートに従って、印刷レポートを作成します。

シートSheetOrgがテンプレートシートで出力はSheet2にレポート印刷するようになっています。

exceltemplate

 

またSheetOrgは、①ReportHeader ②PageHeader ③Detail ④PageFooter ⑤ReportFooter ⑥PageRows とゆうようにセルに名前が付けられており③にお置いては、item1,item2,item3とセル毎に名前がつけられています。