1

Basically what the label on the tin says.

I have multiple userforms, multiple modules, class modules, etc. I have worksheets where data is stored and used by the code. I have added a tab and buttons to the ribbon.

I ran and tested everything from "Come Here" to "Sick 'Em" as a .xlsm file. Every field of every form, every botton, every line of code runs perfectly.

I saved the file as an .xlam and the thing hangs on this line of code in the very first module:

Worksheets("All Welders Data").Activate

As I said, I get Subscript Out of Range Error 9.

This is my first time trying to take a VBA project and save it as an add-in. I must be missing something. I have read (John Walkenbach "Excell 2013...") that the .xlam file is still a workbook with worksheets and everything should work just like it was a .xlsm.

Is it the reference to the sheet that is wrong? Is it the activate? Do i need to prefix Something.Worksheets("blah").Activate ?

I'm tired and I'm totally stumped. I hope someone has some kind of input for me. Thanks in advance!

Update

I want to tank everyone for their responses and comments. I do believe that "ThisWorkbook" will probably solve the issue. I am close now to being able to test it and i will update my question again with my results.

However, since there is so much discussion on this post about my use of activate I want to pose a follow-up question that is related to all of this.

Here is a sample from one of my modules.

Private Sub UserForm_Initialize()
    Dim lastRow As Long
    Dim nameCell As Range
    Dim box As control

    'Set Window size and position
    With Application
        .WindowState = xlMaximized
        Me.Top = .Top * 0.5
        Me.Left = .Left * 1.0015
        Zoom = Int((.Width * 0.85) / (Width * 0.85) * 40)
        Width = .Width * 0.995
        Height = .Height * 0.992
    End With

    'Turn off everything except the radio Buttons and thier labels
    Me.submitButton.Visible = False
    Me.submitButton.Enabled = False
    Me.chooseWelderLabel.Visible = False
    Me.chooseWelderNameComboBox.Visible = False
    Me.chooseWelderNameComboBox.Enabled = False
    Me.welderNameLabel.Visible = False
    Me.welderNameText.Visible = False
    Me.welderNameText.Enabled = False
    Me.checkNameButton.Visible = False
    Me.checkNameButton.Enabled = False

    Application.ScreenUpdating = False

    'Activate the worksheet
    Application.ThisWorkbook.Worksheets("All Welders Data").Activate
    Application.ThisWorkbook.Worksheets("All Welders Data").Range("A1").Activate

    'sort the data in the active sheet by the welder's last name.

    With Application.ThisWorkbook.ActiveSheet.Sort
        .SortFields.Clear
        .SortFields.Add Key:=Range("B3"), Order:=xlAscending
        .SetRange ActiveCell.CurrentRegion.Offset(1)
        .Header = xlYes
        .MatchCase = False
        .Orientation = xlTopToBottom
        .SortMethod = xlPinYin
        .Apply
    End With

    Application.ScreenUpdating = True

    'populate the combox from the active sheet (welder name in the
    'first column, welder ID number in the second column.
    With Application.ThisWorkbook.ActiveSheet
        lastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
        For Each nameCell In .Range("E3:E" & lastRow)
            If nameCell.Value <> "" Then
                With Me.chooseWelderNameComboBox
                    .ColumnCount = 2
                    .AddItem nameCell.Value
                    .List(.ListCount - 1, 1) = nameCell.Offset(, -1).Value
                    'ComboBox now shows the values in column "E" and the values
                    'in coulmn "D" - in that order, as in  "Name" - "ID Number".
                    '(the reverse order of the columns in the worksheet.)
                End With
            End If
        Next
    End With
End Sub

You will see about halfway down that I do activate the sheet. Then on the next line I am activating a range. I need to activate the range because I am then sorting in the With block that follows. Sorting is done because the ComboBox needs to be in alphabetical order.

If I want to activate the Range ("A1") I have to make sure the worksheet is activated. If not, then the Range("A1").Activate will fail with a 1004 error because you can't activate a cell unless the worksheet where the cell lives is the active sheet.

So if I am going to be hard core about never ever using activate what are the suggestions for refactoring this code so that I can use excel's built in sorting. I am not interested in doing any sort of looping routine to sort.

Final Word

I just want to thank Robin MacKenzie for her answer. She was on the right track and gave a good answer that solved the issue.

