[AccessD] Framework Discussion - Moving to a library

John W. Colby jwcolby at colbyconsulting.com
Sun Apr 4 20:30:37 CDT 2004


Framework – Moving to a Library
For the purposes of this lecture, the framework is approaching a useable
state.  We now have:
•	A framework class that initializes the framework service classes.
•	SysVars that can be used to modify program flow and turn on / off
behaviors or modify how behaviors work.
•	A form class that has a functioning control scanner finding specific
control types and loading class instances for these controls.
•	Control classes that are loaded by the form’s control scanner.
•	A troubleshooting class that logs (or can log) all classes loading and
unloading to assist troubleshooting unloading problems.
•	Service classes such as Zip which can be loaded as the application
requires.

>From this point forward, the lectures will concentrate on how to add
functionality to the framework, adding in more service classes, more control
classes, and adding behaviors to the form and control classes as we run
across useful things that we want available to all of our applications.

However a framework is not very useful if it has to be inside the
Application FE.  What we need to do next is create the typical FE/BE system
and reference the framework as a library in an MDA / MDB.  This presents a
few challenges, but it also allows us to easily “exchange” the library for a
new version without damaging the application’s data.

The process of moving our demo framework to a FE/BE is reasonably easy.  All
we do is:
•	Create an FE and export the forms / queries / reports to that, leaving all
the classes here in the original container.
•	Create a BE and export the tables to that.
•	Link the FE to the tables in the BE
•	Change the name of the framework to an MDA extension
•	Reference the framework library from the FE.
•	Delete the forms / queries / reports / tables from the framework library.
•	Expose dclsFrm

The library is Referenced by selecting from the menu while in any module,
Tools / References, click Browse and navigate to the directory where the
copy of the library is stored, then selecting it.

Having done these things we are ready to use the framework as a referenced
lib.

There is a small downside to working this way, namely the requirement to
relink the tables as you get the demos.  I have moved the entire directory
from my normal development drive to the c: drive, c:\ C2DbFW3G so that if
you place it there the table links will always work when you get the latest
version.
Using a class factory
One process required when moving to a library is to make the classes that
you need to reference from inside the FE can be found by your code.  There
are at least two ways to do this.  The simplest is to build a wrapper class
inside the library that looks something like this:

Public function cFrm() as dclsFrm
	Set cfrm = new dclsFrm
End function

This is a class factory that gets a new instance of that class and returns a
pointer to it.  This method works well and like most things has pluses and
minuses to it.  The plus is that you don’t have to expose the class itself
which we shall see in a minute is a little more work.  The minus is that
there is no intellisense available, and when we are dealing with classes,
that is a pretty big minus.
Exposing a class directly
Another way to do this is to expose the class directly using an undocumented
feature in Access.  In order to do this we need to export the class module
to a text file, edit the text file, delete the original class module in the
library, and import the modified class text file back into the library.
Export the class
To export a class to a text file we need to:
•	Go to the database window.
•	Click the modules tab
•	Click on a class module to select it
•	Click File / Export
•	A File Find dialog box will open.  Navigate to the directory for this
project.
•	Create a Classes directory if none exists already, then navigate to that
directory
•	Select save as type: Text files in the bottom combo of the File Find
dialog
•	Add a .txt to the file name
•	Click the Save button.

Next we need to edit the text file in Wordpad.
•	Using Windows Explorer, navigate to the file we just created.  If you
followed the instructions above it should be in a class subdirectory under
the project directory for this framework.
•	Open the file just exported

The top of the file should look similar to:

VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
END
Attribute VB_Name = "clsFramework"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Compare Database
Option Explicit

•	We can then remove the VERSION line and the BEGIN/END section plus the two
Option lines so that all that remains (of that section) is:

Attribute VB_Name = "clsFramework"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False

•	Next we need to edit this section, specifically we need to change
VB_Creatable to True, and VB_Exposed to True

Attribute VB_Name = "clsFramework"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True

