web-dev-qa-db-ja.com

Excel VBAで選択を使用しないようにする方法

私はExcel VBAで.Selectを使うことの理解し難いことについて多くのことを耳にしましたが、それを使わないようにする方法がわからないです。 Select関数の代わりに変数を使用することができれば、私のコードはより再利用可能になると思います。ただし、ActiveCellを使用しない場合、(Selectなどの)ものを参照する方法がわかりません。

私は 範囲に関するこの記事 そして selectを使用しないことの利点に関するこの例 しかしhowでは何も見つけることができませんか?

472
BiGXERO

Selectを避ける方法のいくつかの例

Dim 'd個の変数を使用する

Dim rng as Range

Setは、必要な範囲への変数です。単一セル範囲を参照するには多くの方法があります。

Set rng = Range("A1")
Set rng = Cells(1,1)
Set rng = Range("NamedRange")

または複数セル範囲

Set rng = Range("A1:B10")
Set rng = Range("A1", "B10")
Set rng = Range(Cells(1,1), Cells(10,2))
Set rng = Range("AnotherNamedRange")
Set rng = Range("A1").Resize(10,2)

can Evaluateメソッドへのショートカットを使用することができますが、これは効率が悪く、通常はプロダクションコードでは避けるべきです。

Set rng = [A1]
Set rng = [A1:B10]

上記の例はすべて、 アクティブシート のセルを参照しています。アクティブシートだけで作業したいのでなければ、Worksheet変数も薄暗くしたほうがいいでしょう。

Dim ws As Worksheet
Set ws = Worksheets("Sheet1")
Set rng = ws.Cells(1,1)
With ws
    Set rng = .Range(.Cells(1,1), .Cells(2,10))
End With

もしあなたが do ActiveSheetと一緒に使いたいのなら、明確にするために明示的にするのが最善です。しかしWorksheetメソッドの中にはアクティブシートを変更するものがあるので注意してください。

Set rng = ActiveSheet.Range("A1")

繰り返しますが、これは アクティブなワークブック を指します。特にActiveWorkbookまたはThisWorkbookのみを使用したい場合を除き、Workbook変数もDimを使用することをお勧めします。

Dim wb As Workbook
Set wb = Application.Workbooks("Book1")
Set rng = wb.Worksheets("Sheet1").Range("A1")

もしあなたが do ActiveWorkbookと一緒に使いたいのなら、明確にするために明示的にするのが最善です。しかし、多くのWorkBookメソッドがアクティブブックを変更するので、注意してください。

Set rng = ActiveWorkbook.Worksheets("Sheet1").Range("A1")

実行コードを含む本を参照するためにThisWorkbookオブジェクトを使用することもできます。

Set rng = ThisWorkbook.Worksheets("Sheet1").Range("A1")

よくある(悪い)コードは、本を開いてデータを取得してからもう一度閉じることです。

これは悪いです:

Sub foo()
    Dim v as Variant
    Workbooks("Book1.xlsx").Sheets(1).Range("A1").Clear
    Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
    v = ActiveWorkbook.Sheets(1).Range("A1").Value
    Workbooks("SomeAlreadyOpenBook.xlsx").Activate
    ActiveWorkbook.Sheets("SomeSheet").Range("A1").Value = v
    Workbooks(2).Activate
    ActiveWorkbook.Close()
End Sub

そしてより良いようになります:

SUb foo()
    Dim v as Variant
    Dim wb1 as Workbook
    Dim  wb2 as Workbook
    Set wb1 = Workbooks("SomeAlreadyOpenBook.xlsx")
    Set wb2 = Workbooks.Open("C:\Path\To\SomeClosedBook.xlsx")
    v = wb2.Sheets("SomeSheet").Range("A1").Value
    wb1.Sheets("SomeOtherSheet").Range("A1").Value = v
    wb2.Close()
End Sub

Range変数としてあなたのSubFunctionに範囲を渡します

Sub ClearRange(r as Range)
    r.ClearContents
    '....
End Sub

Sub MyMacro()
    Dim rng as Range
    Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:B10")
    ClearRange rng
End Sub

また、メソッドにFindCopyなどのメソッドを適用する必要があります。

Dim rng1 As Range
Dim rng2 As Range
Set rng1 = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10")
Set rng2 = ThisWorkbook.Worksheets("SomeSheet").Range("B1:B10")
rng1.Copy rng2

