1

This code is designed to take a spreadsheet of raw data, omit several columns, and reformat what remains. I welcome any and all critiques of the code as I am new to VBA and know nothing. The key problem is at the end of the code on the line indicated with astriscs below. This is the spot where the "Compile Error: Invalid Qualifier" comes up. I'm trying to apply the formatting to Columns B and F, but I only want it to go as far as the last row of date. The last row of data will vary from one sheet to the next.

When the error is triggered, the debugger highlights the word "count".

Thanks in advance for your help.

Sub Macro2()
'
' Macro2 Macro
'
Union(Range("A:A"), Range("F:F"), Range("K:Q"), Range("S:V")).Delete
Range("A1").Select
ActiveCell.FormulaR1C1 = "FIRST"
Range("B1").Select
ActiveCell.FormulaR1C1 = "LAST"
Range("C1").Select
ActiveCell.FormulaR1C1 = "G"
Range("D1").Select
ActiveCell.FormulaR1C1 = "PHONE"
Range("E1").Select
ActiveCell.FormulaR1C1 = "ADDRESS"
Range("F1").Select
ActiveCell.FormulaR1C1 = "CITY"
Range("G1").Select
ActiveCell.FormulaR1C1 = "STATE"
Range("H1").Select
ActiveCell.FormulaR1C1 = "ZIP"
Range("I1").Select
ActiveCell.FormulaR1C1 = "MONTH"
Range("J1").Select
ActiveCell.FormulaR1C1 = "YEAR"
Columns("e:h").Insert Shift:=xlToRight
Columns("A:B").ColumnWidth = 12
Columns("C:C").ColumnWidth = 2
Columns("D:d").ColumnWidth = 13
Columns("e:e").ColumnWidth = 0.38
Columns("F:F").ColumnWidth = 5
Columns("G:G").ColumnWidth = 11
Columns("H:H").ColumnWidth = 0.38
Columns("I:N").ColumnWidth = 14
**Union(Range("B:B"),Range("F:F")).Rows.Count.End(xlUp).Row**
Range("B1").Activate
With Selection.Interior
.Pattern = xlSolid
.PatternColorIndex = xlAutomatic
.ThemeColor = xlThemeColorAccent5
.TintAndShade = 0.599993896298105
.PatternTintAndShade = 0
End With


End Sub 
Community
  • 1
  • 1
Thomas
  • 49
  • 1
  • 2
  • 5

2 Answers2

5

I use this technique when I get confused about the type of object I have created by stringing properties.

Within Excel's Visual Basic Editor, create a new module if you do not have an existing one that you wish to use in this way. If you select the module in Project Explorer and click F4, you can change the module's name to "Experiments" say.

Type or copy:

Option Explicit
Sub TestA()

End Sub

I always start my modules with Option Explicit. Look Option Explicit up in VBA Help and it will tell you why this is a good idea.

I have also created an empty sub-routine into which I will type some statements.

Start typing a new statement so you have:

Sub TestA()

  Debug.Print Range("B:B").

End Sub

When you type the period at the end of this new line, a pop-up window will show you the available methods and properties. This list will show, as expected, all the methods and properties of a Range. Type "Address" or select Address from the list to get:

Sub TestA()

  Debug.Print Range("B:B").Address

End Sub

Click F5 to run this macro and the following will appear in the Immediate Window:

$B:$B

This is the address of all rows in column B which is what you would expect.

Now add two further statements to the macro:

  Debug.Print Range("F:F").Address
  Debug.Print Union(Range("B:B"), Range("F:F")).Address

Run this macro again and you will get:

$B:$B
$F:$F
$B:$B,$F:$F

Again this is what was expected.

Now add:

  Debug.Print Union(Range("B:B"), Range("F:F")).Rows.

The pop-up window that appears will be unchanged because Range.Rows is still a range.

Complete the statement by adding or selecting "Address" and run the macro again to get:

$B:$B
$F:$F
$B:$B,$F:$F
$B:$B,$F:$F

