.NETExcelへの処理が遅いのか。

VB2005/2008Excelを扱い始めたころの悩みはこれでした。
どうやら「やり方」次第のようです。   ExcelVBAでは、多少効率の悪い処理方法でワークシートへの展開を行なっていても、それほど処理が遅いとは感じなかったような印象だったのですが、 VB2005/2008/2010で処理結果をワークシートに展開していく場合は、結構遅く感じてしまいました。 その違いが歴然としていたので、そもそも「.NETExcelへの処理が遅い」などという先入観を持っている方も多いのではないかと思います。 ですが、これは「やり方」の違いで結構速くなります。
結論から言えば、「シートへのアクセスの回数をできるだけ減らす」ということになります。サンプルを用意して実験したのでご覧下さい。

このようなサンプルです。

起動フォーム
(画像をクリックすると、このページのサンプルがダウンロードできます)

処理は全て新規Excelワークブックを開いて、ワークシートのA1セルから100列×1000行に渡って「1」からの連番をセットしていくだけのものです。 以下の4つのケースで処理時間の違いを見ていきます。
  • 単純に1セルずつセットしていく(10万回のセルアクセス)
  • 一次元配列変数を用意して、行単位にセットしていく(1千回のセルアクセス)
  • 二次元配列変数を用意して、全ての値を配列変数にセットしてからセル範囲にセットする(1回のセルアクセス)
  • 一旦、JAG配列に格納して、二次元配列に変換してからセル範囲にセットする(1回のセルアクセス)

また、今回の処理方法では、「起動フォーム」と「処理フォーム」を分けてみました。 「起動フォーム」には上記の4種類の処理方法の起動のための4つのボタンがあります。 この「起動フォーム」だけで処理を開始してしまうと、時間が掛かる処理の場合に処理中にオペレータが他のボタンをクリックしてしまうとか、「起動フォーム」を閉じてしまうなどの問題が発生する可能性があります。 この対策として、各ボタンのEnabledプロパティを操作したり、ControlBoxプロパティを操作したりすることでの回避も可能ですが、処理後に「元に戻す」必要が発生するし、 何よりも処理実行中にエラーが起きると、その後に何もできなくなってしまう(フォームを閉じることも!)可能性もあるでしょう。
ここでは「昔流」な方法なのですが、モーダルな「処理フォーム」を上に載せてしまって、その「処理フォーム」の方で処理を行なうことにしました。

処理フォーム

「処理フォーム」の方は閉じるボタン等のないフォーム(ピンク色の部分)で、プログレスバーで処理進捗を表示させることにしています。

では、実際に実行して処理結果を見てみます。
このような処理記述を実際にされている方は、コードの方は想像がつくと思うので、先に処理結果を見てみましょう。
処理しているPCは、Intel Core i7の第2世代(3770K)CPUのものです。
  • 単純に1セルずつセットしていく(10万回のセルアクセス)
    このような結果になりました。
    処理フォーム
    62秒! とんでもない「遅さ」です。
    Excelの表示
    処理結果はこのようになります。
    単にExcelVBAでほぼ同様のことをやると、2秒程度で済むのですから。

  • 一次元配列変数を用意して、行単位にセットしていく(1千回のセルアクセス)
    では、要素数100の一次元配列変数に1行分の値をセットしてから、1行のセル範囲に1回値をセットして次行に進むという処理で試してみます。
    このような結果になりました。
    処理フォーム
    これで、ExcelVBAで1セルずつに値セットしている場合と同程度の速さになったというようなところでしょうか。

  • 二次元配列変数を用意して、全ての値を配列変数にセットしてからセル範囲にセットする(1回のセルアクセス)
    シートへのアクセスを減らせば処理時間が短くできるということなのでしょうから、今度は100列×1000行分の二次元配列変数を用意して、 1100000の値を全てセットしてから、1回でセル範囲への転記を行なうということをやってみます。
    このような結果になりました。
    処理フォーム
    0.16秒! これほどまでの処理時間の短縮になったわけです。

  • 一旦、JAG配列に格納して、二次元配列に変換してからセル範囲にセットする(1回のセルアクセス)
    最初から二次元配列変数が用意できるような処理であれば上記の方法で良いのですが、例えばCSVファイルを読みながら配列に取り込むなどの場合では二次元配列変数は適切ではありません。 なぜなら、二次元配列変数では列方向(最終次元)にしか要素数の動的変更ができないからです。
    このような場合の方法として、一旦JAG配列テーブルに格納してから二次元配列テーブルに置き換えるという方法があります。 JAG配列というのは、一次元配列の1要素に別の配列を格納することで二次元配列的な処理を実現できる方法で、 前のデータベースの章でのDataTableの一種のJAG配列に当たります。
    このサンプルでは行方向の配列変数も最初から1000要素確保するのではなく、要素数不定な場合と同様に1列の列方向の配列ができ次第1要素を追加して格納する方法で行なっています。
    全部の行方向の配列格納が終われば、行列ともに要素数が確定するので変換先の二次元配列テーブルも要素数が先に確定できるわけです。
    但し、JAG配列テーブルを二次元配列テーブルに置き換えるのは行列1要素ずつの転記なので、この工程を行なってでも処理が速いのかが気になるところでした。
    このような結果になりました。
    処理フォーム
    0.24秒! と最初から二次元配列テーブルに配置するより複雑な工程なのに充分に評価できる結果になりました。

