[[20161114125402]] 『【VBA】全体を通したソースコードの見直し』(Seto) ページの最後に飛ぶ

[ 初めての方へ | 一覧(最新更新順) | 全文検索 | 過去ログ ]

 

『【VBA】全体を通したソースコードの見直し』(Seto)

先日よりディレクトリから画像ファイルを検出及び貼り付けをするスクリプトについて質問させて頂いた物です。
とりあえず目的の動作をこなせる形でコードが完成したので、その件につきましては完了となりました。

そのスレッドでもご説明した通り、VBAを学び始めて一週間も経っていない為、コードの書き方が非常に雑になっていると思われます。
画像を大量に扱うスクリプトの為、高速化等が見込まれるのであればそれを目的としたアップデートも行いたいですし、なにより単純にスマートなコードの書き方を学びたいと考えています。

下記に今回作成したコードを公開しますので、お気づきの点があれば指摘していただけるとありがたいです。

−−−−−−−−−−−−−code−−−−−−−−−−−−−

Option Explicit
'------------------------------------------------------------------------
'◆VBA
'
'◆動作概要
' 選択したディレクトリから一階層下までディレクトリの検出、
' 検出されたディレクトリの中から画像ファイルのみを判別、
' 画像ファイルに対してシートへの貼り付けを実行。
'
'◆仕様
' 画像ファイルは画像ファイル用ルートディレクトリから一階層下のみに保管する事
'------------------------------------------------------------------------

Dim Root_path As Variant, Sub_Dir As Variant, File_Name As Variant, pt As Double

Sub Select_Root_of_Image()

    With Application.FileDialog(msoFileDialogFolderPicker)
        .Show
        Root_path = .SelectedItems(1)
    End With

    ScanDir (Root_path)

End Sub

Sub ScanDir(Root_path As Variant)

    Dim CO_SFSO, Root_Dir
    Set CO_SFSO = CreateObject("Scripting.FileSystemObject")
    Set Root_Dir = CO_SFSO.Getfolder(Root_path)

    For Each Sub_Dir In Root_Dir.SubFolders
        MakeSheet CStr(Sub_Dir)
        ScanFile CStr(Sub_Dir)
    Next Sub_Dir

End Sub

