How to avoid using Select in Excel VBA

2020-01-22 07:19发布

I've heard much about the understandable abhorrence of using .Select in Excel VBA, but am unsure of how to avoid using it. I am finding that my code would be more re-usable if I were able to use variables instead of Select functions. However, I am not sure how to refer to things (like the ActiveCell etc.) if not using Select.

I have found this article on ranges and this example on the benefits of not using select but can't find anything on how?

标签: excel vba
14条回答
姐就是有狂的资本
2楼-- · 2020-01-22 08:05

"... and am finding that my code would be more re-usable if I were able to use variables instead of Select functions."

While I cannot think of any more than an isolated handful of situations where .Select would be a better choice than direct cell referencing, I would rise to the defense of Selection and point out that it should not be thrown out for the same reasons that .Select should be avoided.

There are times when having short, time-saving macro sub routines assigned to hot-key combinations available with the tap of a couple of keys saves a lot of time. Being able to select a group of cells to enact the operational code on works wonders when dealing with pocketed data that does not conform to a worksheet-wide data format. Much in the same way that you might select a group of cells and apply a format change, selecting a group of cells to run special macro code against can be a major time saver.

Examples of Selection-based sub framework:

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

The actual code to process could be anything from a single line to multiple modules. I have used this method to initiate long running routines on a ragged selection of cells containing the filenames of external workbooks.

In short, don't discard Selection due to its close association with .Select and ActiveCell. As a worksheet property it has many other purposes.

(Yes, I know this question was about .Select, not Selection but I wanted to remove any misconceptions that novice VBA coders might infer.)

查看更多
叼着烟拽天下
3楼-- · 2020-01-22 08:05

These methods are rather stigmatized, so taking the lead of @Vityata and @Jeeped for the sake of drawing a line in the sand:

Why not call .Activate, .Select, Selection, ActiveSomething methods/properties

Basically because they're called primarily to handle user input through the Application UI. Since they're the methods called when the user handles objects through the UI, they're the ones recorded by the macro-recorder, and that's why calling them is either brittle or redundant for most situations: you don't have to select an object so as to perform an action with Selection right afterwards.

However, this definition settles situations on which they are called for:

When to call .Activate, .Select, .Selection, .ActiveSomething methods/properties

Basically when you expect the final user to play a role in the execution.

If you are developing and expect the user to choose the object instances for your code to handle, then .Selection or .ActiveObject are apropriate.

On the other hand, .Select and .Activate are of use when you can infer the user's next action and you want your code to guide the user, possibly saving him some time and mouse clicks. For example, if your code just created a brand new instance of a chart or updated one, the user might want to check it out, and you could call .Activate on it or its sheet to save the user the time searching for it; or if you know the user will need to update some range values, you can programatically select that range.

查看更多
聊天终结者
4楼-- · 2020-01-22 08:05

Quick Answer:

To avoid using the .Select method you can set a variable equal to the property that you want.

► For instance, if you want the value in Cell A1 you could set a variable equal to the value property of that cell.

  • Example valOne = Range("A1").Value

► For instance, if you want the codename of 'Sheet3` you could set a variable equal to the codename property of that worksheet.

  • Example valTwo = Sheets("Sheet3").Codename

I hope that helps. Let me know if you have any questions.

查看更多
劳资没心,怎么记你
5楼-- · 2020-01-22 08:06

Working with .Parent feature. This example shows how setting only one myRng reference enables dynamic access to the entire environment with no .Select, .Activate, .Activecell, .ActiveWorkbook, .ActiveSheet and so on. (There's no genereic .Child feature)

Sub ShowParents()
    Dim myRng As Range
    Set myRng = ActiveCell
    Debug.Print myRng.Address                    ' an address of the selected cell
    Debug.Print myRng.Parent.name                ' the name of sheet, where MyRng is in
    Debug.Print myRng.Parent.Parent.name         ' the name of workbook, where MyRng is in
    Debug.Print myRng.Parent.Parent.Parent.name  ' the name of application, where MyRng is in

    ' You may use this feature to set reference to these objects
    Dim mySh    As Worksheet
    Dim myWbk   As Workbook
    Dim myApp   As Application

    Set mySh = myRng.Parent
    Set myWbk = myRng.Parent.Parent
    Set myApp = myRng.Parent.Parent.Parent
    Debug.Print mySh.name, mySh.Cells(10, 1).Value
    Debug.Print myWbk.name, myWbk.Sheets.Count
    Debug.Print myApp.name, myApp.Workbooks.Count

    ' You may use dynamically addressing
    With myRng
        .Copy

       ' pastes in D1 on sheet 2 in the same workbook, where copied cell is
        .Parent.Parent.Sheets(2).Range("D1").PasteSpecial xlValues
    ' or myWbk.Sheets(2).Range("D1").PasteSpecial xlValues

       ' we may dynamically call active application too
        .Parent.Parent.Parent.CutCopyMode = False
    ' or myApp.CutCopyMode = False
    End With
End Sub
查看更多
霸刀☆藐视天下
6楼-- · 2020-01-22 08:09

One small point of emphasis I'll add to all the excellent answers given above:

Probably the biggest thing you can do to avoid using Select is to as much as possible, use named ranges (combined with meaningful variable names) in your VBA code. This point was mentioned above, but glossed over a bit; however, it deserves special attention.

Here are a couple additional reasons to make liberal use of named ranges though I am sure I could think of more.

Named ranges make your code easier to read and understand.

Example:

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

It is pretty obvious what the named ranges Months and MonthlySales contain, and what the procedure is doing.

Why is this important? Partially because it is easier for other people to understand it, but even if you are the only person who will ever see or use your code, you should still use named ranges and good variable names because YOU WILL FORGET what you meant to do with it a year later, and you will waste 30 minutes just figuring out what your code is doing.

Named ranges ensure that your macros do not break when (not if!) the configuration of the spreadsheet changes.

Consider, if the above example had been written like this:

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

This code will work just fine at first - that is until you or a future user decides "gee wiz, I think I'm going to add a new column with the year in Column A!", or put an expenses column between the months and sales columns, or add a header to each column. Now, your code is broken. And because you used terrible variable names, it will take you a lot more time to figure out how to fix it than it should take.

If you had used named ranges to begin with, the Months and Sales columns could be moved around all you like, and your code will continue working just fine.

查看更多
孤傲高冷的网名
7楼-- · 2020-01-22 08:11

I noticed that none of these answers mention the .Offset Property. This also can be used to avoid using the Select action when manipulating certain cells, particularly in reference to a selected cell (as the OP mentions with ActiveCell).

Here are a couple examples.

I will also assume the "ActiveCell" is J4.

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

  • This will change the cell J6 to be a value of 12
  • A minus -2 would have referenced J2

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

  • This will copy the cell in k4 to L4.
  • Note that "0" is not needed in the offset parameter if not needed (,2)
  • Similar to the previous example a minus 1 would be i4

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

  • This will clear values in all cells in the column k.

These aren't to say they are "better" than the above options, but just listing alternatives.

查看更多
登录 后发表回答