Visual Basic 6 Business Objects
We can now run our program and see how well this works. When the EditPerson form first appears, the OK and Apply buttons are disabled. As a result of the code we've just entered, these buttons will only be enabled when there are exactly 11 characters in the SSN field.
Handling Canceled Edits
Most forms have OK and Cancel buttons, and many have an Apply button as well. We've included these on our sample form to illustrate how to support them within the object, since there have been some extra steps involved.
So far, we've left the code in the buttons' Click events somewhat vague. Now let's think through the behaviors we require of the business object to support OK, Cancel and Apply.
We've implemented our form so that our Person object's properties are being set every time a field changes on the form. This is important, because it means that any business rules are validated as the user presses each key. A further ramification is that the business object's internal variables are always changing as the user changes values on the screen.
If the OK button is clicked, we just need to save the Person object's variables - and the form goes away. We'll cover different ways of saving the object later in the chapter, but the point here is that the OK button is pretty easy. After all, the object already has its internal variables set and ready to be saved. Since the form goes away when OK is clicked, we don't have to worry about any subsequent editing of the data.
The Cancel button is a different story. When the user clicks this button, the form goes away, but the Person object might have different data stored in its variables, since the user may well have been typing into some fields before they clicked the Cancel button.
On the surface, this might not seem like a problem: the form holds a reference to the Person object and, when it releases that reference, the object, along with its changed data, will just go away or will it? Unfortunately, the object won't go away if some other form or object also holds a reference to it. Perhaps we have a Family object that holds references to a number of Person objects. If we were editing one of those Person objects and we clicked Cancel, we'd expect the Person object itself to stick around as part of the Family object; but we'd also expect any changes to its data to be reset to the original values.
The Apply button ties in here as well. When the user clicks Apply, the Person object's variables will be saved (as we'll see later). However, the user can keep editing the object - because the form doesn't get unloaded by the Apply button.
To make matters just a bit more complicated, there are also combinations: the user might do some editing, then Apply the edits, do some more work, and then click Cancel. Given that sequence of events, we'd need to keep all the changes up to when the Apply was clicked.
Enhancing the Person Object
Let's look at how we can make it easy for a UI developer to support these three buttons. What we're talking about, here, is the ability to start editing the object, and then either commit (Apply) or roll back (Cancel) the edits that were made.
Let's add three methods to our Person object: BeginEdit, ApplyEdit and CancelEdit:
Public Sub BeginEdit() LSet udtSaved = udtPerson flgEditing = True End Sub Public Sub ApplyEdit() ' data would be saved here flgEditing = False End Sub Public Sub CancelEdit() LSet udtPerson = udtSaved flgEditing = False End Sub
We'll walk through the details of these routines over the next few pages.
The ApplyEdit routine contains a comment to indicate where we need to add some code to save the object to a database. In the section on Making Objects Persistent, later in this chapter, we'll discuss saving objects to a database and we'll get into more details about this process.
Right now, let's see how these three new routines provide support for the OK, Cancel and Apply buttons. The code in these routines makes use of two new module-level variables that we need to add to the General Declarations section of the Person class module:
Private udtSaved As PersonProps Private flgEditing As Boolean
Are We Editing the Object?
The flgEditing variable is easy to follow: we just set it to True when we start editing the object and False when we're done.
We do need to initialize this variable up front, however; so add the following line to the Class_Initialize method of the Person class module:
Private Sub Class_Initialize() Set objValid = New BrokenRules objValid.RuleBroken "SSN", True End Sub
By keeping track of whether our object is currently being edited or not, we can make sure that our object's data is only changed when appropriate. We can use this flag to disable all the Property Let routines in our object, so the only time a value can be changed is when the object is in edit mode. By edit mode, I mean when the flgEditing flag is set to True by the BeginEdit method.
Essentially, we are making the object somewhat self-aware. The object will know whether it should allow any client code to change its data. The technique of viewing an object as a self-aware entity is called anthropomorphization. The term is derived from anthropus for human, and morph for change; we are changing our view of the object from a chunk of code to an intelligent entity. This is one of the core tenants of object-oriented design.
Saving a Snapshot of the Object's Data
The udtSaved variable deserves a bit of explanation. Here's how we declare it:
Private udtSaved As PersonProps
Within our BeginEdit routine, above, the Person object's data is stored in this udtSaved variable, which is based on the user-defined type PersonProps:
LSet udtSaved = udtPerson
There are a couple of important reasons for doing this, one of which is to make it easy to handle canceled edits, since we've stored a version of the Person's object data prior to the edit. The other reason is to make it easy to save the object to a database - but again, we'll discuss this later on in the "Making Objects Persistent" section of this chapter.
The Person object uses the udtPerson variable to store all its data values. The udtSaved variable is a snapshot of the object's state at the time the BeginEdit method was called. The copy is very fast and simple, since we use the LSet command to copy one user-defined variable directly into another very efficiently. Visual Basic does virtually no extra work during this call: it is, essentially, a memory copy function, and so it's incredibly fast.
It is certainly possible to store an object's data in Private variables, then declare a second set of the variables, and copy them all one by one to create a snapshot. But the technique we're using with the LSet command is far faster, and it's easier to code.
Using the BeginEdit Method
The BeginEdit method starts the editing process for the object:
Public Sub BeginEdit() LSet udtSaved = udtPerson flgEditing = True End Sub
The routine simply copies the object's current data into the udtSaved variable, in case we need to get back to where we started. It also sets the flgEditing variable to True, to establish that the object is being edited.
This method needs to be called by the UI before any editing takes place, so we'll now add a line to call it from the EditPerson form's Load routine:
Private Sub Form_Load() Set objPerson = New Person EnableOK objPerson.IsValid objPerson.BeginEdit End Sub
Using the ApplyEdit Method
Once the EditPerson form has called the BeginEdit method on the Person object, the object knows that it's being edited. The user can change data on the form to their heart's content, but they'll eventually have to either save any changes or try to cancel them.
If the user clicks either OK or Apply, we need to save the changes in the object. Therefore, we'll also add these lines to the Edit Person form:
Private Sub cmdApply_Click() ' save the object objPerson.ApplyEdit objPerson.BeginEdit End Sub Private Sub cmdOK_Click() ' save the object objPerson.ApplyEdit Unload Me End Sub
Both routines call the ApplyEdit method, and the Apply button's code also calls the BeginEdit method to resume the editing process for the object. We need to do this because the Apply button doesn't make the form unload, so the user must be able to continue editing the data at this point.
As you can see from the code above, the ApplyEdit routine sets the flgEditing variable to False, indicating to the Person object that editing is complete. The ApplyEdit method is also responsible for saving the object's data to the database: once again, a topic we'll cover in more detail in the section on "Making Objects Persistent".
Using the CancelEdit Method
The user might also click the Cancel button, so we'll add the following line to the EditPerson form:
Private Sub cmdCancel_Click() ' do not save the object objPerson.CancelEdit Unload Me End Sub
All this line does is call the CancelEdit method within our Person business object. Here's that CancelEdit method again:
Public Sub CancelEdit() LSet udtPerson = udtSaved flgEditing = False End Sub
This routine does a couple things, including setting the flgEditing variable to False, because the editing process is over, and restoring the Person object's data to the values stored in udtSaved:
LSet udtPerson = udtSaved
Again, this is essentially a memory copy of the data that was saved in the BeginEdit routine back into the object's central repository of data, udtPerson.
It's important to remember that for this to work, all the object's data must be in the user-defined type. If data values were kept in module-level variables, we'd need some extra code to save and restore those values.
Disabling Edits when flgEditing is False
The final set of changes we need to make to the Person object are to make sure that the object doesn't allow itself to be edited until the BeginEdit method has been called. We just need to add a line to each Property Let and Property Set to raise an error unless we're in the middle of editing. We may also choose to disable certain methods; in particular, those methods that impact our object's internal variables.
For example, we'd add this line to the Property Let Name routine:
Public Property Let Name(Value As String) If Not flgEditing Then Err.Raise 383 udtPerson.Name = Value End Property
Error 383 is an error that indicates a property is read-only, so if we don't want our property to be editable we can just raise that error. Of course, we only need to raise it if flgEditing is False, so once the BeginEdit method has been called the error won't be raised.
This line just needs to be added to the top of each Property Let in the Person class module. And we'll do essentially the same thing with the ApplyEdit and CancelEdit methods in the Person class module, but we'll use error 445 for these instead. This error is 'Object doesn't support this action' and it's more appropriate for disabling methods:
Public Sub CancelEdit() If Not flgEditing Then Err.Raise 445 LSet udtPerson = udtSaved flgEditing = False End Sub
Page 8 of 13