Also, I just want to say that I still think there are times when using activate is not a sin!

  • 2
    You cannot activate a sheet in a hidden workbook (and an xlam is hidden by default) BUT - you should avoid activating sheets unless you really need to - a sheet doesn't have to be active for you to work with it. See: https://stackoverflow.com/questions/10714251/how-to-avoid-using-select-in-excel-vba?rq=1. By default `Worksheets("All Welders Data").Activate` will look at the ActiveWorkbook, which may expain the error you see - it would be best to qualify all your sheet references with a Workbook object. – Tim Williams Aug 16 '17 at 23:34
  • 1
    @TimWilliams Actually it appears that you **can** activate a sheet in an `xlam` file. I just tried it with the `xlam` file I always have loaded (which has a dummy "Sheet1" within it). I changed my open workbook so that it didn't have any sheets called "Sheet1" and then executed `workbooks("myXLAMfile.xlam").Worksheets("Sheet1").activate:?activesheet.name` in the Immediate window and it correctly returned "Sheet1". I was somewhat surprised that it actually worked. (But, just because it **can** be done is not a reason for doing it - getting rid of `Activate` is still preferable!) – YowE3K Aug 17 '17 at 00:04
  • @YowE3K - I stand corrected: you can `Activate` but not `Select` – Tim Williams Aug 17 '17 at 00:09
  • 1
    @TimWilliams Which is another good reason not to activate it - because the next question would be "why do my `Select` statements fail?" :D – YowE3K Aug 17 '17 at 00:10
  • I have good reason to activate that does not include any selecting. I activate to be dead certain that my other code is not doing something on the wrong sheet. I have several sheets where things are happening almost simultaneously. Im working with a lot of data and need to be sure that just running a test on on sub does not mess up the data. –  Aug 17 '17 at 04:44
  • And by the way, my next question would not be "Why do my select statements fail?" It's a bit arrogant of you to assume that I'm totally naive based on the fact that I'm activating a sheet. People come here to get help not ridicule. I appreciate you taking the time to provide commentary and input but it would better if you kept you assumptions to yourself. –  Aug 17 '17 at 04:55
  • 1
    "I activate to be dead certain that my other code is not doing something on the wrong sheet" - that is **not** a "good reason" to activate a sheet! You should be *fully qualifying* all of your sheets/ranges anyway, then you should have no reason to activate a sheet, and code which interacts between workbooks will be more robust and easy to debug (helpful since you *are* using code between workbooks). You'll find that @YowE3K wasn't being arrogant or ridiculing you, just perpetuating the joke that *a lot* of VBA questions here can be answered by "don't activate or select". – Wolfie Aug 17 '17 at 08:37
  • @YoE3K, I have updated my question with more detail and follow-on question. –  Aug 17 '17 at 12:48
  • ".. about halfway down that I do activate the sheet. Then on the next line I am activating a range. I need to activate the range because I am then sorting in the With block that follows." There is no need to activate the sheet, or a cell, just to do a sort. Your `With` block should be `Application.ThisWorkbook.Worksheets("All Welders Data").Sort`, the unqualified `Range("B3")` used as the key should be `Application.ThisWorkbook.Worksheets("All Welders Data").Range("B3")`, and the SetRange could be `Application.ThisWorkbook.Worksheets("All Welders Data").Range("A1").CurrentRegion.Offset(1)`. – YowE3K Aug 17 '17 at 19:59
  • And, I agree that there are times when activating a sheet/cell is applicable. One situation that immediately comes to mind is when using the Solver add-in. It will **only** work on the `ActiveSheet`, so activating the sheet before using it is mandatory. – YowE3K Aug 17 '17 at 20:01

1 Answers1

1

Further to this comment from MSDN on the ThisWorkbook property:

Use this property to refer to the workbook that contains your macro code. ThisWorkbook is the only way to refer to an add-in workbook from inside the add-in itself. The ActiveWorkbook property doesn't return the add-in workbook; it returns the workbook that's calling the add-in.The Workbooks property may fail, as the workbook name probably changed when you created the add-in.

(my emphasis)

You should do this instead (and avoid Activate):

Dim wsData As Worksheet

Set wsData = ThisWorkbook.Worksheets("All Welders Data") '<-- use ThisWorkbook

' no need to Activate - just use the data
Dim rngStuff As Range

Set rngStuff = wsData.Range("A1:G5") '<-- change hardcoded range to whatever you need
' now work with the range

Edit

Regarding the updated question, I'd suggest code similar to below to populate the ComboBox on the UserForm without using Activate. It is tested apart from the section that deals with loading up the ComboBox which is mainly your working code anyway.

Option Explicit

Sub UserForm_Initialize()

    Dim wsData As Worksheet
    Dim rngToSortFirstCell As Range
    Dim lngLastRow As Long
    Dim rngNameCell As Range

    ' set a reference to the sheet
    Set wsData = ThisWorkbook.Worksheets("All Welders Data")

    ' clear existing sort fields
    wsData.Sort.SortFields.Clear

    ' set a reference to the header of range to sort
    Set rngToSortFirstCell = wsData.Range("B3")

    ' sort it by CurrentRegion of first cell
    rngToSortFirstCell.Sort Key1:=Range("B3").CurrentRegion, _
        Order1:=xlAscending, _
        Header:=xlYes, _
        MatchCase:=False, _
        SortMethod:=xlPinYin

    ' put welder names to combo box
    With wsData
        lngLastRow = .Cells(.Rows.Count, "A").End(xlUp).Row
        For Each rngNameCell In .Range("E3:E" & lngLastRow)
            If rngNameCell.Value <> "" Then
                With Me.chooseWelderNameComboBox
                    .ColumnCount = 2
                    .AddItem rngNameCell.Value
                    .List(.ListCount - 1, 1) = rngNameCell.Offset(, -1).Value
                    'ComboBox now shows the values in column "E" and the values
                    'in coulmn "D" - in that order, as in  "Name" - "ID Number".
                    '(the reverse order of the columns in the worksheet.)
                End With
            End If
        Next
    End With

