印刷処理で列項目とデータを整列させて出力したいです。

タグの編集
投稿者 たかくん  (社会人) 投稿日時 2013/3/30 17:39:05
こんにちは、お世話になります。
下記のコードは顧客情報の抽出結果を印刷する時のメソッドなのですが項目とデータが綺麗に表示
できず困ってます。
Shift_JISに変換してみたもののまだガタガタの表示になってます。
各項目事にPadding()しているのですがどうもうまくいかないです。
どうすれば綺麗に表示できるでしょうか?
解る方よろしくお願いします。

    ''' <summary>
    ''' 帳票描画
    ''' </summary>
    ''' <param name="size">印刷可能範囲</param>
    ''' <returns>帳票画像</returns>
    ''' <remarks></remarks>
    Private Function DrawReport(ByVal size As Size, ByVal data As DataTable) As Bitmap
        Dim report As New Bitmap(size.Width, size.Height)
        Try
            Using font As Font = New Font("MS 明朝", 10)
                Using pen As New Pen(Brushes.Black, 1)
                    Using g As Graphics = Graphics.FromImage(report)
                        Dim pitch As Integer = font.Height + 3
                        Dim maxrow As Integer = size.Height / pitch
                        Dim rowcounter As Integer = 0
                        Dim locate As Point = New Point(1, 1)
                        Dim strlist As String() = New String() {"氏名", "郵便番号", "住所", "年齢", "性別"}
                        Dim bytedata As List(Of Byte()) = Me.ChangeShift_JisEncoding(strlist)
                        Dim count As List(Of Integer) = Me.GetByteCount(strlist)
                        Dim datalist As List(Of String) = Me.ChangeShift_JisString(bytedata)
                        Dim name As String = datalist(0).PadRight(12 - count(0), " ")
                        Dim post As String = datalist(1).PadRight(12 - count(1))
                        Dim adrs As String = datalist(2).PadRight(25 - count(2), "  ")
                        Dim year As String = datalist(3).PadLeft(4 - count(3))
                        Dim sex As String = datalist(4).PadLeft(4 - count(4))
                        g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
                        g.DrawRectangle(pen, New Rectangle(locate, New Size(size.Width - 2, size.Height - 2)))
                        Dim title As String = name + post + adrs + year + sex
                        g.DrawString(Title, font, Brushes.Black, locate)
                        Do While rowcounter < maxrow
                            locate = New Point(1, rowcounter * pitch + pitch)
                            g.DrawLine(pen, locate, New Point(size.Width - 2, locate.Y))
                            If rowcounter < data.Rows.Count Then
                                strlist(0) = data(rowcounter)("Naming").ToString.Trim
                                strlist(1) = data(rowcounter)("PostalNo").ToString.Trim
                                strlist(2) = data(rowcounter)("Address").ToString.Trim
                                strlist(3) = data(rowcounter)("Year").ToString.Trim
                                strlist(4) = data(rowcounter)("Sex").ToString.Trim
                                bytedata = Me.ChangeShift_JisEncoding(strlist)
                                count = Me.GetByteCount(strlist)
                                datalist = Me.ChangeShift_JisString(bytedata)
                                name = datalist(0).ToString.PadRight(12 - count(0), " ")
                                post = datalist(1).ToString.PadRight(12 - count(1))
                                adrs = datalist(2).ToString.PadRight(25 - count(2), "  ")
                                year = datalist(3).ToString.PadLeft(4 - count(3))
                                sex = datalist(4).ToString.PadLeft(4 - count(4))
                                Dim row As String = name + post + adrs + year + sex
                                g.DrawString(row, font, Brushes.Black, locate)
                            End If
                            rowcounter += 1
                        Loop
                    End Using
                End Using
            End Using
        Catch ex As Exception
            Log(ex, "CustomerSearchFormClass->DrawReport()")
        End Try
        Return report
    End Function

   
