VBAの勉強を始めてみた

色々試しています。

現在アクティブなブックのシートに、複数ブックのデータを挿入貼り付けする

今回は、マクロを使い始めた人がまず最初にやってみそうなことを、やってみようかと思います。

やりたいことは単純に下記のようなこと。

a.xlsx、b.xlsx、c.xlsx、d.xlsx、・・・・・・z.xlsxのように行数が不定な複数ブックがあり、

f:id:kouten0430:20180430153125j:plain

f:id:kouten0430:20180430153143j:plain

f:id:kouten0430:20180430153154j:plain

f:id:kouten0430:20180430153203j:plain

 

これらのデータを、a.xlsxのデータ群の下側に挿入貼り付けをしていくというものです。(貼り付けされる側のブックは、b.xlsx、c.xlsx・・・・・・であってもよい)

f:id:kouten0430:20180430153216j:plain

 

今回のマクロの中身をざっくりと説明すると、Application.GetOpenFilenameというメソッドで、ファイル選択のためのダイアログボックスを開き、ブックを選択します(ブックは複数選択可能です)。
ここでは、a.xlsxにペーストするので、a.xlsxを除いた、b.xlsx~z.xlsxを選択。(a.xlsxは開いた状態にしておく)

f:id:kouten0430:20180430153338j:plain


ここで選択されたブックのブック名(パスを含んだFullName)が配列に格納されるため、配列の内容をFor Each ~Nextで順次読み取り

  • 取得したブック名を用いてブックを開く
  • ブックを開いた時にアクティブなシートのデータをコピー
  • 貼り付けされる側のブックのデータ群の1行下にインサート
  • 開いたブックを変更せずに閉じる

というループ処理を行います。

コピーを開始するための始点となる行はユーザーが指定する必要がありますが、終点となる行はプログラムが自動判別します。
なので、行数は不定であっても、見出しの位置や内容、列の数などは同様なデータの連結を想定しています。(そもそも、様式の異なる資料を連結しても意味ないですし。)

その他は、不具合防止のための処理などがずらずらと並んでいます。 

Sub 現在アクティブなブックに複数ブックのデータを挿入貼り付け()
    '現在アクティブなブックのデータ群の1行下に、選択されたブックのデータを、順番に挿入貼り付けしていきます。
    '対象となるのは、*.xlsx、*.xlsm、、*.csv等のエクセルで扱うことのできるファイルです。
    'ペーストされるブック(現在アクティブなブック)以外は閉じた状態で実行して下さい。
    'コピーするブックは、ペーストされる側のブックと同じ様式であることを想定しています。
    Dim awn As String
    Dim Bcdrive As String
    Dim Bcdir As String
    Dim lwfn As Variant
    Dim ctm As Integer
    Dim yr As Range
    Dim nod As Variant
    Dim anod As Long
    Dim y As Long
    Dim x As Integer
    Dim myWb As Variant
    Dim i As Integer
    Dim cts1 As Integer
    Dim lwn As String
    Dim myLis As ListObject
    Dim lye As Long
    Dim aye As Long
    Dim cts2 As Integer
    Dim ct As Integer
    
    awn = ActiveWorkbook.Name
        If Not awn Like "*.*" Then
            MsgBox "新規ブック上では実行できません。"
            Exit Sub
        End If
        
    Bcdrive = Left(CurDir, InStr(CurDir, ":") - 1)  '現在のカレントドライブを記憶
    Bcdir = CurDir  '現在のカレントディレクトリを記憶
    
    ChDrive Left(ActiveWorkbook.Path, InStr(ActiveWorkbook.Path, ":") - 1)    'カレントドライブを一時的に変更
    ChDir ActiveWorkbook.Path 'カレントディレクトリを一時的に変更
    
    lwfn = Application.GetOpenFilename(Title:="コピーするブックを選択(複数選択可)", MultiSelect:=True)
        If TypeName(lwfn) = "Boolean" Then  'キャンセルを押された場合の処理
            ChDrive Bcdrive
            ChDir Bcdir
            Exit Sub
        End If
        
    ctm = UBound(lwfn)

retry1:

    On Error Resume Next
    
    Set yr = Nothing 'retryした時のためのリセット

    Set yr = Application.InputBox(Prompt:="コピーの始点となる行のセルを選択し、OKして下さい。" & vbCrLf & "(見出しの1行下を推奨)", Type:=8)
        If yr Is Nothing Then    'キャンセルを押された場合の処理
            ChDrive Bcdrive
            ChDir Bcdir
            Exit Sub
        ElseIf yr.Resize(1, 1).Value = "" Then  '複数範囲を選択された場合も想定して、Resizeを適用する
            MsgBox "空白以外のセルを選択してください。"
            GoTo retry1
        End If

    On Error GoTo 0
    
