MS Access Table (and TableDef) Properties - vba

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.

Related

Using movenext and moveprevious

I want to get the next value of the field next to the current row.
I used ADODB movenext and moveprevious together, but there is an error that the current context does not permitted.
How to fix that?
This is the code
objresultset.MoveNext()
yMark = objresultset.Fields(k1).Value
If xMark = yMark Then
m += 1
End If
objresultset.MovePrevious()
I'm not sure. Perhaps you provide too little information.
I assume that ADO recordset features might also dependent upon the underlying database system and/or your database connection properties. So perhaps your specific database/recordset instance does not support MovePrevious in your case.
But if it has worked before, or if it works in a small test application, perhaps you are trying to move beyond the last record in your recordset. In that case you might check the BOF/EOF property values before you try to use MovePrevious/MoveNext.

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

Variable declaration syntax

Basically I follow a spec to create functions in Access 2010. These functions are in VBA. When working with record sets the given declaration in the spec is
Dim obj.Recordset As New ADODB.Recordset
Yet every time I try and write it I get a syntax error so I just use:
Dim Recordset As object
I am not sure if this means the same thing but it compiles and seems to work fine. Basically my question is, is the given declaration for a recordset correct and is my alternative acceptable. Also Access 2010 is used as a user front end and the database is stored in MS- SQL server 2008 backend.
It looks like you are trying to define a variable with a '.' in the variable name. That is not a valid character in a variable name. If I didn't know better, this syntax looks like you are trying to somehow assign a data type of ADODB.Recordset to a property named 'Recordset' of a class object named 'obj' (which would be extremely bizarre and I don't know of a valid syntax for in VBA or why anyone would want to). I would expect the following will compile:
Dim rst As New ADODB.Recordset
Also make sure you have added the appropriate reference in Tools --> References (Microsoft ActiveX Data Objects 2.0 Library or other latest version). As to your second question, that should be a viable alternative but I prefer the strongly typed former.

Global variable loses its value