いかがでしょうか。
全てにおいて、この一番速い方法が採れるわけではなく、動的に行数が変わっていく処理では二次配列変数の一次側の要素数が最初から判っているケースでないと実現できないという問題があります。 ですが、最低でも行単位にアクセスさせることでかなりな処理時間の短縮が可能になります。行内に計算式の列が含まれてしまうような場合は、計算式自体も配列変数内に文字列でセットしてしまって、 Valueプロパティではなく、FormulaR1C1プロパティを使ってセットするなどの方法も採れます。
また、二次元の区画内に値をセットしないセルが含まれる場合でも、区画全域に合わせた二次配列変数(Object)を用意して区画単位で貼り付けて構いません。 Object型で宣言した場合、デフォルトがNothingとなるので、セルに転記された場合はセルをクリア(Empty)された状態と同じになります。

念のため、ソースコードも紹介しておきます。
  • まずは、ExcelVBAのコードです。
    このコードは上の画像のクリックからダウンロードされる中には含まれていません。
    
    Option Explicit
    
    Sub TEST()
        Dim xlAPP As Application
        Dim IX As Long, GYO As Long, COL As Long
        Dim START_TIME As Date, END_TIME As Date
        Set xlAPP = Application
        START_TIME = Now
        With xlAPP
            .ScreenUpdating = False
            .Calculation = xlCalculationManual
            .EnableEvents = False
        End With
        GYO = 1
        IX = 1
        Do While IX <= 100000
            COL = 1
            Do While COL <= 100
                xlAPP.StatusBar = IX
                Cells(GYO, COL).Value = IX
                IX = IX + 1
                COL = COL + 1
            Loop
            GYO = GYO + 1
        Loop
        With xlAPP
            .EnableEvents = True
            .Calculation = xlCalculationAutomatic
            .ScreenUpdating = True
        End With
        END_TIME = Now
        MsgBox START_TIME & "〜" & END_TIME & " " & _
            CStr((END_TIME - START_TIME) * 24 * 60 * 60) & "秒"
    End Sub
    
    これは、本文記述中に即興で作成したため、コメント一切抜きになってしまいました。シリアル値の引き算で秒数を算出しているので秒未満は精度がありません。

  • 続いて、VB.NETの方の「起動フォーム」側のソースコードです。
    最初の画像で説明した通り、「起動フォーム」と「処理フォーム(画像のピンク色の部分)」に別れていて、 「起動フォーム」は実際の処理は行なわずに画面でクリックされたボタンの情報(今回はインデックス番号)を 「処理フォーム」を起動して通知するだけの処理を行ないます。
    
    '***************************************************************************************************
    '   Excelへのデータ貼り付けテスト(起動フォーム)               dlgExcelKidou(Form)
    '
    ' ※Excelワークシートへのデータ貼り付けの方法による処理時間を計測してみます。
    '  データは単なる1からの連番ですが、100列×1000行で、つまり10万件です。
    '   4種類の方法は、
    '   ・1セルずつ直接数値データを貼り付ける
    '   ・1行ずつ一次配列変数に格納してから1行(100列)単位にデータを貼り付ける
    '   ・100列×1000行の二次元配列にデータをセットしてから全体を一回で貼り付ける
    '   ・一旦JAG配列に格納してから二次元配列に変換させて全体を一回で貼り付ける
    '   となります。処理後に処理時間をメッセージ表示します。
    '
    ' ※もう一つですが、このような一括系処理をWindowsフォームで行なう場合の問題で
    '   処理中にオペレータが他のボタンをクリックしてしまうとか、フォーム自体を
    '   閉じてしまう、といったことが起きる可能性があります。
    '   そこでオペレータに処理中であることが視覚的に判るようにサブフォームを開いて
    '   そのサブフォーム側で実際の処理を行なうようにしてみました。
    '   サブフォームはモーダル(TopMost)にしてControlBox=Falseとして処理中は
    '   閉じられないようにしてプログレスバーで処理進捗が判るようにしています。
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '***************************************************************************************************
    ' 変更日付 Rev   変更履歴内容---------------------------------------------------------------------->
    ' 09/06/21(1.0.0.0)新規作成
    ' 17/04/16(1.0.1.0)記述統制見直し、GP_SYORI_MODE4の追加
    '***************************************************************************************************
    Public Class dlgExcelKidou
        '===============================================================================================
        Private Const g_cnsTitle As String = "Excelへのデータ貼り付けテスト"
        Private ReadOnly g_tblSYORI_MODE() As String = {"", "@", "A", "B", "C"}
    
        '***********************************************************************************************
        '   ■■■ フォーム上のコントロールイベント ■■■
        '***********************************************************************************************
        '* 処理名 :Button1_Click
        '* 機能  :Excelへのデータ貼り付けテスト@
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :(既定)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2009年06月21日
        '* 更新者 :井上 治
        '* 機能説明:※1セルずつ直接数値データを貼り付ける
        '* 注意事項: ⇒実際の処理はサブフォーム(dlgExcelSyori)で行なわれます。
        '***********************************************************************************************
        Private Sub Button1_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles Button1.Click
            '-------------------------------------------------------------------------------------------
            ' サブフォーム(処理フォーム)の呼び出し
            Call GP_SHOW_SUBFORM(1)
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :Button2_Click
        '* 機能  :Excelへのデータ貼り付けテストA
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :(既定)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2009年06月21日
        '* 更新者 :井上 治
        '* 機能説明:※1行ずつ一次配列変数に格納してから1行(100列)単位にデータを貼り付ける
        '* 注意事項: ⇒実際の処理はサブフォーム(dlgExcelSyori)で行なわれます。
        '***********************************************************************************************
        Private Sub Button2_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles Button2.Click
            '-------------------------------------------------------------------------------------------
            ' サブフォーム(処理フォーム)の呼び出し
            Call GP_SHOW_SUBFORM(2)
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :Button3_Click
        '* 機能  :Excelへのデータ貼り付けテストB
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :(既定)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2009年06月21日
        '* 更新者 :井上 治
        '* 機能説明:※100列×1000行の二次元配列にデータをセットしてから全体を一回で貼り付ける
        '* 注意事項: ⇒実際の処理はサブフォーム(dlgExcelSyori)で行なわれます。
        '***********************************************************************************************
        Private Sub Button3_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles Button3.Click
            '-------------------------------------------------------------------------------------------
            ' サブフォーム(処理フォーム)の呼び出し
            Call GP_SHOW_SUBFORM(3)
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :Button4_Click
        '* 機能  :Excelへのデータ貼り付けテストC
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :(既定)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2017年04月16日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月16日
        '* 更新者 :井上 治
        '* 機能説明:※一旦JAG配列に格納してから二次元配列に変換させて全体を一回で貼り付ける
        '* 注意事項: ⇒実際の処理はサブフォーム(dlgExcelSyori)で行なわれます。
        '***********************************************************************************************
        Private Sub Button4_Click(ByVal sender As System.Object, _
                                  ByVal e As System.EventArgs) Handles Button4.Click
            '-------------------------------------------------------------------------------------------
            ' サブフォーム(処理フォーム)の呼び出し
            Call GP_SHOW_SUBFORM(4)
        End Sub
    
        '***********************************************************************************************
        ' ■■■ 共通サブ処理 ■■■
        '***********************************************************************************************
        '* 処理名 :GP_SHOW_SUBFORM
        '* 機能  :サブフォーム(処理フォーム)の呼び出し
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = 処理モード値(Integer)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月16日
        '* 更新者 :井上 治
        '* 機能説明:
        '* 注意事項:
        '***********************************************************************************************
        Private Sub GP_SHOW_SUBFORM(ByVal intSYORI_MODE As Integer)
            '-------------------------------------------------------------------------------------------
            Dim strTitle As String = g_cnsTitle & g_tblSYORI_MODE(intSYORI_MODE) ' 処理タイトル
            Dim tspJIKAN As TimeSpan                                    ' 処理時間(TimeSpan)
            Using objSyoriForm As New dlgExcelSyori
                With objSyoriForm
                    .prpSYORI_MODE = intSYORI_MODE
                    .ShowDialog(Me)
                    tspJIKAN = .prpSYORI_JIKAN
                End With
            End Using
            Dim strMSG As String = "処理時間は、" & tspJIKAN.TotalSeconds.ToString & "秒です。" ' MSG
            MessageBox.Show(Me, _
                            strMSG, _
                            strTitle, _
                            MessageBoxButtons.OK, _
                            MessageBoxIcon.Information)
        End Sub
    
        '----------------------------------------<< End of Source >>------------------------------------
    End Class
    
    「処理フォーム」の方は、「処理モード」と「処理時間」の2つのプロパティを仕込んであるので、「処理モード」を渡してモーダル表示させ、「処理時間」を受け取ります。 この部分はほとんど共通な記述なのでサブ処理にしておいて、各ボタンのクリックイベントからは「処理モード」を引数にしてサブ処理を呼ぶだけにしてあります。

  • 最後は、「処理フォーム」側のソースコードです。
    このページで表示しているのは「参照設定版」ですが、上の画像のクリックからダウンロードされるプロジェクトサンプルには「参照設定版」「実行時バインド版」の両方を収容してあります。
    
    '***************************************************************************************************
    '   Excelへのデータ貼り付けテスト(処理フォーム:参照設定版)         dlgExcelSyoriE(Form)
    '
    ' ※Excelワークシートへのデータ貼り付けの方法による処理時間を計測してみます。
    '  データは単なる1からの連番ですが、100列×1000行で、つまり10万件です。
    '   4種類の方法は、
    '   ・1セルずつ直接数値データを貼り付ける
    '   ・1行ずつ一次配列変数に格納してから1行(100列)単位にデータを貼り付ける
    '   ・100列×1000行の二次元配列にデータをセットしてから全体を一回で貼り付ける
    '   ・一旦JAG配列に格納してから二次元配列に変換させて全体を一回で貼り付ける
    '   となります。処理後に処理時間をメッセージ表示します。
    '
    ' ※もう一つですが、このような一括系処理をWindowsフォームで行なう場合の問題で
    '   処理中にオペレータが他のボタンをクリックしてしまうとか、フォーム自体を
    '   閉じてしまう、といったことが起きる可能性があります。
    '   そこでオペレータに処理中であることが視覚的に判るようにサブフォームを開いて
    '   そのサブフォーム側で実際の処理を行なうようにしてみました。
    '   サブフォームはモーダル(TopMost)にしてControlBox=Falseとして処理中は
    '   閉じられないようにしてプログレスバーで処理進捗が判るようにしています。
    '
    '   作成者:井上治  URL:http://www.ne.jp/asahi/excel/inoue/ [Excelでお仕事!]
    '***************************************************************************************************
    ' 変更日付 Rev   変更履歴内容---------------------------------------------------------------------->
    ' 09/06/21(1.0.0.0)新規作成
    ' 17/04/16(1.0.1.0)記述統制見直し、GP_SYORI_MODE4の追加
    ' 17/04/21(1.0.1.0)Excel関連クラス統合作業
    '***************************************************************************************************
    Imports Microsoft.Office.Interop
    Friend Class dlgExcelSyoriE
        '===============================================================================================
        Private Const g_cnsTitle As String = "Excelへのデータ貼り付けテスト"
        ' セルに貼り付ける値の範囲
        Private Const g_cnsIX_MIN As Integer = 1
        Private Const g_cnsIX_MAX As Integer = 100000
        ' カラム数
        Private Const g_cnsCOLCNT As Integer = 100
        '-----------------------------------------------------------------------------------------------
        '   親フォームとの受け渡し用変数
        Private g_intSYORI_MODE As Integer                          ' 処理モード
        Private g_tspSYORI_JIKAN As TimeSpan                        ' 処理時間
    
        '***********************************************************************************************
        ' ■■■ フォームイベント ■■■
        '***********************************************************************************************
        '* 処理名 :Form_Shown
        '* 機能  :フォームイベント(Shown)
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :(既定)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:
        '* 注意事項:
        '***********************************************************************************************
        Private Sub Form_Shown(ByVal sender As Object, _
                               ByVal e As System.EventArgs) Handles Me.Shown
            '-------------------------------------------------------------------------------------------
            With Me
                With .ProgressBar1
                    .Minimum = g_cnsIX_MIN
                    .Maximum = g_cnsIX_MAX
                    .Value = g_cnsIX_MIN
                End With
                .Refresh()
            End With
            Application.DoEvents()
            '-------------------------------------------------------------------------------------------
            Dim objWbk As Excel.Workbook = Nothing                      ' Excel.Workbook
            ' Excel出力クラスの初期化(Escキーのイベントはクラス側に実装済)
            Using clsExcel = New clsAboutExcel1(Me, True)
                '---------------------------------------------------------------------------------------
                Dim strMSGHeader As String = String.Empty               ' メッセージヘッダ
                Try
                    '-------------------------------------------
                    ' Excel出力(処理本体)
                    Call GP_MakeExcelSheet(clsExcel, objWbk, strMSGHeader)
    
                Catch ex As Exception
                    '-------------------------------------------
                    ' 処理中例外メッセージの表示
                    Call clsExcel.ShowFatalMessage(g_cnsTitle, strMSGHeader, ex.Message)
                    ' 例外時後始末(但しExcel応答無しなどではここでの対応は働かない!)
                    Call clsExcel.SuspendExcelProc(objWbk)
                End Try
                '---------------------------------------------------------------------------------------
            End Using
            '-------------------------------------------------------------------------------------------
            ' 閉じる
            Me.Close()
        End Sub
    
        '***********************************************************************************************
        ' ■■■ サブ処理 ■■■
        '***********************************************************************************************
        '* 処理名 :GP_MakeExcelSheet
        '* 機能  :Excel出力(処理本体)
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = Excel出力クラス(Object)
        '*      Arg2 = Excel.Workbook(Object)
        '*      Arg3 = 例外時メッセージヘッダ(String)          ※Ref参照
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2017年04月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:
        '* 注意事項:本処理中の例外は上位でトラップされる(この処理内ではトラップしない)
        '***********************************************************************************************
        Private Sub GP_MakeExcelSheet(ByRef clsExcel As clsAboutExcel1, _
                                      ByRef objWbk As Excel.Workbook, _
                                      ByRef strMSGHeader As String)
            '-------------------------------------------------------------------------------------------
            strMSGHeader = "Excel起動中"
            ' Excel起動(新規ワークブック)
            If Not clsExcel.GetWorkbook(String.Empty, objWbk, strMSGHeader, True) Then Exit Sub
            Dim objSH As Excel.Worksheet = objWbk.Worksheets(1)         ' Excel.Worksheet
            '-------------------------------------------------------------------------------------------
            ' 時間計測開始
            Dim dteSTART_TIME As Date = Now
            strMSGHeader = "Excel出力中"
            '-------------------------------------------------------------------------------------------
            ' 画面描画停止等
            Call clsExcel.StopScreenUpdate()
            '-------------------------------------------------------------------------------------------
            ' 処理モードによる分岐
            Select Case g_intSYORI_MODE
                Case 1
                    Call GP_SYORI_MODE1(objSH)
                Case 2
                    Call GP_SYORI_MODE2(objSH)
                Case 3
                    Call GP_SYORI_MODE3(objSH)
                Case 4
                    Call GP_SYORI_MODE4(objSH)
            End Select
            '-------------------------------------------------------------------------------------------
            ' 画面描画再開等終了処理
            Call clsExcel.SuspendExcelProc(objWbk, True)
            '-------------------------------------------------------------------------------------------
            ' 時間計測終了
            Dim dteFINISH_TIME As Date = Now
            g_tspSYORI_JIKAN = dteFINISH_TIME - dteSTART_TIME
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :GP_SYORI_MODE1
        '* 機能  :処理モード=1の処理
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = 処理対象シート(Object)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:1セルずつ直接数値データを貼り付ける
        '* 注意事項: ⇒引数は処理対象ワークシート
        '***********************************************************************************************
        Private Sub GP_SYORI_MODE1(ByRef objSH As Excel.Worksheet)
            '-------------------------------------------------------------------------------------------
            Dim intIx As Integer = g_cnsIX_MIN                          ' テーブルINDEX
            Dim intRow As Integer = 0                                   ' 行INDEX
            Dim intCol As Integer                                       ' カラムINDEX
            With objSH
                ' 全体ループ
                Do While intIx <= g_cnsIX_MAX
                    ' 行単位処理
                    intRow += 1                                         ' 行を加算
                    intCol = 1                                          ' カラムを先頭に戻す
                    ' 行内ループ
                    Do While intCol <= g_cnsCOLCNT
                        ProgressBar1.Value = intIx
                        ' 1セルずつ直接数値データを貼り付ける
                        .Cells(intRow, intCol).Value = intIx
                        ' 次の値、列へ
                        intIx += 1
                        intCol += 1
                    Loop
                Loop
            End With
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :GP_SYORI_MODE2
        '* 機能  :処理モード=2の処理
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = 処理対象シート(Object)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:1行ずつ一次配列変数に格納してから1行(100列)単位にデータを貼り付ける
        '* 注意事項: ⇒引数は処理対象ワークシート
        '***********************************************************************************************
        Private Sub GP_SYORI_MODE2(ByRef objSH As Excel.Worksheet)
            '-------------------------------------------------------------------------------------------
            Dim intIx As Integer = g_cnsIX_MIN                          ' テーブルINDEX
            Dim intRow As Integer = 0                                   ' 行INDEX
            Dim intCol As Integer                                       ' カラムINDEX
            Dim tblVal(g_cnsCOLCNT - 1) As Object                       ' 1行分のテーブル
            With objSH
                ' 全体ループ
                Do While intIx <= g_cnsIX_MAX
                    ' 行単位処理
                    intRow += 1                                         ' 行を加算
                    intCol = 0                                          ' カラムを先頭に戻す
                    ' 行内ループ
                    Do While intCol < g_cnsCOLCNT
                        ProgressBar1.Value = intIx
                        ' 配列に値をセット
                        tblVal(intCol) = intIx
                        ' 次の値、列へ
                        intIx += 1
                        intCol += 1
                    Loop
                    ' 配列をセル範囲(1行分)にセット
                    .Range(.Cells(intRow, 1), .Cells(intRow, g_cnsCOLCNT)).Value = tblVal
                Loop
            End With
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :GP_SYORI_MODE3
        '* 機能  :処理モード=3の処理
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = 処理対象シート(Object)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2009年06月21日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:100列×1000行の二次配列にデータをセットしてから全体を一回で貼り付ける
        '* 注意事項: ⇒引数は処理対象ワークシート
        '***********************************************************************************************
        Private Sub GP_SYORI_MODE3(ByRef objSH As Excel.Worksheet)
            '-------------------------------------------------------------------------------------------
            Dim intIx As Integer = g_cnsIX_MIN                          ' テーブルINDEX
            Dim intRow As Integer = 0                                   ' 行INDEX
            Dim intCol As Integer                                       ' カラムINDEX
            Dim tblVal((g_cnsIX_MAX / g_cnsCOLCNT) - 1, g_cnsCOLCNT - 1) As Object ' 2次元配列テーブル
            ' 全体ループ
            Do While intIx <= g_cnsIX_MAX
                ' 行単位処理
                intCol = 0                                              ' カラムを先頭に戻す
                ProgressBar1.Value = intIx
                ' 行内ループ
                Do While intCol < g_cnsCOLCNT
                    ' 配列に値をセット
                    tblVal(intRow, intCol) = intIx
                    ' 次の値、列へ
                    intIx += 1
                    intCol += 1
                Loop
                intRow += 1                                             ' 行を加算
            Loop
            ProgressBar1.Value = intIx - 1
            With objSH
                ' 2次元配列をセル範囲(全体)にセット
                .Range(.Cells(1, 1), .Cells(intRow, g_cnsCOLCNT)).Value = tblVal
            End With
        End Sub
    
        '***********************************************************************************************
        '* 処理名 :GP_SYORI_MODE4
        '* 機能  :処理モード=4の処理
        '-----------------------------------------------------------------------------------------------
        '* 返り値 :(なし)
        '* 引数  :Arg1 = 処理対象シート(Object)
        '-----------------------------------------------------------------------------------------------
        '* 作成日 :2017年04月16日
        '* 作成者 :井上 治
        '* 更新日 :2017年04月21日
        '* 更新者 :井上 治
        '* 機能説明:一旦JAG配列に格納してから二次元配列に変換させて全体を一回で貼り付ける
        '* 注意事項: ⇒引数は処理対象ワークシート
        '***********************************************************************************************
        Private Sub GP_SYORI_MODE4(ByRef objSH As Excel.Worksheet)
            '-------------------------------------------------------------------------------------------
            Dim intColMax As Integer = g_cnsCOLCNT - 1                  ' カラム方向最大INDEX
            Dim intRowMax As Integer = (g_cnsIX_MAX / g_cnsCOLCNT) - 1  ' 行方向最大INDEX
            Dim intIx As Integer = g_cnsIX_MIN                          ' テーブルINDEX
            Dim intRow As Integer = -1                                  ' 行INDEX
            Dim intCol As Integer                                       ' カラムINDEX
            Dim tblFld(intColMax) As Object                             ' 列方向テーブル
            Dim tblRec() As Object                                      ' 行方向テーブル
            ReDim tblRec(intRow)
            '-------------------------------------------------------------------------------------------
            ' 全体ループ
            Do While intIx <= g_cnsIX_MAX
                ' 行単位処理
                intCol = 0                                              ' カラムを先頭に戻す
                ProgressBar1.Value = intIx
                ' 行内ループ
                Do While intCol <= intColMax
                    ' 列方向テーブルに値をセット
                    tblFld(intCol) = intIx
                    ' 次の値、列へ
                    intIx += 1
                    intCol += 1
                Loop
                ' 行方向を加算
                intRow += 1                                             ' 行を加算
                ' 行方向テーブル(JAG配列)に格納
                ReDim Preserve tblRec(intRow)
                tblRec(intRow) = tblFld.Clone
            Loop
            ProgressBar1.Value = intIx - 1
            '-------------------------------------------------------------------------------------------
            Dim tblVal(intRowMax, intCol) As Object ' 2次元配列テーブル
            intRow = 0
            ' JAG配列テーブルを二次元配列テーブルに置き換え
            Do While intRow <= intRowMax
                ' 行単位処理
                For intCol = 0 To intColMax
                    ' 配列に値をセット
                    tblVal(intRow, intCol) = tblRec(intRow)(intCol)
                Next intCol
                ' 次行へ
                intRow += 1
            Loop
            '-------------------------------------------------------------------------------------------
            ' 2次元配列をセル範囲(全体)にセット
            With objSH
                .Range(.Cells(1, 1), .Cells(intRow, g_cnsCOLCNT)).Value = tblVal
            End With
        End Sub
    
        '***********************************************************************************************
        ' ■■■ 呼び出しフォームとの受け渡しプロパティ ■■■
        '***********************************************************************************************
        ' 処理モード(1=1セルずつ処理、2=1行ずつ処理、3=全体処理
        '-----------------------------------------------------------------------------------------------
        Friend WriteOnly Property prpSYORI_MODE() As Integer
            Set(ByVal value As Integer)
                g_intSYORI_MODE = value
            End Set
        End Property
    
        '===============================================================================================
        ' 処理時間(TimeSpan)
        '-----------------------------------------------------------------------------------------------
        Friend ReadOnly Property prpSYORI_JIKAN() As TimeSpan
            Get
                prpSYORI_JIKAN = g_tspSYORI_JIKAN
            End Get
        End Property
    
        '----------------------------------------<< End of Source >>------------------------------------
    End Class
    
    「処理フォーム」は開いた瞬間から処理を動かすのでShownイベントで記述をしています。
    Shownイベントの中でプログレスバーの初期化を行なって、Excelを起動して新規ワークブックを用意し、 そのワークブックの最初のシートを引数として各「処理モード」の処理を呼び出します。
    処理の際に、Excelの画面描画、自動計算、イベントをそれぞれ停止させて、処理後に復旧させるところまでの範囲を時間計測させて、 開始・終了の時刻差異をTimeSpan型で「起動フォーム」に戻せるようにしています。
    後方の「共通サブ処理」は以前のページで紹介したものと全く同じです。