End Sub
Robin Mackenzie
  • 18,801
  • 7
  • 38
  • 56
  • So it means i need to be even more explicite in my already lengthy code. Instead of worksheet("blah").... i have to go back and change a lot of code to include Thisworkbook. Would have been nice to pick up on that info prior to writing about 50 pages of code! Sheesh! Anyway, I will test this out tomorrow and come back with my results. Thanks for the answers and the comments. –  Aug 17 '17 at 04:49
  • "Avoid activate" seems to be the theme of the day! :). Point taken. But I do feel it serves its purpose in my case. Besides, why have it available at all if activating is so hideous? Maybe activate and select should just be removed from VBA? While we are at it lets just eliminate integers also. I see no point in using an integer when a long will do just fine. May as well get rid of longs and do everything with floats...just leave out the dicimal part. Can anyone think of other pointless parts of VBA we can eliminate? Tehehehe. :) –  Aug 17 '17 at 05:04
  • @fatmonkey Integer variable types are used when you want integers, not rational numbers (e.g. 1.0078935). Activate and select are used when you specifically want to have the user interface focus on a particular sheet or cell, respectively. For example, you might want to focus on a "Search Results" sheet after running a search on "Search Stuff". Activate and select are discouraged when you only want to calculate stuff or set values since there is no point. Your case only wants to set values. What is focussed on is irrelevant to the function of your macro. Not sure if sarcasm but there you go :) –  Aug 17 '17 at 05:19
  • @TheFizh. Actually, my remarks about integers etc were just goofing around a bit. I know what an integer is. But thanks for the reminder! Also, my case is not simply setting values and running functions. An addin can be much more than just automating some task in excell. My add in is doing a lot of stuff beside simple calculations. As I hinted at in another comment, activate is just how I make myself feel less paranoid about being sure that code is doing things on the sheet I intend it to do things on. Maybe its not best practice, but it makes me feel good! –  Aug 17 '17 at 05:42
  • To avoid having to add `ThisWorkbook` to the start of every statement, consider using [`With...End` statements](https://msdn.microsoft.com/en-us/vba/language-reference-vba/articles/with-statement). For example (`:` means new line), `With ThisWorkbook: .Sheets(1).Range("A1").Value = 0 : .Sheets("Sheet2").Range("A2").Value = 55 : End With`. Note you can only *nest* `With` statements if you're going further into the same object, not for example to do `With ThisWorkbook.Sheets(1) : With ThisWorkbook.Sheets(2) : ' Stuff : End With : End With` – Wolfie Aug 17 '17 at 08:44
  • @Robin Mackenzie I appreciate you answer and I am testing it this morning. Please see the added information I tacked on to my question and the follow-on question at the end. I would like to hear you opinion. –  Aug 17 '17 at 12:49
  • @Robin MacKenzie - Thanks for the revison. I appreciate that a lot. I will definitely put that in my arsenal going forward! Live and learn! –  Aug 17 '17 at 15:19
  • @Robin MacKenzie Throws Run-time error 1004: "Unable to get the Sort property of the range class." It's been I while since I ooriginally wrote my code but I think this the reason why I went with ActiveSheet.Sort instead of Range("B3").Sort. Looking at [THIS](https://msdn.microsoft.com/en-us/vba/excel-vba/articles/range-sort-method-excel) from Microsoft, this error baffles me since it is supposed to be the Range.Sort method. –  Aug 17 '17 at 15:44
  • @Robin MacKenzie - I rewrote it with the with code just as you have it in your answer. Now I get Run-time Error 1004: "The sort Reference is not Valid..." I then noticed that it was trying to perform the sort on the wrong sheet. No matter what sheet I select prior to running the code it will not do the sort. It ONLY works correctly if the sheet referenced is the ACTIVE sheet to begin with. I'm going back to my code I had working...I'm fine with committing the mortal sin of Activating a sheet. It works. –  Aug 17 '17 at 16:09
  • @Wolfie You **can** nest unrelated `With` blocks, although there is usually no point (but sometimes there is!). So `With ThisWorkbook.Sheets(1) : With ThisWorkbook.Sheets(2) : Debug.Print .Name : End With : Debug.Print .Name: End With` would be quite valid and will do what you would expect it to do. – YowE3K Aug 17 '17 at 20:09
  • Fair point, but it's likely more confusing than helpful for someone new to `With` blocks! One slip up thinking you're inside the first when you're actually in both and it's all unclear again:) – Wolfie Aug 17 '17 at 20:48