I have a user-defined function that takes a parameter which has an associated builtin enum:
Public Function bgrcolor_cells(rng As Range, xlcl As Long) As Integer
I want to write a formula like this in a cell
=bgrcolor_cells($A2:$B2,vbRed)
instead of
=bgrcolor_cells($A2:$B2,255)
Is that possible? How?
PS: I have found How can I use enum identifiers in Excel UDF, but it refers to user-defined Enums. I do not know if the fact that I want to use a builtin Enum makes a difference.
EDIT 1
Perhaps some automated reading of the code where the builtin Enum is defined may help in defining either the Class by Jean-François Corbett or named ranges, and then one would avoid typing from scratch. I have seen such automated parsing, possible pointers are:
http://www.cpearson.com/excel/EnumNameList.aspx
http://www.excelforum.com/excel-programming-vba-macros/356892-programmatic-generation-of-enum-to-string-functions.html
EDIT 2 (as per this)
A comment has been posted stating that "there is no difference for built-in and user-defined enums", and that this is a dupe. I think it is not, and that if the quoted comment is correct, then it may be part of an answer (perhaps worth posting as such) for the present specific and different question.
The question you link to already covers this topic and the accepted answer should work just fine. There is no particular shortcut for VBA built-in enums.
Otherwise you can try something like the following.
For entertainment purposes only
Before I get lynched for this, I'd like to say that I did this just for fun as a proof-of-principle and would probably never use this myself!
Create a class called ColorEnums:
Option Explicit
Public vbRed As Long
Public vbGreen As Long
'etc.
Private Sub Class_Initialize()
vbRed = VBA.vbRed
vbGreen = VBA.vbGreen
'etc.
End Sub
Make a user-defined function like this:
Function GetValueOfColorEnumByName(colorEnumName As String)
GetValueOfColorEnumByName = CallByName(New ColorEnums, colorEnumName, VbGet)
End Function
Where CallByName allows us (and this is very ugly) to evaluate the value of a member of a class from its name in a string.
Example usage:
Adapt to your own requirements, at your own risk.
Related
I noticed unbelievable destructive behavior of Excel VBA. It silently and automatically renames variables and function parameters in unrelated modules when I add, say, a new property to class. Observed on Office Professional Plus 2016 and Windows 10.
For concrete example, I have one module and one class. Module looks like this:
Private Function MyRequests() As Collection
Dim requests As Collection
Set requests = New Collection
Dim row As Integer
row = 3
Dim oRequest As New MyRequest
oRequest.Name = "SomeName"
requests.Add oRequest
MyRequests = requests
End Function
Class MyRequest looks like this:
Private sName As String
Property Let Name(sValue As String)
sName = sValue
End Property
Now the unbelievable part comes. I add new property to the MyRequest class:
Private iRow As Integer
Property Let Row(iValue As Integer)
iRow = iValue
End Property
I save code, go to module and its private function which now looks like this:
Private Function MyRequests() As Collection
Dim requests As Collection
Set requests = New Collection
Dim Row As Integer
Row = 3
Dim oRequest As New MyRequest
oRequest.Name = "SomeName"
requests.Add oRequest
MyRequests = requests
End Function
Notice that row became Row! This silent auto-renaming also happens throughout the VBA code in sheets... Basically everywhere, Excel renamed row to Row!
So, my question is what can I do to stop this insane behaviour?
Naming a variable with the same name as a keyword is not a good practice.
If a keyword is used within scope of [and] a variable with [have] the same name, then the keyword must be qualified with the appropriate VBA object model or the variable takes precedence and the keyword will be interpreted by the compiler as that variable. This results in code that is more error prone and significantly more difficult to maintain.
"So, my question is what can I do to stop this insane behaviour?"
The solution is to not declare a variable with the same name as a VBA keyword.
All variables of equal scope share the same casing. Changing the case at the declaration changes every instance of the variable within scope. Keywords are slightly different, in that they are global but their casing can be changed by a local declaration.
So in any scope, declaring a variable of the same name as a keyword will cause every instance of that word to share the same casing project wide and the change persists after the declaration is deleted.
Keywords are defined by the object model.
Link with list of keywords in VBA object model (the link in comments goes .NET):
https://learn.microsoft.com/en-us/office/vba/language/reference/keywords-visual-basic-for-applications
Link to Excel object model (I do not know where to find an condensed list, but if it is an object, property, or method then it is a keyword): https://learn.microsoft.com/en-us/office/vba/api/overview/excel/object-model
It can be challenging, and even impossible, to avoid every keyword and this is where scope comes into play and I can offer some easy to follow advice
When you find yourself naming a variable with a potential for conflict (like row, column, worksheet) etc.
in all cases, it is better to use a more descriptive name (indexRow, lastRow, rowCounter)
If you can't come up with something suitable, press F2 to open the object model viewer and search for that name. Hopefully the search doesn't come up with any results and you can safely use that name. But if not, look for where the conflict occurs
you will not have a problem if you're creating an Excel project and the conflict is found in the Outlook object. You may even consider removing the reference to the Outlook object from your project
if it's a property name (like Color) or a method (like Resize) then you can use with care (in some cases this is actually the ideal, like when assigning properties and methods to your custom classes)
if it is an object (like Workbook) then you should not use it. The risk is too big.
[I struck through the points of scope - it seems I am rather consistent with my naming conventions and was unaware the variables changed case regardless of scope.
Would never have known if not for Michael's answer]
This problem isn't arising because you're using variable names that match a built-in property. It's arising simply because you're using two different case-spellings of the same variable. If you used iRow in one module and irow in another, you would still get the same issue.
You can't stop this behaviour. The VBA IDE automatically changes the names of subroutines, functions, variables and properties to match the case of the last declaration statement that was edited. It also then remembers the case used for all future instances entered/edited anywhere in code.
When doing this renaming, the IDE doesn't distinguish between local and global variables, or properties of different classes. If you change the case anywhere, it will be updated and used everywhere.
This is actually useful to help prevent accidental code errors. VBA is case-insensitive and using different case would not create different variables. Trying to manage different variables based on case, even in different modules, is not good practice.
If they are local and distinct with no risk of confusion, then you might as well use the same case-spelling anyway. Why have one naming convention in one module and a different naming convention in a different module?
However, if you need to distinguish between variables in different modules, then you should be using different character-spelling anyway and in doing so, the issue would not arise.
This behaviour also helps you to ensure code is being correctly entered as you type, especially if you declare all variables, subroutines etc with at least one capital letter. You are then able to edit all code using only lower case, which reduces effort, and then everything that is correctly spelled will be automatically converted to the declared case. If you type a variable in lower case and it does not automatically change case, then you know immediately that you have mistyped the variable, rather than discovering the error only when you try to compile/run.
Note that the issue that does arise from using built-in properties as names for variables and subroutines is that the automatic changing of case applies equally here too. So if you define a variable as VALue, it will rename the .Value property wherever it is used.
If you then define a subroutine as value, it will rename both the existing VALue variable as well as the .Value property.
If you follow good naming conventions, and don't reuse names anywhere, then you won't have this issue.
Instead of using the same defined name in multiple workbooks, can I define a constant in my xlam file that can then be used in all my workbooks?
For example, in VBA:
Public Const nKilobyte = 1024
Then in a worksheet:
=A1 / nKilobyte
I tried this and I get a #NAME? error. Right now, it looks like a worksheet can refer to functions defined in VBA, but not constants. So as a workaround, I made up the following function:
Public Function vConstant(sName As String) As Variant
Select Case sName
Case "kb": vConstant = 1024
Case "Mb": vConstant = 1024 ^ 2
End Select
End Function
which I can then call in a worksheet as:
=A1 / vConstant("kb")
That works, but an actual constant would be better. Is there a way to do that?
That works, but an actual constant would be better. Is there a way to do that?
Nope.
Excel and VBA are two separate things: they talk to each other using a predefined pipeline, and that interface says public procedures in standard modules are exposed as "macros", and public functions in standard modules are exposed as user-defined functions. In the other direction, VBA gets to talk to Excel through its object model.
The Excel calc engine knows nothing of the VBA runtime context that's holding the runtime values of your global variables and constants: that's purely on the VBA side of things. In fact, it wouldn't be unreasonable to assume a constant doesn't even exist in the compiled code - that's actually the case in C# code: the compiler "burns" the value of constants in-place, at the call sites. I wouldn't be surprised to crack open the compiled p-code and find that all constants are just inlined into their call sites... but that's all just speculation.
For whatever it's worth, that magic-string driven function isn't ideal; callers might end up scratching their heads wondering whether it wants "KB", "Kb", "kB" or "kb" (Option Compare Binary being the default string comparison mechanism in VBA, i.e. case-sensitive), or maybe it was "KiB"? Make sure the supported values are well-documented, and use StrComp with text rather than binary comparison to make the match case-insensitive.
Consider exposing a single function / UDF per constant. That'll eliminate the magic strings and enhance the UDF usability.
Public Function BytesInKB() As Long
BytesInKB = CLng(1024)
End Function
Public Function BytesInMB() As Long
BytesInMB = CLng(1024) ^ 2
End Function
While learning VBA I did a lot of copy-paste of code from the internet. At some point I have noticed that whenever I use a method which name is "Add", the VBA IDE automatically change it to "add". For example when I type ActiveSheet.PivotTables.Add it's automatically converted to ActiveSheet.PivotTables.add. The same happens with Worksheets.Add which is changed to Worksheets.add. How can I restore default case of a method?
I know that VBA IDE changes variable's case accordingly to how it was declared (see: How does one restore default case to a variable in VBA (Excel 2010)?). But I am not sure if it might be related to my issue.
This will happen because you have declared some variable or function or sub as add in your Project (or in some add-in you might have loaded).
The VBA environment will get the last declared case representation of your variable names/function names and apply them to every instance where they occur.
E.g.
Function Func1()
Dim ART As Double
ART = 1
' in one function
End Function
' Now if I type the following in another function
Function Func2()
Dim aRt as Double
End Function
' the original declaration in the first function becomes
Dim aRt As Double
aRt = 1
This behavior may be desirable or undesirable, but it's definitely by design.
If you want to declare variables and not want them to change case (useful for certain Enums, etc.) you need to do something like
#If False Then
Dim HeWhoseCaseShallNotBeChanged
Dim ADd ' Even methods can be "declared" here. It will have no effect on execution
#End If
This will work for Enums, etc. And you can declare this compiler directive at the top of your module page.
As it currently stands, this question is not a good fit for our Q&A format. We expect answers to be supported by facts, references, or expertise, but this question will likely solicit debate, arguments, polling, or extended discussion. If you feel that this question can be improved and possibly reopened, visit the help center for guidance.
Closed 10 years ago.
I am learning vba and now I am trying to start using classes because I see that otherwise my programs are too hard to work with and I always writing the similar pieces of code over and over.
Can you recommend any book that covers user-defined objects in vba well? Because everything that I found just gives a short introduction on this subject.
While I was searching for such a book, I found a lot of advanced books on vb.net. As I understood this language is more "powerful" (can be used for wider variety of tasks).
When you want to learn about VBA, look for references on VB6. You can probably find lots of references and books for cheap. I consider myself very good at VBA, and this is how I learned a lot. Even the newest version of VBA (VBA7) is esentially a subset of VB6. Most of what you can learn about VB6 is completely applicable to VBA, particularly in regards to classes and UDTs (which are kind of like structs in the .NET world).
Start at the source -- MSDN: Visual Basic 6.0
When you search, use VB6 rather than VBA if you're not finding what you need, for example:
Google: "vb6 class inheritance" --> http://www.freevbcode.com/ShowCode.asp?ID=2970
From my own experience I would say that user-defined types (declared using the Type keyword) are rarely useful except for private use within in a single module for the purpose of native function declarations which require a pointer to a certain structure. Their fundamental drawback is that they cannot be cast to and from Variant, which is a killer for advanced VBA. Once you get used to it it's not too hard to quickly build a class (a class module in com VB lingo) with the same fields.
Post script: This will not help you when learning about classes or user defined types, so I add this merely as a post script: You can also find lots of good information by searching for VBScript. Most VBScript can be pasted directly in to a VBA project and it will run, with small exceptions like not having access to the Wscript object. Many of the documentation examples such as for MSXML (for interacting with both XML files and the internet) show VBScript, and all of this code can be readily used in VBA.
The VBA Developer's Handbook by Getz and Gilbert gets my vote. It's old and out of print, but you can get an electronic copy here https://play.google.com/store/books/details/Ken_Getz_Vba_Developer_s_Handbook?id=46toCUvklIQC&feature=null-Mike+Gilbert
It's a bit of a beast, but a great book
VB.NET is a full programming language - which does not need to run "in" Excel.
There are several ways to "connect" VB.net and Excel, e.g. "Com Interop" or integrating the code into a workbook (I am vague here because I have never done the latter one myself).
The ease of use is difficult to acess - if you only want to work with internal Excel data, VBA is better suited, simply because it is faster. If you want to read data from the internet, store / load data or build your own UI vb.net is waay faster because VBA can do all this things, but it is like using a screwdriver to pound a nail into the wall.
What do you mean "my programs are too hard to work with"?
Writing classes does not necessarily make your tool easier to work with...
I also don't really see a problem with using the same code over and over, as long as you respect the general rules of good practise (performant, dynamic, transparent, etc...).
What is there to know about classes?
Classes enable the possibility of creating standard encapsulated objects, containing private properties only accessible via class functions (get and let).
You can instantiate a class module into an object instance and create multiple instances of the same class (using the New keyword).
You cannot do this with a standard module; even though you can declare private properties for a standard module (which VBA developers mostly use), there will only exist one "copy" of this variable.
Example comparison:
Standard Module:
"m_Standard_Module"*
Option Explicit
Private lNumber_Test As Long
Property Get get_Number_Test() As Long
get_Number_Test = lNumber_Test
End Property
Property Let letNumber_Test(param_Number_Test As Long)
lNumber_Test = param_Number_Test
End Property
Called from:
Sub test_Standard_Module()
Dim iNumber As Integer
'--> This will produce an error, cannot be instantiated!
'Dim mdlStandard as m_Standard_Module
'Property contained by standard module is unique and cannot be copied.
m_Standard_Module.letNumber_Test = 2
iNumber = m_Standard_Module.get_Number_Test
MsgBox iNumber
End Sub
Class Module:
Class called "c_Class_Module"
Option Explicit
Private pNumber_Class As Long
Public Property Get number_Class() As Long
number_Class = pNumber_Class
End Property
Public Property Let number_Class(param_Number_Test As Long)
pNumber_Class = param_Number_Test
End Property
Called from:
Sub test_Class()
Dim clsClass_Module1 As c_Class_Module
Dim clsClass_Module2 As c_Class_Module
Dim lNumber1 As Long
Dim lNumber2 As Long
'Can instantiate multiple objects!
Set clsClass_Module1 = New c_Class_Module
Set clsClass_Module2 = New c_Class_Module
'property number_Class is copied for each of the class instances
clsClass_Module1.number_Class = 1
clsClass_Module2.number_Class = 2
iNumber1 = clsClass_Module1.number_Class
iNumber2 = clsClass_Module2.number_Class
MsgBox iNumber1 & ", " & iNumber2
Set clsClass_Module1 = Nothing
Set clsClass_Module2 = Nothing
End Sub
So basically, class objects would only useful in the case when you want to create multiple instances of a same type of object (having similar properties).
Whenever you make a tool, it is useful to create an object model first, in which you determine the objects needed and their properties you want to create.
Note that VBA does not support inheritance, which means that one class cannot "inherit" properties from another (of which the basic example would be that an Employee class will inherit from a Person class). It has never really bothered me though.
I've searched the web and I've searched the questions here on stackoverflow, but I haven't been able to find a solution.
Here is what I'd like to do:
Suppose I have the following code in a class module named "MyClass"
Option Explicit
Dim var1 as integer
Sub Initialize(v as integer)
var1 = v
End Sub
Function GetVar1()
GetVar1 = var1
End Function
Then I have a UDF in a separate module with the code
Function InitializeMyClass(v as integer) as MyClass
Dim myvar as MyClass
Set myvar = new MyClass
Call myvar.Initialize(v)
Set InitializeMyClass = myvar
End Function
Function GetMyVar(m as MyClass)
GetMyVar = m.GetVar1()
End Function
Now in cell A1 I have "=InitializeMyClass(3)" and in Cell A2 I have "=GetMyVar(A1)". I get #VALUE errors in both cells. This of course is caused by the fact that I am trying to return a user defined data type into cell A1. I feel that this should be possible, but I am not sure how.
Edit: Oh yeah, the question is, "Is there a way for me to return a user-defined data type into a cell and then have it be callable from another UDF in the example I gave above? I don't know if this requires COM or not. If it does, anyone know how I can get started? Ideally, if someone had an example of how this worked, that would be fantastic!"
Another Edit: Here we go, now i know it can be done: read this description, it's not quantitative, but will give you a sense of what they do, http://www.quanttools.com/index.php?option=com_content&task=view&id=19
As the other answers suggest, the literal answer to your question is "no". You can't store anything other than a number, string, boolean, error, etc. in a cell. You can't return anything other than a simple value such as those, or an array, or a range reference, from a UDF.
However, you can do essentially what you want by passing around (and storing in cells) some kind of handle to your objects that is a legal cell value (i.e. "myclass:instance:42"). This is probably what the example you linked to in your edit does. Your code has to be able to interpret the meaning of the handle values and maintain the objects in memory itself, though. This can get tricky if you care about not leaking objects, since there are lots of ways to erase or overwrite handles that you can't detect if you're using VBA to do all this.
I don't have it in front of me right now, but you might want to look at the book Financial Applications using Excel Add-in Development in C/C++ by Steve Dalton:
http://www.amazon.com/Financial-Applications-using-Development-Finance/dp/0470027975/ref=ntt_at_ep_dpt_1
He discusses ways to work with handles like this more robustly with XLL add-ins.
This looks to be a tough cookie. It's a little hokey, but what you could do is have your Initialize function return a name (string), then add the name parameter to the Get function. Basically manipulating the name string instead of the object directly.
The nesting won't work because myvar goes out of scope as soon as the UDF is done executing. There actually may be other problems associated with trying to return an object in a worksheet function (most certainly there are), but even if there weren't the scope problem would still kill it.
You could store a pointer to the object in a cell and get the object via that pointer, but again the scope will kill it. To get the object from the pointer, it would have to remain in scope, so why bother storing the pointer.
Obviously your real life situation is more complex than your example. So the answer is no as to storing objects in cells, but if you explain what you're trying to accomplish there may be alternatives.