投稿者 たかくん  (社会人) 投稿日時 2013/3/30 17:40:08
続きのコードを掲載します。

 ''' <summary>
    ''' 文字列リストをバイト配列リストに変換する。
    ''' </summary>
    ''' <param name="str"></param>
    ''' <returns>バイト配列リスト</returns>
    ''' <remarks></remarks>
    Private Function ChangeShift_JisEncoding(ByVal str As String()) As List(Of Byte())
        Dim data As New List(Of Byte())
        Dim bytedata As Byte()
        Try
            For Each dat As String In str
                bytedata = System.Text.Encoding.GetEncoding("Shift_jis").GetBytes(dat)
                data.Add(bytedata)
            Next
        Catch ex As Exception
            Log(ex, "CustomerSearchFormClass->ChangeShift_JisEncoding()")
        End Try
        Return data
    End Function

    ''' <summary>
    ''' バイト配列リストをShift_Jis文字列リストに変換する。
    ''' </summary>
    ''' <param name="bytedata"></param>
    ''' <returns>Shift_Jis文字列リスト</returns>
    ''' <remarks></remarks>
    Private Function ChangeShift_JisString(ByVal bytedata As List(Of Byte())) As List(Of String)
        Dim data As New List(Of String)
        Dim tmp As String
        Try
            For Each dat As Byte() In bytedata
                tmp = System.Text.Encoding.GetEncoding("Shift_jis").GetString(dat)
                data.Add(tmp)
            Next
        Catch ex As Exception
            Log(ex, "CustomerSearchFormClass->ChangeShift_JisString()")
        End Try
        Return data
    End Function


    Private Function GetByteCount(ByVal str As String()) As List(Of Integer)
        Dim data As New List(Of Integer)
        Dim count As Integer
        Try
            For Each dat As String In str
                count = System.Text.Encoding.GetEncoding("Shift_jis").GetByteCount(dat)
                data.Add(count)
            Next
        Catch ex As Exception
            Log(ex, "CustomerSearchFormClass->ChangeShift_JisEncoding()")
        End Try
        Return data
    End Function
投稿者 るきお  (社会人) 投稿日時 2013/3/31 15:20:37
あきらかな間違いが2点あります。

1つはフォントの名前が違います。
"MS 明朝"ではなく、"MS 明朝"が正しい名前です。
紛らわしいのですがMSは全角です。(これはフォントを扱う人の間では昔から紛らわしことで有名)。

次にPadLeft、PadRightの使い方を誤解しています。
これらは指定した幅になるようにスペース(または指定した文字)を埋め込む機能です。
そのために何文字埋め込めばよいかは自動的に計算されるので、引数には幅自体を渡します。
ただこの計算は文字数で行われるので全角・半角が混在する場合はたかくんさんが苦慮されているように独自の計算を行う必要があります。
つまり、PadLeft、PadRightを使わないか、使う場合でも本当のサイズではなく全角・半角を考慮したサイズを渡す必要があります。
後者は保守性がとても悪いので私は前者をお勧めします。

つまり、次のようなプログラムになります。
上記2点の他にもいろいろ整理してしまいました。
Public Class Form1

    ''' <summary> 
    ''' 帳票描画 
    ''' </summary> 
    ''' <param name="size">印刷可能範囲</param> 
    ''' <returns>帳票画像</returns> 
    ''' <remarks></remarks> 
    Private Function DrawReport(ByVal size As Size, ByVal data As DataTable) As Bitmap

        Dim report As New Bitmap(size.Width, size.Height)

        Using font As Font = New Font("MS 明朝", 10)
            Using pen As New Pen(Brushes.Black, 1)
                Using g As Graphics = Graphics.FromImage(report)

                    '●1.全体設定 

                    Dim locate As Point = New Point(1, 1)

                    g.Clear(Color.White)
                    g.SmoothingMode = Drawing2D.SmoothingMode.AntiAlias
                    g.DrawRectangle(pen, New Rectangle(locate, New Size(size.Width - 2, size.Height - 2)))

                    '●2.ヘッダー部 

                    Dim nameTitle As String = Fit("氏名", 12, LeftRightAlignment.Left)
                    Dim postTitle As String = Fit("郵便番号", 12, LeftRightAlignment.Left)
                    Dim adrsTitle As String = Fit("住所", 25, LeftRightAlignment.Left)
                    Dim yearTitle As String = Fit("年齢", 4, LeftRightAlignment.Right)
                    Dim sexTitle As String = Fit("性別", 4, LeftRightAlignment.Right)
                    Dim title As String = nameTitle & postTitle & adrsTitle & yearTitle & sexTitle
                    g.DrawString(title, font, Brushes.Black, locate)

                    '●3.明細部 

                    '▼3-1.行間計算等 
                    Dim pitch As Integer = font.Height + 3
                    Dim maxrow As Integer = size.Height / pitch
                    Dim rowcounter As Integer = 0


                    Do While rowcounter < maxrow

                        '▼3-2.罫線 
                        locate = New Point(1, rowcounter * pitch + pitch)
                        g.DrawLine(pen, locate, New Point(size.Width - 2, locate.Y))

                        '▼3-3.明細内容 
                        If rowcounter < data.Rows.Count Then
                            Dim row As DataRow = data.Rows(rowcounter)

                            Dim Name As String = Fit(row(0), 12, LeftRightAlignment.Left)
                            Dim post As String = Fit(row(1), 12, LeftRightAlignment.Left)
                            Dim adrs As String = Fit(row(2), 25, LeftRightAlignment.Left)
                            Dim year As String = Fit(row(3), 4, LeftRightAlignment.Right)
                            Dim sex As String = Fit(row(4), 4, LeftRightAlignment.Right)
                            Dim line As String = Name & post & adrs & year & sex

                            g.DrawString(line, font, Brushes.Black, locate)

                        End If
                        rowcounter += 1
                    Loop

                End Using
            End Using
        End Using

        Return report
    End Function


