Determine Object Type - vba

Is there a way to determine the Object type, when passing a reference to a function?
I'm using a security permissions function, which determines if the user has permission to view/edit the Form passed to it by reference. I'd like to expand this to include reports as well.
To keep the function generic, I'd like to pass a ref for either a Form or a Report as an Object, eg:
function gfSecurity_Permission(obj as Object)
However, I'd need to determine the type of the object within the function.
Does anyone know of a way to do that?
MTIA

Take a look at
typeOf and typeName
Generic object variables (that is, variables you declare as Object)
can hold objects from any class. When using variables of type Object,
you may need to take different actions based on the class of the
object; for example, some objects might not support a particular
property or method. Visual Basic provides two means of determining
which type of object is stored in an object variable: the TypeName
function and the TypeOf...Is operator.
TypeName and TypeOf…Is
The
TypeName function returns a string and is the best choice when you
need to store or display the class name of an object, as shown in the
following code fragment:
Dim Ctrl As Control = New TextBox
MsgBox(TypeName(Ctrl))
The TypeOf...Is operator is the best choice for testing an object's
type, because it is much faster than an equivalent string comparison
using TypeName. The following code fragment uses TypeOf...Is within an
If...Then...Else statement:
If TypeOf Ctrl Is Button Then
MsgBox("The control is a button.")
End If

Simplest way to determine the access type in access is to do an object lookup within the Access' system tables.
Here would be the lookup:
DLookup("Type","MSysObjects","NAME = '" & strObject & "'")
strObject is the name of the object within Access
The result is one of the number below OR NULL if the object does not exist in Access
1 = Access Table
4 = OBDB-Linked Table / View
5 = Access Query
6 = Attached (Linked) File (such as Excel, another Access Table or query, text file, etc.)
-32768 = Access Form
-32764 = Access Report
-32761 = Access Module
so, the dlookup would provide "-32768" for a Form or "-32764" for a Report
Hope that helps

Related

MS Access Table (and TableDef) Properties

A table in MS Access opened in Design View exposes several properties, as does the table's Property Sheet. Many of these properties are undocumented or documented only for other objects. The question is, to which object do these properties belong? Further, how does one identify them in code? Pressing F1 for context help in each case reveals no clues.
Examples include (and recognize that the names below follow from their visual context, not an object model):
Field.Description is a column in Design View (along with Field Name and Data Type) but is undocumented. Also, iterating DAO.Field.Properties reveals no Description field and references to the property fail.
Table.Description appears in the Property Sheet but also is undocumented.
Table.Filter and Table.OrderBy and their ~OnLoad counterparts appear on the Property Sheet but are documented only for other objects. I understand that information specified here is intended somehow to flow through to forms for which the table is the RecordSource, but the mechanism is not obvious and still leaves the initial question, flowing through from which object's property.
Table.LinkChildFields and Table.LinkMasterFields appear in the Property Sheet but are documented only for other objects. Also, their use in this context is not obvious.
Other table properties on the Property Sheet tell the same tale.
Any thoughts, in general or specific to any of the foregoing, would be most helpful and appreciated.
To show properties of some Access database object (table, query, form, report, ...), we can do this on VBA, defining this global function:
Function objShowProperties(ByVal xobj As Object)
Dim i As Long, varPropValue, prop As Object
On Error Resume Next
'
' loop over properties:
'
i = 0
For Each prop In xobj.Properties
varPropValue = prop.Value
'
' sometimes we have error accessing property value:
'
If (Err <> 0) Then
varPropValue = "[UNAVAILABLE]"
Err.Clear
End If
Debug.Print prop.Name, "=", varPropValue
i = i + 1
Next
On Error GoTo 0
Set prop = Nothing
objShowProperties = i
End Function
In my Acccess db I've a table named customers.
To show properties of this table, I call the above function like this:
objShowProperties CurrentDb.TableDefs("customers")
In my debug console, I got this:
All listed properties can then be accessed directly on VBA code, eg, RecordCount property:
dim lngRecords as long
lngRecords = CurrentDb.TableDefs("customers").Properties("RecordCount")
Hope this will help you.
A few things:
Field.Description is a column in Design View (along with Field Name and Data Type) but is undocumented.
No, it is not un-documented.
You are confusing DAO, and that of ms-access.
DAO "field" does not have a description property. So, it not un-documented at all.
Also in Access, there is help. You an put your cursor in the description, and hit help, and you get this:
so, place cursor here, and hit f1 for help:
And now you get this:
So, you are confusing the database engine object called DAO.FIELD with that of ms-access and it allowing you to have/enjoy/see a description in the table desinger.
I should point out that the DAO object model does not have a table designer!!!
In fact, what Access does is add's a custom property to the field, and then display's that. So, field.Description is not un-document, it in fact does not exist.
As noted in the other post here, you can "interate" all of the properties. However, if you use the database engine outside of ms-access, and EVEN create fields in code (or even by sql commands), you WILL STILL find that no descripton property exists. However, as noted, there is this thing called help, and you can give help a try, as it will explain what the description setting in ms-access does.
However, at the end of the day, field.description is not un-documented, and in fact does not exist.
so, if you read/look at/see documentaiton for the DAO field object, then these properties and options will not be found.
After all, you might be using c++, c# or some other system and that database engine that MS-Access just also happens to use.
MS-Access is not the database here. It is a tool that lets you build software, and forms and reports, and write code.
When you using MS-Access, you are not required to use the JET (now called ACE) database engine to store your data. You are free to use the Oracle database, or SQL server or whatever.
So, features of Access and things like link master fields etc.?
Those are MS-Access features, and not the database engine (ACE) features.