•	Save the file and exit Wordpad.  At this point the class is edited and
ready to import back into the library.
•	In the library, delete the class that we are working on.
•	Now from the menu Click Insert / Class module, then Insert / File.  Use
the file find dialog to navigate to the directory where the class txt file
is located and select that file.  The contents will load into the empty
class module we just created.
•	On the menu click Debug/Compile just to check that everything is working
properly.  If not fix the compile errors.  There should be no errors but if
you failed to edit the header of the class in the text file you could get
some stuff up there (like duplicate Option statements).
•	Once it compiles, on the menu click File / Save.  It will ask if you want
to save the class and will offer you a name.  Just click yes.

You have finished the process of making a class “visible” from outside of
the library.

Standard Disclaimer: This is an “undocumented” procedure which means that
Microsoft may at any moment pull some stunt that makes this not work.
However this method works from Access97 up through AccessXP and probably
A2003 (I haven’t personally tested that last).
Using a library
Using a library is more complex than having all of your code inside the FE,
however the pluses far outweigh the minuses.  Some issues in no particular
order:
•	When you are running code in the library “from” a FE, you can edit the
code in the library but those edits are not permanent.  The library is “read
only” and you are just making changes to the code loaded in memory (more or
less).
•	A result of the above is that in order to make changes permanent you must
work in a completely different copy than the one referenced by the project.
In other words, even if you were to open the library from another instance
of Access, any attempt to make edits will cause Access to inform you that
the changes will not be saved.
•	In fact, if I remember correctly, this is not true for A97, it could
indeed make changes to the lib from another instance of Access and make them
stick, but from A2K onwards you cannot do that.  Thus you need to always
have a “referenced” library and a “working” library in different
directories.  The “referenced” library is of course referenced by the
application and in use when the App is open.  The “working” library is the
copy that you modify with changes that you wish to become permanent.

•	Libraries can be shared, i.e. multiple users can reference the lib.
However it is more often the case that (in a multi-user environment) we will
download both the FE and the BE to a local directory on the user’s hard
drive.

This brings up another issue, if the reference is “broken”, i.e. the library
cannot be found in the location that the reference says it should be, then
Access will automatically search a “path” in an attempt to find the file.  I
can’t find information about the exact file path at the moment but I know
that Access will search the Windows directory, System32 and the current
application directory, I believe in that order.  This means that you can
drop your framework in any of those locations and even if the application
has the reference to some other place, the lib will be found and the broken
reference will be fixed automatically.

If the referenced library is not found, then a “broken reference” will be
visible in the Reference wizard and you will need to fix it manually.

•	You also have to initialize the framework before initializing anything
else so that the framework is up and running by the time the application and
its forms try and use it.  In addition the framework has to be unloaded when
the application closes.  It is possible for objects loaded by the framework
to prevent Access closing so we have to have a failsafe method of unloading
every time.

I do this by loading a form usysfrmFWCleanup in the framework’s Init()
function and calls the framework’s Term() function from its Close event.
This form resides in the framework library.  Since Access cannot close
without closing all forms, regardless of how it occurs the application
closing will close this form which cleans up the framework.  Because closing
this form unloads the framework, I also call Application.Quit
acQuitSaveNone.  Users are not supposed to be closing this form (or even
seeing it).  If the form is closed for any reason, the application will not
function correctly so I just close the Application.  Ordinarily this form
closes because the application is already closing, but if it is closed
accidentally, the application will be closed as well.

These are some of the issues that must be considered when using a library.
However the alternative of placing all such code directly in the FE causes
immense maintenance issues if you have more than one project.  A library is
used precisely because a fix to a single library and a redistribution of
that library will fix a bug or problem for every project that uses that lib.
Likewise adding a behavior to a lib will provide that behavior automatically
to every project that uses the library.
Sysvars
Using SysVars in a framework / Library is probably not familiar to most
developers so I will take a few moments to discuss how we implement
framework overrides.  As we have seen in past lectures, the SysVars are used
to turn on/off or otherwise modify behaviors that the framework makes
available to the developer / application.  In order to really be useful
however we have to be able to change the default behaviors from inside of
the application.

