OOPs!
Tim Margush
The University of Akron
Object-oriented programming (OOP) is one of the current buzzwords in
computer
science. Many programmers are trying to jump on the object-oriented
(OO) bandwagon,
but not all of them understand what OOP is really about. Bakopanos
(2000) presented
three different solutions to the problem of applying specific
functionality
to controls across several forms in a Visual Basic project. The
premise of the
article was that there was an advantage to using an OO solution to
this problem.
The author's approach was to show some non-OO solutions and then
demonstrate
an OO approach to solving the original problem. Unfortunately, the OO
solution
was not object oriented, only object confused.
As old languages are updated to include the OO paradigm and new OO
languages
are introduced, changing old programming habits to take advantage of
these new
capabilities becomes more and more important. There is no substitute
for good
OO design that springs from an object approach to representing and
solving a
problem. In this article, I examine the problem presented by Bakopanos
(along
with his so-called OO solution), explain why that solution is not
truly OO,
and then examine the OO techniques (inheritance and composition)
commonly employed
to extend the functionality of an object.
The
Problem
Bakopanos problem is common in the development of a Visual
Basic project
in which standard controls are part of the solution. The built-in
functionality
of the standard controls is seldom sufficient to meet the demands of
each project
and is intended to be customized to each particular need. In the
simplest form,
this is accomplished by providing code for various events that are
associated
with each control. To attain a particular look and feel, it is common
for the
same customization to be applied to many controls. This solution
results in
much duplication of code and increases maintenance costs. These
specialized
routines are often changed during project development, and failure to
update
all of the controls in exactly the same way leads to errors.
The standard controls used in Visual Basic are intended to be viewed
as objects.
The design toolbox provides an icon representation of the standard
classes (e.g.,
text box, label, list box, etc.), and the programmer usually
instantiates objects
of these types by drawing them on a form. This creates an object with
the standard
functionality described in the associated class definition. Bakopanos
sought
an OO solution to the problem of ensuring that all (or many) of the
controls
used in a project have the same display settings and underlying
functionality.
His ideas were demonstrated in a contrived example by focusing on a
single object
type, the text box. Each text box in the project was to have two
attributes
and one behavior that differed from the standard text box (red ForeColor
and FontBold turned on, and automatic selection of
the text
contents when the control's GotFocus event was
triggered).
Three solutions were proposed, but we will examine only the third, as
it is
this one that was claimed to be OO. Indeed, the proposed solution used
an OO
approach to encapsulate the required functionality in a class, but
failed to
incorporate this functionality as a seamless extension to the standard
objects.
An Inappropriate
Solution
The solution proposed by Bakopanos includes three parts. The first is
a public
(global) class definition (Class1)
that must be placed in a code module (Figure 1a). The second is the
instantiation
of an object (mclass1
of type
Class1) and the
inclusion of
some initialization code in the Form_Load
event of each form that will contain one or more of the customized
text box
controls (Figure 1b). The initialization code simply sends each text
box control
to a method of object mclass1
that sets the text boxs display properties to conform to the
customized
standards. The third part of the solution is the inclusion of a method
call
in each textbox's GotFocus event to implement the
auto-select
feature (Figure 1c).
|
'OO
way
'Class1 code
listing
Option
Explicit
Public Property
Let gTextboxSelectAll(ByVal
vTextbox As TextBox)
Dim
intWhere
As Integer
intWhere =
Len(vTextbox.Text)
vTextbox.SelStart =
0
vTextbox.SelLength =
intWhere
End
Property
Public Property
Let gTextboxDisplaySettings(ByVal
vTextbox As TextBox)
vTextbox.FontBold =
True
vTextbox.ForeColor =
vbRed
'Or
whatever
End
Property
|
|
| Figure 1a. |
|
Private mclass1
As New
Class1
'
Private Sub
Form_Load()
mclass1.gTextboxDisplaySettings
= Text1
mclass1.gTextboxDisplaySettings
= Text2
End
Sub
|
|
| Figure 1b. |
|
Private Sub
Text1_GotFocus()
mclass1.gTextboxSelectAll
= Text1
End
Sub
Private Sub
Text2_GotFocus()
mclass1.gTextboxSelectAll
= Text2
End
Sub
|
|
| Figure 1c. |
The solution is inefficient, misleading, and does not make
appropriate use
of OO design. It is inefficient in that it requires the instantiation
of a separate
object for each form. It also requires the programmer to add
additional code
in the Form_Load and GotFocus
events,
which if overlooked will result in malfunctioning controls. In
addition, the
same statements must be written again and again, resulting in a great
amount
of code duplication.
The solution is misleading, as it uses Properties (gTextboxDisplaySettings
and gTextboxSelectAll)
of a class
to modify the state of an external object (a text box). When an object
is sent
to a Property procedure, the usual intent is to modify the state of
the base
object, not the argument. A greater clarity of expression would be
attained
through the use of a public method of the class rather than a
Property.
Finally, the solution is not a result of good OO design. What is
really needed
is a way to encapsulate the new functionality inside the text box
itself, rather
than create the customization externally. A good solution would
incorporate
the custom functionality with no external coding required and would
hide the
implementation details from the user. None of these objectives are
realized
in Bakopanos solution.
An Object-Oriented
Approach
The task to be accomplished was to extend the functionality of an
existing
object (the text box control). The OO solution would be to create a
new class
type that incorporates the underlying functionality of the original
class and
extends its capabilities to implement the desired customization. In
OOP terminology,
this is often accomplished through a technique called
inheritance. By
defining a new control (class) that extends the text box class, we
would keep
all of the original control behaviors, but could customize selected
ones with
a minimum of programming effort.
Unfortunately, Visual Basic does not support inheritance.
Nevertheless, it
is still possible to attain the goal without compromising our OO
principles.
As an alternative to inheritance, programmers sometimes utilize a
technique
known as class composition. A new class (called a wrapper) is
defined.
It contains an object of the original type (for example, a text box)
as a member.
Public methods that duplicate the inner objects methods are
provided so
the new class appears to function identically to the original. Custom
behavior
is incorporated in these wrapper methods, and sometimes, new methods
are included.
Objects instantiated from this new class type, will then
automatically
contain all the functionality of the original type, plus the desired
custom
behavior. No extra code needs to be included in the form modules, and
updates
to the custom behavior are localized in one place, the new class
definition.
This means of accomplishing inheritance is straightforward only when
a class
is relatively small. The text box control has many properties,
methods, and
a visual component that must be replicated in the wrapper class. Many
methods
and properties will need to be extended to the wrapper class. None of
the work
is complicated, but it is tedious if done manually. Fortunately,
Visual Basic
provides a powerful tool, the ActiveX Control Interface Wizard, that
drastically
reduces the overall effort extended.
Inheritance the Visual
Basic Way
Here are the steps I took to solve the original problem using an OO
approach.
Rather than using the ActiveX Control Interface Wizard, I performed
each step
manually so you can understand the mechanisms involved in extending
the original
control. The result is a limited version of an enhanced text box
control. Feel
free to duplicate these steps for yourself.
- Begin a new VB ActiveX Control Project. This automatically creates
a new
user control class (UserControl1)
that is the container for our wrapper class.
- In the project properties window, set the name of the project to
CTextBox.
- Rename UserControl1
to CustomTextBox.
- Place a standard text box control of default size on the user
control design
window.
- Position it at the top left corner, then resize the user control
to match
the size of the text box. You can do this by setting the Height
and Width properties to match those of the text
box.
- Name the text box txtText.
Step six accomplishes object compositionincluding an
object
as a member of the new class. Now, when a CustomTextBox
object is instantiated, it will contain a normal text box as a
member. This
member is referred to as the constituent control. The CustomTextBox
will need to expose properties, methods, and events of the inner
text box,
to achieve the illusion of inheritance.
The next step shows how to achieve the behavioral distinction
between a standard
text box and our new CustomTextBox.
When the textbox contained in the CustomTextBox
control receives focus, the textbox contents must be automatically
selected
for editing
- Add two statements to the txtText
control's GotFocus
event (Figure
2).
|
Private Sub
txtText_GotFocus()
txtText.SelStart =
0
txtText.SelLength
= Len(txtText.Text)
End
Sub
|
|
| Figure 2. |
- Close the UserControl Object Design window.
The Visual Basic IDE toolbar should now contain a new item, your
CustomTextBox
control. To test the new control:
- Add a Standard EXE project (Choose Add Project from the File menu)
to the
design environment, creating a project group.
- Select the new project as the Start Up project.
- Add two CustomTextBoxes to
the form of the new project. If you draw this control on the form,
you may
notice that the inner text box does not resize to fit the size of
the user
control. You may also notice that the default text contents are the
same for
each new CustomTextBox; in
a standard text box, the default contents match the name of the
control. These
issues can be addressed with some additional code in the UserControl
module.
To keep this example simple, we will ignore the need for these
"extra
features."
- Run the program and observe that the text contained in either
CustomTextBox
is automatically selected when the control gets the focus. You can
change
the focus between the two controls by pressing Tab or clicking with
the mouse.
This automatic text selection functionality will now occur for every
CustomTextBox
object on any form of any project. You do not need to write any
additional code
to make it happen. Already we can see a great improvement over the
solution
proposed by Bakopanos. There is no need to instantiate a class in each
form
to provide the custom routines for automatic text selection. There is
no need
to add a method call to each text boxs GotFocus
event to implement the new functionality and there is no possibility
of forgetting
to add this statement, which would result in a text box that lacks the
desired
custom behavior. Indeed, the customization is now automatic, and
totally transparent
to the application programmer.
Exposing
Properties
Much work still needs to be done to make the CustomTextBox
control useful, however. As it stands, there is no way for the
application to
access the important properties of the inner text box. If Visual Basic
provided
inheritance, this visibility would be automatically provided; one
would simply
add the additional code necessary to implement the desired
customization. In
Visual Basic you must implement the connection to the original control
manually.
In most OO languages, we would solve the problem of providing access
to the
nested object either by making the object public (which goes against
the standard
conventions dealing with information hiding) or provide get and
set
methods in the wrapper class that allow access to the nested
objects properties.
Both of these approaches result in syntax requirements that work
against the
transparency of the solution, especially in Visual Basic, where
properties of
a control appear to function as public data members, even when they
are not.
In Visual Basic, special methods, known as property procedures,
are used
to create properties of a control that imitate (both in syntax and
functionality)
the properties of the standard controls. This allows the solution to
retain
the same syntax as would be used with a standard text box while
retaining the
protection afforded by the traditional get/set paradigm.
Nearly all uses of a text box control require access to its Text
property so we will focus on this to illustrate the process of
exposing a property.
The goal is to create a new property for the CustomTextBox
control that imitates the constituent control's Text property.
Figure 3 shows how to make the inner text boxs Text
property visible outside the User Control by adding two Property
procedures
to the user controls code window.
|
Public Property
Get Text()
As Variant
Text =
txtText.Text
End
Property
Public Property
Let Text(ByVal
New_Text As Variant)
txtText.Text =
New_Text
PropertyChanged "Text"
End
Property
|
|
| Figure 3. |
The new code adds a Text property
to the CustomTextBox
control
that exactly imitates the inner text boxs corresponding Text
property. Even the syntax is identical. Figure 4 shows the code
necessary to
implement a simple test of the new property. The test will display the
text
stored in a CustomTextBox control
in a message box and then clear the CustomTextBox
when the message box is closed. To carry out this test, you will need
to add
a command button to the test project form, and place the code in
Figure 4 in
its Click event.
|
MsgBox
CustomTextBox1.Text
'Uses Get method
CustomTextBox1.Text = ""
'Uses Let (set) method
|
|
| Figure 4. |
Run the project, enter something in CustomTextBox1
and click the command button. The contents will be displayed in the
message
box. When the message box is closed, the text in CustomTextBox1
will be cleared.
Selecting Default
Property Values
Our CustomTextBox is
still lacking
many features of the usual text box control. To make this control
really useful,
we will need to do quite a bit more work. Our new control only
provides access
to the constituent text box's Text property. By
imitating
the technique used to expose this Text
property, we can expose any other properties that we think will be
useful. We
can also establish new persistent defaults for the inner text
boxs properties
that can be selectively changed for each object. This will provide an
elegant
solution to the next part of the problem, to establish a consistent
look for
all of the CustomTextBox controls
in a project without sacrificing flexibility. Once again, we will
illustrate
the technique manually; for real applications, the ActiveX Control
Interface
Wizard is a powerful tool to help the programmer map the inner
controls
properties, methods, and events to those of the container.
To satisfy Bakopanos original requirements of bold style and
red text,
we could simply set the constituent control's (txtText)
corresponding properties, but this would not allow the programmer to
override
these new settings for CustomTextBox
objects when they are placed on a form. A better solution is to expose
the FontBold
and ForeColor
properties
of the text box, and establish new default values. Figure 5 shows the
code required
to accomplish these changes.
|
'Default Property
Values:
(these go in the General Declarations area)
Const
m_def_ForeColor =
vbRed
Const
m_def_FontBold =
True
'Initialize
Properties
for User Control
Private Sub
UserControl_InitProperties()
txtText.ForeColor =
m_def_ForeColor
txtText.FontBold = m_def_FontBold
End
Sub
'Read property
values from
storage
Private Sub
UserControl_ReadProperties(PropBag
As PropertyBag)
txtText.FontBold = PropBag.ReadProperty("FontBold",
m_def_FontBold)
txtText.ForeColor =
PropBag.ReadProperty("ForeColor",
m_def_ForeColor)
End
Sub
'Write property
values
to storage
Private Sub
UserControl_WriteProperties(PropBag
As PropertyBag)
Call
PropBag.WriteProperty("FontBold",
txtText.FontBold, m_def_FontBold)
Call
PropBag.WriteProperty("ForeColor",
txtText.ForeColor, _
m_def_ForeColor)
End
Sub
'Expose ForeColor
and BackColor
properties
Public Property
Let Text(ByVal
New_Text As Variant)
txtText.Text =
New_Text
PropertyChanged "Text"
End
Property
Public Property
Get FontBold()
As Boolean
FontBold =
txtText.FontBold
End
Property
Public Property
Let FontBold(ByVal
New_FontBold As Boolean)
txtText.FontBold = New_FontBold
PropertyChanged "FontBold"
End
Property
Public Property
Get ForeColor()
As OLE_COLOR
ForeColor =
txtText.ForeColor
End
Property
Public Property
Let ForeColor(ByVal
New_ForeColor As OLE_COLOR)
txtText.ForeColor()
= New_ForeColor
PropertyChanged "ForeColor"
End
Property
|
|
| Figure 5. |
To see this in action, you must be sure the user control design
window is closed,
then remove the original CustomTextBox
controls from the test form and add two new ones. Each should
be instantiated
with red text in the bold style. Select one of them, and change the
FontBold
property to false. This setting will be retained in that control's
Property
Bag. The Property Bag can be used to make any of the properties set at
design
time persist across multiple invocations of the control. The ActiveX
Control
Interface Wizard can automatically handle the creation of this
Property Bag
code.
By exposing these two properties, and employing the Property Bag
technique,
we have created custom default properties for our control. Each new
instance
of the CustomTextBox control automatically begins
with these
default settings. The programmer is free to change them for particular
instances
at design time, or runtime. Furthermore, by multiselecting all of the
CustomTextBox
controls on a from (you do this by holding down the Ctrl key as each
is selected),
the font properties can be set for all at the same time, achieving the
desired
consistency without sacrificing flexibility. This results in another
tremendous
improvement over Bakopanos non-OO approach. By establishing
custom defaults,
each instantiation begins with the desired attributes. By introducing
persistent
property settings, we allow the programmer to override the defaults in
a consistent
and straightforward way. Because there is no need to place
initialization code
in the Form_Load
events, there
is no possibility of forgetting to invoke this code. In addition,
unlike the
non-OO solution, the programmer is free to override those defaults for
individual
objects. In short, the CustomTextBox
behaves just like a standard text box, with new default values.
Exposing
Events
Although the CustomTextBox control
as it now stands meets the requirements imposed by Bakopanos, our
solution has
inadvertently handicapped the programmer. Since Bakopanos simply
programmed
the custom behavior into each text box, all of the original text
boxs
functionality would still be available, but the CustomTextBox,
so far, has exposed only a few properties, and the text boxs
methods are
still hidden inside the wrapper. Additional properties (indeed, all of
them)
could be exposed as required, but to complete the illusion of
inheritance, the
events associated with the text box need to be connected to events
associated
with the CustomTextBox.
The Change
event, for example, is sometimes used to validate the information
entered in
a text box. Although the text box txtText can
respond to
change events, the event is not passed out to the CustomTextBox
control level. As a result, the application programmer cannot write
any code
to program this event. However, by adding just a few lines to the
CustomTextBox
controls code, we can provide this functionality (Figure 6).
|
Event Change()
'This must
go into the General Declarations area
Private Sub
txtText_Change()
RaiseEvent
Change
End
Sub
'For testing, add
this
code to the test project
'It customizes
the Change
Event for CustomTextBox2
' causing each
change to
be duplicated in CustomTextBox1
Private Sub
CustomTextBox2_Change()
CustomTextBox1.Text
= CustomTextBox2.Text
End
Sub
|
|
| Figure 6. |
Run the program and notice that each change to the CustomTextBox2
causes the contents to be duplicated in CustomTextBox1.
Conclusions
Implementing a custom look and feel in a control across all forms in
a project,
while retaining the simplicity of drag and drop design, is efficiently
accomplished
in Visual Basic using an OO approach. Although Visual Basic does not
support
object inheritance, it does allow class composition. By using this
simple OO
technique and the ActiveX Control Interface Wizard, it is a fairly
simple task
to create a custom control that inherits the essential functionality
of an existing
control, but adds additional properties and methods that are desired
for specific
applications. The use of Property methods allows these custom controls
to mimic
the standard controls in every way.
Bakopanos presented a basic solution to the problem of modifying a
standard
control to achieve custom functionality and appearance, but his
solution fell
short of its potential. It required special code to be written at the
application
level rather than hiding it within the control code itself. Requiring
this additional
code violated the basic OO principle of information hiding and
resulted in a
solution that was inflexible and prone to error. In addition, by
separating
the custom code from the object to which it was to be applied, he also
violated
principles of encapsulation. The solution developed in this article,
applied
class composition to provide a truly OO approach that illustrated the
principles
of information hiding and encapsulation. We also saw how the ActiveX
Control
Interface Wizard could be used to imitate inheritance, even though it
is not
a feature of the Visual Basic language.
As a final note, regarding object reuse, the Visual Basic IDE
provides a method
of packaging this new control to make it instantly available to any
project.
By compiling the CustomTextBox
control to create an ActiveX Control file (CustomTextBox.ocx),
it can be included in the project components of any new project. By
simply dropping
an instance of this control on a form, we get all of the desired
custom functionality,
plus the added benefit of modifying any of its properties or methods
on an individual
control basis.
Reference
Bakopanos, K. (2000, February). Applying functionality on controls
the OO way.
Visual Basic Online [Online serial]. (Note: At press time, this
periodical
was no longer available online. It may reappear, however. If it does,
we will
add the link so that you may read the piece that inspired this
article.)
Contributor
Dr. Margush has been teaching computer science for over twenty years.
His main
interests are in the area of discrete mathematics and software
development in
Visual Basic, C++, and Java. He has authored several custom packages
for various
non-profit organizations and engaged in consulting activities with
several firms.
Tim is active in community organizations, and has appeared in several
drama
productions through his church. When he is not thinking about
programming, he
enjoys spending time with his family or singing and playing
guitar.
Contact
Dr. Tim Margush
Department of Computer Science
The University of Akron
Akron, OH 44325
Margush@uakron.edu
www.cs.uakron.edu/~margush
330.972.7109
Copyright ©
2001, ISTE (International Society for Technology in Education). All
rights reserved.
|