Novice problems with Class Properties and Arrays. Likely syntax.

April15Hater

Accountant
Local time
Today, 16:52
Joined
Sep 12, 2008
Messages
349
Hi-

I am pretty new to class modules, properties, methods, arrays, etc etc. I'm trying to learn more and in doing so I've run into a snag that I just can not get past. Moreover, it's probably my unfamiliarity with arrays, when to use parentheses, when not to use them, how to dimension them, etc.

Form_frmTemplate is the calling module.

clsFieldData is a series of properties that I am trying to call upon from clsSTIData (and other classes as well) that will store values that other procedues can call upon.

clsSTIData is (as of right now) 2 methods that will combine the workbooks of a user defined location, and a few properties that aren't used yet.

So Form_frmTemplate calls upon clsSTIData which pulls various properties from clsFieldData. Now I know I can assign the values using a UDT, but what is the fun in that? I also read in Reddick's Coding Convention that classes can replace UDTs and are better coding practice.

I'm getting the following error on the following line in the clsSTIData.CombineExports method (Full code below):
"Object doesn't support this property or method"
Code:
ReDim FieldInfo().Description(1 To .RecordCount) As String

If I remove parentheses, I get the following:
Metod or Data Member not found.
on
Code:
ReDim FieldInfo.Description(1 To .RecordCount) As String

Form_frmTemplate:
Code:
Private Sub cmdExport_Click()
    Dim objSTIExport As clsSTIData
 
    Set objSTIExport = New clsSTIData
 
    objSTIExport.CombineExports
End Sub

clsFieldData:
Code:
Option Explicit
Private pFieldID() As String
Private pDescription() As String
Private pStartColumn() As Long
Private pEndColumn() As Long
Private pDataType() As Long
Private pColumnWidth() As Long
Private pImportColumn() As Long
Public Property Let FieldID(Value() As String)
    pFieldID() = Value()
End Property
Public Property Get FieldID() As String()
    FieldID = pFieldID()
End Property
Public Property Let Description(Value() As String)
    pDescription() = Value()
End Property
Public Property Get Description() As String()
    Description = pDescription()
End Property
Public Property Let StartColumn(Value() As Long)
    pStartColumn() = Value()
End Property
Public Property Get StartColumn() As Long()
    StartColumn = pStartColumn()
End Property
Public Property Let EndColumn(Value() As Long)
    pEndColumn() = Value()
End Property
Public Property Get EndColumn() As Long()
    EndColumn = pEndColumn()
End Property
Public Property Let DataType(Value() As Long)
    pDataType() = Value()
End Property
Public Property Get DataType() As Long()
    DataType = pDataType()
End Property
Public Property Let ColumnWidth(Value() As Long)
    pColumnWidth() = Value()
End Property
Public Property Get ColumnWidth() As Long()
    ColumnWidth = pColumnWidth()
End Property
Public Property Let ImportColumn(Value() As Long)
    pImportColumn() = Value()
End Property
Public Property Get ImportColumn() As Long()
    ImportColumn = pImportColumn()
End Property

clsSTIData:
Code:
Option Explicit
Private xlApp As excel.Application
Private plngBeginLocation As Long
Private plngEndLocation As Long
Private plngDataType As Long
Private Const mvarDefaultLocation As Variant = "[URL="file://ns-tampvfs01/Shared/Securities"]\\NS-TAMPVFS01\Shared\Securities[/URL] Financial Management" & _
                                              "\Derivatives\Data Integrity\DAR to PRIME Rec\"
Private Sub Class_Initialize()
 
    Set xlApp = CreateObject("Excel.Application")
    With xlApp
        .Application.Visible = True
        '.ScreenUpdating = False
        .DisplayAlerts = False
        .EnableEvents = False
    End With
 
 
End Sub
Public Property Get BeginLocation(Header As String) As Long
    plngBeginLocation = DLookup("BeginLocation", "tblSTIDataSchema", "Description = '" & Header & "'")
    BeginLocation = plngBeginLocation
End Property
Public Property Get EndLocation(Header As String) As Long
    plngEndLocation = DLookup("EndLocation", "tblSTIDataSchema", "Description = '" & Header & "'")
    EndLocation = plngEndLocation