This may not be what you expected but think about it. $B:$B,$F:$F is all rows in columns B and F so adding the property Rows does not change the address.

Now add the following statements to the macro:

  Debug.Print Union(Range("B:B"), Range("F:F")).Count
  Debug.Print Union(Range("B:B"), Range("F:F")).Rows.Count

Run the macro and these statements will each output an integer. I am using Excel 2003 so I get:

 131072 
 65536 

If you are using a later version of Excel, you will get larger integers. The second integer is the number of rows in a worksheet for your version of Excel. The first integer is the number of cells in two columns of a worksheet for your version of Excel.

Now add:

  Debug.Print Union(Range("B:B"), Range("F:F")).Rows.Count.

When you type the final period, no pop-up window will appear because an integer has no method or property that you can select in this way. Method .End(xlUp) operates on a range; it is not a property of Count which is why you get "Invalid qualifier".

It is very easy to get oneself confused when stringing properties together. Personally I avoid stringing properties because even if it is faster to run, it takes longer for me to understand and debug. There are situations in which minimising runtime is the top priority but is this one of those cases? How many hours have you wasted with this approach?

Consider:

  Dim Rng1 As Range
  Dim Rng2 As Range
  Dim Rng3 As Range
  Dim RowMax As Long

  Set Rng1 = Range("B:B")
  Set Rng2 = Range("F:F")
  Set Rng3 = Union(Rng1, Rng2)
  RowMax = Rng3.Count

  Debug.Print RowMax

  Debug.Print Rng3.Find("*", Range("B1"), xlValues, xlWhole, xlByRows, xlPrevious).Row

You do not need RowMax but I have included it so you are absolutely clear what Rng3.Count returns. I have also gone OTT with the ranges. I would be happy to type: Set Rng3 = Union(Range("B:B"), Range("F:F")) because I find it easy to understand.

Method .End(xlUp) operates on a cell. MultiCellRange.End(xlUp).Row is valid syntax but I cannot get it to return useful information. If you want to use .End(xlUp) consider:

  Dim RowMaxColB As Long
  Dim RowMaxColF As Long

  RowMaxColB = Cells(Rows.Count, "B").End(xlUp).Row
  RowMaxColF = Cells(Rows.Count, "F").End(xlUp).Row

I agree with Siddharth, Find appears to be the best approach in this situation. However, you should look at this answer of mine, https://stackoverflow.com/a/20849875/973283, to a different question. It includes a macro that demonstrates a selection of methods of finding last rows and columns and shows the situations in which they fail.

Community
  • 1
  • 1
Tony Dallimore
  • 12,335
  • 7
  • 32
  • 61
  • @L42. Thanks. It may be over 10 years since I first learnt Excel VBA but I still remember how confused I was with the object model. – Tony Dallimore Jan 05 '14 at 15:42
  • Thanks to all for your contributions here. Tony, I greatly appreciate the time you took to share your knowledge. BAD CODE INDEED...LOL I have LOTS to learn. Thanks for your help! There is a lot to absorb here. – Thomas Jan 06 '14 at 02:41
  • @Thomas. I doubt my first macro was this ambitious or this good so don’t feel bad. – Tony Dallimore Jan 06 '14 at 09:19
1

You have not asked the question to which this is an answer. See Requested answer for the answer you requested. Your code works so I doubt you know it is bad VBA.

From the comment at the top, I assume this code was created by the Macro Recorder. The Macro Recorder outputs syntactically correct code but it is poor code. At least part of the reason for the poor code is that statements are being recorded as you type. The Recorder does not know your objective. If the cursor is in cell A1 and you press Right, the Recorder will select B1 because that is what happens on the screen. There may be situations in VBA for which Select is a useful method but they are rare.

The Recorder also operates on the active worksheet which is rarely a good idea. If the wrong worksheet is active when this macro is started, it will be destroyed with 13 columns deleted.