ある範囲のセルをループしている場合は、最初に範囲の値をバリアント配列にコピーしてからループする方が良い(速い)方法がよくあります。

Dim dat As Variant
Dim rng As Range
Dim i As Long

Set rng = ThisWorkbook.Worksheets("SomeSheet").Range("A1:A10000")
dat = rng.Value  ' dat is now array (1 to 10000, 1 to 1)
for i = LBound(dat, 1) to UBound(dat, 1)
    dat(i,1) = dat(i,1) * 10 'or whatever operation you need to perform
next
rng.Value = dat ' put new values back on sheet

これは可能なことの小さなテイスターです。

481
chris neilsen

.Select/.Activate/Selection/Activecell/Activesheet/Activeworkbook etc ...を避けるべき2つの主な理由

  1. それはあなたのコードを遅くします。
  2. 通常これがランタイムエラーの主な原因です。

どうすればそれを回避できますか?

1) 関連するオブジェクトを直接操作する

このコードを考えてください

Sheets("Sheet1").Activate
Range("A1").Select
Selection.Value = "Blah"
Selection.NumberFormat = "@"

このコードは次のように書くこともできます。

With Sheets("Sheet1").Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With

2) 必要ならば変数を宣言してください。上記と同じコードを次のように書くことができます。

Dim ws as worksheet

Set ws = Sheets("Sheet1")

With ws.Range("A1")
    .Value = "Blah"
    .NumberFormat = "@"
End With
185
Siddharth Rout

ちょっとした強調点上記のすべての優れた回答に追加します。

おそらく、Selectを使用しないようにするためにできる最大のことは、できる限り、VBAコードで名前付き範囲を(意味のある変数名と組み合わせて)使用する)にすることです。少し;しかし、それは特別な注意に値する。

ここで、名前付き範囲を自由に使用するための2つの追加の理由がありますが、私はもっと考えることができると確信しています。

名前付き範囲を使用すると、コードを読みやすく理解しやすくなります。

例:

Dim Months As Range
Dim MonthlySales As Range

Set Months = Range("Months")
'e.g, "Months" might be a named range referring to A1:A12

Set MonthlySales = Range("MonthlySales")
'e.g, "Monthly Sales" might be a named range referring to B1:B12

Dim Month As Range
For Each Month in Months
    Debug.Print MonthlySales(Month.Row)
Next Month

名前付き範囲MonthsMonthlySalesに何が含まれているのか、そしてプロシージャが何をしているのかは明らかです。

何でこれが大切ですか?部分的には他の人にとって理解しやすいためですが、あなたが自分のコードを見たり使ったりする唯一の人であっても、YOU WILL FORGET1年後にそれをどうするつもりだったのか、そしてあなたは無駄になるでしょう30分で、コードが何をしているのか理解することができます。

名前付き範囲は、スプレッドシートの設定が変更されたときにマクロが壊れないようにします。

上記の例が次のように書かれているとします。

Dim rng1 As Range
Dim rng2 As Range

Set rng1 = Range("A1:A12")
Set rng2 = Range("B1:B12")

Dim rng3 As Range
For Each rng3 in rng1 
    Debug.Print rng2(rng3.Row)
Next rng3

このコードは最初はうまくいくでしょう - それはあなたか将来のユーザーが "gee wiz、私はColumn Aに年の新しいコラムを追加するつもりだ"と決めるまで、あるいは月の間に経費コラムを入れるまでまたはsales列をクリックするか、各列にヘッダーを追加します。今、あなたのコードは壊れています。そして、あなたがひどい変数名を使ったので、それを修正する方法を理解するのにそれが取るべきであるよりはるかに多くの時間がかかります。

最初に名前付き範囲を使用していた場合は、Months列とSales列を好きな場所に移動でき、コードは正常に機能し続けます。

75
Rick Teachey

他のみんなが長い答えを出したので、私は短い答えを出すつもりです。

マクロを記録してそれらを再利用するときはいつでも.selectと.activateが得られます。セルまたはシートを選択すると、アクティブになります。それ以降は、Range.Valueのような修飾されていない参照を使用するときはいつでも、アクティブセルとシートのみを使用します。コードが配置されている場所やユーザーがブックをクリックした場所を見ていない場合も、これは問題になります。