End Property
Public Property Get DataType(Header As String) As Long
    plngDataType = DLookup("DataType", "tblSTIDataSchema", "Description = '" & Header & "'")
    DataType = plngDataType
End Property
Property Get FolderLocation(DefaultLocation) As String
    'Get workbook File Name and Extension
    Dim strLocation As String
    FolderLocation = FolderBrowser(mvarDefaultLocation)
    ChDir FolderLocation
End Property
Public Sub CombineExports()
    Dim intA As Integer
    Dim intImportColumnCnt As Integer
    Dim rsFieldData As New ADODB.Recordset
    Dim wksImport As Worksheet
 
    Dim FieldInfo As New clsFieldData
 
    With rsFieldData
        .Open _
        ActiveConnection:=CurrentProject.Connection, _
        Source:="SELECT ID, Description, BeginLocation, EndLocation, DataType " & _
                "FROM tblSTIDataSchema;", _
        CursorType:=adOpenStatic, _
        LockType:=adLockReadOnly
 
        ReDim FieldInfo.Description(1 To .RecordCount) As String
        ReDim FieldInfo().ColumnWidth(1 To .RecordCount) As Long
        ReDim FieldInfo().DataType(1 To .RecordCount) As Long
        ReDim FieldInfo().ImportColumn(1 To UBound(FieldInfo().FieldID))
        intImportColumnCnt = 0
        For intA = 1 To .RecordCount
            FieldInfo().Description(intA) = !Description
            FieldInfo().ColumnWidth(intA) = (!EndLocation - !BeginLocation) + 1
            If UBound(Filter(FieldInfo.FieldID, !ID)) < 0 Then
                FieldInfo().DataType(intA) = 9
            Else
                FieldInfo().DataType(intA) = !DataType
                intImportColumnCnt = intImportColumnCnt + 1
                FieldInfo().ImportColumn(intA) = intImportColumnCnt
            End If
            .MoveNext
        Next intA
        .Close
    End With
    Set wksImport = OpenSourceWks(FieldInfo())
End Sub
Private Function OpenSourceWks(FieldInfo As clsFieldData) As Worksheet
    Dim wksSource As Worksheet
    Dim qtbSource As QueryTable
    Dim rngLastRow As Range
    Dim strFolderLocation As String
    Dim strFileName As String
    Dim intA As Integer
 
    strFolderLocation = FolderLocation(mvarDefaultLocation) 'Set folder location
    strFileName = Dir(strFolderLocation & "\*.dat") 'Set filename
    Set wksSource = xlApp.Workbooks.Add.ActiveSheet 'Set worksheet
    With wksSource
        'Write description header and format columns for data type
        For intA = 1 To UBound(FieldInfo().Description)
            .Cells(1, intA).Value = FieldInfo().Description(intA)
            Select Case FieldInfo().DataType(intA)
                Case Is = 1
                    .Cells(, FieldInfo().ImportColumn(intA)).EntireColumn.NumberFormat = "General"
                Case Is = 2
                    .Cells(, FieldInfo().ImportColumn(intA)).EntireColumn.NumberFormat = "@"
            End Select
        Next intA
        'Loop files in user chosen directory
        Do While strFileName <> ""
            'Find LastRow
            Set rngLastRow = .Range("A:A").Find(What:="*", _
                                                SearchOrder:=xlByRows, _
                                                SearchDirection:=xlPrevious)
            Select Case rngLastRow.Row
                'No headers, therefore empty FieldInfo argument.
                Case Is = 0
                    Exit Function
                Case Is = 1
 
                Case Is > 1
            End Select
            'Import text data into excel columns
            Set qtbSource = wksSource.QueryTables.Add(Connection:="TEXT;" & strFolderLocation & strFileName, _
                                                      Destination:=wksSource.Cells(1, 1))
            With qtbSource
                .TextFileParseType = xlFixedWidth
                .TextFileFixedColumnWidths = FieldInfo.ColumnWidth
                .TextFileColumnDataTypes = FieldInfo.DataType
                .TextFileTrailingMinusNumbers = True
                .Refresh BackgroundQuery:=False
            End With
            strFileName = Dir
         Loop
    End With
End Function
 
