Visual programming
Visual programming
and Beans
So far in this book you’ve seen how valuable
Java is for creating reusable pieces of code. The “most reusable”
unit of code has been the class, since it comprises a cohesive unit of
characteristics (fields) and behaviors (methods) that can be reused either
directly via composition or through inheritance.
Inheritance and polymorphism are essential parts of
object-oriented programming, but in the majority of cases when you’re
putting together an application, what you really want is components that do
exactly what you need. You’d like to drop these parts into your design
like the electronic engineer puts together chips on a circuit board. It seems,
too, that there should be some way to accelerate this “modular
assembly” style of programming.
“Visual programming” first became
successful—very successful—with Microsoft’s Visual
Basic (VB), followed by a second-generation design in Borland’s Delphi
(the primary inspiration for the JavaBeans design). With these programming tools
the components are represented visually, which makes sense since they usually
display some kind of visual component such as a button or a text field. The
visual representation, in fact, is often exactly the way the component will look
in the running program. So part of the process of visual programming involves
dragging a component from a palette and dropping it onto your form. The
application builder tool writes code as you do this, and that code will cause
the component to be created in the running program.
Simply dropping the component onto a form is usually
not enough to complete the program. Often, you must change the characteristics
of a component, such as what color it is, what text is on it, what database
it’s connected to, etc. Characteristics that can be modified at design
time are referred to as properties. You can manipulate the properties of
your component inside the application builder tool, and when you create the
program this configuration data is saved so that it can be rejuvenated when the
program is started.
By now you’re probably used to the idea that an
object is more than characteristics; it’s also a set of behaviors. At
design-time, the behaviors of a visual component are partially represented by
events, meaning “Here’s something that can happen to the
component.” Ordinarily, you decide what you want to happen when an event
occurs by tying code to that event.
Here’s the critical part: the application
builder tool uses reflection to dynamically interrogate the component and find
out which properties and events the component supports. Once it knows what they
are, it can display the properties and allow you to change those (saving the
state when you build the program), and also display the events. In general, you
do something like double-clicking on an event and the application builder tool
creates a code body and ties it to that particular event. All you have to do at
that point is write the code that executes when the event occurs.
All this adds up to a lot of work that’s done
for you by the application builder tool. As a result you can focus on what the
program looks like and what it is supposed to do, and rely on the application
builder tool to manage the connection details for you. The reason that visual
programming tools have been so successful is that they dramatically speed up the
process of building an application—certainly the user interface, but often
other portions of the application as well.
What is a Bean?
After the dust settles, then, a component is really
just a block of code, typically embodied in a class. The key issue is the
ability for the application builder tool to discover the properties and events
for that component. To create a VB component, the programmer had to write a
fairly complicated piece of code following certain conventions to expose the
properties and events. Delphi was a second-generation visual programming tool
and the language was actively designed around visual programming so it is much
easier to create a visual component. However, Java has brought the creation of
visual components to its most advanced state with JavaBeans, because a Bean is
just a class. You don’t have to write any extra code or use special
language extensions in order to make something a Bean. The only thing you need
to do, in fact, is slightly modify the way that you name your methods. It is the
method name that tells the application builder tool whether this is a property,
an event, or just an ordinary method.
In the Java documentation, this naming convention is
mistakenly termed a “design pattern.” This is unfortunate, since
design patterns (see Thinking in Patterns with Java, downloadable at
www.BruceEckel.com) are challenging enough without this sort of
confusion. It’s not a design pattern, it’s just a naming convention
and it’s fairly simple:
- For a property named xxx, you typically create
two methods: getXxx( ) and setXxx( ). Note that the
first letter after “get” or “set” is automatically
lowercased to produce the property name. The type produced by the
“get” method is the same as the type of the argument to the
“set” method. The name of the property and the type for the
“get” and “set” are not related
- For a boolean property, you can use the
“get” and “set” approach above, but you can also use
“is” instead of “get.”
- Ordinary methods of the Bean don’t conform to the
above naming convention, but they’re public.
- For events, you use the Swing “listener”
approach. It’s exactly the same as you’ve been seeing:
addFooBarListener(FooBarListener) and
removeFooBarListener(FooBarListener) to handle a FooBarEvent. Most
of the time the built-in events and listeners will satisfy your needs, but you
can also create your own events and listener interfaces.
Point 1 above answers a question
about something you might have noticed when looking at older code vs. newer
code: a number of method names have had small, apparently meaningless name
changes. Now you can see that most of those changes had to do with adapting to
the “get” and “set” naming conventions in order to make
that particular component into a Bean.
We
can use these guidelines to create a simple Bean:
//:
frogbean:Frog.java
//
A trivial JavaBean.
package
frogbean;
import
java.awt.*;
import
java.awt.event.*;
class
Spots {}
public
class
Frog {
private
int
jumps;
private
Color color;
private
Spots spots;
private
boolean
jmpr;
public
int
getJumps() {
return
jumps; }
public
void
setJumps(int
newJumps) {
jumps = newJumps;
}
public
Color getColor() {
return
color; }
public
void
setColor(Color newColor) {
color = newColor;
}
public
Spots getSpots() {
return
spots; }
public
void
setSpots(Spots newSpots) {
spots = newSpots;
}
public
boolean
isJumper() {
return
jmpr; }
public
void
setJumper(boolean
j) { jmpr = j; }
public
void
addActionListener(
ActionListener l) {
//...
}
public
void
removeActionListener(
ActionListener l) {
// ...
}
public
void
addKeyListener(KeyListener l) {
// ...
}
public
void
removeKeyListener(KeyListener l) {
// ...
}
// An "ordinary" public
method:
public
void
croak() {
System.out.println("Ribbet!");
}
}
///:~
First, you can see that it’s just a class.
Usually, all your fields will be private, and accessible only through
methods. Following the naming convention, the properties are jumps,
color, spots, and jumper (notice the case change of the
first letter in the property name). Although the name of the internal identifier
is the same as the name of the property in the first three cases, in
jumper you can see that the property name does not force you to use any
particular identifier for internal variables (or, indeed, to even have
any internal variables for that property).
The events this Bean handles are ActionEvent
and KeyEvent, based on the naming of the “add” and
“remove” methods for the associated listener. Finally, you can see
that the ordinary method croak( ) is still part of the Bean simply
because it’s a public method, not because it conforms to any naming
scheme.
Extracting BeanInfo
with the
Introspector
One of the most critical parts of the Bean scheme
occurs when you drag a Bean off a palette and plop it onto a form. The
application builder tool must be able to create the Bean (which it can do if
there’s a default constructor) and then, without access to the
Bean’s source code, extract all the necessary information to create the
property sheet and event handlers.
Part of the solution is already evident from the end
of Chapter 12: Java reflection allows all the methods of an anonymous
class to be discovered. This is perfect for solving the Bean problem without
requiring you to use any extra language keywords like those required in other
visual programming languages. In fact, one of the prime reasons that reflection
was added to Java was to support Beans (although reflection also supports object
serialization and remote method invocation). So you might expect that the
creator of the application builder tool would have to reflect each Bean and hunt
through its methods to find the properties and events for that
Bean.
This is certainly possible, but the Java designers
wanted to provide a standard tool, not only to make Beans simpler to use but
also to provide a standard gateway to the creation of more complex Beans. This
tool is the Introspector class, and the most important method in this
class is the static getBeanInfo( ). You pass a Class
reference to this method and it fully interrogates that class and returns a
BeanInfo object that you can then dissect to find properties, methods,
and events.
Usually you won’t care about any of
this—you’ll probably get most of your Beans off the shelf from
vendors, and you don’t need to know all the magic that’s going on
underneath. You’ll simply drag your Beans onto your form, then configure
their properties and write handlers for the events you’re interested in.
However, it’s an interesting and educational exercise to use the
Introspector to display information about a Bean, so here’s a tool
that does it:
//:
c13:BeanDumper.java
//
Introspecting a Bean.
//
<applet code=BeanDumper width=600 height=500>
//
</applet>
import
java.beans.*;
import
java.lang.reflect.*;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
com.bruceeckel.swing.*;
public
class
BeanDumper
extends
JApplet {
JTextField query =
new
JTextField(20);
JTextArea results =
new
JTextArea();
public
void
prt(String s) {
results.append(s +
"\n");
}
public
void
dump(Class bean){
results.setText("");
BeanInfo bi =
null;
try
{
bi = Introspector.getBeanInfo(
bean,
java.lang.Object.class);
}
catch(IntrospectionException
e) {
prt("Couldn't introspect
" +
bean.getName());
return;
}
PropertyDescriptor[] properties =
bi.getPropertyDescriptors();
for(int
i = 0; i < properties.length; i++) {
Class p = properties[i].getPropertyType();
prt("Property type:\n
" + p.getName() +
"Property name:\n
" +
properties[i].getName());
Method readMethod =
properties[i].getReadMethod();
if(readMethod
!=
null)
prt("Read method:\n
" + readMethod);
Method writeMethod =
properties[i].getWriteMethod();
if(writeMethod
!=
null)
prt("Write method:\n
" + writeMethod);
prt("====================");
}
prt("Public
methods:");
MethodDescriptor[] methods =
bi.getMethodDescriptors();
for(int
i = 0; i < methods.length; i++)
prt(methods[i].getMethod().toString());
prt("======================");
prt("Event
support:");
EventSetDescriptor[] events =
bi.getEventSetDescriptors();
for(int
i = 0; i < events.length; i++) {
prt("Listener type:\n
" +
events[i].getListenerType().getName());
Method[] lm =
events[i].getListenerMethods();
for(int
j = 0; j < lm.length; j++)
prt("Listener method:\n
" +
lm[j].getName());
MethodDescriptor[] lmd =
events[i].getListenerMethodDescriptors();
for(int
j = 0; j < lmd.length; j++)
prt("Method descriptor:\n
" +
lmd[j].getMethod());
Method addListener =
events[i].getAddListenerMethod();
prt("Add Listener Method:\n
" +
addListener);
Method removeListener =
events[i].getRemoveListenerMethod();
prt("Remove Listener Method:\n
" +
removeListener);
prt("====================");
}
}
class
Dumper
implements
ActionListener {
public
void
actionPerformed(ActionEvent e) {
String name = query.getText();
Class c =
null;
try
{
c = Class.forName(name);
}
catch(ClassNotFoundException
ex) {
results.setText("Couldn't find
" + name);
return;
}
dump(c);
}
}
public
void
init() {
Container cp = getContentPane();
JPanel p =
new
JPanel();
p.setLayout(new
FlowLayout());
p.add(new
JLabel("Qualified bean
name:"));
p.add(query);
cp.add(BorderLayout.NORTH, p);
cp.add(new
JScrollPane(results));
Dumper dmpr =
new
Dumper();
query.addActionListener(dmpr);
query.setText("frogbean.Frog");
// Force
evaluation
dmpr.actionPerformed(
new
ActionEvent(dmpr, 0,
""));
}
public
static
void
main(String[] args) {
Console.run(new
BeanDumper(), 600, 500);
}
}
///:~
BeanDumper.dump( ) is the method that does
all the work. First it tries to create a BeanInfo object, and if
successful calls the methods of BeanInfo that produce information about
properties, methods, and events. In Introspector.getBeanInfo( ),
you’ll see there is a second argument. This tells the Introspector
where to stop in the inheritance hierarchy. Here, it stops before it parses all
the methods from Object, since we’re not interested in seeing
those.
For properties, getPropertyDescriptors( )
returns an array of PropertyDescriptors. For each
PropertyDescriptor you can call getPropertyType( ) to find
the class of object that is passed in and out via the property methods. Then,
for each property you can get its pseudonym (extracted from the method names)
with getName( ), the method for reading with
getReadMethod( ), and the method for writing with
getWriteMethod( ). These last two methods return a Method
object that can actually be used to invoke the corresponding method on the
object (this is part of reflection).
For the public methods (including the property
methods), getMethodDescriptors( ) returns an array of
MethodDescriptors. For each one you can get the associated Method
object and print its name.
For the events, getEventSetDescriptors( )
returns an array of (what else?) EventSetDescriptors. Each of these can
be queried to find out the class of the listener, the methods of that listener
class, and the add- and remove-listener methods. The BeanDumper program
prints out all of this information.
Upon startup, the program forces the evaluation of
frogbean.Frog. The output, after removing extra details that are
unnecessary here, is:
class
name: Frog
Property
type:
Color
Property
name:
color
Read
method:
public
Color getColor()
Write
method:
public
void
setColor(Color)
====================
Property
type:
Spots
Property
name:
spots
Read
method:
public
Spots getSpots()
Write
method:
public
void
setSpots(Spots)
====================
Property
type:
boolean
Property
name:
jumper
Read
method:
public
boolean
isJumper()
Write
method:
public
void
setJumper(boolean)
====================
Property
type:
int
Property
name:
jumps
Read
method:
public
int
getJumps()
Write
method:
public
void
setJumps(int)
====================
Public
methods:
public
void
setJumps(int)
public
void
croak()
public
void
removeActionListener(ActionListener)
public
void
addActionListener(ActionListener)
public
int
getJumps()
public
void
setColor(Color)
public
void
setSpots(Spots)
public
void
setJumper(boolean)
public
boolean
isJumper()
public
void
addKeyListener(KeyListener)
public
Color getColor()
public
void
removeKeyListener(KeyListener)
public
Spots getSpots()
======================
Event
support:
Listener
type:
KeyListener
Listener
method:
keyTyped
Listener
method:
keyPressed
Listener
method:
keyReleased
Method
descriptor:
public
void
keyTyped(KeyEvent)
Method
descriptor:
public
void
keyPressed(KeyEvent)
Method
descriptor:
public
void
keyReleased(KeyEvent)
Add
Listener Method:
public
void
addKeyListener(KeyListener)
Remove
Listener Method:
public
void
removeKeyListener(KeyListener)
====================
Listener
type:
ActionListener
Listener
method:
actionPerformed
Method
descriptor:
public
void
actionPerformed(ActionEvent)
Add
Listener Method:
public
void
addActionListener(ActionListener)
Remove
Listener Method:
public
void
removeActionListener(ActionListener)
====================
This reveals most of what the Introspector sees
as it produces a BeanInfo object from your Bean. You can see that the
type of the property and its name are independent. Notice the lowercasing of the
property name. (The only time this doesn’t occur is when the property name
begins with more than one capital letter in a row.) And remember that the method
names you’re seeing here (such as the read and write methods) are actually
produced from a Method object that can be used to invoke the associated
method on the object.
The public method list includes the methods
that are not associated with a property or event, such as croak( ),
as well as those that are. These are all the methods that you can call
programmatically for a Bean, and the application builder tool can choose to list
all of these while you’re making method calls, to ease your task.
Finally, you can see that the events are fully parsed
out into the listener, its methods, and the add- and remove-listener methods.
Basically, once you have the BeanInfo, you can find out everything of
importance for the Bean. You can also call the methods for that Bean, even
though you don’t have any other information except the object (again, a
feature of reflection).
A more sophisticated Bean
This next example is slightly more sophisticated,
albeit frivolous. It’s a JPanel that draws a little circle around
the mouse whenever the mouse is moved. When you press the mouse, the word
“Bang!” appears in the middle of the screen, and an action listener
is fired.
The properties you can change are the size of the
circle as well as the color, size, and text of the word that is displayed when
you press the mouse. A BangBean also has its own
addActionListener( ) and removeActionListener( ) so you
can attach your own listener that will be fired when the user clicks on the
BangBean. You should be able to recognize the property and event
support:
//:
bangbean:BangBean.java
//
A graphical Bean.
package
bangbean;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.io.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class
BangBean
extends
JPanel
implements
Serializable {
protected
int
xm, ym;
protected
int
cSize = 20; // Circle
size
protected
String text =
"Bang!";
protected
int
fontSize = 48;
protected
Color tColor = Color.red;
protected
ActionListener actionListener;
public
BangBean() {
addMouseListener(new
ML());
addMouseMotionListener(new
MML());
}
public
int
getCircleSize() {
return
cSize; }
public
void
setCircleSize(int
newSize) {
cSize = newSize;
}
public
String getBangText() {
return
text; }
public
void
setBangText(String newText) {
text = newText;
}
public
int
getFontSize() {
return
fontSize; }
public
void
setFontSize(int
newSize) {
fontSize = newSize;
}
public
Color getTextColor() {
return
tColor; }
public
void
setTextColor(Color newColor) {
tColor = newColor;
}
public
void
paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.black);
g.drawOval(xm - cSize/2, ym - cSize/2,
cSize, cSize);
}
// This is a unicast listener,
which is
// the simplest form of listener
management:
public
void
addActionListener (
ActionListener l)
throws
TooManyListenersException {
if(actionListener
!=
null)
throw
new
TooManyListenersException();
actionListener = l;
}
public
void
removeActionListener(
ActionListener l) {
actionListener =
null;
}
class
ML
extends
MouseAdapter {
public
void
mousePressed(MouseEvent e) {
Graphics g = getGraphics();
g.setColor(tColor);
g.setFont(
new
Font(
"TimesRoman",
Font.BOLD, fontSize));
int
width =
g.getFontMetrics().stringWidth(text);
g.drawString(text,
(getSize().width - width) /2,
getSize().height/2);
g.dispose();
// Call the listener's
method:
if(actionListener
!=
null)
actionListener.actionPerformed(
new
ActionEvent(BangBean.this,
ActionEvent.ACTION_PERFORMED,
null));
}
}
class
MML
extends
MouseMotionAdapter {
public
void
mouseMoved(MouseEvent e) {
xm = e.getX();
ym = e.getY();
repaint();
}
}
public
Dimension getPreferredSize() {
return
new
Dimension(200, 200);
}
}
///:~
The first thing you’ll notice is that
BangBean implements the Serializable interface. This means that
the application builder tool can “pickle” all the information for
the BangBean using serialization after the program designer has adjusted
the values of the properties. When the Bean is created as part of the running
application, these “pickled” properties are restored so that you get
exactly what you designed.
You can see that all the fields are private,
which is what you’ll usually do with a Bean—allow access only
through methods, usually using the “property” scheme.
When you look at the signature for
addActionListener( ), you’ll see that it can throw a
TooManyListenersException. This indicates that it is unicast,
which means it notifies only one listener when the event occurs. Ordinarily,
you’ll use multicast events so that many listeners can be notified
of an event. However, that runs into issues that you won’t be ready for
until the next chapter, so it will be revisited there (under the heading
“JavaBeans revisited”). A unicast event sidesteps the
problem.
When you click the mouse, the text is put in the
middle of the BangBean, and if the actionListener field is not
null, its actionPerformed( ) is called, creating a new
ActionEvent object in the process. Whenever the mouse is moved, its new
coordinates are captured and the canvas is repainted (erasing any text
that’s on the canvas, as you’ll see).
Here is the BangBeanTest class to allow you to
test the bean as either an applet or an application:
//:
c13:BangBeanTest.java
//
<applet code=BangBeanTest
//
width=400 height=500></applet>
import
bangbean.*;
import
javax.swing.*;
import
java.awt.*;
import
java.awt.event.*;
import
java.util.*;
import
com.bruceeckel.swing.*;
public
class
BangBeanTest
extends
JApplet {
JTextField txt =
new
JTextField(20);
// During testing, report
actions:
class
BBL
implements
ActionListener {
int
count = 0;
public
void
actionPerformed(ActionEvent e){
txt.setText("BangBean action
"+ count++);
}
}
public
void
init() {
BangBean bb =
new
BangBean();
try
{
bb.addActionListener(new
BBL());
}
catch(TooManyListenersException
e) {
txt.setText("Too many
listeners");
}
Container cp = getContentPane();
cp.add(bb);
cp.add(BorderLayout.SOUTH, txt);
}
public
static
void
main(String[] args) {
Console.run(new
BangBeanTest(), 400, 500);
}
}
///:~
When a Bean is in a development environment, this
class will not be used, but it’s helpful to provide a rapid testing method
for each of your Beans. BangBeanTest places a BangBean within the
applet, attaching a simple ActionListener to the BangBean to print
an event count to the JTextField whenever an ActionEvent occurs.
Usually, of course, the application builder tool would create most of the code
that uses the Bean.
When you run the BangBean through
BeanDumper or put the BangBean inside a Bean-enabled development
environment, you’ll notice that there are many more properties and actions
than are evident from the above code. That’s because BangBean is
inherited from JPanel, and JPanel is also Bean, so you’re
seeing its properties and events as well.
Packaging a Bean
Before you can bring a Bean into a Bean-enabled visual
builder tool, it must be put into the standard Bean container, which is a JAR
file that includes all the Bean classes as well as a “manifest” file
that says “This is a Bean.” A manifest file is simply a text file
that follows a particular form. For the BangBean, the manifest file looks
like this (without the first and last lines):
//:!
:BangBean.mf
Manifest-Version:
1.0
Name:
bangbean/BangBean.class
Java-Bean:
True
///:~
The first line indicates the version of the manifest
scheme, which until further notice from Sun is 1.0. The second line (empty lines
are ignored) names the BangBean.class file, and the third says,
“It’s a Bean.” Without the third line, the program builder
tool will not recognize the class as a Bean.
The only tricky part is that you must make sure that
you get the proper path in the “Name:” field. If you look back at
BangBean.java, you’ll see it’s in package bangbean
(and thus in a subdirectory called “bangbean” that’s off
of the classpath), and the name in the manifest file must include this package
information. In addition, you must place the manifest file in the directory
above the root of your package path, which in this case means placing the
file in the directory above the “bangbean” subdirectory. Then you
must invoke jar from the same directory as the manifest file, as
follows:
jar
cfm BangBean.jar BangBean.mf bangbean
This assumes that you want the resulting JAR file to
be named BangBean.jar and that you’ve put the manifest in a file
called BangBean.mf.
You might wonder “What about all the other
classes that were generated when I compiled BangBean.java?” Well,
they all ended up inside the bangbean subdirectory, and you’ll see
that the last argument for the above jar command line is the
bangbean subdirectory. When you give jar the name of a
subdirectory, it packages that entire subdirectory into the jar file (including,
in this case, the original BangBean.java source-code file—you might
not choose to include the source with your own Beans). In addition, if you turn
around and unpack the JAR file you’ve just created, you’ll discover
that your manifest file isn’t inside, but that jar has created its
own manifest file (based partly on yours) called MANIFEST.MF and placed
it inside the subdirectory META-INF (for “meta-information”).
If you open this manifest file you’ll also notice that digital signature
information has been added by jar for each file, of the
form:
Digest-Algorithms:
SHA MD5
SHA-Digest:
pDpEAG9NaeCx8aFtqPI4udSX/O0=
MD5-Digest:
O4NcS1hE3Smnzlp2hj6qeg==
In general, you don’t need to worry about any of
this, and if you make changes you can just modify your original manifest file
and reinvoke jar to create a new JAR file for your Bean. You can also add
other Beans to the JAR file simply by adding their information to your manifest.
One thing to notice is that you’ll probably want
to put each Bean in its own subdirectory, since when you create a JAR file you
hand the jar utility the name of a subdirectory and it puts everything in
that subdirectory into the JAR file. You can see that both Frog and
BangBean are in their own subdirectories.
Once you have your Bean properly inside a JAR file you
can bring it into a Beans-enabled program-builder environment. The way you do
this varies from one tool to the next, but Sun provides a freely available test
bed for JavaBeans in their “Beans Development Kit” (BDK) called the
“beanbox.” (Download the BDK from java.sun.com/beans.) To
place your Bean in the beanbox, copy the JAR file into the BDK’s
“jars” subdirectory before you start up the beanbox.
More complex Bean support
You can see how remarkably simple it is to make a
Bean. But you aren’t limited to what you’ve seen here. The JavaBeans
architecture provides a simple point of entry but can also scale to more complex
situations. These situations are beyond the scope of this book, but they will be
briefly introduced here. You can find more details at
java.sun.com/beans.
One place where you can add sophistication is with
properties. The examples above have shown only single properties, but it’s
also possible to represent multiple properties in an array. This is called an
indexed property. You simply provide the appropriate methods (again
following a naming convention for the method names) and the Introspector
recognizes an indexed property so your application builder tool can respond
appropriately.
Properties can be bound, which means that they
will notify other objects via a PropertyChangeEvent. The other objects
can then choose to change themselves based on the change to the
Bean.
Properties can be constrained, which means that
other objects can veto a change to that property if it is unacceptable. The
other objects are notified using a PropertyChangeEvent, and they can
throw a PropertyVetoException to prevent the change from happening and to
restore the old values.
You can also change the way your Bean is represented
at design time:
- You can provide a custom property sheet for your
particular Bean. The ordinary property sheet will be used for all other Beans,
but yours is automatically invoked when your Bean is selected.
- You can create a custom editor for a particular
property, so the ordinary property sheet is used, but when your special property
is being edited, your editor will automatically be invoked.
- You can provide a custom BeanInfo class for your
Bean that produces information that’s different from the default created
by the Introspector.
- It’s also possible to turn “expert”
mode on and off in all FeatureDescriptors to distinguish between basic
features and more complicated ones.
More to Beans
There’s another issue that couldn’t be
addressed here. Whenever you create a Bean, you should expect that it will be
run in a multithreaded environment. This means that you must understand the
issues of threading, which will be introduced in Chapter 14. You’ll find a
section there called “JavaBeans revisited” that will look at the
problem and its solution.
There are a number of books about JavaBeans; for
example, JavaBeans by Elliotte Rusty Harold (IDG, 1998).
|