そのため、セルを直接参照することでこれらの問題を解消できます。それは行きます:

'create and set a range
Dim Rng As Excel.Range
Set Rng = Workbooks("Book1").Worksheets("Sheet1").Range("A1")
'OR
Set Rng = Workbooks(1).Worksheets(1).Cells(1, 1)

それともあなたができる

'Just deal with the cell directly rather than creating a range
'I want to put the string "Hello" in Range A1 of sheet 1
Workbooks("Book1").Worksheets("Sheet1").Range("A1").value = "Hello"
'OR
Workbooks(1).Worksheets(1).Cells(1, 1).value = "Hello"

これらの方法にはさまざまな組み合わせがありますが、それは私のようなせっかちな人にはできるだけ早く表現される一般的な考え方です。

41
MattB

以下では、Selectアプローチ(OPが回避したいアプローチ)とRangeアプローチを比較しています(そしてこれが質問に対する答えです)。それで、あなたが最初のSelectを見たときに読むのを止めないでください。

それは本当にあなたがやろうとしていることによります。とにかく簡単な例が役に立つでしょう。アクティブセルの値を "foo"に設定したいとしましょう。 ActiveCellを使用すると、このようなものを書くでしょう:

Sub Macro1()
    ActiveCell.Value = "foo"
End Sub

アクティブではないセル、たとえば "B2"に使用する場合は、最初に選択する必要があります。

Sub Macro2()
    Range("B2").Select
    Macro1
End Sub

範囲を使用すると、任意のセルの値を任意の値に設定するために使用できる、より一般的なマクロを作成できます。

Sub SetValue(cellAddress As String, aVal As Variant)
    Range(cellAddress).Value = aVal
End Sub

その後、Macro2を次のように書き直すことができます。

Sub Macro2()
    SetCellValue "B2", "foo"
End Sub

そしてMacro1は次のようになります。

Sub Macro1()
    SetValue ActiveCell.Address, "foo"
End Sub

これが少し物事を片付けるのに役立つことを願っています。

28

「...そして、Select関数の代わりに変数を使用することができれば、私のコードはより再利用可能になるでしょう。」

.Selectが直接セル参照よりも優れた選択であるという、ほんの一握りの状況以上のことを考えることはできませんが、Selectionを防御することにし、.Selectがそうであるのと同じ理由で捨てられるべきではないと指摘します。避けてください。

いくつかのキーをタップするだけで使用できるホットキーの組み合わせに割り当てられた、短くて時間を節約するマクロサブルーチンを使用すると、多くの時間を節約できます。ワークシート全体のデータ形式に準拠していないポケットデータを処理するときに、作業上の操作コードを制定するためにセルのグループを選択できることは驚きです。セルのグループを選択してフォーマットの変更を適用するのと同じように、特別なマクロコードを実行するためのセルのグループを選択することは、大きな時間の節約になります。

選択ベースのサブフレームワークの例:

Public Sub Run_on_Selected()
    Dim rng As Range, rSEL As Range
    Set rSEL = Selection    'store the current selection in case it changes
    For Each rng In rSEL
        Debug.Print rng.Address(0, 0)
        'cell-by-cell operational code here
    Next rng
    Set rSEL = Nothing
End Sub

Public Sub Run_on_Selected_Visible()
    'this is better for selected ranges on filtered data or containing hidden rows/columns
    Dim rng As Range, rSEL As Range
    Set rSEL = Selection    'store the current selection in case it changes
    For Each rng In rSEL.SpecialCells(xlCellTypeVisible)
        Debug.Print rng.Address(0, 0)
        'cell-by-cell operational code here
    Next rng
    Set rSEL = Nothing
End Sub

Public Sub Run_on_Discontiguous_Area()
    'this is better for selected ranges of discontiguous areas
    Dim ara As Range, rng As Range, rSEL As Range
    Set rSEL = Selection    'store the current selection in case it changes
    For Each ara In rSEL.Areas
        Debug.Print ara.Address(0, 0)
        'cell group operational code here
        For Each rng In ara.Areas
            Debug.Print rng.Address(0, 0)
            'cell-by-cell operational code here
        Next rng
    Next ara
    Set rSEL = Nothing
End Sub

