[[20240921053711]] 『VBA コピペのループが重くて困ってます。』(朝男) ページの最後に飛ぶ

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

 

『VBA コピペのループが重くて困ってます。』(朝男)

色の無いところに決まった数字を打ち込むマクロなのですが、
一応機能するのですが、とても重くて困っています。
どなたかご教授ください。よろしくお願いします。

Sub 稼働()

Application.ScreenUpdating = False
Application.Calculation = xlCalculationManual '手動計算

Dim i
Dim r
Dim s As Integer

r = 131
For s = 1 To 30
If Cells(58, r) = "" Then

Application.CutCopyMode = False

 Range("B10").Select
 Application.Calculation = xlCalculationAutomatic  '自動計算
 Application.ScreenUpdating = True
Exit Sub
Else
End If

i = 59

Do While i < 91
Cells(91, r).Copy

If Cells(i, r).Interior.ColorIndex = xlNone Then
Cells(i, r).PasteSpecial _

                                 Paste:=xlPasteValues, _
                                 Operation:=xlNone, _
                                 SkipBlanks:=False, _
                                 Transpose:=False
Else
End If
i = i + 1
Loop
r = r + 1

Next
Application.CutCopyMode = False

 Range("B10").Select
 Application.Calculation = xlCalculationAutomatic  '自動計算
 Application.ScreenUpdating = True
End Sub

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


 予め範囲が決まっているので、
 For .. Nextを使ったほうが分かりやすくなると思います。
 自身で i = i + 1 などと書かなくてよいですし、その都度範囲判定しなくてよいので。

 また、ポイントは、Cells(91, c).Copyの位置でしょうか。

 以下のようなコードで同じ機能になりますか?十分検証していませんけれども。参考にしてみてください。

 Sub 稼働()
     Dim r As Long
     Dim c As Long

     Application.ScreenUpdating = False
     Application.Calculation = xlCalculationManual    '手動計算

     For c = 131 To 160
         If Cells(58, c) = "" Then Exit For
         Cells(91, c).Copy
         For r = 59 To 90
             If Cells(r, c).Interior.ColorIndex = xlNone Then
                 Cells(r, c).PasteSpecial Paste:=xlPasteValues
             End If
         Next
     Next
     Application.CutCopyMode = False
     Range("B10").Select

     Application.Calculation = xlCalculationAutomatic  '自動計算
     Application.ScreenUpdating = True
 End Sub

(xyz) 2024/09/21(土) 07:02:06


 >ポイントは、Cells(91, c).Copyの位置でしょうか。
 補足すると、現在のコードでは同じものを何度もコピーしているということです。

 まずは、以下の点に留意されることを推奨します。
 (1)適切なインデントが必要です(これによってコードの構造がより明確に分かるようになります。)
 (2)繰り返しの範囲が事前に判明しないときは Do ...Loop に依らざるを得ないことがありますが、
    大抵は範囲が決まっていることが多く、For .. Nextのほうがコードが分かりやすくなります。 
(xyz) 2024/09/21(土) 15:43:14

重複するところも多分にありますが何点か。

■1
提示のコードについて、インデントがついていません。
VBAはインデントの位置で結果が変わる言語ではありませんが、インデントを適切につけるとコードの全体の構造が把握しやすくなり、ご自身のデバッグ作業の効率アップに寄与すると思いますので、こだわりがなければインデントをつけることをお勧めします。

■2
上記を踏まえて、私なりにインデントをつけてみると↓のようになります。

    Sub インデントをつけてみる()
        Application.ScreenUpdating = False
        Application.Calculation = xlCalculationManual '手動計算
        Dim i
        Dim r
        Dim s As Integer

        r = 131
        For s = 1 To 30
            If Cells(58, r) = "" Then
                Application.CutCopyMode = False
                Range("B10").Select
                Application.Calculation = xlCalculationAutomatic  '自動計算
                Application.ScreenUpdating = True
                Exit Sub
            Else
            End If

            i = 59
            Do While i < 91
                Cells(91, r).Copy

                If Cells(i, r).Interior.ColorIndex = xlNone Then
                    Cells(i, r).PasteSpecial Paste:=xlPasteValues
                Else
                End If

                i = i + 1
            Loop

            r = r + 1
        Next

        Application.CutCopyMode = False
        Range("B10").Select
        Application.Calculation = xlCalculationAutomatic  '自動計算
        Application.ScreenUpdating = True
    End Sub