I will assume you only have one workbook open so I can assume the active workbook is the required workbook. It is only a little more complicated to handle multiple workbooks but I would rather ignore that complication.

Consider:

Sub TestB()

  With Worksheets("Sheet1")
    .Range("A1").Value = "FIRST"
  End With

End Sub

With Worksheets("Sheet1") states this code is to operate on the worksheet named "Sheet1" of the active workbook. End With terminates a With statement.

You can nest With statements:

  With Worksheets("Sheet1")
    With .Range("A1")
      .Value = "FIRST"
      .Font.Bold = True
      .Font.Color = RGB(0, 255, 255)
    End With
  End With

The period at the beginning of .Range("A1") states .Range("A1") is to operate within the current With which is Worksheets("Sheet1"). If I omit that period, Range("A1") operates on the active worksheet.

The period at the beginning of .Value states it is to operate on the current With which is Range("A1") within Worksheets("Sheet1"). If I omit that period, Value operates on the active cell.

The use of With statements makes your code more compact and much clearer. However, you must include the periods. Consider:

  Dim Rng1 As Range

  With Worksheets("Sheet2")
    Set Rng1 = Union(Range("A:A"), Range("C:D"), Range("F:G"))
    Debug.Print Rng1.Address
    Debug.Print Rng1.Worksheet.Name
    Set Rng1 = Union(.Range("A:A"), .Range("C:D"), .Range("F:G"))
    Debug.Print Rng1.Address
    Debug.Print Rng1.Worksheet.Name
  End With

If the active worksheet is "Sheet1", the output is:

$A:$A,$C:$D,$F:$G
Sheet1
$A:$A,$C:$D,$F:$G
Sheet2

The range addresses look the same but they apply to different worksheets.

You could replace a lot of your code with:

  With Worksheets("Sheet1")
    .Range("A1").Value = "FIRST"
    .Range("B1").Value = "LAST"
    .Range("C1").Value = "G"
    .Range("D1").Value = "PHONE"
    .Range("E1").Value = "ADDRESS"
    .Range("F1").Value = "CITY"
    .Range("G1").Value = "STATE"
    .Range("H1").Value = "ZIP"
    .Range("I1").Value = "MONTH"
    .Range("J1").Value = "YEAR"
  End With

You can also write:

  With Worksheets("Sheet1")
    .Range("A1:J1").Value = Array("FIRST", "LAST", "G", "PHONE", "ADDRESS", _
                                  "CITY", "STATE", "ZIP", "MONTH", "YEAR")
 End With

This VBA is a lot more advanced. You can copy values from a worksheet range to an array or from an array to a worksheet range. There are a number of questions with answers that explore this capability and I will not repeat those answers here. This is just a demonstration for you to explore later if you are interested.

I do not like what you are doing. In your first statement you delete some columns so column B becomes column A and column R becomes column I. You then change the column headings in their new positions. Since you are only moving the data, why do you need to change the column headings? More importantly, in nine months someone is going to add a column or rearrange existing columns. Your code will carry on regardless and your name will be mud.

I would prefer something like:

  With Worksheets("Sheet1")
    If .Range("B1").Value = "FIRST" And .Range("C1").Value = "LAST" And _
       .Range("D1").Value = "G" And .Range("E1").Value = "PHONE" And _
       .Range("G1").Value = "ADDRESS" And .Range("H1").Value = "CITY" And _
       .Range("I1").Value = "STATE" And .Range("J1").Value = "ZIP" And _
       .Range("R1").Value = "MONTH" And .Range("W1").Value = "YEAR" Then
      ' Column headings as expected.  Continue with macro.
    Else
      Call MsgBox("I am sorry but I cannot proceed because the column " & _
                  "headings are not as I expected.", vbOKOnly)
      Exit Sub
    End If
  End With

If your code requires a worksheet to be in a particular format and that format might change over time then check that format.

Community
  • 1
  • 1
Tony Dallimore
  • 12,335
  • 7
  • 32
  • 61