実際に処理するコードは、単一行から複数​​のモジュールまでです。この方法を使用して、外部ワークブックのファイル名を含む不規則なセルの選択で長時間実行されるルーチンを開始しました。

要するに、Selection.SelectおよびActiveCellと密接に関連しているため、捨てないでください。ワークシートのプロパティとして、他にも多くの目的があります。

(はい、この質問はSelectionではなく.Selectに関するものであったことを知っていますが、初心者のVBAコーダーが推測していたかもしれない誤解を取り除きたかったのです。)

28
user4039065

SelectActivateを避けることは、VBA開発者を少し良くする動きです。一般に、マクロが記録されるときにSelectActivateが使用されます。したがって、Parentワークシートまたは範囲は常にアクティブなものと見なされます。

これは、次のような場合にSelectActivateを回避する方法です。


新しいワークシートを追加してその上にセルをコピーする:

From(マクロレコーダーで生成されたコード):

Sub Makro2()
    Range("B2").Select
    Sheets.Add After:=ActiveSheet
    Sheets("Tabelle1").Select
    Sheets("Tabelle1").Name = "NewName"
    ActiveCell.FormulaR1C1 = "12"
    Range("B2").Select
    Selection.Copy
    Range("B3").Select
    ActiveSheet.Paste
    Application.CutCopyMode = False
End Sub

に:

Sub TestMe()
    Dim ws As Worksheet
    Set ws = Worksheets.Add
    With ws
        .Name = "NewName"
        .Range("B2") = 12
        .Range("B2").Copy Destination:=.Range("B3")
    End With
End Sub

ワークシート間で範囲をコピーしたい場合:

から:

Sheets("Source").Select
Columns("A:D").Select
Selection.Copy
Sheets("Target").Select
Columns("A:D").Select
ActiveSheet.Paste

に:

Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").Range("a1")

豪華な名前付き範囲を使う

あなたは[]でそれらにアクセスすることができます。他の方法と比べて、これは本当に美しいです。自分自身で調べて:

Dim Months As Range
Dim MonthlySales As Range

Set Months = Range("Months")    
Set MonthlySales = Range("MonthlySales")

Set Months =[Months]
Set MonthlySales = [MonthlySales]

上記の例は次のようになります。

Worksheets("Source").Columns("A:D").Copy Destination:=Worksheets("Target").[A1]

値をコピーするのではなく、それらを取る

通常、もしあなたがselectを喜んで使うのであれば、おそらくあなたは何かをコピーしているでしょう。値にだけ興味があるなら、これはselectを避けるための良い選択肢です:

Range("B1:B6").Value = Range("A1:A6").Value


常にワークシートも参照するようにしてください

これはおそらく vba の最も一般的な間違いです。範囲をコピーするときはいつでもワークシートが参照されないため、VBAはActiveWorksheetを考慮します。

'This will work only if the 2. Worksheet is selected!
Public Sub TestMe()
    Dim rng As Range
    Set rng = Worksheets(2).Range(Cells(1, 1), Cells(2, 2)).Copy
End Sub

'This works always!
Public Sub TestMe2()
    Dim rng As Range
    With Worksheets(2)
        .Range(.Cells(1, 1), .Cells(2, 2)).Copy
    End With
End Sub

本当に.Select.Activateを何かのために使うことはできませんか?

.Activate.Selectを使用することが正当化されるのは、視覚的な理由から特定のワークシートが選択されていることを確認するときだけです。たとえば、Excelは常に最初に選択されたカバーワークシートで開き、ファイルが閉じられたときにどちらがアクティブシートだったかは無視します。したがって、このようなものは絶対に問題ありません。

Private Sub Workbook_Open()
    Worksheets("Cover").Activate
End Sub
19
Vityata

ワークブック、ワークシート、セル/範囲を必ず明記してください。

例えば:

Thisworkbook.Worksheets("fred").cells(1,1)
Workbooks("bob").Worksheets("fred").cells(1,1)

エンドユーザーは常にボタンをクリックするだけで、フォーカスがワークブックから外れるとすぐにコードが処理したいので、事態は完全に失敗します。

また、ワークブックのインデックスは絶対に使用しないでください。

Workbooks(1).Worksheets("fred").cells(1,1)

ユーザーがコードを実行したときに他のどのブックが開かれるのかわかりません。

13
user1644564