■3
上記のように整理すると気になる点がいくつかありますので順番に挙げます。

■4
【s】が意味をなしていない。
結局【For〜Nextステートメント】のカウンターとしての役割しかないので、↓でも同様の効果になります。

 For r = 131 to 160

■5
以下について、Else節がないのに記述されているのも気になりますが、プロシージャを抜けるんじゃなくて、ループを抜けるだけで事足りるでしょう。

 If Cells(58, r) = "" Then
     Application.CutCopyMode = False
     Range("B10").Select
     Application.Calculation = xlCalculationAutomatic  '自動計算
     Application.ScreenUpdating = True
     Exit Sub
 Else
 End If

■7
以下についても、同じくElse節がないのに記述されているのも気になりますが、それより条件判定よりCopyが前になっているので、貼り付け対象じゃないときもコピーする無駄動作になっています。

 Cells(91, r).Copy             
 If Cells(i, r).Interior.ColorIndex = xlNone Then
     Cells(i, r).PasteSpecial Paste:=xlPasteValues
 Else
 End If

(おそらく、処理時間がかかっているのはこれが原因)

■6
推奨する意図はありませんが、セルに値だけ書き込むのであれば値貼付け以外にも、Valueを直接参照するという手もあります。
この場合、コピーではないので「CutCopyMode」や「選択セル」に影響は出ません。
ただし、状況によっては「01」、「4/1」のような文字列が、「1」「4月1日」のように勝手に変換される可能性がありますので、状況に合わせて使い分けされるとよいと思います。

■7
ということを踏まえると、以下でも類似の効果が得られると思います。
(読み間違っていたらごめんなさい。)

    Sub 整理()
        Dim 行 As Long, 列 As Long

        Application.ScreenUpdating = False
        Application.Calculation = xlCalculationManual '手動計算

        With ActiveSheet
            .Range("B10").Select

            For 列 = 131 To 160 Step 1
                If Cells(58, 列) = "" Then
                    Exit For
                Else
                    For 行 = 59 To 90 Step 1
                        If .Cells(行, 列).Interior.ColorIndex = xlNone Then
                            .Cells(行, 列).Value = Cells(91, 列).Value
                        End If
                    Next 行
                End If
            Next 列
        End With

        Application.Calculation = xlCalculationAutomatic  '自動計算
        Application.ScreenUpdating = True
    End Sub

(もこな2) 2024/09/21(土) 23:43:50


 | ■7
 | 以下についても、同じくElse節がないのに記述されているのも気になりますが、
 | それより条件判定よりCopyが前になっているので、貼り付け対象じゃないときもコピーする無駄動作になっています。

 条件判定との前後関係というよりも、
 59行から90行目への書き込み処理ですが、転記元は91行目と固定である。
 それにもかかわらず、32回のCopy処理を無駄に実行していること自体が問題と言うべきでしょうね。

 ------------------------------
 さて、発言の本題です。
 無駄は無駄であるが、こんな少ない件数の単なるコピーペイストだけで、35秒もかかるのは不思議と言う他ない。
 これが腑に落ちなかったが、 どうやら「クリップボードエラー」というものが発生していて、これが悪さをしているようです。

 クリップボードエラーのエラーメッセージは以下です。
 | 「別のアプリケーションで使用されているため、
 | コンテンツをクリップボードにコピーできませんでした。
 | このブック内にコンテンツを貼り付けすることはできますが、他のアプリケーションでは使用できません。」

 調べたところ、
 「クリップボード アクセスの競合による Office でのコピー-貼り付け失敗」
