by Michael C. Amundsen
In Inside Visual Basic's December 1997 articles “Classy Development” and “Come Fly the Friendly Skies of Visual Basic 5.0,” we discussed object-oriented development and how you can build standalone classes for use in your development work. This approach involves designing and building complete, compiled external objects and using those objects as the building blocks for your applications.
In this article, we’ll continue discussing class development. Specifically, we’ll show you how to create an object that writes data to text files. (The article “Using External Objects with Visual Basic” [Inside Visual Basic, January 1998] covers techniques for making use of the object you’ll design and build here.)
Creating a set of reusable component objects is much more powerful than writing source code libraries for a couple of reasons. First, source code libraries are tied to a single language, such as VB, VC++, or J++. In fact, source code libraries are sometimes tied to a particular version of the language. Compiled components don’t suffer a single-language limit. You can use the external objects you build with VB in several development environments, including VC++, J++, Access, Excel, and other Office 97 applications.
Passing out source code also creates management problems. It’s all too easy for programmers to alter the source code to fit specific needs—and even unintentional changes can render the object useless for another programmer. By passing out only the compiled component, others can’t alter the object in any way, either accidentally or on purpose.
Although the process of designing, coding, and testing your building blocks takes time, the real advantage of creating compiled building blocks comes when it’s time to build a complete application. Once you’ve built a set of objects, creating a custom solution for customers (internal or external) is much quicker and less error-prone. The time invested in building these objects can pay back in big ways after just a few uses of the component in a production application.
Let’s say you’ve been asked to build a set of routines that allow programmers to easily create log file entries in a disk file. This set of routines should be simple to use and very portable. It should meet a number of general needs:
You can use it to get basic user input and write it to a disk file.
You can use it to log user actions in a secure application (log in/out, etc.)
You can use it as an error log for applications.
You can use it to create a log of unattended operations for applications.
In addition, programmers should easily be able to add these routines to existing programs or access them from programs without the need to incorporate source code directly into their applications. Finally, the routines should be flexible enough that programmers can create their own dialogs and interfaces to the routines.
The first step in designing an object is to clearly identify the actions needed to complete the task. Then, you must identify all the materials needed to perform those actions. Armed with this information, you can design an object with all the necessary methods and properties.
To start, let’s call our object the TextFile object. Programmers will use this object to write data to a disk file. Although we only want a method to write data to the disk file, we’ll need a few other methods in order to complete the task. Table A lists the methods required for the TextFile object.
Table A: Methods for the TextFile object
Method | Public | Description |
OpenFile | No | Opens the disk file for write access. |
CloseFile | Yes | Closes the disk file after all writing is complete. |
WriteLine | Yes | Writes a line of text to the open disk file. |
CheckChar | No | Checks for invalid characters in filename. |
MakeFullFileName | No | Ensures that a complete filename, including folder, body name, and file tail, is created. |
You’ll notice that of the five methods in Table A, the only two marked Public are CloseFile and WriteLine. These are the only two methods available for others to use. The other three methods (OpenFile, CheckChar, and MakeFullFileName) are internal routines that won’t be published.
Next, you must define the materials needed to complete the actions—these are the properties of the TextFile object. Table B shows the suggested property list for our example.
Table B: Properties for the TextFile object
Property | Public | RW/RO | Description |
FileFolder | Yes | RW | Contains computer, drive, and path of disk file (i.e., C:\MYFOLDER). |
FileName | Yes | RW | Contains name of disk file (i.e., LOGFILE). |
FileTail | Yes | RW | Contains file tail of the disk file (i.e., TXT). |
FileMode | Yes | RW | Contains value to determine if file writes will overwrite or append to existing file. |
FullFileName | Yes | RO | Contains complete name of target disk file (i.e., C:\MYFOLDER\LOGFILE.TXT). |
Of the five properties defined in Table B, only one is read-only (RO). This read-only property can be seen by other users, but not altered. It’s common for objects to have one or more read-only properties.
Now that we’ve discussed all the actions and materials needed to complete the TextFile object task, it’s time to use Visual Basic to create the required object. (You can download our sample files from www.cobb.com/ivb, as part of the file jan98.zip. Just click the Source Code hyperlink.)
In order to get the most use out of the TextFile object, you must create a standalone DLL. You can do this using Visual Basic 5.0 or Visual Basic 4.0-32. If you’re using Visual Basic 4.0-16, you can still complete this project by creating an OLE EXE module. (The example in this article was built using Visual Basic 5.0.)
The first step in building the TextFile object is to create a new Visual Basic project. If you’re using Visual Basic 5.0, start Visual Basic and select ActiveX DLL as the project type. You’re now ready to begin adding code to the class module, so skip the next paragraph.
If you’re using Visual Basic 4.0 (32 or 16 bit), start Visual Basic, remove the default form, and add a BAS Module to the project by selecting Module from the Insert menu. Enter a single subroutine in this BAS module called Main, as follows:
Public Sub Main()
End Sub
Don’t add any code to this routine. It only exists to make sure Windows executes the DLL/EXE and registers its class object for use by others. Next, add a class module to the project (Select Insert | Class Module).
Now, set the Name property of the class module to TextFile. If you’re using Visual Basic 5.0, set the Instancing property to 5 - MultiUse. If you’re using Visual Basic 4.0, set the Public property to True and the Instancing property to 2 - Creatable MultiUse. This step guarantees that others can create their own copies of the TextFile object.
Next, set the name of the project. If you’re using Visual Basic 5.0, highlight the project in the Project Window, and set the Name property to prjTextFile. If you’re using Visual Basic 4.0, select Tools | Options | Project, and set the Project Name to prjTextFile.
Please note: It’s very important that you set the Class Name and Project Name properties correctly. All programmers who want to access your object methods and properties will use these two properties.
You’re now ready to add code to the project. First, you must create some local storage space to hold the values of the published properties of the object. To do so, add the code in section 1 of Listing A to the General Declarations section of the class module.
Listing A: TextFile external object
Section 1
Option Explicit'
' define local storage for properties
Private mstrFileName As String
Private mstrFileFolder As String
Private mstrFileTail As String
Private mstrFullFileName As String
'
' for vb5 only
' create enumerated type
Enum tfFileMode
overwrite = 0
append = 1
End Enum
Private mtfMode As tfFileMode
' for vb4 only
'Private Const overwrite = 0
'Private Const append = 1
'Private mtfMode As Integer
'
' define private vars
Private mintFileChannel As Integer
Section 2
Private Sub Class_Initialize()'
' set initial values
'
mstrFileName = "logfile"
mstrFileFolder = App.Path
mstrFileTail = "txt"
MakeFullFileName
'
mintFileChannel = -1
mtfMode = append
'
End Sub
Section 3
Private Sub MakeFullFileName()'
' build full filename
'
mstrFullFileName = mstrFileFolder & "\" & _ mstrFileName & "." & mstrFileTail
'
End Sub
Section 4
Private Function CheckChar(strTemp As String, strInvalid As String) As Boolean'
' check for invalid filechars
'
Dim intLoop As Integer
Dim intLen As Integer
Dim strChar As String
'
strTemp = Trim(strTemp)
intLen = Len(strTemp)
'
For intLoop = 1 To intLen
strChar = Mid(strTemp, intLoop, 1)
If InStr(strInvalid, strChar) <> 0 Then
CheckChar = True
Exit Function
End If
Next
'
CheckChar = False
'
End Function
Section 5
Private Sub OpenFile()'
' open file for output
'
On Error GoTo OpenFileErr
'
mintFileChannel = FreeFile
'
If mtfMode = append Then
Open mstrFullFileName For Append As mintFileChannel
End If
'
If mtfMode = overwrite Then
Open mstrFullFileName For Output As mintFileChannel
End If
'
Exit Sub
'
OpenFileErr:
Err.Raise Err.Number, App.EXEName & "_OpenFile", Err.Description
'
End Sub
Section 6
Public Sub WriteLine(strLine As String)'
' write line to output file
'
On Error GoTo WriteLineErr
'
If mintFileChannel = -1 Then
OpenFile
End If
'
Print #mintFileChannel, strLine
'
Exit Sub
'
WriteLineErr:
Err.Raise Err.Number, App.EXEName & "_WriteLine", Err.Description
'
End Sub
Section 7
Public Sub CloseFile()'
' close open channel
'
On Error Resume Next
'
Close #mintFileChannel
mintFileChannel = -1
'
End Sub
Section 8
Public Property Get FileName() As String'
FileName = mstrFileName
'
End Property
Section 9
Public Property Let FileName(ByVal vNewValue As String)'
vNewValue = Trim(vNewValue)
If CheckChar(vNewValue, "`!@^*:;'<>,.?/\|") Then
Err.Raise 380 ' invalid property
Else
mstrFileName = vNewValue
MakeFullFileName
End If
'
End Property
Section 10
Public Property Get FileTail() As String'
FileTail = mstrFileTail
'
End Property
Public Property Let FileTail(ByVal vNewValue As String)
'
vNewValue = Trim(vNewValue)
If CheckChar(vNewValue, "`!@^*:;'<>,.?/\|") Then
Err.Raise 380 ' invalid property
Else
mstrFileTail = vNewValue
MakeFullFileName
End If
'
End Property
Section 11
Public Property Get FileFolder() As String'
FileFolder = mstrFileFolder
'
End Property
Public Property Let FileFolder(ByVal vNewValue As String)
'
vNewValue = Trim(vNewValue)
If CheckChar(vNewValue, "`!@^*;'<>,.?/|") Then Err.Raise 380 ' invalid property
Else
If Right(mstrFileFolder, 1) <> "\" Then mstrFileFolder = mstrFileFolder & "\"
End If
mstrFileFolder = vNewValue
MakeFullFileName
End If
'
End Property
Section 12
Public Property Get FullFileName() As String'
FullFileName = mstrFullFileName
'
End Property
Public Property Let FullFileName(ByVal vNewValue As String)
'
Err.Raise 383 ' read-only msg
'
End Property
Section 13
Public Property Get FileMode() As tfFileMode'Public Property Get FileMode() As Integer ' vb4
FileMode = mtfMode
'
End Property
Public Property Let FileMode(ByVal vNewValue As tfFileMode)'Public Property Let FileMode(ByVal vNewValue As Integer) ' vb4
'
If vNewValue = append Then
mtfMode = append
Exit Property
End If
'
If vNewValue = overwrite Then
mtfMode = overwrite
Exit Property
End If
'
Err.Raise 380 ' invalid property value
'
End Property
Section 14
Private Sub Class_Terminate()'
CloseFile
'
End Sub
In the first section of code, you can see that, along with the four string variables, we used an Enum to create an enumerated data type. This is a custom data type that we’ll use to define the file mode (overwrite or append). The Enum is only available in Visual Basic 5.0. If you’re using Visual Basic 4.0, use the constants that are commented out. You should also note that we defined a private variable to use as the file channel pointer for the actual disk write operation (mintFileChannel).
Next, you need to add code to the Class_Initialize event to set the default values for your object properties. It’s always a good idea to set defaults for your properties. Add the code from section 2 to the Class_Initialize event.
You can see that the internal file pointer is set to -1. This is an invalid value for file pointers. You can use this value as a flag to let you know that the file pointer hasn’t yet been set.
The TextFile object has three support methods: CheckChar, MakeFullFileName, and OpenFile. These are internal routines that handle some of the behind-the-scenes work within the object. Although these routines are necessary, you probably don’t want to allow other users to access this code. For this reason, make all three of these routines Private rather than Public.
First, add the MakeFullFileName method to your class. If you’re using Visual Basic 5.0, select Tools | Add Procedure. If you’re using Visual Basic 4.0, select Insert | Procedure. Then, type MakeFullFileName in the Name box. Set the Type to Sub and the Scope to Private. Now, add the code from section 3 to the MakeFullFileName subroutine.
You can see this method just concatenates the stored file folder, filename, and file tail values into a single variable. This is the variable that we’ll use to open the disk file.
Next, add the CheckChar method to the class. This method takes two parameters: the string to check and a list of characters to look for. If at least one of those characters is in the string, the routine returns true. You’ll use this routine to check for invalid characters in the folder, file, and tail properties. Add the CheckChar function to your class and enter the code from section 4.
Finally, add the OpenFile method to the class. This routine will be called from within the public WriteLine method. Other users will never get access to this method. Add the OpenFile subroutine to your class and enter the code from section 5.
There are two points of interest in the OpenFile method. First, note the use of If statements to check the value of tfMode. This value determines whether the file will be overwritten each time or appended. Also notice the use of the Err.Raise method in the error trap. The Err.Raise method will send an error report back to the calling program instead of popping up a dialog box. It’s very important that all objects that have no interface return error reports, not dialogs.
Now you’re ready to add the two public methods, WriteLine and CloseFile, to the object. These two methods will be available to others to use. First, add the WriteLine method to the class object. Be sure to set the Scope to Public. Then add the code from section 6 to the WriteLine method. Please note that the WriteLine method first checks to see if the file has been opened. If not, it calls OpenFile.
Next, add the CloseFile method from section 7 to the object. This routine simply closes out the file when requested. As you can see, the file channel pointer is reset to -1 at this time in case the user tries to perform a WriteLine after executing the FileClose method. After adding the object methods, you’re ready to add the object properties.
There are five TextFile object properties defined in Table B. Properties are a way to give others controlled access to your local storage space. Two methods are actually used: The Let method allows users to send you data to store, and the Get method allows users to get a copy of your stored data. To build a complete property interface for your object, you must add a Let and a Get method for each property in your object.
Luckily, Visual Basic provides an easy way to do this. If you’re using Visual Basic 5, select Tools | Add Procedure. If you’re using Visual Basic 4.0, select Insert | Procedure. Then, enter FileName in the Name box. Be sure to select Property as the Type and Public as the Scope before you press the OK button. You’ll see that this creates both the Let and the Get methods for your property.
You can now add code to each method. Since the Get method lets users get a copy of your stored data, all you need to do is write code that copies your local memory variable back to the user. Section 8 shows how to do this. Notice that we changed the return type from Variant to String in order to match the local storage type.
Now you’re ready to code the Let method. Others will use this method to send you data to store. The real advantage of the Let method is that you can write code to check the values received before you store them. This gives you a chance to send an error message back to the user, if necessary. Section 9 shows the code for the FileName Let method.
Notice the error checking and reporting. After the CheckChar() method is called and if it returns true, the Err.Raise method is invoked using an existing error number. Visual Basic reports error number 380 when a property setting is invalid. It’s always a good idea to re-use existing Visual Basic errors for the same errors in your objects. Doing so will give users a familiar feedback and makes it easier for them to write error-handling code for your objects. Also, note that after the new property is accepted, the MakeFullFileName method is called to update the disk filename to use in all write actions.
The Property Let and Get statements for the other properties are almost identical. The Get statements simply echo the storage value and the Let statements perform the same error-checking before finally storing the received data in a local storage variable. Next, create the FileTail property and add the code from section 10 to the Property Let and Property Get methods.
The FileFolder property has a slight variance. Since the folder names can contain slashes and semicolons, the list of invalid chars is altered to fit folder names. Add the FileFolder property and enter the code from section 11.
The next property to build is the FullFileName property. This property is marked as read-only (RO) in Table B. You want to allow users to perform the Get operation, but not the Let operation. Create the FullFileName property and add the code from section 12.
The final property to build is the FileMode property. If you’re using Visual Basic 5.0, you’ll be creating a property of the type tfFileMode. If you’re using Visual Basic 4.0, make the property type Integer. Section 13 has the code for the Property Let and Get methods. That’s the end of the properties for the TextFile object.
There’s one more method you must add to the TextFile object: the Class_Terminate event. This event fires each time the object is destroyed. This is a good place to add code to clean up any unfinished business, close open files, etc. Section 14 has the code for the Class_Terminate event.
You can see that the only thing that occurs here is a call to the CloseFile method. This will ensure that the data is properly flushed to the disk and that the file is safely closed.
Now that you’ve completed the coding, you’re ready to compile the object into a usable module. If you’re using Visual Basic 5.0 or Visual Basic 4.0-32, you’ll create a DLL. If you’re using Visual Basic 4.0, you’ll create a special kind of EXE called an OLE Server EXE.
Usually, you should test your object before compiling for the final time. Testing the object is quite different with Visual Basic 4.0 or Visual Basic 5.0. For this reason, we’ll skip the details of testing right now. The article “Using External Objects with Visual Basic” [Inside Visual Basic, January 1998] shows you how to test your object.
If you’re using Visual Basic 5.0, select File | Make prjTextFile DLL… to build the DLL. If you’re using Visual Basic 4.0-32, select File | Make OLE DLL. If you’re using Visual Basic 4.0-16, select File | Make EXE. This will build the final product that other programmers can use. Remember that the Visual Basic 4.0-16 version will create an EXE, while the 32-bit versions of Visual Basic will create a DLL.
You can use this final compiled object not only within other Visual Basic applications, but also from Access, Excel, and other Office 97 applications.
Michael C. Amundsen keeps busy writing and working as an IS consultant specializing in systems analysis and design. He co-authored Teach Yourself Database Programming with Visual Basic in 21 Days with Curt Smith and was a contributing author for Visual Basic 4 Unleashed. When Mike isn’t traveling to client sites or writing, he spends time with his family in Kentucky. You can reach Mike via CompuServe at 102641,1267 or on the Internet at MikeAmundsen@msn.com.
Copyright © 1998 The Cobb Group, a division of Ziff-Davis Inc. The Cobb Group and The Cobb Group logo are trademarks of Ziff-Davis Inc. All rights reserved. Reproduction in whole or in part in any form or medium without express written permission of Ziff-Davis is prohibited.