これらのメソッドはどちらかというと汚名なので、@ Vityataと@Jeepedのリードを取って砂の中に線を引きます。

.Activate.SelectSelectionActiveSomethingの各メソッド/プロパティを呼び出さないでください。

基本的に、それらは主にアプリケーションUIを介したユーザー入力を処理するために呼び出されるからです。これらはユーザーがUIを通じてオブジェクトを処理するときに呼び出されるメソッドであるため、マクロレコーダーによって記録されたものであり、ほとんどの場合、それらを呼び出すのは脆弱または冗長です。直後にSelectionでアクションを実行するようにしてください。

しかし、この定義は、それらが求められる状況を解決します。

.Activate.Select.Selection.ActiveSomethingの各メソッド/プロパティを呼び出すタイミング

基本的に 最終ユーザー が実行において役割を果たすことを期待するとき。

あなたが開発していて、ユーザがあなたのコードが扱うオブジェクトインスタンスを選ぶことを期待しているなら、.Selectionまたは.ActiveObjectが適切です。

一方、.Select.Activateは、ユーザーの次の行動を推論することができ、あなたのコードでユーザーをガイドしたいときに役立ちます。たとえば、コードでチャートのまったく新しいインスタンスを作成した場合や更新した場合は、ユーザーがそれをチェックアウトし、そのシートまたはシート上で.Activateを呼び出して、ユーザーが検索する時間を節約できます。あるいは、ユーザーがいくつかの範囲値を更新する必要があることがわかっている場合は、プログラムでその範囲を選択できます。

6
LFB

素早い回答:

.Selectメソッドを使用しないようにするには、必要なプロパティに等しい変数を設定します。

►たとえば、Cell A1の値が必要な場合は、そのセルのvalueプロパティに等しい変数を設定できます。

  • valOne = Range("A1").Value

►たとえば、 'Sheet3`のコードネームが必要な場合は、そのワークシートのcodenameプロパティに等しい変数を設定できます。

  • valTwo = Sheets("Sheet3").Codename

それが役立つことを願っています。質問があれば教えてください。

4
FinPro.Online

私のように.selectの使い方は、マクロを記録し、それから.selectとそれに続くselectionが単なる不必要な仲介者であることに気づかずに必要に応じてVBAを学び始めた人々から来ています。

.selectは、すでに投稿されているように、既存のオブジェクトを直接操作することで回避できます。これにより、複雑な方法でiとjを計算してcell(i、j)を編集するなどの間接参照が可能になります。

そうでなければ、.select自体に暗黙のうちに間違っているものは何もありません。私は、日付を入れてスプレッドシートを作成し、それに魔法をかけて別のシートに適切な形式でエクスポートするマクロをアクティブにします。ただし、隣接するセルへの最終的な手動の(予測不可能な)入力が必要です。それで、ここに.selectの瞬間が来ました。それは私にその追加のマウスの動きとクリックを救ってくれます。

4
Eleshar

私はこれらの答えのどれも .Offsetプロパティ に言及していないことに気づいた。特に選択されたセルに関して(OPがSelectで述べているように)特定のセルを操作するときにActiveCellアクションを使わないようにするためにこれを使うこともできます。

ここにいくつかの例があります。

また、 "ActiveCell"が J4 であると仮定します。

ActiveCell.Offset(2, 0).Value = 12

  • これはセルJ6を値12に変更します
  • マイナス-2はJ2を参照しているはずです。

ActiveCell.Offset(0,1).Copy ActiveCell.Offset(,2)

  • これはk4のセルをL4にコピーします。
  • 不要な場合、offsetパラメータに "0"は不要です(、2)
  • 前の例と同様に、マイナス1はi4になります。

ActiveCell.Offset(, -1).EntireColumn.ClearContents

  • これにより、列kのすべてのセルの値が消去されます。

これらは上記のオプションよりも「優れている」と言っているのではなく、単に選択肢を列挙しているだけです。

1
PGCodeRider

これは、セル "A1"の内容(選択の種類がxllastcellなどの場合はそれ以上)を消去する例です。セルを選択しなくてもすべて完了です。

Application.GoTo Reference:=Workbook(WorkbookName).Worksheets(WorksheetName).Range("A1")
Range(Selection,selection(selectiontype)).clearcontents 

これが誰かに役立つことを願っています。

0
marionffavp