It could be that your assignment fails because the Private pDescription() As String declaration in clsFieldData is not an array type. It's just a string, but you are trying, ultimately, to ReDim it. Aren't you? Maybe I'm not paying close enough attention, but if it was me, I would change this...
Code:
Private pDescription() As String
Public Property Let Description(Value() As String)
    pDescription() = Value()
End Property
Public Property Get Description() As String()
    Description = pDescription()
End Property
...to this...
Code:
private m_vDescriptionList [COLOR="Green"] 'defaults to variant[/COLOR]
Property Get DescriptionList() As Variant  [COLOR="Green"]'or you can declare it[/COLOR]
   DescriptionList = m_vDescription
End Property
...because assigning an array to a variant is by far the simplest, even a strongly typed array, like...
Code:
Dim vArray
ReDim vArray(3) As String
...so the variant variable vArray now contains a strongly typed array of strings, even though you didn't do....
Code:
Dim sArray() as String
ReDim sArray(3) As String
...and I'm not even sure that second one will work, but why bother? What matters is that the elements of the array are strings and it's fine with me that the variable that contains the array itself is a variant.
And, you might not need a Property Let, since you may never make an assignment to this variable. You use the variable, like...
Code:
ReDim FieldInfo.Description(1 To .RecordCount) As String
...but that's different from...
Code:
FieldInfo.DescriptionList = Split("1 2 Hello World 5 6")
...which would require the Property Let, because only then is your class property the target of an assignment.

Does that make sense?

I really like what you are doing here. Further thoughts: Your clsFieldData has no methods, so it's in some ways a sophisticated type, and you could just do...
Code:
Type FieldData
    pFieldID() As String
    pDescription() As String
    pStartColumn() As Long
    pEndColumn() As Long
    pDataType() As Long
    pColumnWidth() As Long
    pImportColumn() As Long
End Type
...and declare a variable...
Code:
dim myFieldData as FieldData
...and get some other class to use it for data storage. But a class *should* take care of it's own data, so I'd want a clsFieldData to open its own recordset and populate itself with its own data->encapsulation. I'd want to just throw a header at it, or a path to a file, or an ID. Think about how you instantiate a recordset. You pass it a very distinct SQL statement and connection data, so the recordset object is total cookie cutter, but by passing in the specific SQL and connection, cursor, and so on, that class becomes an instance. You want to pass specific start-up information to your class and get it to do it's thing internally, and offer up that stable data/processing as a robust unit of programming that calling code can reuse.

Anyway, hope I didn't bore you. Classes are the bomb, and thinking about programming without them is like thinking about dinner without the meat.

Cheers,
 
'Well, if it makes you feel any better, you certainly didn't bore me. Thank you very much for the information. I know I could use a UDT for this, but I love working with this stuff and the constant challenges, and I love the fact I learn something new everytime I program in it. So while I know a UDT would be the easy way out, I wouldn't learn anything..

I decided to take my code and rewrite it into something a little more basic so I can maybe see the concept that way keep from overloading other readers. I just have a calling procedure and a class with one property and I'm still not having any luck with it. I'm still having issues getting it to work on the same redim line.

clsFieldData:
Code:
Option Explicit

Private m_vIDList As Variant
Public Property Get IDList() As Variant
    IDList = m_vIDList
End Property

Module1:
Code:
[CODE]Sub CallingProcedure()

Dim clsObject As clsFieldData
Dim arrGetID As Variant
Dim lngA As Long

Set clsObject = New clsFieldData

ReDim arrGetID(10) As Long
ReDim clsObject(10) As Long

For lngA = LBound(arrGetID) To UBound(arrGetID)
    arrGetID(lngA) = lngA - 1
Next lngA

clsObject.IDList = arrGetID

End Sub
[/CODE]
 
Consider this code, and there's a fair bit going on here, but this first block is a class called clsFieldData...
Code:
Option Compare Database
Option Explicit

Private m_init As Long
Private m_list As Variant

[COLOR="Green"]'********************************************************************
'*                                                        Oct 19 2012
'*       Properties>
'*
'********************************************************************[/COLOR]
Public Property Get IDList() As Variant
    If IsEmpty(m_list) Then BuildList
    IDList = m_list