The way we do this is to copy the framework SysVar table into the FE.  We
will leave a copy of those SysVars that we want this specific application to
be able to override.  Now, by setting the SysVars to whatever state we
desire for this Application and merging in the SysVar table in the
application, we can perform the Override.  I have done this in the demo for
this lecture.

I have also added two more SysVar systems to the Framework, one for the
Application control (code) and the other for the Application Data.  These
two SysVars are so common that I just include them right in the framework so
that all the developer has to do is run the Init method and start using
them.  The Application’s control SysVar table is usually embedded in the FE
so that the SysVars controlling the Application travel with the FE
container.  The Application’s data SysVar table is generally placed in the
BE so that things like Company information etc. do not get corrupted when a
new Framework or FE is distributed.

The additional code for consist of two additional variables in the framework
class’ header:

Private mclsSVAppCont As clsSysVars
Private mclsSVAppData As clsSysVars


Then in the framework’s Initialize we SET these variables:

    Set mclsSVAppCont = New clsSysVars
    Set mclsSVAppData = New clsSysVars

We add a new set of functions to initialize the framework instance, return a
pointer to the class instance and return a SysVar from each Instance.

One set for the Application Control:

'
'Initialize the SysVars for the Application Control
'
Public Function SVAPPContInit(strTblName As String)
    mclsSVAppCont.Init Nothing, gcnn, strTblName
End Function
'
'Get a pointer to the Application Control SV class instance
'
Public Function cSVAppCont() As clsSysVars
    Set cSVAppCont = mclsSVAppCont
End Function
'
'Return SysVars from the Application Control SysVar class
'
Public Function SVAppCont(strSVName As String) As Variant
    SVAppCont = mclsSVAppCont.SV(strSVName)
End Function

And one set for the Application Data:

'
'Initialize the SysVars for the Application Data
'
Public Function SVAPPDataInit(strTblName As String)
    mclsSVAppData.Init Nothing, gcnn, strTblName
End Function
'
'Get a pointer to the Application Data SV class instance
'
Public Function cSVAppData() As clsSysVars
    Set cSVAppData = mclsSVAppData
End Function
'
'Return SysVars from the Application Data SysVar class
'
Public Function SVAppData(strSVName As String) As Variant
    SVAppData = mclsSVAppData.SV(strSVName)
End Function

I have exposed the SysVars class so that you can easily instantiate your own
SysVars if you need more than the Application Control and Application Data
SysVars provided by the framework.

In order to demonstrate using these two SysVar sets, I have created a label
in the switchboard that displays a version number in the format YYYY.MM.DD.
The visible property of this label is set to false.  In the FE I created the
table usystblAppCont where I set a DisplayVersion SysVar = True.  In the
application’s init() I called the SVAPPDataInit method of the framework:

Function AppInit()
    If Not blnAppInitialized Then
        'initialize the framework
        fwinit
        blnAppInitialized = True
        fw.SVAPPContInit "usystblAppCont"
        fw.SVAPPDataInit "usystblAppData"
	End If
End Function

This has the effect of loading all the SysVars in usystblAppCont (there’s
only one at the moment).  Then in the switchboard’s Open event I set
lblVersion.Visible to the value in the SysVar table.

Private Sub Form_Open(Cancel As Integer)
' Minimize the database window and initialize the form.

    AppInit
    ' Move to the switchboard page that is marked as the default.
    Me.Filter = "[ItemNumber] = 0 AND [Argument] = 'Default' "
    Me.FilterOn = True
    lblVersion.Visible = fw.SVAppCont("DisplayVersion")
    txtDeveloper = fw.SVAppData("Developer") & vbCrLf &
fw.SVAppData("DevEmail")
End Sub

Since I have the DisplayVersion set to True in the table, the label will be
visible.  This demonstrates the usage of SysVars to control Application
behaviors.