retry2:
    
    nod = Application.InputBox(Prompt:="行×列 に含まれる、最大データ数を指定して下さい。" & vbCrLf & _
    "・コピーするブックがこの値を超えた場合、処理をスキップします。" & vbCrLf & _
    "・フリーズ防止策です。環境に応じて増減して下さい。", Default:="10000", Type:=1)
        If TypeName(nod) = "Boolean" Then  'キャンセルを押された場合の処理
            ChDrive Bcdrive
            ChDir Bcdir
            Exit Sub
        Else
            anod = WorksheetFunction.CountIf(Range("A1:XFD1048576"), "<>")
            If anod > nod Then
                MsgBox "ペーストされる側のブックに" & Format(nod, "#,###") & "を超える、" & Format(anod, "#,###") & "データが存在します。" _
                & vbCrLf & vbCrLf & "フリーズを避けるために、軽量なブック上で実行することを推奨します。" & vbCrLf & _
                "コピーする側のブックではさらにメモリを必要としますので、必要に応じ、" & vbCrLf & "最大データ数を増減して下さい。"
                GoTo retry2
            End If
        End If
    
    Application.ScreenUpdating = False  '画面表示の更新をオフにする
    
    y = yr.Row  'yrから行を取得
    x = yr.Column   'yrから列を取得
    
    If Not ActiveSheet.AutoFilter Is Nothing Then   '念のための処理1
        Cells(y, x).AutoFilter
    End If

    For Each myWb In lwfn
        For i = 1 To Workbooks.Count
            If Workbooks(i).FullName = myWb Then    '既に開いているブックは処理をスキップ
                If Not myWb Like "*" & awn Then 'ペーストされるブックはスキップにカウントしない
                    cts1 = cts1 + 1
                End If
                GoTo skip
            End If
        Next i
        
        Workbooks.Open Filename:=myWb 'ブックを開くと同時にアクティブとなる
        lwn = ActiveWorkbook.Name   'パスを含まないブック名を取得しておく
        
        If ActiveSheet.ListObjects.Count > 0 Then   '念のための処理2
            For Each myLis In ActiveSheet.ListObjects
                myLis.Unlist
            Next myLis
        End If
        
        If Not ActiveSheet.AutoFilter Is Nothing Then   '開いたブックのシートにオートフィルターが設定されている場合は解除する
            Cells(y, x).AutoFilter
        End If
        
        lye = Cells(y, x).CurrentRegion.Rows(Cells(y, x).CurrentRegion.Rows.Count).Row
            
        If WorksheetFunction.CountIf(Rows(y & ":" & lye), "<>") <= nod Then '指定した最大値以下の場合のみ処理する
        
            Rows(y & ":" & lye).Copy
        
            Workbooks(awn).Activate
            aye = Cells(y, x).CurrentRegion.Rows(Cells(y, x).CurrentRegion.Rows.Count).Row
            Rows(aye + 1).Insert
        
            Workbooks(lwn).Application.CutCopyMode = False  'コピーモードを解除する
            
        Else
        
            cts2 = cts2 + 1
            
        End If
        
        Workbooks(lwn).Close savechanges:=False
        DoEvents    'Closeが完了するまで待つ

skip:

        ct = ct + 1
        Application.StatusBar = "処理実行中..." & ct & "/" & ctm
        
    Next myWb
    
    ChDrive Bcdrive
    ChDir Bcdir
    
    MsgBox "処理成功:" & ct - cts1 - cts2 & "/" & ctm & vbCrLf & "スキップ:" & cts1 + cts2 & "(Open重複:" & cts1 & _
    ",最大数超過:" & cts2 & ")"
    
    Application.StatusBar = False

End Sub

 

このマクロの動作確認をするために、色々なブックを使ってテストしてみたんですが、データをコピーする際にデータ数があまりに多いとフリーズしてしまうようです。
私の環境では約35,000を超えるデータ(1,000行×35列にびっしりとデータが詰まっている感じ)から、動作が不安定になるので、環境によって取り扱えるデータ数を増減できるようにしてみました。(ユーザーが指定したデータ最大数を超えてコピーしようとすると、コピーせずに次のブックに処理を飛ばします)
感覚的に、同じデータ数でも、縦に長いより横に長いほうが処理が思いように感じます。
コピーする場合ほどではないにせよ、ペーストされる側のブック(常時開いたままになるブック)にデータが多い場合も、処理が重くなるように感じたので、これも先に指定した値を超えるデータを含むブックであった場合、処理できない(マクロを実行できない)ようにしました。
フリーズを避けるために、がんじがらめにしてしまいましたが、PCのスペックが高いのならば最大値を2,147,483,647(約46340行×46340列)まで拡げることもできるのではないかと思います。

 

補足:

  • ブックを処理する順番は、ファイル選択のダイアログボックスで選択した順ではなく、ダイアログボックスでの表示順(上から)になります(要するに上から順番に配列に格納される)。なので、順番を変えたい場合は、ダイアログボックスで右クリックし、名前順・更新日時順・サイズ順などで並び替えて下さい。

    f:id:kouten0430:20180430154007j:plain

  • 処理の対象となるブックにブック保護・シート保護・セル保護などがかかっていないことを確認して下さい。
  • データの下端はCurrentRegionで判定します。CurrentRegionでどのような範囲が取得されるかは、該当のセルで「Shift」+「Ctrl」+「*」を押せば確認することができます。(該当のセルが空白でも、隣接する8方向のいずれかにデータが存在していれば、そこから空白で囲まれたデータ郡を矩形範囲で選択します)

 

ブックを開く・コピーする・ペーストする・ブックを閉じるといった、人間が行う処理をマクロで原始的に行っているだけなので、数が多くなるとどうしても処理に時間がかかります。
といっても、トイレに行って戻ってくる頃には終わっていますが・・・・・・。
もっとこう、せっかくプログラムを使うのだから、内部的に処理できないものなのかな?(画面表示の更新をFalseにするという意味ではなく)