*Note: Editing this question to reach out to VBA people, since it might be the same process that Excel uses. SVB is just a specialized form of VBA.
I have a macro stored in file FileA.svb.
In this case, it's Main(), which calls other functions in the file.
I need my macro to find out the name of the file it's saved in.
The following works, but of course it will get a wrong answer if there is more than one macro open.
' Get the name of this macro / analysis
Dim strMacroName As String
strMacroName = Application.Macros.Item(1)
Debug.Print strMacroName
In Statistica (or Excel), is there a property that will allow the a function/macro to see the name of the file in which it is stored? For Excel, I don't want the XLS file, but rather the name of the code module containing the functions. I don't think SVB has the "Me" keyword, unfortunately.
In Excel, the following only really work while you're in the VBE:
Application.VBE.VBProjects(1).FileName
Application.VBE.ActiveVBProject.Name
Application.VBE.ActiveCodePane.CodeModule
I'm not aware of a way to figure it out, other then having it hard-coded into your macros.
See Mathieu Guindon's post: Code Review: Managing a programmatically accessible stack trace
It goes something like this...but with the CallStack Class fully implemented:
Option Explicit
Private Const ModuleName As String = "Module1"
Sub DoSomething(ByVal value1 As Integer, ByVal value2 As Integer, ByVal value3 As String)
CallStack.Push ModuleName, "DoSomething", value1, value2, value3
TestSomethingElse value1
CallStack.Pop
End Sub
MsgBox Application.ActiveMacroEx(False).Name
or
MsgBox Application.ActiveMacroEx(False).FullName
Related
I am trying to figure out how scope works.
For instance: I'm working on a userform in which a check procedure validates that new entries are not already present in the list. Because I'm probably going to use the full name (concatenation of first and last name) in different userforms within the same worksheet, I entered it as a Global variable in the general module:
Public Sub PublicVars()
Public FullName As String
End Sub
When I add the definition of the variable to the public sub, the check box does not work, but when I define it in the object in the userform, it does.
Private Sub cmbLastName_AfterUpdate()
FullName = cmbFirstName & " " & cmbLastName
If Application.WorksheetFunction.CountIf(Range("List_Full_Name"), FullName) Then
FullNameError = MsgBox("Name already included in contact list. Edit contact?", vbYesNo, "Error")
Else
End If
End Sub
This seems sensitive to mistakes, as when I want to use FullName for another object in the same userform or in a different userform in the same workbook, I would have to redefine it locally. I am probably overlooking something, as I cannot believe this is the right/most effective way.
Are there any other ways to tackle this?
I'm conducting a VBA code where I use a function defined by the company. This function is contained in a Project which is Password protected, which I will not get access to, and I'm rather sure it should not be needed.
I can call the function through the worksheet "directly", i.e. in a cell I can write =TONNES(B2,B3,B4) and the cell output is the correct value. However in VBA when I use:
Private Sub CommandButton1_Click()
Dim param1 as String, param2 as String, param3 as String
param1 = Cells(2,2)
param2 = Cells(2,3)
param3 = Cells(2,4)
Cells(1,3) = TONNES(param1, param2, param3)
End sub
I get the error that the function TONNES is not a sub or function.
I have saved my Macro in a Module, and I don't have access to the actual code of the User defined function. Is there a way to be able to use this through VBA? As it can be used directly in the worksheet, but not through VBA, can that conclude that the function is disabled through VBA for some reason?
EDIT: The function is an add-in (more specifically I have a Company add-in which contains the data from production in a summarization page, which has created these functions in order to present these in the summarization page).
So I've finally found a solution. Basically going into Tools -> References in the VBA Environment (Alt + F11) and adding in the specific references caused a solution to this issue. Hopefully this may be of help for someone else with this problem.
I have a VBA template project that runs automatically when a Word document is opened. However, if I open multiple documents, they all share the variables values. How can declare these variables to be only associated with the active window or active document?
I tried declaring them in a Class Module, but that did not help. Switching between opened document I can see that these variables are shared.
Any input is appreciated...
This what I have in my Module:
Option Private Module
Dim CurrentCommand As String
Public Function SetCurrentCommand(command)
CurrentCommand = command
End Function
Public Function GetCurrentCommand()
GetCurrentCommand = CurrentCommand
End Function
More Info: The code/Macro start at AutoExec like this:
Public Sub Main()
Set oAppClass.oApp = Word.Application
If PollingRate <> "" Then Application.OnTime Now + TimeValue(PollingRate), "CaptureUserViewState"
End Sub
And the CaptureUserViewState is a Sub that resides in a different Module and does all teh checks (comparing new values to last recorded ones) and here how this Sub does the check:
If WL_GetterAndSetter.GetLastPageVerticalPercentage <> pageVerticalPercentScrolled Then
'Update the last value variable
WL_GetterAndSetter.SetLastPageVerticalPercentage (pageVerticalPercentScrolled)
'log change
End If
You don't give us much information, but I assume you declared public variables at module level like this:
Public myString As String
Public myDouble As Double
From VBA documentation:
Variables declared using the Public statement are available to all procedures in all modules in all applications unless Option Private Module is in effect; in which case, the variables are public only within the project in which they reside.
The answer is to use Option Private Module.
When used in host applications that allow references across multiple projects, Option Private Module prevents a module’s contents from being referenced outside its project.
[...] If used, the Option Private statement must appear at module level, before any procedures.
EDIT You have now clarified that you declare your variables using Dim at module level. In this case, Option Private Module is irrelevant.
Variables declared with Dim at the module level are available to all procedures within the module.
i.e. regardless of whether you're using Option Private Module or not.
If you're finding that the values are retained between runs, then that must be because you are running a procedure from the same module from the same workbook. You may think you're doing something else, but in reality this is what you're doing.
EDIT
In your class module, instead of Dim CurrentCommand As String try Private CurrentCommand As String. Without more information it's hard to debug your program. I'm just taking random potshots here.
What you need to do is store multiple versions of the variables, one set per document.
So I would suggest that you create a simple class to hold the different values.
You then store them in a collection mapping the data-set with the document name or similar as the key.
In classmodule (MyData), marked as public:
Public data1 as String
Public data2 as Integer
In module with the event-handlers:
Dim c as new Collection 'module global declaration
Sub AddData()
Dim d as new MyData 'Your data set
d.data1 = "Some value"
d.data2 = 42
c.add Value:=d, Key:=ActiveDocument.name
End Sub
Then when you enter the event-handler you retrieve the data and use the specific set for the currently active document.
Sub EventHandler()
Dim d as MyData
set d = c.item(ActiveDocument.name)
'use data
'd.data1...
End Sub
Please not that this code is just on conceptual level. It is not working, You have to apply it to your problem but it should give you some idea on what you need to do. You will need to add alot of error handling, checking if the item is already in the collection and so on, but I hope you understand the concept to continue trying on your own.
The reason for this is because, as I understand the situation from your question, you only have one version of your script running, but multiple documents. Hence the script have to know about all the different documents.
On the other hand, If each document would have their own code/eventhandlers, hence having multiple versions of the script running, then you don't need the solution provided above. Instead you need to be careful what document instance you reference in your script. By always using "ThisDocument" instead of "ActiveDocument" you could achieve isolation if the code is placed in each open document.
However, as I understood it, you only have one version of the script running, separate from the open documents, hence the first solution applies.
Best of luck!
You might want to store the Document Specific details using
The Document.CustomDocumentProperties Property
http://msdn.microsoft.com/en-us/library/office/aa212718(v=office.11).aspx
This returns a
DocumentProperties Collection
Which you can add new Properties to Using
Document.CustomDocumentProperties.Add(PropertyName, LinkToContent, Value, Type)
And then Read From using
Document.CustomDocumentProperties.Item(PropertyName)
A downside, or bonus, here is that the properties will remain stored in the document unless you delete them.
This may be a good thing or a bad thing
I want to do this but it won't compile:
Public MyVariable as Integer = 123
What's the best way of achieving this?
.NET has spoiled us :)
Your declaration is not valid for VBA.
Only constants can be given a value upon application load. You declare them like so:
Public Const APOSTROPHE_KEYCODE = 222
Here's a sample declaration from one of my vba projects:
If you're looking for something where you declare a public variable and then want to initialize its value, you need to create a Workbook_Open sub and do your initialization there.
Example:
Private Sub Workbook_Open()
Dim iAnswer As Integer
InitializeListSheetDataColumns_S
HideAllMonths_S
If sheetSetupInfo.Range("D6").Value = "Enter Facility Name" Then
iAnswer = MsgBox("It appears you have not yet set up this workbook. Would you like to do so now?", vbYesNo)
If iAnswer = vbYes Then
sheetSetupInfo.Activate
sheetSetupInfo.Range("D6").Select
Exit Sub
End If
End If
Application.Calculation = xlCalculationAutomatic
sheetGeneralInfo.Activate
Load frmInfoSheet
frmInfoSheet.Show
End Sub
Make sure you declare the sub in the Workbook Object itself:
Just to offer you a different angle -
I find it's not a good idea to maintain public variables between function calls. Any variables you need to use should be stored in Subs and Functions and passed as parameters. Once the code is done running, you shouldn't expect the VBA Project to maintain the values of any variables.
The reason for this is that there is just a huge slew of things that can inadvertently reset the VBA Project while using the workbook. When this happens, any public variables get reset to 0.
If you need a value to be stored outside of your subs and functions, I highly recommend using a hidden worksheet with named ranges for any information that needs to persist.
Sure you know, but if its a constant then const MyVariable as Integer = 123 otherwise your out of luck; the variable must be assigned an initial value elsewhere.
You could:
public property get myIntegerThing() as integer
myIntegerThing= 123
end property
In a Class module then globally create it;
public cMyStuff as new MyStuffClass
So cMyStuff.myIntegerThing is available immediately.
Little-Known Fact: A named range can refer to a value instead of specific cells.
This could be leveraged to act like a "global variable", plus you can refer to the value from VBA and in a worksheet cell, and the assigned value will even persist after closing & re-opening the workbook!
To "declare" the name myVariable and assign it a value of 123:
ThisWorkbook.Names.Add "myVariable", 123
To retrieve the value (for example to display the value in a MsgBox):
MsgBox [myVariable]
Alternatively, you could refer to the name with a string: (identical result as square brackets)
MsgBox Evaluate("myVariable")
To use the value on a worksheet just use it's name in your formula as-is:
=myVariable
In fact, you could even store function expressions: (sort of like in JavaScript)
(Admittedly, I can't actually think of a situation where this would be beneficial - but I don't use them in JS either.)
ThisWorkbook.Names.Add "myDay", "=if(isodd(day(today())),""on day"",""off day"")"
Square brackets are just a shortcut for the Evaluate method. I've heard that using them is considered messy or "hacky", but I've had no issues and their use in Excel is supported by Microsoft.
There is probably also a way use the Range function to refer to these names, but I don't see any advantage so I didn't look very deeply into it.
More info:
Microsoft Office Dev Center: Names.Add method (Excel)
Microsoft Office Dev Center: Application.Evaluate method (Excel)
As told above, To declare global accessible variables you can do it outside functions preceded with the public keyword.
And, since the affectation is NOT PERMITTED outside the procedures, you can, for example, create a sub called InitGlobals that initializes your public variables, then you just call this subroutine at the beginning of your statements
Here is an example of it:
Public Coordinates(3) as Double
Public Heat as double
Public Weight as double
Sub InitGlobals()
Coordinates(1)=10.5
Coordinates(2)=22.54
Coordinates(3)=-100.5
Heat=25.5
Weight=70
End Sub
Sub MyWorkSGoesHere()
Call InitGlobals
'Now you can do your work using your global variables initialized as you wanted them to be.
End Sub
You can define the variable in General Declarations and then initialise it in the first event that fires in your environment.
Alternatively, you could create yourself a class with the relevant properties and initialise them in the Initialise method
This is what I do when I need Initialized Global Constants:
1. Add a module called Globals
2. Add Properties like this into the Globals module:
Property Get PSIStartRow() As Integer
PSIStartRow = Sheets("FOB Prices").Range("F1").Value
End Property
Property Get PSIStartCell() As String
PSIStartCell = "B" & PSIStartRow
End Property
there is one way to properly solve your question. i have the same concern with you for a long time. after searching and learning for a long time, finally i get a solution for this kind of question.
The solution is that no need to declare the variable and no need to set value to the variable, and even no need VBA code. Just need the "named range" in excel itself.
For example, the "A1" cell content is "hello, world". and we define the "A1" cell a name as "hello", that is, the "A1" cell have a name now, it's called "hello".
In VBA code, we just need use this method [hello], then we can get the "A1" value.
Sub test()
msgbox [hello]
end sub
the msgbox will show "Hello, word".
this way, we get a global variable without any declaration or assignment. it can be used in any Sub or Function.
we can define many named range in excel, and in VBA code we just use [] method to get the range value.
in fact, the [hello] is a abbreviation of the function Evaluate["Hell"], but it's more shorter.
It's been quite a while, but this may satisfy you :
Public MyVariable as Integer: MyVariable = 123
It's a bit ugly since you have to retype the variable name, but it's on one line.
For a very long time, when I have an error handler I make it report what Project, Module, and Procedure the error was thrown in. I have always accomplished this by simply storing their name via constants. I know that in a Class you get the name programmatically with TypeName(Me), but obviously that only gets me one out of three pieces of information and only when I'm not in a "Standard" module.
I don't have a really huge problem with using constants, it's just that people don't always keep them up to date, or worse they copy and paste and then you have the wrong name being reported, etc. So what I would like to do is figure out a way to get rid of the Constants shown in the example, without losing the information.
Option Compare Binary
Option Explicit
Option Base 0
Option Private Module
Private Const m_strModuleName_c As String = "MyModule"
Private Sub Example()
Const strProcedureName_c As String = "Example"
On Error GoTo Err_Hnd
Exit_Proc:
On Error Resume Next
Exit Sub
Err_Hnd:
ErrorHandler.FormattedErrorMessage strProcedureName_c, m_strModuleName_c, _
Err.Description, Err.Source, Err.Number, Erl
Resume Exit_Proc
End Sub
Does anyone know ways to for the code to tell where it is? If you can conclusively show it can't be done, that's an answer too:)
Edit:I am also aware that the project name is in Err.Source. I was hoping to be able to get it without an exception for other purposes. If you know great, if not we can define that as outside the scope of the question.
I am also aware of how to get the error line, but that information is of course only somewhat helpful without knowing Module.Procedure.
For the project name, the only way I can think of doing this is by deliberately throwing an error somewhere in Sub Main(), and in the error handling code, save the resulting Err.Source into an global variable g_sProjectName. Otherwise, I seem to remember that there was a free 3rd party DLL called TLBINF32.DLL which did COM reflection - but that seems way over the top for what you want to do, and in any case there is probably a difference between public and private classes. And finally, you could use a binary editor to search for the project name string in your EXE, and then try to read the string from the position. Whilst it is frustrating that the names of every project and code module is embedded in the EXE, there seems to be no predictable way of doing this, so it is NOT recommended.
There are several questions here.
You can get the Project Name by calling App.Name
You cannot get the name of the method you are in. I recommend using the automated procedure templates from MZ Tools, which will automatically put in all the constants you need and your headache will be over.
The last piece is possibly having to know the name of the EXE (or lib) that invoked your ActiveX DLL. To figure this out, try the following:
'API Declarations'
Private Declare Function GetModuleFileName Lib _
"kernel32" Alias "GetModuleFileNameA" (ByVal _
hModule As Long, ByVal lpFileName As String, _
ByVal nSize As Long) As Long
Private Function WhosYourDaddy() As String
Dim AppPath As String
Const MAX_PATH = 260
On Error Resume Next
'allocate space for the string'
AppPath = Space$(MAX_PATH)
If GetModuleFileName(0, AppPath, Len(AppPath)) Then
'Remove NULLs from the result'
AppPath = Left$(AppPath, InStr(AppPath, vbNullChar) - 1)
WhosYourDaddy = AppPath
Else
WhosYourDaddy = "Not Found"
End If
End Function
Unfortunately, you'll need to have individual On Error GoTo X statements for individual modules and procedures. The project is always stored in Err.Source. The VBA error handling isn't all that great in this area -- after all, how much good does the project name as the source of the error, as opposed to procedure/module, do you.
If you manually or programatically number your lines (like old-school BASIC), you can use ERL to list the line number the error occurred on. Be warned, though, that an error that occurs on a line without a number will make ERL throw its own error, instead of returning a zero. More information can be found at this blog post.
If you're using Access 2007 (not sure about other Office apps/versions), try this code snippet dug out of the help documentation:
Sub PrintOpenModuleNames()
Dim i As Integer
Dim modOpenModules As Modules
Set modOpenModules = Application.Modules
For i = 0 To modOpenModules.Count - 1
Debug.Print modOpenModules(i).Name
Next
End Sub
And Microsoft includes these remarks:
All open modules are included in the
Modules collection, whether they are
uncompiled, are compiled, are in
break mode, or contain the code
that's running.
To determine whether an individual
Module object represents a standard
module or a class module, check the
Module object's Type property.
The Modules collection belongs to the
Microsoft Access Application object.
Individual Module objects in the
Modules collection are indexed
beginning with zero.
So far, I haven't been able to find anything on referencing the current Project or Procedure. but this should point you in the right direction.
I suggest you take a look at CodeSMART for VB6, This VB6 addin has a customizable Error Handling Schemes Manager, with macros that will insert strings for module name, method name, etc., into your error handling code with a single context menu selection.
Has some other very nice features as well - a Find In Files search that's superior to anything I'd seen till ReSharper, a Tab Order designer, and much more.
At my previous employer, we used this tool for many years, starting with the 2005 version. Once you get used to it,it's really hard to do without it.