End Property

[COLOR="Green"]'********************************************************************
'*                                                        Oct 19 2012
'*       Methods>
'*
'********************************************************************[/COLOR]
Public Function Create(InitData As Long) As clsFieldData
    m_init = InitData
    Set Create = Me
End Function

[COLOR="Green"]'********************************************************************
'*                                                        Oct 19 2012
'*       Utilities>
'*
'********************************************************************[/COLOR]
Private Sub BuildList()
    Dim i As Long
    
    ReDim m_list(m_init) As Long
    For i = 0 To m_init
        m_list(i) = i
    Next
End Sub
...and this routine, in a standard module, is a consumer of that class...
Code:
Private Sub Test823476918746()
    Dim tmp As New clsFieldData
    Dim var
    
    For Each var In tmp.Create(10).IDList
        Debug.Print var
    Next
End Sub
...but step through and watch what happens. Mainly notice how the ReDim and all the array initialization is contained by methods of the class itself, and look at how spare the code is in the consumer.
And see how IDList 'lazy loads?' This class doesn't run any code (except to save the initialization data) until a consumer actually references the property, and then the class checks if the property has been built yet, and if not, builds it. Once. And then it's available for the lifetime of that instance.

So when you read about how a class "hides its implementation" this is what that looks like. The consumer doesn't need to know how the class does what it does, and you could rewrite how the class works, but as long as it exposes the same members, consumers of that class will still function correctly.

Cool thread, thanks,
 
Last edited:
Thanks Mark, that is really cool! Interesting more than anything.... I'm still trying to wrap my head around it. I had to edit this post because the first time I ran the code, it didn't run buildlist sub, but it is now.

Thanks for the insight, these classes are really cool and after playing with VBA for almost 6 years, I'm glad I finally stepped into them, no pun intended :)

Joe
 
Last edited:
That's a really cool demo to throw together lagbolt. Shows a quite sophisticated technique and principles of encapsulation in a neat package.

It does, however, also show another principle: don't use classes unless they're really needed!

Don't get me wrong, it's great to learn them and I know that that demo was just that - a demo.

But I have worked with a colleague who, nice guy and very intelligent and all, would make classes where they weren't required and it would make life a pain.