On this Access form I am working on I have a global variable that take its value from another form on its Form_Load event. For some reason unknown to me the variable "loses its value" (becomes = "") after some time elapses or some event occurs. I have not been able to notice anything in particular that triggers this behaviour. Are global variables reset after some time of "inactivity" on the form ?
Here is how I set the global variables I am talking about:
Private Sub Form_Load()
'...
Set prev_form = Form_Identification.Form
PasswordSybase = prev_form.Password.Value & vbNullString
UserSybase = prev_form.UserID.Value & vbNullString
'...
End Sub
An alternate solution (Only 2007 and onwards) I've started using is TempVars instead of globals in the odd situation I "needed" something global. It's a collection and it persists for the duration of the application unless you explicitly release it. So in some cases I feel its more useful than globals and in some cases worse.
TempVars.Add myVarName, myVarValue ' To initialize
TempVars.Item(myVarName) = newVarValue ' To reference and assign a new value
TempVars.Remove(myVarName) ' To release
Quick search should show you more lot references, but I've included link to a basic one
http://blogs.office.com/b/microsoft-access/archive/2010/09/27/power-tip-maximize-the-user-of-tempvars-in-access-2007-and-2010.aspx
I do hope that visitors see this post, as it provides an important additional piece.
Even if you declare a variable globally, it appears that - in the event that you set that variable's value in a form module - that value is lost when you UNLOAD the form.
The solution (in my case) was as simple as replacing:
Unload Me
...with...
Me.Hide
The variables (and objects) that I set in that code module then retained their values through the entire lifetime of the application instance.
This may help:
https://accessexperts.com/blog/2011/01/12/multi-session-global-variables/
Juan Soto explains how to use a local table to keep variables and how to call them when needed. It may serve your purpose in 2000 since TempVars isn't an option. You could always delete the variables "on close" of the database so that UID and PWD aren't kept.
You can create a "fake" global variable by
creating a form (e.g. named frmGlobal)
make sure the form is always open but hidden
create a TextBox for each global variable you want (e.g. tVar1)
in your code, reference as e.g. Form_frmGlobal.tVar1
The disadvantage is that an unbound text box may not give you a specific data type you want
The two ways around that are
in your code, explicitly convert the textbox to the data type when referencing the global variable
e.g Clng(Form_frmGlobal.tVar1)
another option is create a one-row table and bind your textboxes on your hidden form to the table, so your data types are enforced
A bonus of this method is you can use for persistent storage between sessions
Caveat: make sure this table is local to a single user only, in the front end database file (don't want to put it in the back end database because of multi-users over-writing each other). This assumes you are using front end + back end separated databases, with distribution of front end to each user's workstation.
I see nothing in that statement that tells me it's a global variable. You set global variables above ALL Subs/Functions and below an Options Compare statement in a module, by stating:
PUBLIC X as string
Any other variable is only good until the sub or function has completed.
Also, Global variables MUST be declared on a PROPER MODULE. You can't declare them on a form's module.

When to use a Class in VBA?

When is it appropriate to use a class in Visual Basic for Applications (VBA)?
I'm assuming the accelerated development and reduction of introducing bugs is a common benefit for most languages that support OOP. But with VBA, is there a specific criterion?
It depends on who's going to develop and maintain the code. Typical "Power User" macro writers hacking small ad-hoc apps may well be confused by using classes. But for serious development, the reasons to use classes are the same as in other languages. You have the same restrictions as VB6 - no inheritance - but you can have polymorphism by using interfaces.
A good use of classes is to represent entities, and collections of entities. For example, I often see VBA code that copies an Excel range into a two-dimensional array, then manipulates the two dimensional array with code like:
Total = 0
For i = 0 To NumRows-1
Total = Total + (OrderArray(i,1) * OrderArray(i,3))
Next i
It's more readable to copy the range into a collection of objects with appropriately-named properties, something like:
Total = 0
For Each objOrder in colOrders
Total = Total + objOrder.Quantity * objOrder.Price
Next i
Another example is to use classes to implement the RAII design pattern (google for it). For example, one thing I may need to do is to unprotect a worksheet, do some manipulations, then protect it again. Using a class ensures that the worksheet will always be protected again even if an error occurs in your code:
--- WorksheetProtector class module ---
Private m_objWorksheet As Worksheet
Private m_sPassword As String
Public Sub Unprotect(Worksheet As Worksheet, Password As String)
' Nothing to do if we didn't define a password for the worksheet
If Len(Password) = 0 Then Exit Sub
' If the worksheet is already unprotected, nothing to do
If Not Worksheet.ProtectContents Then Exit Sub
' Unprotect the worksheet
Worksheet.Unprotect Password
' Remember the worksheet and password so we can protect again
Set m_objWorksheet = Worksheet
m_sPassword = Password
End Sub
Public Sub Protect()
' Protects the worksheet with the same password used to unprotect it
If m_objWorksheet Is Nothing Then Exit Sub
If Len(m_sPassword) = 0 Then Exit Sub
' If the worksheet is already protected, nothing to do
If m_objWorksheet.ProtectContents Then Exit Sub
m_objWorksheet.Protect m_sPassword
Set m_objWorksheet = Nothing
m_sPassword = ""
End Sub
Private Sub Class_Terminate()
' Reprotect the worksheet when this object goes out of scope
On Error Resume Next
Protect
End Sub
You can then use this to simplify your code:
Public Sub DoSomething()
Dim objWorksheetProtector as WorksheetProtector
Set objWorksheetProtector = New WorksheetProtector
objWorksheetProtector.Unprotect myWorksheet, myPassword
... manipulate myWorksheet - may raise an error
End Sub
When this Sub exits, objWorksheetProtector goes out of scope, and the worksheet is protected again.
I think the criteria is the same as other languages
If you need to tie together several pieces of data and some methods and also specifically handle what happens when the object is created/terminated, classes are ideal
say if you have a few procedures which fire when you open a form and one of them is taking a long time, you might decide you want to time each stage......
You could create a stopwatch class with methods for the obvious functions for starting and stopping, you could then add a function to retrieve the time so far and report it in a text file, using an argument representing the name of the process being timed. You could write logic to log only the slowest performances for investigation.
You could then add a progress bar object with methods to open and close it and to display the names of the current action, along with times in ms and probable time remaining based on previous stored reports etc
Another example might be if you dont like Access's user group rubbish, you can create your own User class with methods for loging in and out and features for group-level user access control/auditing/logging certain actions/tracking errors etc
Of course you could do this using a set of unrelated methods and lots of variable passing, but to have it all encapsulated in a class just seems better to me.
You do sooner or later come near to the limits of VBA, but its quite a powerful language and if your company ties you to it you can actually get some good, complex solutions out of it.
Classes are extremely useful when dealing with the more complex API functions, and particularly when they require a data structure.
For example, the GetOpenFileName() and GetSaveFileName() functions take an OPENFILENAME stucture with many members. you might not need to take advantage of all of them but they are there and should be initialized.
I like to wrap the structure (UDT) and the API function declarations into a CfileDialog class. The Class_Initialize event sets up the default values of the structure's members, so that when I use the class, I only need to set the members I want to change (through Property procedures). Flag constants are implemented as an Enum. So, for example, to choose a spreadsheet to open, my code might look like this:
Dim strFileName As String
Dim dlgXLS As New CFileDialog
With dlgXLS
.Title = "Choose a Spreadsheet"
.Filter = "Excel (*.xls)|*.xls|All Files (*.*)|*.*"
.Flags = ofnFileMustExist OR ofnExplorer
If OpenFileDialog() Then
strFileName = .FileName
End If
End With
Set dlgXLS = Nothing
The class sets the default directory to My Documents, though if I wanted to I could change it with the InitDir property.
This is just one example of how a class can be hugely beneficial in a VBA application.
I use classes if I want to create an self-encapsulated package of code that I will use across many VBA projects that come across for various clients.
I wouldn't say there's a specific criterion, but I've never really found a useful place to use Classes in VBA code. In my mind it's so tied to the existing models around the Office apps that adding additional abstraction outside of that object model just confuses things.
That's not to say one couldn't find a useful place for a class in VBA, or do perfectly useful things using a class, just that I've never found them useful in that environment.
For data recursion (a.k.a. BOM handling), a custom class is critically helpful and I think sometimes indispensable. You can make a recursive function without a class module, but a lot of data issues can't be addressed effectively.
(I don't know why people aren't out peddling BOM library-sets for VBA. Maybe the XML tools have made a difference.)
Multiple form instances is the common application of a class (many automation problems are otherwise unsolvable), I assume the question is about custom classes.
I use classes when I need to do something and a class will do it best:) For instance if you need to respond to (or intercept) events, then you need a class. Some people hate UDTs (user defined types) but I like them, so I use them if I want plain-english self-documenting code. Pharmacy.NCPDP being a lot easier to read then strPhrmNum :) But a UDT is limited, so say I want to be able to set Pharmacy.NCPDP and have all the other properties populate. And I also want make it so you can't accidentally alter the data. Then I need a class, because you don't have readonly properties in a UDT, etc.
Another consideration is just simple readability. If you are doing complex data structures, it's often beneficial to know you just need to call Company.Owner.Phone.AreaCode then trying to keep track of where everything is structured. Especially for people who have to maintain that codebase 2 years after you left:)
My own two cents is "Code With Purpose". Don't use a class without a reason. But if you have a reason then do it:)
You can also reuse VBA code without using actual classes. For example, if you have a called, VBACode. You can access any function or sub in any module with the following syntax:
VBCode.mysub(param1, param2)
If you create a reference to a template/doc (as you would a dll), you can reference code from other projects in the same way.
Developing software, even with Microsoft Access, using Object Oriented Programming is generally a good practice. It will allow for scalability in the future by allowing objects to be loosely coupled, along with a number of advantages. This basically means that the objects in your system will be less dependent on each other, so refactoring becomes a lot easier. You can achieve this is Access using Class Modules. The downside is that you cannot perform Class Inheritance or Polymorphism in VBA. In the end, there's no hard and fast rule about using classes, just best practices. But keep in mind that as your application grows, the easier it is to maintain using classes.
As there is a lot code overhead in using classes in VBA I think a class has to provide more benefit than in other languages:
So this are things to consider before using a class instead of functions:
There is no class-inheritance in vba. So prepare to copy some code when you do similar small things in different classes. This happens especially when you want to work with interfaces and want to implement one interfaces in different classes.
There are no built in constructors in vba-classes. In my case I create a extra function like below to simulate this. But of curse, this is overhead too and can be ignored by the one how uses the class. Plus: As its not possible to use different functions with the same name but different parameters, you have to use different names for your "constructor"-functions. Also the functions lead to an extra debug-step which can be quite annoying.
Public Function MyClass(ByVal someInit As Boolean) As MyClassClass
Set MyClass = New MyClassClass
Call MyClass.Init(someInit)
End Function
The development environment does not provide a "goto definition" for class-names. This can be quite annoying, especially when using classes with interfaces, because you always have to use the module-explorer to jump to the class code.
object-variables are used different to other variable-types in different places. So you have to use a extra "Set" to assign a object
Set varName = new ClassName
if you want to use properties with objects this is done by a different setter. You have to use "set" instead of "let"
If you implement an interface in vba the function-name is named "InterfaceName_functionName" and defined as private. So you can use the interface function only when you cast the Variable to the Interface. If you want to use the function with the original class, you have to create an extra "public" function which only calls the interface function (see below). This creates an extra debug-step too.
'content of class-module: MyClass
implements IMyInterface
private sub IMyInterface_SomeFunction()
'This can only be called if you got an object of type "IMyInterface"
end function
private sub IMyInterface_SomeFunction()
'You need this to call the function when having an object of the type "MyClass"
Call IMyInterface_SomeFunction()
end function
This means:
I !dont! use classes when they would contain no member-variables.
I am aware of the overhead and dont use classes as the default to do things. Usually functions-only is the default way to do things in VBA.
Examples of classes I created which I found to be useful:
Collection-Classes: e.g. StringCollection, LongCollection which provide the collection functionality vba is missing
DbInserter-Class: Class to create insert-statements
Examples of classes I created which I dont found to be useful:
Converter-class: A class which would have provided the functionality for converting variables to other types (e.g. StringToLong, VariantToString)
StringTool-class: A class which would have provided some functionality for strings. e.g. StartsWith
You can define a sql wrapper class in access that is more convenient than the recordsets and querydefs. For example if you want to update a table based on a criteria in another related table, you cannot use joins. You could built a vba recorset and querydef to do that however i find it easier with a class. Also, your application can have some concept that need more that 2 tables, it might be better imo to use classes for that. E.g. You application track incidents. Incident have several attributes that will hold in several tables {users and their contacts or profiles, incident description; status tracking; Checklists to help the support officer to reply tonthe incident; Reply ...} . To keep track of all the queries and relationships involved, oop can be helpful. It is a relief to be able to do Incident.Update(xxx) instead of all the coding ...
In VBA, I prefer classes to modules when:
(frequent case) I want multiple simultaneous instances (objects) of a common structure (class) each with own independent properties.
Example:Dim EdgeTabGoogle as new Selenium.EdgeDriverDim EdgeTabBing as new
Selenium.EdgeDriver'Open both, then do something and read data to and from both, then close both
(sometimes) I want to take advantage of the Class_Initialize and Class_Terminate automatic functions
(sometimes) I want hierarchical tree of procedures (for just variables a chain of "Type" is sufficient), for better readability and Intellisense
(rarely) I want public variables or procedures to not show in Intellisense globally (unless preceded by the object name)
I don't see why the criteria for VBA would be any different from another language, particularly if you are referring to VB.NET.