Access OOP examples wanted Some people have shown interest in custom classes lately, and was asked for some more examples. I use custom classes extensively, but the most important use in Access is the ability to extend control functionality and basically simulate building a user control. Once...
The class concepts in this are actually very straight forward. A lot less complicated than the common controls. The complicated part is generic dao code to figure out the all the information needed. Using DAO and you where the field is and what table it is from then you can use DAO to find out a lot. Then you store these in your custom properties. Building a custom collection is relatively straight forward. Once you see it once they are always basically the same.
Lesson: Composition.
Composition is one of the most important aspects of Object Oriented Programming. If not familar with the term you are familar with the concept. A Composite Class is a class that has some properties which are also class objects. Almost everything in Access that you usually use is a composite class. It is like producing any kind of machine, you would have parts that make an assembly, and that is a sub assembly of a higher part, which in turn is a sub assembly of an even higher part ... Composite classes are a hierrarchy of organization.
A car manufacturer has suppliers for engines, seats, radios, lights etc ....
--- The engine manufacturer has suppliers for pistons, cranks, blocks, sparkplugs, valves
------ Piston manufacturer may buy piston rings from someone
---The seat manufacturer may have suppliers for seatbelts, cushions, and electronic controls
------ The seat belt manufacturer may have a supplier of latches, springs..
In Access you have a Form Class
Form has Recordset, Controls, ...
--- Recordset is composed of Fields, Properties, ...
------ Field has Attributes, Properties ...
--------- Attribute (lowest Level)
---Controls have Controls as well, could have Pages (if a tab)
------- Page (lowest level)
If you understand composition it makes VBA notation more understandble
Forms("Form1").Recordset ' returns Form1's recordset object
Forms("Form1").Recordset.Fields("firstName") ' returns a field object of the recordset
Forms("Form1").Recordset.Fields("firstName").Attributes("somevalue") ' returns an attribute value
In building a custom composite class I think from the top down and build the class from the bottom up, building the child classes first.
For the UnboundFormController I imagine it would be like a top level class "UnboundFormController" which would control a lot of the adding, deleting, and updating. It would need to have a reference to all the unbound controls in a custom collection "UnboundControls", and each UnboundControl object would keep information about the UnboundControl.
At the bottom of the hierarchy the UnboundControl needs to have a reference to the real control and knowledge of what field in what table it is related to. Working from the bottom I build the UnboundControl class and test it. Then build the UnboundControls class and test it by adding UnboundControls to an instance. Then build the final composite class UnboundFormController that has the UnboundControls as a property.
The first demo is the test putting together the lower two classes. This is testing the UnboundControls collection. The higher class UnboundFormController is not built.
Lesson: Custom Collection Classes, Psuedo Inheritance, Default Properties, and For Each Loop
Before showing the UnboundControl class and UnboundControls collection class I wanted to show how to do these with a very basic example.
First build a simple Pet class that has some simple properties Name, AnimalType, Age, Sound that they make. There are lets and gets for all of these. I also added another property called ToString. I do this in most classes as an easy way to print out information. The tostring property only has a Get. Then I added 1 method MakeSound. I almost always have a method called Initialize that lets you set all of the properties up front.
Code:
'------------------------- Class variables ----------------------
'Normally all class variables are private and you use Lets, Gets, and Sets accessors to retrieve and set.
'Common to start with m to mean 'member' of a class.
Private m_Name As String
Private m_AnimalType As String
Private m_AgeInYears As Integer
Private m_Sound As String
'---------------------------- My Initialize -------------------------------------------------
'Most languages have what is called a parameterized contructor for the class. Does not exist in vba
'This is my way of cheating and I do this on most classes, but not required.
'It is just a method to set a lot of properties
Public Sub Initialize(TheName As String, TheAgeInYears As Integer, TheAnimalType As String, TheSound As String)
Me.Name = TheName
Me.AgeInYears = TheAgeInYears
Me.AnimalType = TheAnimalType
Me.Sound = TheSound
End Sub
'--------------------------------Property Accessors: Let, Get, Set -------------------------------
Public Property Get AnimalType() As String
AnimalType = m_AnimalType
End Property
Public Property Let AnimalType(ByVal TheType As String)
m_AnimalType = TheType
End Property
Public Property Get Name() As String
'Attribute Item.VB_UserMemId = 0
Name = m_Name
End Property
Public Property Let Name(ByVal TheName As String)
'Attribute Item.VB_UserMemId = 0
m_Name = TheName
End Property
Public Property Get Sound() As String
Sound = m_Sound
End Property
Public Property Let Sound(ByVal TheSound As String)
m_Sound = TheSound
End Property
Public Property Get AgeInYears() As Integer
AgeInYears = m_AgeInYears
End Property
Public Property Let AgeInYears(ByVal TheAgeInYears As Integer)
m_AgeInYears = TheAgeInYears
End Property
'I add this in all classes simply for debuging. A property that returns as string of the other properties
'Notice there is not let procedure
Public Property Get ToString() As String
ToString = "I am a:" & Me.AnimalType & ", My Name is:" & Me.Name & ", I am: " & Me.AgeInYears & ", The Sound I make: " & Me.Sound
End Property
'------------------------------- Class Methods ------------------------------------------
Public Sub MakeSound()
MsgBox Me.Sound
End Sub
To test this method
Code:
Public Sub TestPet()
Dim myPet As New Pet
myPet.Initialize "Toto", 5, "Dog", "Bow Wow"
myPet.MakeSound
Debug.Print myPet.ToString
Dim myPet2 As New Pet
myPet2.Initialize "Flipper", 8, "Dolphin", "Squeak whistle"
myPet2.MakeSound
Debug.Print myPet2.ToString
'Show default works do not need name
myPet2 = "Tiger"
Debug.Print myPet2
End Sub
Default Property:
Now here is a trick for making a default property, if that is something you would like. Most vba classes have defaults that you do not have to type. Controls it is Value, Forms it is Controls, a collection is Item
MyCollection.item(1) same as MyCollection(1)
MyControl.value same as MyControl
To do this in a custom class you have to set attributes for the compiler, but this can not be done in VBE. You have to export the class, edit it with a text editor, and then reimport. You will not see the attribute when you import.
I wanted to make the Name property the default. So I have to type this in the Let and Get. It has to be the first line
Attribute Item.VB_UserMemId = 0
Custom Collection Class:
Making a custom collection is often needed to manage your multiple instance of your collection. A custom collection has all the same properties of a normal collection (add, remove, count, item). I usually have 2 or more add methods giving some options. All custom classes look similar to this.
Code:
Private m_Pets As Collection
Public Function Add(TheName As String, TheAgeInYears As Integer, TheAnimalType As String, TheSound As String) As Pet
'create a new Pet and add to collection
Dim NewPet As New Pet
NewPet.Initialize TheName, TheAgeInYears, TheAnimalType, TheSound
m_Pets.Add NewPet, NewPet.Name
Set Add = NewPet
End Function
Public Sub Add_Pet(ByVal ThePet As Pet)
'I also add a second Add to allow you to build the object and then assign it
m_Pets.Add ThePet, ThePet.Name
End Sub
Public Property Get Count() As Long
Count = m_Pets.Count
End Property
Public Property Get NewEnum() As IUnknown
'Attribute NewEnum.VB_UserMemId = -4
'Attribute NewEnum.VB_MemberFlags = "40"
'This is allows you to iterate the collection "For Each pet in pets"
Set NewEnum = m_Pets.[_NewEnum]
End Property
Public Property Get Item(Name_Or_Index As Variant) As Pet
'Attribute Item.VB_UserMemId = 0
'Export the class and uncomment the below in a text editer to allow this to be the default property
'Then reimport
Set Item = m_Pets.Item(Name_Or_Index)
End Property
Sub Remove(Name_Or_Index As Variant)
'remove this person from collection
'The name is the key of the collection
m_Pets.Remove Name_Or_Index
End Sub
Public Property Get ToString() As String
Dim strOut As String
Dim i As Integer
For i = 1 To Me.Count
strOut = strOut & Me.Item(i).ToString & vbCrLf
Next i
ToString = strOut
End Property
'----------------------------------------------- All Classes Have 2 Events Initialize and Terminate --------
Private Sub Class_Initialize()
'Happens when the class is instantiated not related to the fake Initialize method
'Do things here that you want to run on opening
Set m_Pets = New Collection
End Sub
Private Sub Class_Terminate()
'Should set the object class properties to nothing
Set m_Pets = Nothing
End Sub
They will have one class property which is a collection. Private m_Pets As Collection 1. Add
The add function creates a new instance of Pet and then adds it to the collection. Notice it returns what was created so you could call it like:
set NewPet = myPets.add("Toto",5,"Dog","Bark")
2. Remove:
Pretty basic. It removes from the class collection.
3. Count.
Simple gives the count of items in the collection
4. Item.
Returns the item from the collection
5. Add_Pet
Sometimes you may create a pet object ahead of time and then want to add it to the collection, instead of passing in the values for the Add function.
6. ToString
Lets you print out all objects in a collection and returns that string
7. Get NewEnum
This only purpose is to allow you to do a for each. If you did not do it you would have to iterate like
dim i as integer
for i = 1 to myPets.count
....myPets.item(i)
next i
with this you can do
for each myPet in MyPets
...
next
Enabling For Each
Code:
Public Property Get NewEnum() As IUnknown
'Attribute NewEnum.VB_UserMemId = -4
'Attribute NewEnum.VB_MemberFlags = "40"
'This is allows you to iterate the collection "For Each pet in pets"
Set NewEnum = m_Pets.[_NewEnum]
End Property
Again you have to export this procedure and uncomment the attribute and reimport.
Noticed that I also made the Item property a default.
All classes have two events Initialize and Terminate. The event Initialize is not related to my fake constructor "Initialize". These happen when the object is created and destroyed. Use initialize to set start conditions. In terminate it is a good idea to set to nothing class objects and any cleanup.
Inheritance
Vba does not have inheritance. Inheritance lets you make a sub class and have it inherit the properties of the parent class. Imagine you wanted to create a dog class with more specialized properties and methods, but you wanted to use the current properties and methods of the Pet class. You would simply inherit instead of building all of those into your Dog Class. In VBA the work around is what I did here. Our custom collection wants to inherit from the collection class. To do this a collection is made a class property of the custom collection class. Similar to the other User Control classes were I take a listbox and add more properties and methods to make it into a find as you type listbox.
The test for the CustomClass
Code:
Dim myPets As New Pets
myPets.Add "Toto", 5, "Dog", "Bow wow"
myPets.Add "Flipper", 8, "Dolphin", "Squeak whistle"
'Using the other add method
Dim myPet As New Pet
myPet.Initialize "Dino", "100", "Dinosaur", "Roar"
myPets.Add_Pet myPet
Debug.Print "I have " & myPets.Count & "Pets"
Debug.Print myPets.Item(1).Name
'The default works on the collection
Debug.Print myPets(2).Name
'The default works on the Pet class and you can use key
Debug.Print myPets("dino")
For Each myPet In myPets
myPet.MakeSound
Next myPet
Debug.Print myPets.ToString
myPets.Remove ("toto")
Debug.Print "I have " & myPets.Count & " pets"
myPets(1) = "Tiger"
Debug.Print myPets(1)
End Sub