The example you created lagbolt, if it were meant to be just what it is and used for that purpose, which of course it isn't, it's a demo (I reiterate - I understand that and I'm not knocking it), then it would be better as a simple function:

Code:
Public Function BuildList(InitData As Long) As Variant
    Dim vList As Variant
    Dim i As Long
    ReDim vList(InitData) As Long
    For i = 0 To InitData
        vList(i) = i
    Next
    BuildList = vList
End Function

Private Sub TestBuildList()
    Dim varList, varItem
    varList = BuildList(10)
    For Each varItem In varList
        Debug.Print varItem
    Next
End Sub

And it illustrates how, in this case and in many others, a class would be overkill.

Classes are also often created when an appropriate and often better class already exists in Access.

Without looking into the definition (which I have so I see this isn't necessarily the case but just going by the name) the clsFieldData sounds like it might be reinventing a wheel called a Recordset.

I think it should be advised to be wary in that respect. Even if there are definite advantages to creating your own there is always the disadvantage that it is a class that nobody else will be familiar with. Whereas the existing classes, imperfect as they may be in this case, are well known to other developers. Problems with them can be googled. Problems with your custom classes can't (not directly).

So, that's my advice with classes: When learning them is over and building a real frontline database then use them sparingly and with caution. Many times a normal module would do (and would indeed be easier because methods could be used without having to first declare an object of the class) and many other times using existing classes would be wiser.

The examples given here by April15Hater are perhaps good examples of when they might indeed be a good idea: They could encapsulate some quite complex things into an easy to use object. However, I'm not certain a standard module with subs and functions couldn't do the same things with less code and easier implementation:

Would anyone ever want to create a clsSTIData object but not immediately OpenSourceWks? CombineExports could be a boolean flag to the function. It could all be done in one go in one function and the resultant FieldData could be returned and reused if need be.

I'm not saying that necessarily is the case but it's important to think about or you can end up creating horribly complex and proprietry designs if classes are used where they're not needed.

And, I repeat, lagbolts sample is an excellently neat demo of creating dynamic arrays and exposing them in classes and encapsulation but also serves as a good demo of when making a class is overkill. If all you want to do is what the sample does then a class is too much.

Personally, I very rarely use classes for those two reasons:
  • Existing classes are normally adequate (and by default better because they're familiar to others)
  • Often a function in a standard module would work just as well and be easier to write and utilise.

But I'm not the Oracle and others more experienced than me may disagree and I'd welcome their thoughts.

Perhaps I'm too class-averse because I've had to go through databases replacing complex classes with much more simple functions ;)
 
Last edited:
Any intelligent fool can make things bigger and more complex... It takes a touch of genius - and a lot of courage to move in the opposite direction.

Albert Einstein
 
Some developers use class modules in trivial or pointless ways. One that springs to mind is the practice of putting sets of functions into a class ostensibly to ensure they don't have name conflicts with functions in standard modules.

Indeed I suspect some developers think this is the main reason for class modules. It really just makes more work instantiating an instance of the class before functions can be used and extending the length of the function name.

However I think in general custom classes are otherwise underused by many developers.

A well designed class encapsulates many objects and references to others. It can greatly simplify the implementation and maintenance of code avoiding references to specific objects and variables scattered throughout the project.

I don't accept VilaRestal's point about ordinary objects being more familiar to other developers at all. If a developer doesn't understand classes then they should not be touching databases that include them.

Perhaps I'm too class-averse because I've had to go through databases replacing complex classes with much more simple functions

When I see statements like this I wonder how much good construction gets destroyed by those adverse to classes. No doubt there are cases where the class was not used to its full advantage but removing classes for the sake of it seems as ill advised as including them for the same reason.
 
I don't accept VilaRestal's point about ordinary objects being more familiar to other developers at all. If a developer doesn't understand classes then they should not be touching databases that include them.

What I mean is they are familiar with the function of the established classes.

When I see rs.Edit in code (and rs is a standard DAO recordset) I know I know how it behaves and can debug it much more easily. If I see obj.Edit and obj is declared as clsSuperRecordset then I don't know without delving into the class how it behaves.

Custom classes are not documented in the ways that the standardised classes are. So my point is custom classes always start out at a modest disadvantage unless they're a totally new object that isn't modelled by a standard class. But let's face it, most things have been done already in this respect. It's got nothing to do with being familiar with classes. Custom classes can make code more time-consuming to debug for other people and make adapting someone else's design more likely to go wrong.

When I see statements like this I wonder how much good construction gets destroyed by those adverse to classes. No doubt there are cases where the class was not used to its full advantage but removing classes for the sake of it seems as ill advised as including them for the same reason.

Well perhaps that happens but I doubt it. Only a masochist would do it where it's not a time-saver.

I assure you I don't strip out working classes. I'm far too busy to do that even if I wanted to. Only when they need to be reworked (or are totally buggy) and I discover that a simple module would achieve the same thing with a tenth of the code plus a lot less code to use them. I certainly don't think I've ever destroyed a good construction in this way. There's classes in the databases I maintain I would like to get rid of because they're pointless and would be better done as normal modules but I leave them because they work and I haven't got time to fix things that aren't broken.

What I meant by averse is I'm perhaps more careful than I need be not to put others in the same situation of having to do that sort of fixing of my code.

Replacing a standard module that could be better done with a reusable, encapsulated class is quite fun. It's inventive and creative and makes for a more ingenious and elegant design.

Doing the opposite is not (not until you've finished and experience the satisfaction of seeing how much cleaner and clearer the whole project has become ;)).
 
As somebody just delving into classes, I too have often asked myself, 'why not just write a standard mod function instead of a property' or 'why not put all of this code from the class I just built into the form's class and call the form'. But in hesitating, and writing it in a class anyways, I find that the code that I am putting into a class can be used elsewhere to not only provide more control of the process, but also have a better definition of scope, a higher level of organization, and more coding efficiency.

To strike an example, I have a class devoted entirely to controlling a listbox that I use on several forms. While I could use the same code to control and read the listbox in each of the form's classes, I only had to write the listbox's code once and call the class whenever I need to control the listbox. Since the behavior and attributes of the listbox are the same for among all of the forms, I have found that maintaining the code is much easier and it promotes consistency across the project. Is it overkill? I don't think so. As long as the class is well documented, it really isn't much to open up the class and see what is happening. Furthermore, I am much less intimidated opening a form's class with a small amount of code referencing various classes than one with a lot of code that needs to be stepped through. Therefore, I find that once I explore the purpose for the class I'm calling, I only have to figure it out once, rather than figuring out various iterations of the same code.

I look back and think about how much more efficient I could have coded past projects and wish I had forced myself into this technology earlier. Hindsight, I have also witnessed that I haven't been nearly as inventive as I could have been in coding forms simply because I have always treated a form's class as a standard module.

What's most exciting for me though is that I was able to spark a discussion on the subject, and learn that there are more than one school of thought when it comes to the usability of classes. It's more than just the plain old 'this is the code you need to use to do this and here's why.' In other words, I feel I've reached a point where I can express artistic license in my programming, and that's something I haven't really had much wiggle room with before.

Thanks for all the insight guys!

Joe
 
One of the things seldom asked is; why do I need a Class module?

We can put that question another way; what is technically essential for using a Class module?

As far as I can see, the reason is to be able to pass system arguments to a common procedure. We can pass system events to a standard module but we can’t pass the arguments of those events.

An example is the MouseMove event:-
Private Sub SomeFormControl_MouseMove(Button As Integer, Shift As Integer, X As Single, Y As Single)
Here we can call the MouseMove event in a standard module but we can’t pass the system arguments ‘Button As Integer, Shift As Integer, X As Single, Y As Single’.

In Access there is a requirement to use [Event Procedure] as the call argument to call a VBA procedure and pass the system argument(s) for that event. Now, [Event Procedure] can only call a procedure which is in a Class module, be that a Form Class module or an external Class module.

Note that [Event Procedure] does not specify which event to call or what argument(s) to pass, if any. It is a system ability to know, at run time, what to call and what to pass based on the property of the object doing the call.

In the property sheet of a Form Control, [Event Procedure] is assigned to a particular control property. The system can then evaluate the Control property, at run time, in order to determine which event procedure needs to be called and what arguments need to be passed to that event procedure, if any.

In an external Class module the same applies. The external Class module will need to specify, for example:-
Private WithEvents objThisControl As Access.Label
note WithEvents and Access.Label.

Subsequently, the Control objThisControl, which in this case is a Label, needs to be told:-
objThisControl.OnMouseMove = "[Event Procedure]"
That means that the system is told to evaluate [Event Procedure] as the OnMouseMove event for this instance of a Label in an external Class module.

In both cases, Forms and external Class modules, the system is being told which event it should evaluate and what argument(s) it should pass to that event, if any. That is done by using [Event Procedure] as the event property and letting the system figure it out at run time. In other words, [Event Procedure] abstracts the call to the condition of that call as found, by the system, at run time. In ‘C’ parlance, the system looks for a predefined prototype which satisfies the [Event Procedure] call for that object with that property. If it finds that prototype it is used to make the call and pass the argument(s) for that call, if any.

The same is not true for user defined procedures. Those procedures need to be specified absolutely before they are called. They can be changed at run time but they still must be specified absolutely before they are called. Also, system arguments are generally not substituted for user defined arguments. (One exception is [Form] meaning the Form which is calling the user defined procedure, but that’s rare.)

So the one thing that stands out as a technical imperative, as to the difference between Class modules and Standard modules, is for the ability of Class modules to receive system arguments while Standard modules generally can’t.

Other concepts, such as encapsulation and the like, are only relevant if both Class modules and Standard modules can be compared directly one to one. If a Class module is the only way, due to system arguments being passed, then there is no debate, it must be used. If either a Class module or a Standard module can be used then there is room for a debate.

Chris.
 
Some developers use class modules in trivial or pointless ways. One that springs to mind is the practice of putting sets of functions into a class ostensibly to ensure they don't have name conflicts with functions in standard modules.

I have two styles of classes I have developed...
1) HasA Classes
2) IsA Classes

The "HasA" type are what you seem to be referring to, Galaxiom. Even with those, I still try to make a conscious effort to group the included methods together by the type of functionality. But yes, the class is nothing more than an isolated container to insure naming isolation and what not.

The "IsA" type are where subclassing is supported. For the Object Rexx (ooRexx) language I started a new version of my class library architecture in a totally "IsA" design style.The base class includes an exception handler which is able to be utilized by the entire program so long as application components make use of what is available... or if one calls classes developed by someone else, the exception handler may be ignored in that code, and when the exception is passed back to an area of the program where my exception handler is in use, then the exception handler kicks in and handles the exception.

My point is... there is useful purpose for both styles of classes... no one need be ashamed for using a class as a container encapsulating stand-alone functions.
 
Here's the power of programming with class modules IMO ...
Code:
Private Sub Form_Current()
[COLOR="Green"]   'invalidate the previous cJob object[/COLOR]
   Set m_job = nothing
[COLOR="Green"]   'get the full text of the billing address of the customer
   'of the current job using one line of code[/COLOR]
   Me.tbBilling = Me.Job.Customer.BillingAddress.FullText
End Sub
... so in this example the Form_fJob's current event invalidates the cJob object exposed by the form, and rebuilds it with the current JobNumber ...
Code:
Private m_job As cJob

Property Get Job As cJob
[COLOR="Green"]'  This fJob form exposes a cJob object which rebuilds itself 
'  on every current event
[/COLOR]   If m_job is Nothing Then 
      Set m_job = new cJob
      m_job.Load Me.JobNumber
   End If
   Set Job = m_job
End Property
... and in the same way that the form exposes the Job, which automatically creates itself when referenced, so too does the Job expose a Customer which automatically creates itself when referenced, and so on. So when you create an instance of a cJob, it is then a trivial matter to retrieve the billing address of that job's customer. A trivial matter.
 
Here's the power of programming with class modules IMO ...

As far as I can tell from your example the stuff done in the extra class could be built inside the Form Class Module itself by adding properties and methods to it. Such is the case with many things developers do with classes.

Of course the encapsulation of this functionality in the separate class is potentially a lot tider than modifying the Form object itself. Moreover the separate class allows the functionality to be easily tacked onto many different forms or other objects as required.

However if the functionality is entirely specific to the particular form then there isn't much to gain by using a separate class over just adding the properties and methods directly to that form.

Designing the class such that can be generally applied and reused in other projects is the real art of class construction.
 
Another problem with external Class modules is WithEvents.

It has generally not been acceptable to call an external Class module, which uses WithEvents, from a Report.
But it now is…
http://www.access-programmers.co.uk/forums/showthread.php?t=235950

I raised the question on MSDN and only MVP Marshall Barton posted even close to the answer, his post made me rethink the problem:-
http://social.msdn.microsoft.com/Fo...6695e3e/#2b209754-3126-48a2-b44e-215ca07fb424

As simple as the answer proved to be in the long run, it was not well known at the time.

Chris.
 
Just yesterday I had reason to write a simple class, and I think it might add something to this thread so here's the background. A customer of mine needs to cut a partial ellipse using his CNC machine, but that machine requires the dimensions of the ellipse, so the semi-major axis and semi-minor axis, AND--and this is the harder part--the origin and destination of the cut in x, y coordinates. This would be easy if the start and end of the ellipse were exactly at the top, left, bottom, or right extremes, but he wants to cut the ellipse not in half, but in 60%, so just over half.

To me, an ellipse is a good candidate for a class because it has dimensions that distinguish it from other ellipses, so I can pass in some initialization information and create a unique instance that manages it's own data and behaviours. So here's a class called cEllipse ...
Code:
[FONT="Lucida Console"][SIZE="1"]Option Compare Database
Option Explicit

[COLOR="Green"]'********************************************************************
'*                                                        Oct 29 2012
'*      The formula for an ellipse is ...
'*
'*          x^2 / a^2 + y^2 / b^2 = 1
'*
'*      Where a is the semimajor axis (long radius) and b is the
'*  semiminor axis (short radius)
'*
'*      Alternatively we can express the formula as b^2 = a^2 - c^2
'*  which allows us to load the object using the distance c, from the
'*  center of the ellispe to a focus
'*
'*      All math in this module assumes the center of the ellipse is
'*  located at (0, 0).
'*
'********************************************************************
[/COLOR]Private m_a As Single
Private m_b As Single

Property Get a() As Single
[COLOR="Green"]'   Semimajor axis[/COLOR]
    a = m_a
End Property

Property Get b() As Single
[COLOR="Green"]'   Semiminor axis[/COLOR]
    b = m_b
End Property

Property Get c() As Single
[COLOR="Green"]'   Center to focus along semimajor axis[/COLOR]
    c = Sqr(a ^ 2 - b ^ 2)
End Property
[COLOR="Green"]
'********************************************************************
'*                                                        Oct 29 2012
'*       Constructors>
'*
'********************************************************************[/COLOR]
Function LoadFromRadi(a As Single, b As Single) As cEllipse
    m_a = a
    m_b = b
    Set LoadFromRadi = Me
End Function

Function LoadFromFocusAndSemimajor(c As Single, a As Single) As cEllipse
[COLOR="Green"]'   Loads the object using the distance from the center to a focus
'   and the semimajor axis[/COLOR]
    Set LoadFromFocusAndSemimajor = Me.LoadFromRadi(a, Sqr(a ^ 2 - c ^ 2))
End Function

Function LoadFromFocusAndSemiMinor(c As Single, b As Single) As cEllipse
[COLOR="Green"]'   Loads the object using the distance from the center to a focus
'   and the semiminor axis[/COLOR]
    Set LoadFromFocusAndSemiMinor = Me.LoadFromRadi(Sqr(b ^ 2 + c ^ 2), b)
End Function

[COLOR="Green"]'********************************************************************
'*                                                        Oct 30 2012
'*       Methods>
'*
'********************************************************************
[/COLOR]Function GetX(y As Single) As Single
[COLOR="Green"]'   This function solves for x, given a, b and y[/COLOR]
    GetX = Sqr(a ^ 2 - (a ^ 2 * y ^ 2 / b ^ 2))
End Function

Function GetY(x As Single) As Single
[COLOR="Green"]'   This function solves for y, given a, b, and x[/COLOR]
    GetY = Sqr(b ^ 2 - (b ^ 2 * x ^ 2 / a ^ 2))
End Function[/SIZE][/FONT]
And a typical consumer ...
Code:
[FONT="Lucida Console"][SIZE="1"]Private Sub Test12938674193460239()
    Dim tmp As New cEllipse
    Dim a As Single
    Dim b As Single
    Dim x As Single
    
    a = 7.375 / 2
    b = 3.125 / 2
[COLOR="Green"]    '60% of the ellipse is 2a * 0.6,
    'but the ellipse is centered at 0,0
    'so we also subtract a
[/COLOR]    x = 0.6 * 2 * a - a     [COLOR="Green"]'calculates the x coord[/COLOR]

    debug.print tmp.LoadFromRadi(a, b).GetY(x)  [COLOR="Green"]'calculates the y coord.[/COLOR]

End Sub
[/SIZE][/FONT]
Yes, you could also write a series of functions in a standard module, like ...
Code:
Function GetEllipseX(a as single, b as single, y as single) as single
Function GetEllipseY(a as single, b as single, x as single) as single
... but you'd need more of them, because you don't have any capacity to do initialization calculations ...
Code:
Function GetEllipseYFromFocusAndMajor(c as single, a as single, x as single)
Function GetEllipseYFromFocusAndMinor(c as single, b as single, x as single)
Function GetEllipseXFromFocusAndMajor(c as single, a as single, y as single)
Function GetEllipseXFromFocusAndMinor(c as single, b as single, y as single)
... so the class is a much tidier and simpler approach.

But that's not all. The class is a datatype, so I can declare a variable of that type and pass a reference to that instance to another function ...
Code:
Sub Do2DRotation(Obj as Object, Degrees as single)
   Dim tmp As New c2dAffineMatrix
   Select Case Typename(Obj)
       Case "cEllipse"
          tmp.Rotation = Degrees
          tmp.TransformEllipse obj
       Case ...
   End Select
End Sub
... and you can't do anything close to that using a series of functions in a standard module. This begins to express the power of OOP and programming with class modules.
 

Users who are viewing this thread

Back
Top Bottom