Sub MakeSheet(Sub_Dir As Variant)

    Dim Sheet_Name As String
    Worksheets.Add
    Sheet_Name = Right(Sub_Dir, InStr(StrReverse(Sub_Dir), "\") - 1)
    ActiveSheet.Name = Sheet_Name

End Sub

Sub ScanFile(Sub_Dir As Variant)

    Dim CO_SFSO, File_Names, is_Image, POS
    Set CO_SFSO = CreateObject("Scripting.FileSystemObject")
    is_Image = True

    For Each File_Name In CO_SFSO.Getfolder(Sub_Dir).Files

        POS = InStrRev(File_Name, ".")

        If POS > 0 Then
            Select Case LCase(Mid(File_Name, POS + 1))
                Case "jpeg"
                Case "jpg"
                Case "png"
                Case Else
                    is_Image = False
            End Select
        Else
            is_Image = False
        End If

        If is_Image = True Then
            PasteFile CStr(File_Name)
        End If

    Next File_Name

End Sub

Sub PasteFile(File_Name As Variant)

    Set File_Name = ActiveSheet.Shapes.AddPicture( _
        filename:=File_Name, _
        LinkToFile:=False, SaveWithDocument:=True, _
        Left:=Selection.Left, Top:=Selection.Top, _
        Width:=0, Height:=0)

    File_Name.ScaleHeight 1!, msoTrue
    File_Name.ScaleWidth 1!, msoTrue

    MoveDown File_Name.Height

End Sub
Sub MoveDown(pt As Double)

    Dim moved As Double
    moved = 0

    Do While moved <= pt
        moved = moved + ActiveCell.Height
        ActiveCell.offset(1, 0).Activate
    Loop

    ActiveCell.offset(2, 0).Activate

End Sub

< 使用 Excel:unknown、使用 OS:Windows7 >


VBA初心者であっても、C言語の熟練者っぽいコードのようなので、そのつもりのアドバイスなぞ。

Select_Root_of_Image()
・ダイアログで指定せずにキャンセルした場合はどうする?

ScanDir()
・2度目の実行時、既に同名のシートが存在するのでエラーになるが、どうする?
(元の質問内容には含まれなかったので、私も手抜きしましたが、元シートへの追記を考えても良いのでは?)

ScanFile()
・フォルダ数分呼ばれる訳ですが、その度にCreateObjectしているので、どんどんメモリを食っていきますよ?
(使い終わったリソースは、開放すべき。プロシジャ終了時にVBAが自動開放してくれますが、この場合は明示した方が良い)
・処理無しCase文の縦の列挙は格好悪いです。また、目的の拡張子ならば処理、というコーディングの方が自然では?
・過剰なフラグ化は、コードが読みづらくなります。変数 is_Image を使わずに書いてみては?

MoveDown()
・大した処理ではないので、PasteFile() に含めてみては? VBAはインタプリンタ言語なので、完全な最適化より、プロシジャ呼び出しのオーバーヘッドを減らす方が良いでしょう。
・ActiveCellやoffsetによる相対指定は、後でコードを読んだときにロジックが判りにくいと感じます。
(???) 2016/11/14(月) 13:31


レスありがとうございます。
Cもてんで分かりません、プログラミングと言えばJavaでハイアンドローを趣味で作ったのとWeb系の言語で古臭いHPが組める程度です。

・そもそもディレクトリを選択しなかった場合は、下位メソッドで指定エラーが出る為、それ以上コードが走らないはずと考えています。
・同一名によるエラーは、Root_DirのサブディレクトリをSub_Dirに一つずつ格納しながら下位メソッドを呼び出すfor文になっている為、サブディレクトリそのものが同名で存在しない限りエラーは起こらないと考えています。(少なくとも私の環境では動作確認済みです)
・CreateObjectの根本的な動作について理解していませんでした、これは怖いですね、、、スグに修正します。
・動作フローとCase文の記載方法について、仰るとおりだと思います、自分でも何故こんなややこしい書き方にしたのか分かりません。
・MoveDown()のオーバーヘッド削減の考え方については、そもそもインタプリタ言語と言う物を理解していないので、根本的に学習し直してから修正を検討してみたいと思います。
・ActiveCellとOffsetについて、VBAを習う上でOffsetとRangeとColumnにActiveCellを絡めて使えるようになればセル移動に困らないと考えこれ関係ばかり記事を読んだので、むしろこれ以外の方法が分からない状態です。このあたりはもう少し基礎から調べてみます。

多くの意見が頂けて非常に助かります。

(Seto) 2016/11/14(月) 14:12


少し指摘追加します。

Select_Root_of_Image()
・試せば一瞬で判る事でしたが、ご理解頂けなかったようなので、対策だけ。

        If .Show <> True Then Exit Sub

欄外
・先頭で4つの変数を宣言してますが、この位置に宣言すると、モジュール内共通変数になります。 つまり、引数指定せずとも、他のプロシジャ全てで値の参照やセットができる、ということ。 それなのに、作成したプロシジャには引数宣言があります。 これはミスの元なので、止めましょう。 カプセル化の考え方を壊す使い方です。
(呼び元のプロシジャ内で宣言し、引数指定で受け渡すべき。スコープは最小限にすること)

MakeSheet()
・StrReverse命令は、存在を忘れてしまっても良いくらい、出番のないものです。 文字列を全部ひっくり返すという、どう考えても遅い命令です。 他のプロシジャではInStrRevを使っているのだから、統一しましょう。

    Sheet_Name = Right(Sub_Dir, InStr(StrReverse(Sub_Dir), "\") - 1)
        ↓
    Sheet_Name = Mid(Sub_Dir, InStrRev(Sub_Dir, "\") + 1)

MoveDown()
・Offset や Activate を避ける方法として、Cells(行、列) でセル指定する方法があります。 これだと絶対位置指定なので、判りやすいでしょう。 そもそも、ループして比較せずとも、画像サイズとセルサイズを比べれば、次はどのセルかが判明するのでは?
(セル高さが1行毎に違う、とかならば意味がありますが、遅そう)
そもそも、AddPictureに指定する値は座標なのだから、セル位置は使わない方が良いのでは?
(???) 2016/11/14(月) 15:28


マクロ起動時のダイアログ参照について、コードを見てやっと意味が分かりました。
自分で使うだけのコードなので、正常終了しなくとも固定のエラーが出る特定動作まで潰し切る考えが無かったようです。

グローバル変数宣言の乱用、と言う奴ですね。
スコープは最小限にと言う言葉は今後コードを書く際に肝に銘じて作業します。

InStrRevのコード、非常に助かりました。
ここは私のほうでも統一をさせようとしていたのですが、エラーをはく為保留としていました。
正解を見てしまったせいか自分がどう間違っていたかが思い出せなくなってしまったのですが、各コード別に関連付けて学習し理解出来るよう努力します。

確かに、MoveDownは結構無駄と言うか適性の低い処理になっていますね。
仰られている事をスグにコードにたたき出す知識が無いので、方法を模索して修正に望みます。

淡々とレスポンスを返していますが目から鱗ばかりで本当に助かっています。
(Seto) 2016/11/14(月) 16:26


本日はそろそろ作業環境から離れる為、ここまで意見を参考にしながら修正したコードを改めて公開しておきます。
残りの課題はやはりMoveDown上の処理を適性の高い物にシフトする事ですかね。

Option Explicit
'--------------------------------------------------------------------------------------
'◆VBA
'
'◆動作概要
' 選択したディレクトリから一階層下までディレクトリの検出、
' 検出されたディレクトリの中から画像ファイルのみを判別、
' 画像ファイルに対してシートへの貼り付けを実行。
'
'◆仕様
' 画像ファイルは画像ファイル用ルートディレクトリから一階層下のみに保管する事
'--------------------------------------------------------------------------------------

Sub Select_Root_of_Image()
'各画像ファイルが格納されたディレクトリを管理しているルートディレクトリを選択

    Dim Root_path As Variant

    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show <> True Then Exit Sub
        'ファイル参照されなかった場合スクリプトを終了
        Root_path = .SelectedItems(1)
    End With
    'Windowsのダイアログを使用して選択、選択後は値を変数へ代入

    ScanDir (Root_path)
    'ルートディレクトリのフルパスを引数にして下位プロシージャの呼び出し

End Sub

Sub ScanDir(Root_path As Variant)
'ルートディレクトリ直下にあるサブディレクトリの検出

    Dim CO_SFSO, Root_Dir, Sub_Dir
    Set CO_SFSO = CreateObject("Scripting.FileSystemObject")
    Set Root_Dir = CO_SFSO.Getfolder(Root_path)
    'CreateObjectを使用しルートディレクトリ直下のサブディレクトリをオブジェクト化して変数へ代入

    For Each Sub_Dir In Root_Dir.SubFolders
        Set CO_SFSO = Nothing
        '*****メモリ開放(位置を確認)
        NewSheetAndScanFiles (Sub_Dir)
    Next Sub_Dir
    'For文内でプロシージャ内の上位メソッドで検出したサブディレクトリを一つずつ判定
    'その判定毎にシート作成と画像ファイル検出の下位プロシージャを呼び出し

End Sub

Sub NewSheetAndScanFiles(Sub_Dir)
'引数サブディレクトリ内から条件分岐等で画像ファイルのみを判定させる

    Dim CO_SFSO, File_Name, File_Names, isImage, POS, Sheet_Name As String
    Worksheets.Add
    Sheet_Name = Mid(Sub_Dir, InStrRev(Sub_Dir, "\") + 1)
    ActiveSheet.Name = Sheet_Name
    'シート作成後サブディレクトリのフルパスから、末尾の名前のみを抽出、それをディレクトリ名へ適用

    Set CO_SFSO = CreateObject("Scripting.FileSystemObject")
    'CreateObjectでサブディレクトリ内の画像ファイルをオブジェクト化

    For Each File_Name In CO_SFSO.Getfolder(Sub_Dir).Files
    '同プロシージャ内でオブジェクト化したファイル群をFor文で一つずつ処理
        Set CO_SFSO = Nothing
        '*****メモリ開放(位置を確認)

        POS = InStrRev(File_Name, ".")
        '拡張子に伴うピリオドの位置を変数に代入した値

        If POS > 0 Then
            Select Case LCase(Mid(File_Name, POS + 1))
                Case "jpeg", "jpg", "png"
                    PasteFile CStr(File_Name)
                Case Else
            End Select
        Else
        End If
        '上記変数とそれ以降の文字列から画像ファイルか否かの判定
        '画像ファイルと判定された場合のみペースト用のプロシージャを呼び出し

    Next File_Name

End Sub

Sub PasteFile(File_Name As Variant)
'引数で受け取ったファイルパスを元にアクティブセルへ画像をペースト

    Dim moved As Double
    moved = 0
    'このプロシージャは上位プロシージャ内のFor文内で処理される物なので
    '画像間隔を統一する為にプロシージャ呼び出し毎に変数を初期化

    Set File_Name = ActiveSheet.Shapes.AddPicture( _
        filename:=File_Name, _
        LinkToFile:=False, SaveWithDocument:=True, _
        Left:=Selection.Left, Top:=Selection.Top, _
        Width:=0, Height:=0)
        '引数の内容をそのままアクティブセルへ貼り付け
        'プロパティの参照等は行っていないので画像サイズが不明なので
        '0*0の仮想的な最小サイズで一時的に対応

    File_Name.ScaleHeight 1!, msoTrue
    File_Name.ScaleWidth 1!, msoTrue
    '最小サイズで一時対応だった画像ファイルを適切なサイズへ修正
    '元ファイルからサイズ情報をそのまま参照し、等倍でサイズ決定

    Do While moved <= File_Name.Height
        moved = moved + ActiveCell.Height
        ActiveCell.offset(1, 0).Activate
    Loop
    '引数のHeight以上になるまでセルを一つずつ動かしその位置を記憶

    ActiveCell.offset(2, 0).Activate
    'Heightに依存しない固定された間隔を設ける為の固定値移動
    '同プロシージャ内で判明した画像ファイルの高さを引数に下位プロシージャ呼び出し

End Sub
(Seto) 2016/11/14(月) 17:09


For文の内側でオブジェクト開放するのは大間違いです。1回しか作成していないのに、フォルダが複数あると、複数回開放しますよ?
また、コメント文は一般的には、処理の前か、同じ行の後ろに書きます。 次の行に書くものではないですよ。

序盤のみ整形した例です。

 '各画像ファイルが格納されたディレクトリを管理しているルートディレクトリを選択
 Sub Select_Root_of_Image()
    Dim Root_path As String

    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show <> True Then Exit Sub      'ファイル参照されなかった場合スクリプトを終了
        Root_path = .SelectedItems(1)       'Windowsのダイアログを使用して選択、選択後は値を変数へ代入
    End With

    Call ScanDir(Root_path)                 'ルートディレクトリのフルパスを引数にして下位プロシージャの呼び出し
 End Sub

 'ルートディレクトリ直下にあるサブディレクトリの検出
 Sub ScanDir(Root_path As String)
    Dim Root_Dir As Object
    Dim Sub_Dir As Object

    Set Root_Dir = CreateObject("Scripting.FileSystemObject").Getfolder(Root_path)
    For Each Sub_Dir In Root_Dir.SubFolders
        Call NewSheetAndScanFiles(Sub_Dir)  'シート作成と画像ファイル検出の下位プロシージャを呼び出し
    Next Sub_Dir
 End Sub

 '引数サブディレクトリ内から条件分岐等で画像ファイルのみを判定させる
 Sub NewSheetAndScanFiles(Sub_Dir As Object)
    Debug.Print Sub_Dir.Path
 End Sub
(???) 2016/11/14(月) 17:30

本当にこんな突っ込み所だらけのコードの修正に手伝って頂いてありがとうございます。
整形していただいた部分についてはこちらのローカルでもテストさせて頂きました。

明日、ご指摘の内容に沿って学習し、また午後にでも修正した結果を公開できればと考えています。
(Seto) 2016/11/14(月) 17:51


昨日はお世話になりました。
午前の空き時間に修正が済んだので改めてアップします。

セル移動に関しては無駄なDoWhileこそ無くしましたが、どうも個人的にActiveCellとOffsetの方が理解しやすいので、若干指摘からそれた方向性になっています。
オブジェクト参照の解放について場所があっているかどうかが少し不安です。

Option Explicit
'--------------------------------------------------------------------------------------
'◆VBA
'
'◆動作概要
' ルートディレクトリの選択、直下サブディレクトリ検出、サブディレクトリ毎のシート作成、
' サブディレクトリ内の画像検出、シートへのペーストと移動
'
'◆仕様
' 画像ファイルは画像ファイル用ルートディレクトリから一階層下のみに保管する事
'--------------------------------------------------------------------------------------

Sub Select_Root_of_Image() 'ルートディレクトリの選択

    Dim Root_path As Variant

    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show <> True Then Exit Sub      '選択されないと終了
        Root_path = .SelectedItems(1)
    End With

    ScanDir (Root_path)     '選択したフルパスを引数に設定
End Sub

Sub ScanDir(Root_path As Variant) '引数パス直下のサブフォルダ検出

    Dim dirFSO, Root_Dir As Object, Sub_Dir As Object
    Set dirFSO = CreateObject("Scripting.FileSystemObject")
    Set Root_Dir = dirFSO.Getfolder(Root_path)

    For Each Sub_Dir In Root_Dir.SubFolders     'オブジェクト参照で検出しFor文で回す
        Call NewSheetAndScanFiles(Sub_Dir)      '各サブフォルダのフルパスを引数に設定
    Next Sub_Dir

    Set dirFSO = Nothing    'プロシージャ終了前に明示的に解放
End Sub

Sub NewSheetAndScanFiles(Sub_Dir As Object) 'シートの挿入と画像ファイルの検出

    Dim imgFSO, File_Name, File_Names, POS, Sheet_Name As String
    Set imgFSO = CreateObject("Scripting.FileSystemObject")

    Worksheets.Add
    Sheet_Name = Mid(Sub_Dir, InStrRev(Sub_Dir, "\") + 1)
    ActiveSheet.Name = Sheet_Name       '引数パスからフォルダ名を抽出しシート名に反映
    ActiveCell.offset(3, 0).Activate        '見出しやタイトル用余白

    For Each File_Name In imgFSO.Getfolder(Sub_Dir).Files       'サブフォルダ検出時と手法は同じ
        POS = InStrRev(File_Name, ".")      '拡張子に付随するピリオドを変数化

        If POS > 0 Then     'ピリオド以降の文字列を利用し拡張子の判別
            Select Case LCase(Mid(File_Name, POS + 1))
                Case "jpeg", "jpg", "png"
                    PasteFile CStr(File_Name)       '画像ファイルと認識したら下位プロシージャへ
                Case Else
            End Select
        Else
        End If

    Next File_Name

    Set imgFSO = Nothing        'こちらもプロシージャ終了時に明示的に解放
End Sub

Sub PasteFile(File_Name As Variant) '画像貼り付けとそれに伴うセル移動

    Dim imgHeight As Double

    Set File_Name = ActiveSheet.Shapes.AddPicture( _
        filename:=File_Name, _
        LinkToFile:=False, SaveWithDocument:=True, _
        Left:=Selection.Left, Top:=Selection.Top, _
        Width:=0, Height:=0)        '元ファイルとのリンクは無し

    File_Name.ScaleHeight 1!, msoTrue
    File_Name.ScaleWidth 1!, msoTrue

    imgHeight = File_Name.Height / ActiveCell.Height        '画像の高さをセルを単位に算出
    imgHeight = Application.RoundUp(imgHeight + 2, 0)
    ActiveCell.offset(imgHeight, 0).Activate        '統一された余白確保の為固定値を足して整数分移動

End Sub

(Seto) 2016/11/15(火) 12:33


資源開放は問題ありません。思った通りに動作したかと思います。
が、コードは変数とオブジェクトの利用がごっちゃになっていて、見苦しい…。

再度直したコードをアップする必要はありませんし、そのままにしておいて、数年後にご自分でコードを読み直してみるのも良いですよ。とりあえず、指摘を列挙しておきますね。

Select_Root_of_Image()
・Root_pathは.SelectedItems(1) と、選択されたフォルダの先頭の文字列だけ格納しているので、Variant型である必要はありません。だから String型に変更しておいたのですが、理解して頂けなかったようです。変数のスコープはなるべく狭く、というのと同様に、型宣言もなるべく狭くした方が、コードの可読性が上がるのですよ。(Variant型だと、そこは配列が入るのか、日付型の可能性はあるのか、等、全体を読まないと値の範囲が判りませんが、String型ならば文字列なんだな、と一目で判ります)

ScanDir()
・引数は親フォルダ名1つしかあり得ないので、Variant型は不適切です。何でもかんでも可変定義するのは、手抜き。
・変数宣言は1行にまとめず、バラしましょう。例えば、

    Dim dirFSO, Root_Dir As Object, Sub_Dir As Object

こう書いていますが、dirFSOは指定がないので、Variant型になります。つまり、手抜き。読みにくい。予期せぬ型変換による不具合を潜在してしまう可能性。必要な場合以外は、Variant型は使わない癖を付けましょう。 なお、CreateObjectとオブジェクト生成しているのだから、dirFSOはObject型にすべきですね。
・Call NewSheetAndScanFiles(Sub_Dir) と、Sub_Dirオブジェクトを引数渡ししていますが、受け渡すサイズは小さい方が良い事を考えると、フォルダ名さえ渡せば良いので、Sub_Dir.Path を渡すべき。 整形例で Debug.Print を残したのは、プロパティを伝えたかったためですが、伝わらなかったようですね。 プロパティを省略するとPathが採用されるので、たまたま正しく動くだけです。これも手抜き。

Sub_Dirオブジェクトを使っている箇所にブレークポイントを設定し実行。止まったところでSub_Dirを「デバッグ」−「ウォッチ式の追加」としてみてください。Sub_Dirオブジェクトの持つプロパティやメソッドを確認することで、オブジェクト型の大きさを理解できる事でしょう。(ウォッチウィンドウのSub_Dirを右クリックし、削除することで、元に戻ります)

NewSheetAndScanFiles()
・これも同様。引数がオブジェクトなのか文字列なのか、はっきりさせること。現状は、Object型と明記していながら、文字列として使っているので、気持ち悪いコーディングになってます。たまたま正しく動いているだけです。
・他のプロシジャもですが、File_Names という変数は未使用です。必要な変数は必ず宣言し、不要な変数は宣言しない事が、可読性を上げます。

PasteFile()
・今回はアクティブセルのままとの事なのでこのままですが、もし座標またはセル位置を明示するならば、このプロシジャに引数追加し、Y座標またはセルの行番号を指定すると良いでしょう。 アクティブセルを利用するのは、カプセル化の考えから外れているので。

全体
・行後ろにコメントを書くときは、TABを利用して、縦が同じ位置になるよう統一すると、ロジックが追いやすくなります。
(Sub 宣言のコメントは、ロジック中のと揃える必要は無し)
(???) 2016/11/15(火) 14:30


素人の雑感ですが、参考になれば、、、、

>Sub Select_Root_of_Image()
確かにフォルダの選択から始まるのでしょうが、
どうせなら、フォルダーの選択を関数にした方がいいのかなと思いました。

メインの作業の流れは、

0)プログラム始め
1)  フォルダの選択
2)  選択したフォルダーの各サブフォルダーに対して繰り返し
3)    各サブフォルダーの中の各ファイルに対して繰り返し
4)      もし、各ファイルが、画像ファイルならシートに挿入
5)    次のファイルへ
6)  次のフォルダーへ
7)プログラム終わり

だと思うので、それを主体に書いて、
各作業をサブルーチンにしたらいいかなと思いました。

>CreateObject("Scripting.FileSystemObject")
が、別べつのプロシージャに出てくるので、
当初に一回開いて、
最後に閉じちゃえばいいかなと思いました。
また、FSOを使ってるならFSOで拡張子が簡単に得られるようなので、
そっちを使ったらいいかなぁと思いました。

>imgHeight = File_Name.Height / ActiveCell.Height
画像の大きさで、次の貼付位置を計算してるのかなぁ?
セルを相対位置で指定していけばいいかなぁと思いました。。。

Option Explicit

'※参照設定でMicrosoft Scripting Runtimeにチェックを入れること
Sub test()

    Dim sRoot_path As String
    Dim FSO As FileSystemObject
    Dim oFolder As Folder
    Dim oFile As File
    Dim rngTarget As Range
    Dim wbResult As Workbook

    If GetSelectFolder(sRoot_path) = False Then Exit Sub

    'Application.ScreenUpdating = False

    Set FSO = New FileSystemObject
    For Each oFolder In FSO.GetFolder(sRoot_path).SubFolders
        Set rngTarget = GetNewSheet(wbResult, oFolder.Name)
        For Each oFile In oFolder.Files
            Select Case FSO.GetExtensionName(oFile.Path)
                Case "jpeg", "jpg", "png", "JPEG", "JPG", "PNG"
                    SetInsertPicture oFile.Path, rngTarget
            End Select
        Next
        Application.StatusBar = oFolder.Name & "フォルダー処理中"
    Next

    Application.StatusBar = False
End Sub

Private Function GetSelectFolder(ByRef sFolderName As String) As Boolean

    With Application.FileDialog(msoFileDialogFolderPicker)
        If .Show Then
            sFolderName = .SelectedItems(1)
            GetSelectFolder = True
        End If
    End With
End Function

Private Function GetNewSheet(ByRef wb As Workbook, _

                             ByVal sName As String) As Range
    If wb Is Nothing Then
        ThisWorkbook.Sheets(1).Copy
        Set wb = Workbooks(Workbooks.Count)
    Else
        ThisWorkbook.Sheets(1).Copy After:=wb.Sheets(wb.Sheets.Count)
    End If

    With wb.Sheets(wb.Sheets.Count)
        .Name = sName
        Set GetNewSheet = .Range("A1")
    End With
End Function

Private Sub SetInsertPicture(ByVal sFullPath As String, _

                             ByRef rngTarget As Range)
    Dim pic As Shape

    With rngTarget
        Set pic = rngTarget.Worksheet.Shapes.AddPicture( _
                  Filename:=sFullPath, _
                  LinkToFile:=False, _
                  SaveWithDocument:=True, _
                  Left:=.Left, Top:=.Top, Width:=0, Height:=0)
        pic.ScaleHeight 1, msoTrue
        pic.ScaleWidth 1, msoTrue
        pic.LockAspectRatio = msoTrue
        pic.Height = .Height - 10
        pic.Top = .Top + 5
        pic.Left = .Left + (.Width - pic.Width) / 2
    End With
    Set rngTarget = rngTarget.Offset(1)
End Sub

※横着してコメントは入れてないです^^;
※写真貼付用のテンプレートは自ブックにあり、それをコピーして
新規ブックに画像を挿入する前提で書いてます。
※こちらも、面白そうなテーマなので勉強して書いてみただけなので、
「こんな書き方も出来るんだ。」という、参考程度と考えておいてください。
これが模範となるようなベストなコードではないと思いますので、
その辺はご了承願います。
※動作確認もあんまり詳しくはしてません。バグがあるかも知れません。
※高速化は、画面の更新を止めたら、少しは速くなると思います。
(まっつわん) 2016/11/15(火) 16:02


Setoさんの考えも理解できるので、そこだけ解説。

PasteFile() で画像サイズを見ているのは、画像は縮小せずそのまま貼って、2行空けた位置に次の画像、としているためです。
画像高さから、使った行数を計算している訳ですね。 行数を切り上げているのはこのため。 画像サイズがバラバラだと、行位置がバラバラになるので、アクティブ位置に貼る(そのために、貼り終わったらアクティブ位置を移動しておく)というロジックです。

Excelに画像を貼る場合、元サイズで貼ると大きすぎたり、貼る位置がバラバラになるので、固定サイズに縮小・拡大して、等間隔にするのが一般的かと思いますが、原寸を重視する考えも理解できるので、そこはそのままでも良い、と思いました。
(まっつわんさん案は、1セルの内側に画像を貼る、という方法なので、セルを大きくしておく必要があります)

まっつわんさんのコードの弱点は、拡張子の大文字小文字への対処ですかね。".jPg"とかだと反応しない…。Setoさんは、そこはLCaseで対応済みですね。
(???) 2016/11/15(火) 16:32


補足ありがとうございます。
私の環境ですとExcelのシートに保存するSSは、WindowsOS上でLinux仮想OSを経由しそこからWindowsServerに入りやっとの事で目的のソフトウェアをたちあげた画面、みたいな解像度がめちゃくちゃになりがちな環境の操作記録(画像のログ)みたいな物なので、記録として残すSSはファイルサイズが大きくても原寸大が原則となっている為このようにしています。
本来は単発で必要になったVBAの知識なので、今回このスクリプトだけ無事組めれば良しと言う認識だったのですが、想像以上に面白そうでかつまだまだ自分のコードが穴だらけなのを痛感しているので、長い目で見て掘り下げてみます。

(Seto) 2016/11/15(火) 17:08


コメント返信:

[ 一覧(最新更新順) ]


YukiWiki 1.6.7 Copyright (C) 2000,2001 by Hiroshi Yuki. Modified by kazu.