Likewise I added a table to the BE called usystblAppData and linked that
into the FE.  In that table I added two SysVars, Developer and DevEMail.  I
then created a txtDeveloper text box on the switchboard that displays this
information. Notice that this one pulls its data from fw.SVAppData.  Now as
the switchboard opens it pulls these developer SysVars out of the BE.

Perhaps Developer data wasn’t the best thing to use for Application data but
I didn’t particularly want to put my TaxID in there.  As you can see the end
user of the system can load their data into this table and have it displayed
in the switchboard, report footers etc.

SysVars can be used for many purposes, but the Framework control,
Application control and Application data are so common that I just include
these three SysVar tables right in the framework for immediate use.
Modifications
The modifications required when we move to an FE consist of building an
AppInit() and calling it from somewhere.

Private blnAppInitialized As Boolean
Function AppInit()
    If Not blnAppInitialized Then
        'initialize the framework
        fwinit
        blnAppInitialized = True
    End If
End Function

Notice that we set a Boolean so that if the function is called multiple
times it will only execute once.  You can then place any code that you need
to initialize in this function as well.  To run this code the options are an
Autoexec macro or a splash screen or switchboard.  To keep things simple I
just built a very simple switchboard and then call this function from the
Open of that switchboard.

Private Sub Form_Open(Cancel As Integer)
    AppInit
End Sub

We then open usysFrmFWCleanup from the FWInit()

'
'The init function for the framework
'
Public Function FWInit()
    If mblnFWInitialized = False Then '

        mblnFWInitialized = True
        DoCmd.OpenForm "usysFrmFWCleanup", , , , , acHidden
        Set mfwcnn = CodeProject.Connection
        Set mcnn = CurrentProject.Connection
        Randomize
        Set mclsInstanceStack = New clsInstanceStack
        mclsInstanceStack.Init Nothing
        Set mclsFramework = New clsFramework
        mclsFramework.Init Nothing
    End If
End Function

I built a simple form called usysFrmFWCleanup with code in that form’s Close
event which cleans up and forces the app closed if that form is closed.

Private Sub Form_Close()
    FWTerm
    On Error Resume Next
    Application.Quit acQuitSaveNone
End Sub

Opening the switchboard calls APPInit() which calls FWInit() which
initializes the framework and loads usysFrmFWCleanup.  Closing the app in
any manner forces usysFrmFWCleanup to close.  usysFrmFWCleanup closing calls
FWTerm() cleaning up the framework and forcing Access to close if it wasn’t
already..
Summary
In order to be useful the framework has to be split off into a library.
Doing this causes a handful of issues that you have to be aware of.  We may
need to expose classes using the method shown above – I have already exposed
dclsFrm so that the forms would work correctly.  It also requires setting up
init and cleanup code so that the framework is shutdown reliably as the app
closes.

Additionally we need to add a new pair of SysVar instances for the developer
to use for control of the Application and data for the Application.

>From this point forward, the lectures will focus on adding functionality to
the framework, generally to the form class or control class or both in order
to implement some behavior that the developer has found useful, has found or
developed code for, and now wishes to make available to all his projects by
moving that behavior to the framework.

In order to use the library we need to take the steps to break out the FE /
BE and library modules into separate containers.  I have outlined the
process in the top of this document and have actually performed this process
and provided a set of three mdb containers – the FE/BE and Library.

I do not intend to leave the demo broken into the three pieces however; I
only did so to demonstrate the process and what goes where.  For the
purposes of writing a demo and allowing me and the readers following this
lecture series to easily work with the demos it is much more helpful in a
single file.  I don’t want to lose any readers because they can’t deal yet
with the nitty gritty of actually doing development in three parts, getting
references working, getting table links fixed etc.  Therefore this document
and associated files actually consist of the three MDBs but the next
document will have them merged back into a single file.  The framework is
ready however, and can be split out again at a moment’s notice.

John W. Colby
www.ColbyConsulting.com





More information about the AccessD mailing list