https://officesupportjp.github.io/blog/cl0m4xkl2003deovs5ntr794w/
 と言う記事があった。
 どうやら、これに該当しているように思います。

 | Windows 10 のクリップボード履歴機能
 | Windows 10 バージョン 1807 から追加された新機能で、コピーしたデータの過去複数件の履歴を保持して
 | 貼り付けられるようになりました。この機能の実現のため、クリップボードにデータが格納されると、
 | クリップボード履歴機能のプロセスからクリップボードにアクセスが発生します。

 クリップボードオープン と クリップボードクローズを短期間に何度も繰り返す結果、上記の履歴機能のプロセスと
 本来のExcelのコピーペイストとの間で競合が起き、エラーになるということのようです。

 # 質問者さんから何の反応もないのは残念。調べたら、9月の6日の質問についてもこちらのコメントに何の反応もなかった。
 # 質問掲示板は返事が必要ということを理解していないのかも知れない。

(xyz) 2024/09/23(月) 20:51:13


すみません。
9月6日の件でもお世話になりました汗
実は丁寧にお礼の文を送ったはずなのですが。。。反映されていませんでした。失礼しました。
反応遅れてすみません。一通り読んでみたのですが、理解するまでに少しかかりそうです。
丁寧な説明ありがとうございます。
試してみたのち、再度コメントさせていただきます。
(朝男) 2024/09/25(水) 04:10:15


■8
xyzさんのコメントを拝見して再考。
    Sub 整理_改()
        Dim 列 As Long
        Dim bufRNG As Range, MyRNG As Range

        Application.ScreenUpdating = False
        Application.Calculation = xlCalculationManual '手動計算

        With ActiveSheet.Range("EA58:FD90")
            For 列 = 1 To .Columns.Count
                If .Cells(1, 列).Value = "" Then Exit For

                Set bufRNG = Nothing
                For Each MyRNG In Intersect(.Columns(列), .Columns(列).Offset(1))
                    If MyRNG.Interior.ColorIndex = xlNone Then
                        If bufRNG Is Nothing Then
                            Set bufRNG = MyRNG
                        Else
                            Set bufRNG = Union(bufRNG, MyRNG)
                        End If
                    End If
                Next MyRNG

                If Not bufRNG Is Nothing Then
                    .Cells(.Rows.Count + 1, 列).Copy
                    bufRNG.PasteSpecial Paste:=xlPasteValues
                End If
            Next 列
        End With

        Application.Calculation = xlCalculationAutomatic  '自動計算
        Application.ScreenUpdating = True
    End Sub

 ※確かに、コピーするデータは91行目に固定なのだから、コピペは列ごとに1回でいいですね。

(もこな2 ) 2024/09/25(水) 19:48:41


トピ主がまだ見ているかわかりませんが追加で。

■9
すでに[[20240906065309]]のほうで述べましたが、速度を気にされるのであれば、【1度で済むことを何度もやらない】ということは鉄則です。

その観点でいえば、「■8」で示したコードは【1セルずつコピペ】するよりはマシですが、【列ごとにコピペ】するので最大で30回のコピペをすることになります。

このことについて、[[20240906065309]]のほうで提案があったように【配列】を利用することでコピペ(というかセル範囲への書き込み)を1度で済ますということも可能だと思いますので参考に提示します。

    Sub 配列を利用()
        Dim 二次元配列 As Variant
        Dim x As Long, y As Long

        With ActiveSheet.Range("EA58:FD91")
            二次元配列 = .Value
            For y = 1 To UBound(二次元配列, 2) - 1 Step 1
                If 二次元配列(1, y) = "" Then
                    Exit For
                End If

                For x = 2 To UBound(二次元配列, 1) Step 1
                    If .Cells(x, y).Interior.ColorIndex = xlNone Then
                        二次元配列(x, y) = 二次元配列(UBound(二次元配列, 1), y)
                    End If
                Next x
            Next y

            '▼セルへの書き込みは↓の1回のみ
            .Value = 二次元配列
        End With
    End Sub

ただ、配列を利用する方法ですと、結局Valueプロパティを参照していることになるので「■6」のような弊害が起こる可能性もありますし、コードを理解する(読み解く)には配列を理解していることが前提になるので、難易度が2段階ほどアップしてしまうように思います。

したがって、列ごとにコピペする程度であれば許容できるということであれば、当面はそちらで対応しておき、おいおい配列を学習されてから改修に着手されるとよいと思います。

(もこな2) 2024/09/25(水) 23:24:43


コメント返信:

[ 一覧(最新更新順) ]


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