I want to retrieve the value of a worksheet-code-level private constant via VBA. I'm trying to do something similar to having "tags" on my worksheets that are accessible via VBA. I'd like to use the CodeModule in the VBAProject so that I can have a number of "tags", not just one. I just can't figure out how to grab the constant, even after reviewing much code and forums on-line. Does anyone have any insight on this? I am running Excel 2013 on a Windows 8.1 machine.
Maybe the easiest way to do this is to have a function that can the value of a private constant with a public accessor method. For instance, if you have the following code in your worksheet Sheet1:
Private Const myValue = "the answer is 42"
Public Function getSecret()
getSecret = myValue
End Function
Then you can access it from another module with
Sub test()
Dim sheetName = "Sheet1"
MsgBox "The sheet says that " & Sheets(sheetName).getSecret()
End Sub
You can make this fancier - you could create a collection of tags and index them… but I think that goes beyond the question you had. Note that you have to "fully qualify" the name of the accessor macro when it is in a worksheet; the advantage is that you can use the same function to examine the tag of different worksheets just by changing the sheetName.
I would export the code to a .BAS file. You can then read the material back in as a text file and retrieve the constant
Related
I'm starting with VBA and I'm very upset to discover I have fallen at the first hurdle in this book I am following.
I should type the following
Function Hello() As String
Hello = "Greetings"
End Function
This all works fine but next I am supposed to change the text and see the function change on the Excel spreadsheet. Unfortunately I can't get this to work. Does anyone know why?
I've saved the document as an Excel Macro-enabled workbook and tried opening and closing.
You seem to have this function in the code-file for thisWorkbook, but it should be in a module. Add a module to your project, place your code there and make the function Public. See also: How to Call VBA Function from Excel Cells?
Normally a Function should be in a Module, not in ThisWorkbook. You store Event handlers in the ThisWorkbook or a Sheet module.
It doesn't recalculate because it doesn't have a Range input, since the function doesn’t have any arguments, it is treated constant output and hence updating the function doesn’t update the cell value.
But if you modify it to accept a Range input, and if there are any changes to the input range, it will recalculate.
declare your function right
Function Hello(str As String) As String
then use something like
cells(1,1).value=str
function does the job for you but first you must call the function by parameter like this
cells(1,1).value=Hello("How are you")
the result will be that in cell 1,1 will "How are you" be written.
but from this on I am not sure what are you trying to accomplish. If you need funct. to write in specific cell all the time you should use something like
Funtion Hello(row as integer, column as integer) as string
cells(row,column).value=inputbox("give me the input")
end function
then to use this you write into code
result=Hello(1,2)
this example works
Function Area(row As Integer, column As Integer) As String
Cells(row, column).Value = InputBox("give something here")
End Function
Sub my()
result = Area(2, 2)
End Sub
run my()
Ok I worked it out, I had written the function into the file "This Workbook" instead of into the Module I created...
I knew it would be something simple! All working now.
Function Hello() As String
Application.Volatile
Hello = "Greetings"
End Function
I am creating a userform (userform1) in workbookA and in the userform, I'll be needing a module(module1) created in other workbook (workbookB) to made some calculation. In order to launch the calculation, the module need some info (TTAA, tolerance). I would really love to know the easiest way to transfer some variable from userform1(workbookA.userform1) to module1(workbookB.module1) and re-transfer the results to userform1.
below, you can find a small part of the programm which I've coded to call the module.
TTAA = CDbl(Me.TTAA_textbox.Value)
tolerance = CDbl(Me.tolerance_textbox.Value)
application.Run ("'Workbooks.xls'!module1")
I've declared as public, the variable TTAA and tolerance in the module1
The easiest way (which I know to work) is to temporarily "save" the variable on a sheet in one of either file and then reference it as
Worksheets.Add.Name = "tmp"
Workbooks(WorkbookA.Name).Worksheets("tmp").Range("A1").Value2
'And remove it later on
Application.DisplayAlerts = False
Workbooks(WorkbookA.Name).Worksheets("tmp").Delete
Application.DisplayAlerts = True
An alternative might be to declare the subs in WorkbookB as functions. Apart from public variables this is the only way to get return values (in a native format) from independent VBA code segments. Yet, if you had no luck with public variables I doubt this will work.
But why don't you import the module into WorkbookA instead? The following SO answer might be able to help you with that.
Writing a macro that writes a macro to another Excel-file
I was so pleased that this works that I thought I would share it...
It allows me to have generic event handlers in the ThisWorkbook module to initiate worksheet-specific code in an anonymous ActiveSheet.
I use the CalByName function to access user-defined, Public methods in the Sheet objects.
I'm using it to re-start a timer that is killed by auto-recover saves and it works great. I've got it on two sheets.
In ThisWorkbook Module:
Private Sub Workbook_AfterSave(ByVal Success As Boolean)
Dim ws As Worksheet
Set ws = ActiveSheet
On Error GoTo afterSaveFailed
CallByName ws, "afterSave", VbMethod
If debugEvents Then Debug.Print timeStamp & ": " & "AfterSave: afterSave called in sheet: " & ws.Name
On Error GoTo 0
Exit Sub
afterSaveFailed:
If debugEvents Then Debug.Print timeStamp & ": " & "AfterSave: afterSave Failed in sheet: " & ws.Name
Err.Clear
End Sub
then in each applicable Worksheet Module:
Public Sub afterSave()
'sheet specific after save handler
End Sub
CallByName is a very old and well known method. :)
Also in your code
What is debugEvents You might want to use Option Explicit
Why _AfterSave and not _BeforeSave? What if afterSave() makes a change to the workbook? You will have to save again before closing.
Also instead of pasting the code in each applicable worksheet and creating duplicate code why not create a common code in a module?
Public Sub afterSave(oWs as Worksheet)
With oWs
'sheet specific after save handler
End With
End Sub
Worst Case Scenario:
Assuming that the afterSave is different for every worksheet. You could always use a Select Case. This will ensure that you have your code in one place. You don't have to hop around to check the code.
Public Sub afterSave(oWs As Worksheet)
With oWs
Select Case .Name
Case "AAA"
Case "BBB"
Case "CCC", "DDD" '<~~ If two or more worksheet have the same code
End With
End Sub
thanks for the comments. My responses are inserted below:
CallByName is a very old and well known method. :)
Which is why I didn't entitle the post "CallByName".
If you read the title, thats what its about. I couldn't find a solution to this on the web so I had to figure out my own method. My point is not about CallByName, its about cross-talk between the modules and using CallByName to maximum effect to achieve this, I'm not suggesting that CallByName is novel.
I guess I could achieve the same thing using RaiseEvents but I think maybe this is more straight-forward.
I was just happy coz VBA was letting me do what I wanted for a change :q
Also in your code
What is debugEvents You might want to use Option Explicit
It's a global flag to control how noisy the immediate window is. Yes I always use option explicit and do so in this case. I assume that's a given.
Why _AfterSave and not _BeforeSave? What if afterSave() makes a change to the workbook? You will have to save again before closing.
Because the stuff I'm doing needs to be done after the save has completed, not before it starts. The Autorecovery save event kills a timer that I'm running so I need to re-start it after the save.
Also instead of pasting the code in each applicable worksheet and
creating duplicate code why not create a common code in a module?
Public Sub afterSave(oWs as Worksheet)
With oWs
'sheet specific after save handler
End With End Sub
Worst Case Scenario:
Because its "sheet specific"
Assuming that the afterSave is different for every worksheet. You
could always use a Select Case. This will ensure that you have your
code in one place. You don't have to hop around to check the code.
Public Sub afterSave(oWs As Worksheet)
With oWs
Select Case .Name
Case "AAA"
Case "BBB"
Case "CCC", "DDD" '<~~ If two or more worksheet have the same code
End With End Sub
OK, I'll think about that: thanks for the suggestion :)
When I have generic features, I prefer to handle that with Class Modules. To me that's neater: I just instance the object in the sheet to expose the generic features, and as in your suggestion, I only have to check it once. I also use the Class_Initialize and Class_Terminate events to transparently manage life cycle of the generic features. I couldn't do that with generic code in the ThisWorkbook object.
Thats how I handle general functionality, for object-specific features, my preference is to collect the code that is specific to the object, in the actual object: I prefer shy objects. There's really no need to expose that detail to supervisory code. Also, if I'm working on the sheet, I don't have to skip back to a general module and I don't have to try and remember where I put the code or invent some convention, coz it's right there in sheet.
Thank you very much for taking the time to respond in detail!
Question
How do I access a public constant defined in another Excel workbook?
Setup
Workbook.xls
My VBA code runs in the current Excel workbook called "MyWorkbook.xls".
It has some VBA code in it which reads a file name --"OtherWorkbook.xls in this case-- from cell "A1". Then, the code should read a constant defined in that other workbook and write it to cell "A2". Here is what my code looks like:
Sub GetValueFromOtherWorkbook()
otherWorkbook = Range("A1").Value ' Value = "OtherWorkbook.xls"
Dim returnedValue As String
' Idea 1: Doesn't work
Set returnedValue = OtherWorkbook.xls!Module1.MY_CONSTANT
' Idea 2: Doesn't work either
Set returnedValue = Application.Run(otherWorkbook & "!Module1.MY_CONSTANT")
Range("A2").Value = returnedValue ' MY_CONSTANT = "testValue"
End Sub
OtherWorkbook.xls
The second Excel workbook is called "OtherWorkbook.xls" as mentioned above.
It has a VBA module called Module1.
Module1 defines a public constant. The code looks like this:
Public Const MY_CONSTANT As String = "testValue"
Problem
The code from MyWorkbook.xls does not run, it returns the following error
Run-time error '424'
Object required
I don't quite know what to do with it even after reading the help documentation. In another context, I managed to call a routine in another workbook with this code:
otherWorkbookName = "OtherWorkbook.xls"
Application.Run (otherWorkbookName & "!Module1.SomeRoutine")
So, calling a routine works, but accessing a public constant does not.
Any ideas what I could try next?
A code module is not an object that can be accessed like a worksheet.
You can access and manipulate it, but in order to do that you need to reference the Microsoft Visual Basic for Applications Extensibility library and allow access to the VBA project (Tools - Options - Security - Macro security - Trusted publishers - Trust access to VBA project), after which you can use the Workbook.VBProject property of the second workbook.
You don't need to do this. Instead you can create a reference to the other workbook: in VBA IDE, go Tools - References, click Browse and locate your workbook with the constant.
If that's no good, you can create a method in that workbook that only returns the value of the constant, and call it as you have demonstrated.
I've created a program that creates and populates a custom document property in an Excel 2007 workbook file. However I haven't been able to show the value of this property in a worksheet cell. In Word 2007 you can just select "Insert -> Quick Parts -> Field..." and use the DocProperty field to show the value of the custom field in a document. However I haven't found a similar function in Excel 2007.
Does anybody know how to display the value of a custom document property in an Excel worksheet cell? I would prefer a solution similar to the Word 2007 solution mentioned above. I rather not use a macro/custom code for this.
Unfortunately I believe you need to use an user defined function. Add a new VBA module to your workbook and add this function:
Function DocumentProperty(Property As String)
Application.Volatile
On Error GoTo NoDocumentPropertyDefined
DocumentProperty = ActiveWorkbook.BuiltinDocumentProperties(Property)
Exit Function
NoDocumentPropertyDefined:
DocumentProperty = CVErr(xlErrValue)
End Function
The call to Application.Volatile forces the cell to be updated on each recalculation ensuring that it will pick up changes in the document properties.
The equivalent in Excel would be via formula and I don't think it's possible to extract a document property without code. There are no native functions to pick out document properties. (An alternative could be to store information in workbook/worksheet Names, which ARE accessible via formula)
In VBA you'd have to create a function something like:
Public Function CustomProperty(ByVal prop As String)
CustomProperty = ActiveWorkbook.CustomDocumentProperties(prop)
End Function
and then call it in a formula with =CustomProperties("PropertyName").
There is another subtle point. Formula dependencies only relate to other cells; this formula depends on a custom property. If you update the custom property a pre-existing formula involving CustomProperty will not be updated automatically. The cell will have to be re-evaluated manually or the entire workbook forced through a recalc. Your best chance would be to make the function volatile, which means the formula would be recalc'd on every cell change -- but this still means you only get an update if a cell has been changed.
Select the cell you want to extract
Rename the cell to some useful. From "B1" to "Project_Number".
Open "Advance Properties" click the "Custom" tab. Enter a name for the new property. click "Link to content" the select the cell name from the "Value" pull down list.
I wish i could take cerdit but I found the answer online:
http://pdmadmin.com/2012/03/displaying-custom-property-values-in-excel-using-a-named-range/
You can link a named range to a custom property, but then the custom property reflects the value of the [first cell in the] range. It's effectively read-only; you can change the content of the cell to update the property, but not the other way around.
I know you want to avoid it, but if you want to use the property value in a formula, you'll have to create a custom worksheet function to do so.
I have experienced the same issues other people have. So I will try to comprehensively cover how I addressed it.
First of all, you have no other option than writing a function meant to get whatever you put in a custom or built-in property and make the "problem" cell to point at it this way:
=yourPropertyGettingFunctionName(PropertyName)
PropertyName being a string referring to the name of the custom/built-in property whose value you want to be shown in the cell.
The function could be written (as formerly suggested) as:
Public Function StdProp(ByVal sPropName As String) As String
Application.Volatile
StdProp = ActiveWorkbook.BuiltinDocumentProperties(sPropName).Value
End Function
for a built-in property, or as:
Public Function UsrProp(ByVal sPropName As String) As String
Application.Volatile
On Error GoTo UndefinedProp
UsrProp = ActiveWorkbook.CustomDocumentProperties(sPropName)
GoTo Exit
UndefinedProp:
UsrProp = "n/a"
Exit:
End Function
As already mentioned, including Application.Volatile will allow for a semi-automatic cell contents update.
However, this poses a problem on its own: whenever you open your Excel file, all the cells using such a relationship will get updated and, by the time you exit the file, Excel will ask you for your permission to update it, no matter if you did introduce any change on it or not, because Excel itself did.
In my development group, we use SubVersion as a version control system. In case you inadvertently hit "update" on exit, SVN will notice it and next time you want to commit your changes, the excel file will be included in the pack.
So I decided to use everything at hand to do whatever I needed and avoid, at the same time, this self-update effect I didn't want.
That means using named ranges in combination with property accessing function/s.
Given the fact I can't expect old files to have provision for my new needs, I wrote this function:
Private Function RangeAssign(sRange As String, sValue As String) As Integer
Dim rDest As Range
If RangeCheck(sRange) Then
Set rDest = Range(sRange)
Else
Set rDest = Application.InputBox(sMsg + vbCrLf + vbCrLf + _
"Please, select a cell to get" + vbCrLf + _
"the name " + sRange + " assigned", sCopyRight, Type:=8)
rDest.Name = sRange
End If
rDest.Cells(1, 1).NumberFormat = "#"
rDest.Cells(1, 1).Value = sValue
RangeAssign = True
End Function
It allows for a proper selection of the destination cell. When assigning values to a property (let's say "Author", which happens to be a built-in one), I also update the value stored in the named range, and can write in a cell:
=Author
if I happen to have defined a range named "Author" and filled its "A1" cell with the value for built-in property "Author", which I need to have updated for our own external tracking purposes.
This all didn't happen overnight. I hope it can be of some help.
I used this for extracting the SharePoint properties (based on Martin's answer):
Public Function DocumentProperty(Property As String)
Application.Volatile
On Error GoTo NoDocumentPropertyDefined
DocumentProperty = ActiveWorkbook.ContentTypeProperties(Property).Value
Exit Function
NoDocumentPropertyDefined:
DocumentProperty = CVErr(xlErrValue)
End Function