folks. I'm a programming newbie, trying to write a macro to extract some rows of data from multiple workbooks and compile them into a new workbook, then graph them. I have figured out how to loop through the source spreadsheets, and a few other things, but right now I'm stuck on a couple of places. (I'll ask my different questions in different threads for clarity.)
This question is about an error when using WorksheetFunction.CountA to get the number of a list of items I'm searching for. I want to know the number so that I can know when I have found all of my search strings. If I can't find them all, I want to inform the user and quit.
Following advice on this question, I wrote a snippet to test the CountA function. I get an "object required" error. I have reviewed several threads on that topic, but I'm still not getting it. Here is my code snippet:
Sub a_test_kpi_count()
Dim kpi_list_count As Integer
Set kpi_list_count = Application.WorksheetFunction.CountA("kpi_list")
MsgBox "There are " & kpi_list_count & "kpis in the list."
End Sub
When I run the code, the editor stops with either "kpi_list_count" OR "CountA" highlighted. Well, THAT'S helpful!
I should add that "kpi_list" is a named range on the worksheet where the macro lives. However, I get the same error when I specify the range in this manner:
Set kpi_list_count = Application.WorksheetFunction.CountA("K3:K7")
Ergo, I don't think the named range is my problem.
A nudge in the right direction would be much appreciated!
OR, feel free to call me an idiot and point out my obvious error! ;-)
EDIT:
Thanks, Ben.
This code is working:
Sub a_test_KPI_count()
Dim KPIListCount As Long
KPIListCount = WorksheetFunction.CountA(Sheet1.Range("KPI_list"))
MsgBox "There are " & KPIListCount & "KPIs in the list."
End Sub
Thank you! Oddly, it does NOT work when I use the sheet name, buttons. Are sheet names case sensitive? I ask because the editor insists on "Buttons" rather than, "buttons".
A couple more questions for my learning, if I may, about some of your tips:
3, long vs. integer. In this case, I'm counting a short list (<6) items. Is integer acceptable here, or would it be best practice to stick with Long?
Camelcase vs snakecase. Noted. Is this simply a preference of more experienced programmers? Or is there a functional difference?
Your answers have all been of the kind I hope for. A fishing lesson, rather than a fish! Thank you!
Set is for Object variables - remove it.
You need a Range call when specifying a named range.
kpi_list_count = Application.WorksheetFunction.CountA(Range("kpi_list"))
99.9999% of the time you want a Long, not an Integer; see this question:
Dim kpi_list_count as Long
Best practice is to specify which sheet the Range is on (change as necessary):
kpi_list_count = Application.WorksheetFunction.CountA(Sheet1.Range("kpi_list"))
You can drop the Application if you want.
Consider using camelCase instead of snake_case.
Related
I have a question about naming conventions and using the same variable name in different subs. It's been bugging me since I started with VBA.
Is it good practice to use the same variable name in different modules? The subs don't interact with each other. For example, I cycle through the sheets in my workbook in two different modules and so far have used the same name for the counter variable (count_ws):
Module 1:
Sub Test()
Dim count_ws As Long
For count_ws = 2 To ThisWorkbook.Worksheets.Count
Debug.Print "Module1"
Next count_ws
End Sub
Module 2:
Sub Test2()
Dim count_ws As Long
For count_ws = 2 To ThisWorkbook.Worksheets.Count
Debug.Print "Module2"
Next count_ws
End Sub
If this is not ok, what's the best alternative? The reason I repeat the name is that I didn't want to make the variable name too long, like count_ws_module1 and count_ws_module2
Passing a variable into another sub: Same question, is it advisable to keep the same name? I feel like it could be confusing if I call the variable one name in the first sub, and then something else in the other sub.
Sub Test3()
Dim wsLoans As Worksheet
Dim wsBS As Worksheet
Set wsLoans = ThisWorkbook.Sheets(2)
Set wsBS = ThisWorkbook.Sheets(3)
Call Test4(wsLoans)
End Sub
Sub Test4(ByVal wsLoans As Worksheet)
wsLoans.Range("A1").Value = "Module 4"
End Sub
So, for me this approach seems the most readable and avoids confusion, but I'm happy to hear other opinions. In Sub Test4 I could simply name the Sheet ws. Or wsLoans_Test4, but does this actually help?
I just want to make sure I get this right and build good habits.
This is a question that can lead to endless discussions, and the more you think about the whole subject of naming conventions, the deeper it goes. And as this is strongly opinion bases, those kind of questions are usually closed on Stack Overflow.
I will quickly list 3 aspects:
(1) A subroutine (or function) can be seen as an closed object, often seen as a black box. It should do a defined task, however, how this is done shouldn't matter. It could be stored in a different module and could be written by a different person. You shouldn't have to ask someone "Have you already used the variable name count_ws - if not, I want to reserve it for me. Every routine should use whatever name it likes.
(2) You as a programmer should have some naming conventions. They don't need to be written down, but you should have a specific consistency. Do you name a sheet variable wsData or dataWs, do you use camelCase, PascalCase or snake_case, use Hungarion Notation or not... As a consequence, you will probably name variables identically in different routines when they serve a identical or similar purpose - and why not. Again, you shouldn't have to look into your code if you used the same name already. Exception is if you are dealing within the same routine, don't use the same variable for different purposes, and be careful when naming iteration variables in nested loops.
(3) Function parameter names serves as a documentation. The parameters are the interface between two routines and if you give them good names, it gets easier to figure out what's the purpose of it. If you want to call a routine Copy that receives 2 parameters which are named p1 and p2, you first have to figure out what is source and what is destination, while pFrom and pTo makes it obvious. That said, if you are happy with your naming, there is no reason not to name a variable of a calling routine like the parameter name of a subroutine.
I have been developing a VBA, UserForm1 program for the past few weeks and I have performed numerous test and never once had this problem.
The code, shown below, is part of the Initialization routine. I made some enhancements to the program, far deeper in the code than the code under discussion. Now, I can't get past this point in the program. I replaced "Activate" with "Select", but this did not affect the outcome, i.e., an aborted run.
Please, can someone suggest what I am doing wrong? Or, how can a program that been tested dozens of times, suddenly develop a fault out of thin air?
'======================================================================
Dim WPA() As Variant 'Workbook path for Category Class
Dim WBA() As Variant 'Workbook name for Category Class
Dim WSA() As Variant 'Worksheet name for Category Class
'======================================================================
Dim WBK() As Workbook
..........
Set WBK(S) = Workbooks.Open(WPA(S))
Workbooks(WBA(S)).Worksheets(WSA(S)).Activate 'Activate Worksheet
NWS(S) = CheckSheetExists(WSA(S)) 'Check for Worksheet
..........
Function CheckSheetExists(SheetName) As Boolean
CheckSheetExists = Evaluate("ISREF('" & SheetName & "'!A1)")
If CheckSheetExists = False Then
MsgBox "Worksheet " & SheetName & " does not exist. Run aborted."
End '<==================Run aborts
End If
End Function
It looks like you are using global variables?
If so, you should be aware that they are not as permanent as you might expect.
There are a number of things which cause global variables to reset and become undefined.
See this answer for more info: https://stackoverflow.com/a/7043901/1473412
Using "End"
An unhandled runtime error
Editing code
Closing the workbook containing the VB project
Is it possible that the "enhancements to the program, far deeper in the code than the code under discussion", causes one of these things to happen, and so resets the global variables?
If you have to use global variables, you could store them safely in a worksheet. Or, everytime you try and use one, you could check that it is defined, and if not, redefine it?
That is a good question, Storaxs' answer should have fixed this though to my knowledge.
However when something like this happens I try to create a whole new workbook and paste all of this code into it (just for the sheet in question) and then copy all the cells data into the new sheets.
This has saved me once before and it may help you in this case as well.
I also recommend not deleting your old workbook in case you don't port everything over.
If this still doesn't work let us know and we will continue to look into this.
Thanks for your help in advance. I haven't stumbled across a post dealing with this, so maybe I'm asking the wrong question. I have quite a specific problem using Excel with my own VBA code. I'm writing a budget script, which has an item code '010'.
I'm using a script to process line items per code (not necessary to go into details here). Now, my problem is that I typecast the code as text in the source sheet by using ='010.
However, when I pull the data across into the destination sheet using the script, it always displays 10.
My question is - how can I prevent this? Please note that the budget code is set by our clients, so I cannot possibly change this.
Much appreciated and kind regards
If you're looking out for a Solution in Excel-VBA, then here it is..
Sub test()
Dim Rng As Range
Set Rng = Range("A1:A" & Cells(Rows.Count, 1).End(xlUp).Row)
Rng.NumberFormat = "000"
End Sub
I have a userform that is supposed to insert a row on Sheet3 and populate some cells in that row with some values. it works great as long as I have sheet3 displayed. (The form is shown modeless to give me access to the sheets).
Anyway, I happened to have another sheet active and ran it again and was surprised to see it inserted the row not in sheet3, but in the one I had displayed... (Thank God I saved first!)
In the code, I specified a range object as follows to find the insertion point: (I'll truncate the code a bit to keep it simple)
Dim RecordRange As Range
Set RecordRange = Sheet3.Cells(RowVariable,ColumnVariable)
RecordRange.Offset(1,0).EntireRow.Insert
blah blah blah.
A workaround is to activate the sheet first:
Sheet3.Activate
That at least inserts into the correct sheet, but I'd rather not have sheet3 be displayed when I add the record, so I even surrounded that line with:
Application.ScreenUpdating = False
Sheet3.Activate
Application.ScreenUpdating = True
unfortunately, ScreenUpdating doesn't work from within a Userform Code module, so that bites...
I still don't understand why it chooses to insert the row into whatever sheet is active, when I've already specified sheet3 in the code. I have another Macro very similar that doesn't have this problem.
any ideas?
...so you figure your workaround needs a workaround? Hmm... I think that qualifies as a double-negative since a workaround is supposed to, by definition, fix problems rather than cause them...
A workaround is a bypass of a recognized problem in a system. A
workaround is typically a temporary fix that implies that a genuine
solution to the problem is needed. But workarounds are frequently as
creative as true solutions, involving outside the box thinking in
their creation.
Typically they are considered brittle in that they will not respond
well to further pressure from a system beyond the original design. In
implementing a workaround it is important to flag the change so as to
later implement a proper solution.
Placing pressure on a workaround may result in later system failures.
For example, in computer programming workarounds are often used to
address a problem or anti-pattern in a library, such as an incorrect
return value. When the library is changed, the workaround may break
the overall program functionality, effectively becoming an
anti-pattern, since it may expect the older, wrong behaviour from the
library. (Wikipedia)
Just sayin'... :-)
So instead of fixing the workaround, your original issue is (thankfully) straightforward.
You're using Sheet3 as an object, and I suspect you haven't assigned anything (like Worksheets("Sheet3") to an object called Sheet3.
Try this instead:
Dim RecordRange As Range
Set RecordRange = Worksheets("Sheet3").Cells(RowVariable,ColumnVariable)
RecordRange.Offset(1,0).EntireRow.Insert
If you indeed intended to use Sheet3 as an object then make sure it's declared as set, so for example you could instead use:
Dim RecordRange As Range
Dim Sheet3 As Worksheet
Set Sheet3 = Worksheets("Sheet3")
Set RecordRange = Sheet3.Cells(RowVariable,ColumnVariable)
RecordRange.Offset(1, 0).EntireRow.Insert
Also, just to point out: If either of these solves your issue then the problem would've "made itself known", by including one line at the top of every module (at least during development & troubleshooting):
Option Explicit
This link has a short explanation, but basically it forces you to properly declare all variables (thus helping to prevent future workarounds!)
I am an old man trying to compare dates from two different files in Excel.
My code is:
Dim i As Integer
For i = 1 To 7
IF Data_for_Processing.xls (“SOLARLOG”). Cells (i,”A”).Value = Day_Conversion_chart.xls (Sheet1).Cells (i+2, “B”) Then
Cells(7+I, “B”)=”Equal”
Else: Cells(7+i, “B”) = “NotEQ”
End If
Next i
Will anyone help?
First of all, I would recommend following #simoco 's advice - Reading the documentation will provide the tools for solving future problems and give you the basics. As well as that, I would recommend using vba help. And another very useful tool for trying commands could be recording macros and analyzing them later on the editor.
So, first you need the code to be inside a macro. It will look like this (I chose the name TestMacro):
Sub TestMacro()
'Code here.
End sub
You should take into account that when your macro is running, it does so from the sheet you are working in, so any partial references to cells, etc. will refer to the book you are in and the sheet you are in when you run the macro. It is possible to select another sheet or book, and if you do so manually or on the code, references will be applied on that selected book or sheet.
What I call partial references here are those that read simply "ThisCell" instead of "ThisBook.ThisSheet.ThisCell". I would say that using partial references, though, is appropriate in a vast majority of cases.
The way your code is organized, be careful to run it from the workbook where you want the data to be in the end, let's say, in your 'main' workbook, so that the final values will be written there..
Another comment: whenever you want to use another file, this file must be open (as far as I know), while you are using it. In your code, you don't open any file, so the macro will not work if you don't open ALL referenced workbooks manually prior to running the macro.
When you want to reference something inside something, you mostly use ".". Please read the documentation - You will get a better idea of how this works. For this example:
Book.Sheet.Cell.Value is the structure you are using.
A book can be referenced as Workbooks("Name.xls") if it is open.
A sheet or worksheet can be referenced as Sheets("Name") or Worksheets("Name"), and also with numbers, like Sheets(1) or Worksheets(1). The differences can be seen on vba editor help. I chose Sheets(...) for your example.
Be careful with the symbols. I guess this was probably my problem, but I have to mention it just in case: When I copied your code, instead of double quotes (""), I got something different, that Excel would not recognize. If for any reason you are using different symbols, Excel might not understand.
A cell can be referenced in various ways too. The Range object is used extensively, and if you want to use "A1", "C44", etc., it's probably better to go for it. I like using Cells(,) as you did, when possible. As far as I know, this works nice with numbers (Cells(1,2), for example), which may be very convenient too. I kept this on your code, changing "A" and "B" and writing 1 and 2, respectively.
With all these changes incorporated:
Comments:
'NOTICE THAT ALL WORKBOOKS MUST BE OPEN.
'["A"] changed to [1]
'[Sheet1] changed to [1]
'["B"] changed to [2]
'Data_for_Processing.xls(“SOLARLOG”).Cells(i, 1).Value
'becomes Workbooks("Data_for_Processing.xls").Sheets(“SOLARLOG”).Cells(i,1).Value
'Day_Conversion_chart.xls(1).Cells(i + 2, 2).Value
'becomes Workbooks("Day_Conversion_chart.xls").Sheets(1).Cells(i+2,2).Value
'["B"] changed to [2]
And one possible solution:
Sub TestMacro()
Dim i As Integer
For i = 1 To 7
If Workbooks("Data_for_Processing.xls").Sheets("SOLARLOG").Cells(i, 1).Value _
= Workbooks("Day_Conversion_chart.xls").Sheets(1).Cells(i + 2, 2).Value Then
Cells(7 + i, 2) = "Equal"
Else: Cells(7 + i, 2) = "NotEQ"
End If
Next i
End Sub
This worked on my Excel example - I hope it is of some help.
Regards.