投稿者 るきお  (社会人) 投稿日時 2013/3/31 15:29:29
続きです。

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click

        Dim table As New DataTable
        table.Columns.Add("Naming"GetType(String))
        table.Columns.Add("PostalNo"GetType(String))
        table.Columns.Add("Address"GetType(String))
        table.Columns.Add("Year"GetType(Integer))
        table.Columns.Add("Sex"GetType(String))

        table.Rows.Add("徳川家康""111-1111""千代田区千代田1-1", 40, "男")
        table.Rows.Add("楠正成""222-2222""奈良県吉野市1-100", 30, "男")
        table.Rows.Add("後桜町天皇""222-2222""京都府1-1", 20, "女")

        Dim report As Bitmap = DrawReport(New Size(640, 800), table)

        PictureBox1.Image = report

    End Sub

    Private sjis As System.Text.Encoding = System.Text.Encoding.GetEncoding("shift_jis")
    ''' <summary> 
    ''' 文字列が指定された幅になるようにスペースを埋め込みます。 
    ''' 文字列のサイズはShiftJISに換算して計算します。 
    ''' <para>指定された文字列が幅を上回る場合の処理は未実装です。</para> 
    ''' </summary> 
    ''' <param name="value">対象の文字列。</param> 
    ''' <param name="size">幅。通常の印字領域の幅をバイト数で指定します。</param> 
    ''' <param name="align">右寄せか左寄せか。スペースを埋め込む方向を変化させます。</param> 
    Private Function Fit(value As String, size As Integer, align As LeftRightAlignment) As String

        Dim byteCount As Integer = sjis.GetByteCount(value)

        If size = byteCount Then
            '領域のサイズと実際の値のサイズが一致する場合何もする必要なし。 
            Return value
        ElseIf size < byteCount Then
            '領域のサイズより実際の値のサイズが大きい場合。 
            '実際の値を切り詰めて表示するか、領域サイズを広げるなど仕様変更が必要。 
            'ここでは仮にこのまま返す。(=がたがたになる。) 
            Return value
        Else
            '領域のサイズより実際の値のサイズが小さい場合。 
            '領域サイズになるように" "をくっつけて返す。 
            Dim result As String
            Dim padding As New String(" "c, size - byteCount)
            If align = LeftRightAlignment.Left Then
                result = value & padding
            Else
                result = padding & value
            End If
            Return result
        End If

    End Function

End Class



問題の個所以外で私が整理したポイント。
・Try ~ Catch で未知の例外をキャッチしないでください。(例外が発生したときは例外を返してください。想定していない例外が発生した場合、処理を続行しても問題が発生しないことが保証できません。ログに出力したい場合は、アプリケーションイベントのUnhandledExceptionイベントを使用すると良いと思います。デバッグなしで実行すると機能します。)
・変数の名前がわかりにくいように感じました。data(rowcounter)("Naming")、strlist(0)、datalist(0)の役割文体が不明瞭だったため整理しました。


なお、今回のような投稿をされる場合は、
プログラムをコピー&貼り付けすればすぐに実行できる状態にすると回答者が多くなると思います。
私の例で行くとButton1_Clickがあった方がよいということです。

たかくんさんのプログラムを試すためには回答者が自分でButton1_Clickを作る必要があり手間です。
熟練のプログラマはプログラムをみただけで回答してくれると期待されているかもしれませんが、明確なビルドエラーでもなければそれはたいてい無理です。
投稿者 たかくん  (社会人) 投稿日時 2013/3/31 22:16:13
今晩は、るきおさんお久しぶりです。
プロになって8ヶ月が経ちました。
今の自分に一番足りない部分を学んだ気がします。
「相手、お客様、上司」等人を意識した仕事をする事が大切ですね。
るきおさんのコードや説明はどまでも人に親切ですよね。
「ここまでしていただけるのか・・・」そんな印象でした。
ここまでしていただいた自分は嬉しかったです。
目の前の事に精一杯ではまだまだですね。
このコードで理想の動きが実現できる事が確認できました。
「得した感満喫です」
ありがとうございます。
相手が嬉しいと感じてもらえる仕事を目指します。