How to handle object declaration in VBA (Error 91)

I'm stuck in VBA and I couldn't find a good answer in the other questions related to error 91. I want to create an object and store variables and arrays inside that object. I tried an approach like I would do in js:
Dim block As Object
...
Set block = Nothing
block.Name = "Unbekannter Prüfblock"
block.Awf = "Unbekannter Anwendungsfall"
block.Conditions = Array()
block.Checks = Array()
I use the "Set block = Nothing" because I will use it multiple times in a loop.
But all I get is error 91 - Object variable not set
How can I set the object?
Do I really have to declare everything in vba?
Isn't there a "stop annoying me with declaration notices" toggle? ;-)
Update
Thank you all so much for the detailed answers!
As suggested I created a class for "block" and also a class for "condition" and "check". Block for example:
Option Explicit
Public name As String
Public awf As String
Public conditions As Collection
Public checks As Collection
Then I use it inside my code like this:
Dim bl As Block
Dim co As Condition
Dim ce As Check
Set bl = New Block
bl.name = ws.Range("B" & i).value
bl.awf = ws.Range("B" & i).value
Set co = New Condition
co.attr = ws.Range("B" & i).value
co.value = ws.Range("C" & i).value
bl.conditions.Add co
VBA isn't Javascript; objects and their members cannot be created inline, they need a class definition.
When you make a member call against an object, you refer to it by name, and whenever that name refers to a null reference (Nothing) you'll get error 91.
To fix it, you need to ensure every member call is made against a valid object reference. Using the Set keyword you can assign such a reference, and to create a new instance of an object you can use the New keyword followed by the name of the class that defines the type you want a new instance of:
Dim Block As Object
Block.Something = 42 ' Error 91
Set Block = New SomeClass ' set reference
Block.Something = 42 ' OK
Note that because the object is declared As Object, every member call is late-bound (resolved at run-time); if the member doesn't exist (or if there's a typo), you'll get error 438 at run-time.
You can move this error to compile-time with early binding by using a more specific data type for the declaration:
Dim Block As SomeClass
Because the members of SomeClass are known at compile-time, the IDE will now provide you with a member completion list when you type up a member call, and typos will no longer be valid at compile-time: strive to remain in the early-bound realm whenever possible! Note: As Variant (explicit or not) is also similarly late-bound.
So we add a new class module and call it SomeClass and we add a couple of public fields:
Option Explicit
Public Name As String
Public Case As String
Public Condition As Variant
Public Check As Variant
And now you can create and consume a new instance of that class, and add instances of it to a collection to process later (note: you can't do that with a UDT/Type).
The VBIDE settings have an annoying option ("automatic syntax check", IIRC) that immediately pops a message box whenever there's a compilation error on the current line; uncheck it (invalid lines will appear in red, without a message box), but do have the "require variable declaration" setting checked: it will add Option Explicit to every module, and that will spare you from a number of easily avoidable run-time errors, moving them to compile-time.
In JS, you can add properties (together with values) on the fly to an object. That's not possible in VBA (and most other languages).
Your declaration Dim block As Object is defining a variable that is supposed to point to an Object. But it isn't pointing to anything yet, per default it is initialized with Nothing, which is, literally, nothing, and has neither properties nor methods, it's just a placeholder to signal "I don't point to anything yet". Furthermore, Object cannot be instantiated.
in VBA, you assign something to an object variable with Set (this is different to most other languages). If you want to create a new object, you use the keyword New.
However, before you do that, you need to know what kind of object (which class) you need. This can be an existing class, eg a Worksheet or a Range in Excel, or it can be an own defined class (by creating a new class module in your code). In any case, you need to define the properties and the methods of that class. Considering the most simple class module Class1 (of course you should think about a more usefull name):
Option Explicit
Public name as String
Public case as String
(I will not start to talk about private members and getter and setter).
You then write
Dim block As Class1
Set block = New Class1
block.name = "Unbekannter Prüfblock"
(But block.data = "Hello world" will not be possible as data is not a member of your class.)
Big advantage of this attempt is that the compiler can show you when you for example mistyped a property name before you even start your code (Debug->Compile). In JS, you will get either a runtime error or a new property on the fly - hard to find nasty bugs.
If you later need a new (empty) object, just create a new object using New. If the old object is not referenced anywhere else, the VBA runtime will take care that the memory is freed (so no need to write Set block = Nothing).
In case you really don't know the properties in advance (eg when you read XML or JSON files or a key-value list from an Excel sheet...), you can consider to use a Collection or a Dictionary. Plenty of examples on SO and elsewhere.
One remark to block.Condition = Array(). Arrays in VBA are not really dynamic, you cannot add or remove entries easily during runtime. In VBA, you have static and dynamic arrays:
Dim a(1 to 10) as String ' Static array with 10 members (at compile time)
Dim b() as String ' Dynamic array.
However, for dynamic members you need to define how many members you need before you write something into it, you use the ReDim statement for that. Useful if you can calculate the number of members in advance:
Redim b(1 to maxNames) ' Dynamic array, now with maxNames members (whatever maxNames is)
You can change the array size of a dynamic array with Redim Preserve, but that should be an exception (bad programming style, inperformant). Without Preserve, you will get a new array, but the former data is lost.
Redim Preserve b(1 to maxNames+10) ' Now with 10 more members.
If you really don't know the number of members and it can change often during runtime, again a Collection or a Dictionary can be the better alternative. Note that for example a Dictionary can itself a Dictionary as value, which allows to define Tree structures.
Regarding your issue adding to the collection:
You need to add this code to your class module "Block" - only then you can add objects to the collections
Private Sub Class_Initialize()
Set conditions = New Collection
set checks = new Collection
End Sub

Sender.Gettype from multiple handles

I have code that handles multiple events. FYI - I use Devexpress Components. I have two items, a Lookupedit and a GridLookupEdit, that are handled by the same code. I am trying to do something like the following:
Dim type = sender.GetType()
Select Case DirectCast(sender, Type).Name
Case "mgrLUE"
log("View metrics for manager: " & mgrLUE.Properties.GetDisplayText(mgrLUE.EditValue), Me.Name)
Case "sectLUE"
log("View metrics for section: " & sectGLUE.Properties.GetDisplayText(sectGLUE.EditValue), Me.Name)
End Select
I am getting errors at the select case line. I cant figure out how to dynamically get the type to be able to direct cast to it. The types will be DevExpress.XtraEditors.GridLookUpEdit and DevExpress.XtraEditors.LookUpEdit in this case. I have tried searching for a solution, but everything I have tried is failing.
Thank you for the help.
Casting can't be done dynamically because its sole purpose is to let the compiler know that you expect an object to be of a certain type. This is necessary so that the compiler knows what members the object contains when you try to access it.
I should mention that VB.NET has a feature called late binding, which allows you to access members of a type wrapped in an Object by looking up if the member you're trying to access exists at runtime. Using late binding, however, is not recommended as it is very easy to make mistakes and break your code.
Now, as for your problem: Casting can be done if an object is of a certain type, or if it inherits from that type. Since I'm guessing what you're using are controls (that you've placed on your form) they all should inherit from System.Windows.Forms.Control, thus you can cast them to that which contains the base property Name:
Select Case DirectCast(sender, Control).Name

How do I assign an object type dynamically?

I am trying to loop through all of the controls in a panel. Some of the controls as classes that I created. In those classes, I want a subroutine run when the object is removed. So I am trying to create a temporary object that I can use to run that routine.
For Each window As Control In main_window.Controls
If window.Handle = hdl Then
Dim temp_window as window.getType()
temp_window.close_me()
main_window.Controls.Remove(window)
End If
Next
However, the getType assignment is not allowed.
How can I accomplish this?
Object.GetType is not what you want, it returns the Type instance of the object which contains meta data of that type, normally used for reflection.
What is the actual type that you want? It has to have a close_me method. You could use OfType:
Dim windowsToClose = main_window.Controls.OfType(Of YourWindowType)().
Where(Function(w) w.Handle = hdl).
ToArray()
For Each window In windowsToClose
window.close_me()
main_window.Controls.Remove(window)
Next
Your For Each doesn't work for another reason: you can't remove items from the collection while you are enumerating it. Above approach stores the windows you want to remove in an array.
The right way to do this is to use a base class that your controls Inherit or an interface that your controls Implement with close_me on the base or interface. Then, you can TryCast each member of Controls to the base or interface and, if it succeeds, call close_me on it. If you use the base class approach, you may wish to make it abstract (MustInherit) and then close_me would be MustOverride, depending on if the behavior should be different in each derived type.
e.g. assuming you use ICloseable,
Interface ICloseable
Sub close_me()
End Interface
'...
For Each window As Control In main_window.Controls
If window.Handle = hdl Then
Dim asCloseable = TryCast(window, ICloseable)
If asCloseable IsNot Nothing Then
asCloseable.close_me()
EndIf
EndIf
Next

Is it possible to declare a local/class instance member reference variable in VB.net to read/update same object referred by another variable

In C++, it is possible to do:
int x;
int& foo = x;
// foo is now a reference to x so this sets x to 56
foo = 56;
But is an equivalent possible in Visual Basic .net (VB.net)?
I am aware of the ByRef term in VB.net but I have only seen (and by extensive research) this in passing parameters by reference to functions/subroutines, so that the function modifies the same variable declared by the calling code.
But I haven't seen ByRef used to declare variables (as class members or local variables). I have tried to use ByRef to do such a declaration but I get compiler errors for each ordering of the declaration I try.
The reason why I want to do this is because I want a class instance to be able to refer to a variable declared in another class instance.
Specifically, I am working on some existing VB.net code where program logic is mixed up with UI logic in VB.net form classes. I want to separate the program logic out of the form code so that it can be more easily reused. To do this I need the program logic code to be able to 'talk' to the form code so I need access to its variables.
I am using Visual Studio 2010 Express with VB.net and would prefer to remain within this environment even though I am aware that full Visual Studio has extended capabilities.
Pointers exist but they're called object references
(Now other posters, please don't quibble with me about the actual differences here. I am talking about the high level task the OP wants to accomplish)
Obviously you can't do literally what you've said - that is, surgically manipulate one member. But if you have control of the code, you can do almost as well. And from what you describe is your problem, this method will be much better, as you can pass references to an object that has many members you wish to update, instead of having to pass many individual pointers.
First define a class:
Class MyScreenValues
' Properties will work... using public fields for brevity
Public TextBox1Value as String
Public SpinControl1Value as Integer
public CheckBox1Value as Boolean
End Class
Now the equivalent of the code you posted:
Dim x as new MyScreenValues 'int x;
dim foo as MyScreenValues = x 'int& foo = x;
'// c++: foo is now a reference to x so this sets x to 56
'// .net: foo is now a reference to x so this sets x.SpinControl1Value to 56
foo.SpinControl1Value = 56;
If what you're doing is trying to pass pointers to, say, every control on your form's value to a sub, like so:
Button1_Click(...
Dim MyObject as new BusinessObject
MyObject.DoSubmit(TextBox1.Text, SpinButton1.Value, CheckBox1.Checked)
You can use the method provided by superbDeveloper, and use the ByRef keyword on the definition of the DoSubmit Sub:
Public Sub DoSubmit(ByRef Name as String, ByRef Age as Integer, ByRef Employed as boolean)
... business logic...
However this just gives you a 2-layer separation. Look into MVP, MVC, etc - however consider submitting the entire view worth of data. It's a lot of research you you may settle with what you have now. For example your business logic will be firing events on the form as it changes the form values (well, actually, they won't fire until the sub exits, due to the way VB deals with byref properties [=temp variable], but it needs to be considered).
You can also map an object's properties to a Form's properties with other libraries. Check out VALUE INJECTOR on the web. It can take a webform/winform and maybe even a WPF form and map the control values to and from an object you have predefined. Excellent for complete separation.
Hi well not sure about VB.NET but I know how do it in C#, therefore I did the solution in C# and then i used a C# to VB.NET convertor to get the VB.NET code. maybe it might help you:
http://www.developerfusion.com/tools/convert/csharp-to-vb/
C# Code
protected void TestFoo()
{
string strFooA = string.Empty;
GetFoo(ref strFooA);
Response.Write(strFooA);
}
private void GetFoo(ref string strFooA)
{
strFooA = "FooA";
}
VB.NET converted
Protected Sub TestFoo()
Dim strFooA As String = String.Empty
GetFoo(strFooA)
Response.Write(strFooA)
End Sub
Private Sub GetFoo(ByRef strFooA As String)
strFooA = "FooA"
End Sub
Pointers don't exist in the .NET framework like they do in C/C++, so you won't be able to directly achieve what you want.
There are some possible solutions on this SO page
See #NathanW answer on there:
If you are using VB the only thing that is really close to a pointer is a IntPtr. If you have access to C# you can use unsafe C# code to do pointer work.
In Addition
If you want to be able to use references then you can achieve this by using a class:
Private Class RefClass
Public x As Integer
End Class
Then you can reference one from the other:
Dim bar As New RefClass
Dim foo As RefClass = bar
bar.x = 45
Debug.WriteLine(bar.x) 'outputs 45
Debug.WriteLine(foo.x) 'outputs 45
You may already know enough to get the job done from the info at http://www.dreamincode.net/forums/topic/135354-reference-types-value-types-byval-byref/ ; however, your remark "ByRef is not required in declaration of local variables (and possibly class variables)" left me wondering if there's some confusion here.
For starters, the C++ example above, int x; int& foo = x; confuses what is meant by a "reference". My C++ isn't very strong, but, semantically speaking, I believe this C++ reference operates more like an aliasing mechanism. In VB (and C#) a reference operates like an identification code that locates an instance of a class in memory. Neither VB or C# has anything like the C++ reference type.
By now, you probably already know you can pass in your Form as a ByVal parameter, and change its properties and fields without problem. (ByVal is the default in VB.NET, so you don't even need to use it - in VB6 ByRef was the default.) If you're happy enough, you can skip the rest. But, yeah, in the case of .NET Reference variables, assuming:
Dim objA as New MyClass()
Dim objB as MyClass = objA
Then objA and objB both reference the very same instance of MyClass. You can modify via objA and objB, and you can read back from either objA or objB, because they each affect the very same instance. You can pass either objA or objB into some subroutine Foo with parameter objC As Object passed ByVal (i.e. Sub Foo(ByVal objC As Object) ) and Foo can then modify that same instance too.
The ByRef of VB and the ref of C# indicate a modifiable parameter, which means some "identification code" reference is passed instead of a value. Yet this ByVal vs. ByRef thing is clear as mud because in .NET there is a distinction made between "Value" types and "Reference" types that confuses many on whether a ByRef or ref is needed or not.
Visual Basic and C# dichotomizes variables (and data types) into two species: the "Value" (or "Structure"), and the "Reference" (or "Class").
The "Value" type means an actual collection of bits that represents an Integer, or a Boolean, or even a bitmap, or some other kind of object. In old school parlance, this is the "image" of the instantiation of an object. It is the state space of the object. It is what makes an object essentially itself, independent of where in memory it may be.
The "Reference" type means a code (which might look like an integer or a pointer) that somehow indicates the data type of the object and where in memory it resides. The computer will interpret a "Reference" to obtain the actual image of the object (i.e. its "Value").
When a "Value" parameter is passed ByVal, that means a new object is created that is in the identical image of the original expression being passed, and it is upon this copy that the function or method operates. The original image of the "Value" cannot be affected.
When a "Value" parameter is passed ByRef, that means a new "Reference" variable is created, and that "Reference" variable will contain the information that will interpret back to the image of the original "Value". Now the original image of the "Value" can be changed.
When a "Reference" parameter is passed ByVal, its "identification code", which gets interpreted back to the actual image of the object, gets copied. It is upon this copy of the code that the function or subroutine or method operates. This copy still points to the actual image of the object. Which means that an object of a Reference variable that is passed by ByVal can still have its image (i.e. its "Value") changed. However, the code of the original "Reference" itself cannot be changed.
(Note that the String type is an odd duck: It will behave as if it were a "Value" parameter even though it is in fact a "Reference" type. Hence a String passed ByVal will not be affected in the same way any other class would. Actually, String is an example of an immutable type - which means that steps are taken to prevent changes to its "Value".)
When a "Reference" parameter is passed ByRef, one now has created a new "Reference" object that points to the original "Reference" object (that, in turn, points to the "Value" of some other object via its "identification code"). The use of ByRef on a "Reference" allows one to modify (or create anew) the "identification code" of the original "Reference" object being passed as a parameter. A function or subroutine or method that performs a swap operation will use ByRef on "Reference" parameters.