Would like to define reference variables (calls a value from a cell in the sheet using ActiveSheet.Cells[row, col]) in one location in a module, for use across multiple subs in an MS Excel file. The file is an action tracker, the subs automate some of the emailing (each subs opens emails under given conditions). All reference variables are the same for each sub - defining in one place will make maintaining the spreadsheet much simpler.
Tried to define variables above first sub, error message appears on first value (as detailed below). I've searched (a) Global Variables and (b) how to define above the subs. However (a) variables all in the same module (b) error message as detailed below. I haven't located a guide on defining variables using ActiveSheet.Cells() references.
Option Explicit
'Defines variables for all macros:
'Defining Reference Variables
Today = ActiveSheet.Cells(2, 4)
ActionLogTitle = ActiveSheet.Cells(3, 3)
IPT_Leader = ActiveSheet.Cells(7, 7)
(On Today = ActiveSheet.Cells(2,4) error highlights on "2")
Compile error:
Invalid outside procedure
As the compiler is hinting, you cannot write assignments outside of a Sub/Function.
You can declare a function for each variable:
Function MyValue()
MyValue = ActiveSheet.Cells(2, 4).Value
End Function
Ideally you don't use ActiveSheet unless that's really what you want though.
There are a lot of ways to define today, but using a word, which is used by Excel English formula =TODAY(), is probably a discussable idea (although it will work!). In general, consider declaring the variable like this somewhere in the modules:
Public myToday as Date
Then, you may reset it everytime the worksheet is openned:
Private Sub Workbook_Open()
myToday = Date
'or
myToday = Worksheets("Name").Range("D2").Value
End Sub
Anyway, working with Public variables is in general discouraged in any programming language, thus it is probably a better idea to come up with a dedicated function or class for it.
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.
This question already has answers here:
Getting an error `xlValues` is not defined when Cells format to the others
(3 answers)
Closed 6 years ago.
I want to use VBScript to process EXCEL instead of VBA. But I encountered code errors when calling method Range.Find, see below
Function find_range(wb, domain, var)
Dim sheet
Dim rg1, rg2, rg3
Set sheet = wb.Sheets(domain)
Set rg1 = sheet.Range("D:D").Find(var, , xlValues, xlWhole )
If Not rg1 is Nothing Then
'msgbox rg1.Cells(1,1).row
Set rg2 = sheet.Cells(rg1.Cells(1,1).row, 19)
msgbox(rg2.value)
End if
End Function
when executed find_range function, I got
variable is undefined "xlValues" "xlWhole"
error.
So I guess that I cannot just use excel built-in constants this way in VBScript.
So what is the correct way?
Unfortunately these Named Constants are part of the Excel Object Library which VBScript has no way of referencing so the best approach is to lookup the named constants in Object Browser inside Excel VBA or online via various references then create your own named constants and use them in your code.
In this example you are using two enumerations that can be identified by looking up the Range.Find() method.
xlValues is a named constant in the xlFindLookIn enumeration and has the value -4163.
xlWhole is a named constant in the xlLookAt enumeration and has a value of 1.
So once you know the value you can define them and your code should work without any more changes being required.
Const xlValues = -4163
Const xlWhole = 1
Ideally these values should be declared in the global scope of your script so they are accessible to any function or procedure.
You might ask why not specify the numeric value? Well while this is indeed a valid approach if you use the value in multiple places you then have to modify that value in multiple places if the value ever changes (however unlikely in this scenario). With a named constant you make one change and wherever in your code that value is referenced is also changed.
It's also worth noting that VBScript is only interested in the numeric value so technically you can name the constants anything you wish. However, it is good practice to follow the naming convention, especially if you re-used the function code in Excel VBA for example.
I'd like to preface this question by saying that I am an undergrad in college who knows C++ and has a very rudimentary understanding of VBA.
Now then, as stated in the title I need some help configuring some VBA code for an Excel worksheet so that whenever a cell in a column (specifically the D column) is modified it will automatically update other cells within the same row.
Essentially I want this to work such that when user Bob modifies cell D26 (for example) it will call a custom function I built and insert that code into cell B26 and then repeat with a different function for cell C26.
However, this function needs to be such that if cell D27 is modified it will only modify other cells in row 27, leaving row 26 and prior or subsequent rows alone until such a time as this function is called in D28 and so on.
I'm not entirely sure if this is even possible but I'd be gracious if anybody could help me configure this.
The code I built/scavenged from the internet for my custom function is this:
http://pastebin.com/RE0V2nrT
The second function I want to call for this project is the =TODAY() function built into Excel.
The code I have scraped together so far for checking if the cell has changed is this:
http://pastebin.com/S5E8cmty
If anybody could help me understand how to write what I'm looking for it would be much appreciated. If you have a different approach to solving the issue I would also love to hear it... as long as you could help me then enact your solution, haha!
Anyways, thanks to anybody who replies.
Have a look at the worksheet events available within the Excel namespace.
For this, you would use the Change event
If you double click on the worksheet you want to monitor, you can insert a Worksheet_Change sub. Then you can use the intersect function to check if the changed cell was within your range you want to monitor (e.g. D:D).
You can specify which cells you want to change. Here I just gave an example based on what you asked. This will put the output of your function into cell B[R] and put the current date into cell C[R]. Note that I'm using the Now() function since there is no Today() function in VBA. Since this returns both date and time, I'm using the Format function to get just the date.
Just for fun, let's go a little further into the object model and first get the Worksheet object to which the target range belongs. This is not 100% necessary - you could just rely on ActiveSheet. Now, you probably don't need to do this, and it's mostly just for fun, but it's also worth noting that if you were programmatically making changes to this sheet, but had not activated this sheet first (so another sheet was active) and you had not turned off EnableEvents you would get some strange results :)
Private Sub Worksheet_Change(ByVal Target As Range)
Dim TargetSheet As Worksheet
Set TargetSheet = Target.Parent
With TargetSheet
If Not Application.Intersect(Target, .Range("D:D")) Is Nothing Then
.Cells(Target.Row, 2) = ExtractWindowsUser()
.Cells(Target.Row, 4) = Format(Now(), "YYYY-MM-DD")
End If
End With
End Sub
Explanation
Worksheet change sub is declared like this. The Worksheet objects have pre-defined method stubs for events. Kind of like an interface, though not listed as an interface in the documentation. If you think of it in that concept, this is your event handshake. See the link I posted above for a list of the worksheet events available.
Private Sub Worksheet_Change(ByVal Target As Range)
In the next lines we are getting the worksheet object to which the object named Target belongs. You can see in the sub declaration that Target is declared as an object of the type Range. If you check out the Worksheet object (linked above) or the Range object documentation you'll see that the range object is a member of the worksheet object, and the documentation kind of sucks here, but FYI the worksheet object is contained within the Parent property. Now, originally I had my code using the ActiveSheet member of the Application object - but I've edited it for the reasons given in my answer above.
Dim TargetSheet As Worksheet
Set TargetSheet = Target.Parent
I use With Blocks to save typing the same Worksheet reference in multiple places. A With block just lets me access the members of the namespace specified (in this case members of the object TargetSheet) by typing .SomeMember. The compiler understands that every reference like this refers to whatever is specified in the opening With .... statement. I personally like this for readability, but I also recommend it for maintenance (change reference one place vs many). Also having a single reference gives a tiny, insignificant, probably not worth mentioning performance boost over multiple references as well.
With TargetSheet
Next we check whether or not Target is within the range of cells we want to watch. The If....Then should look familiar enough. For our condition we use the boolean operator Not to check if the result of the intersect function (linked above) Is Nothing. The reason we do this is to check if the return is allocated. If an object is allocated the Not SomeObject Is Nothing condition will evaluate to False. If the object is not allocated (i.e. our Intersect function failed to return anything) then the statement evaluates to True. So, from the Intersect function documentation we know that if our return is allocated, the ranges intersect and the intersecting range object was returned. Thus if we want to know if they intersect, we can just check for the opposite of a failure.
If Not Application.Intersect(Target, .Range("D:D")) Is Nothing Then
The next lines then just execute some code on cells within the same row as Target. We use the Cells member of the worksheet object to specify what cells to modify. Per the documentation, the default property for Cells is Item which lets us access a range object through a row and column index like this: .Cells[Row,Column]. So, I simply use the row of our Target object and the column you wanted (column "A" =1, "B"=2, etc. You can see this by changing excel properties to R1C1 reference style if you are interested).
.Cells(Target.Row, 2) = ExtractWindowsUser()
And I think the Format() and Now() functions are pretty well explained in the documentation.
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.
To my astonishment, I realized that I could not set a range at some point of my code. And I've found nowhere any mention of this issue.
So I declared (with dim) a Range variable.
And then I do Set Myvar = Worksheets("blabla").Range("A1:C3")
And Excel throw me an error code 9, out of range...
When I put the same line at the top of my code, it works (where I have my other sets).
Is that a known spec of VBA or is there something wrong for me ?
edit:
Apparently, the problem wasn't "set", but the fact that I can select a range only when the workbook is activated.
My code:
Transform_file.Worksheets("general balance").Range(Cells(startline, 1), Cells(LastCellNumber, 1)).Value = "cool"
Works only if I have Transform_file.activate before
Hence the issue
of my question wasn't Set, which can apparently be used anywhere.
I think the problem is that you are not aware how VBA uses some common defaults and assumptions. When you use:
Worksheets("blabla")
Excel assumes you mean this:
ActiveWorkbook.Worksheets("blabla")
I fact it assumes even more:
Application.ActiveWorkbook.Worksheets("blabla")
The best way to avoid these kinds of issues is to be explicit in your code by creating a workbook object when you open the workbook. Like so:
Dim wkb As Workbook
Set wkb = Workbooks.Open("blah.xls")
wkb.Worksheets("blabla")
Or if you your code refers to a worksheet that is in the same workbook as your code then you could use:
ThisWorkbook.Worksheets("blabla")
These assumptions also work for for properties of objects which is why the language has the SET command. So example when you code:
Range("A1")
What Excel will acually return is:
Range("A1").Value
This is because Value is the default property of a Range Object. The language is built this way I assume to save coding - most of the time you would wnat the Value of the range. The problem is when you want to assign the object to a variable and not it's default property which when you need to use SET. SET just says return the entire object.