Use the JDK1.1 event model. This includes generating your own events.
Put the code into a .jar with a manifest. This is easier said than done.
Write a BeanInfo class to assign icons to the Bean.
You'll also need to understand exactly where and how to use Beans. I'm not sure that I do yet.
Name: friendless/awt/SpinControl.class Java-Bean: TrueHowever that information was not added to the manifest in the jar at all, and the BeanBox kept complaining that I hadn't specified anything in the jar as being a bean. Instead, what I did was made the jar, extracted the manifest.mf using Moa (see Resources below), and edited that manifest by putting "Java-Bean: True" in for every Bean class. Then I used that version in my make command. If any new files were created, they would be automatically added to the manifest which was added to the jar. If any files were deleted, I had to take their entries out of "my" copy of the manifest. The manifest in the jar now had the beans marked appropriately.
Here's the batch file I use to make my beans. This is used to make the SpinControl and SpinButtons beans.
set CLASSPATH=.;k:\jdk112\lib\moa.zip del friendless\awt\*.class del *.jar javac -deprecation friendless\awt\SpinControl.java javac -deprecation friendless\awt\SpinButtons.java javac friendless\awt\SpinControlBeanInfo.java javac friendless\awt\SpinButtonsBeanInfo.java jar cvfm SpinControl.jar Manifest.mf friendless copy SpinControl.jar \BDK\jarsI had to set the CLASSPATH to not include my standard directories, to make sure that all code for the Bean was in the friendless subtree under the current directory - no point making a jar file that was missing required classes because they were elsewhere on the machine. I use the -deprecation flag to make sure I'm not falling back into JDK 1.02 habits. Note that the BeanInfo classes have to be compiled separately, because they aren't used in the Bean classes and so don't get compiled by default. "jar cvfm" means "create the jar verbosely given the filename and the manifest file". "friendless" is the directory that all code lives under - jar will archive the entire directory subtree. I had to start at that directory, because the Beans are in package friendless.awt, and the filenames in the jar wouldn't match the package names otherwise. BTW this makefile puts the source code in the jar as well, which is fine for me, but maybe you wouldn't want to do that.
package friendless.awt; import java.awt.Image; import java.beans.*; public class SpinButtonsBeanInfo extends SimpleBeanInfo { public Image getIcon(int iconKind) { java.awt.Image img; if (iconKind == BeanInfo.ICON_COLOR_16x16) { img = loadImage("SpinButtonIco16.gif"); } else if (iconKind == BeanInfo.ICON_COLOR_32x32) { img = loadImage("SpinButtonIco32.gif"); } else if (iconKind == BeanInfo.ICON_MONO_16x16) { img = loadImage("SpinButtonIcoBW16.gif"); } else if (iconKind == BeanInfo.ICON_MONO_32x32) { img = loadImage("SpinButtonIcoBW32.gif"); } else img = null; return img; } }There are other things you can do with BeanInfo, but really it gets terribly complicated, and I spent a day just figuring this stuff out, so I don't understand them yet. This BeanInfo just returns icons to make your Bean list nicely in the BeanBox. It's otherwise useless. Notice the method loadImage(), which takes the filename of an image and returns the java.awt.Image for you. More on that later, but you must keep those images in the same directory as the .class files that refer to them, and then they get put in the jar. Here are those images, in case you are wondering:
Making the icons is one of the hardest parts of making the Bean!
I used PaintShop Pro and LView Pro.
My SpinButtons bean uses three images: . Yes, this means you cannot customise the Bean to your own colours, maybe I will do it that way in a future version. The problem is, how do I load those images from the jar file? I tried calling loadImage(), but it is only a method in SimpleBeanInfo. After some investigation, I discovered that a Bean can be an Applet, and you can load images like an applet does. This sounded a mite suspicious (like, where does it get its AppletContext from?) but I coded it up that way and got it working in the BeanBox and in an application (see below).
Then I proceeded to write my SpinControl class, which has some SpinButtons which control the value of a number. As I was writing the SpinControl to be distributed in the same jar as the SpinButtons, I figured it could just use the SpinButtons class without having to go through the Bean instantiation procedure. Although it compiled, the code would not work. The problem was that when I tried to instantiate the SpinButtons control from the SpinControl control, getCodeBase() in the SpinButtons control was null. If you read the documentation for java.beans.Beans.instantiate(), you'll notice that it says that the Beans instantiation procedure creates a fake AppletContext and AppletStub if the Bean is an instance of java.applet.Applet. So it will work if your Bean is an Applet itself, but if you want create that Bean inside another Bean, you have to make the fake AppletContext and AppletStub, or use the Beans instantiation procedure.
I decided that this was all getting a little too far removed from sensible coding practice, and determined to find the One True Way. Remember the reason I made SpinButtons an Applet in the first place was to be able to load images, so I really needed to solve the image-loading problem better. I knew a couple of the samples I downloaded from the net used images, so I scrounged around in the code and came up with this:
public Image loadImage(String resourceName) { try { java.net.URL url = getClass().getResource(resourceName); java.awt.Toolkit tk = java.awt.Toolkit.getDefaultToolkit(); return tk.createImage((java.awt.image.ImageProducer) url.getContent()); } catch (Exception ex) { return null; } }This is from Bob Leurck's HoverLabel stuff, thanks Bob. To tell you the truth, this code doesn't strike me as blindingly obvious. First it builds the URL of a resource by asking the ClassLoader (via class Class) to find it. I guess you can give the name of the resource relative to the package of the current class, or maybe even absolutely. Then it calls the Toolkit to create the image from the data contained in the URL. It's easy when you know how! The URLs generated look like this, both when running in the BeanBox and when running in an application.
simpleresource:SIMPLEBeanBox/+/friendless/awt/updown.gif
So, by getting the ClassLoader involved in the loading of images, they can be easily loaded from the jar file. Making the Bean an Applet was a Bad Idea, and you shouldn't do it, although that's how Sun's Juggler example is implemented. I don't understand why JavaSoft haven't made loadImage a standard function in class Component or somewhere like that. Royalties to Bob please.
the real difficulty is that if you want to use a SpinButtons in your program, but you don't have access to the SpinButtons class at compile-time, you can't actually mention the word SpinButtons. You have to get the JavaBeans infra-structure to create you a Bean from a given jar file, and give it back as a Component. This is how you do that:
public static Component getComponent(String jarFile, String classname) { JarInfo info = (JarInfo)infos.get(jarFile); if (info == null) { try { JarLoader loader = new JarLoader(jarFile); info = loader.loadJar(); } catch (Exception e) { System.out.println(e); return null; } if (info != null) infos.put(jarFile,info); } if (info == null) return null; Object o = info.getInstance(classname); if (o instanceof Component) { Component c = (Component)o; return c; } else { System.out.println(classname + " is not a component."); return null; } } public static Component getSpinButtons() { return getComponent(SpinButtonJar,"friendless.awt.SpinButtons"); }I had to rip off the JarInfo and JarLoader classes from the BeanBox. Now this might seem like a silly game, but the JavaBeans documentation claims I need to know nothing about an object to begin using it, and what better way to know nothing about it than to have it not available at comple-time :-)?
I also wanted to register to receive action events from this object. Of course, I do this by calling addActionListener() in the object, but Component doesn't define that method. In fact, there is no interface which defines that method, so I cannot actually use instanceof to determine whether I can receive ActionEvents from an object. So I defined my own interface (ActionEventSource), and made the SpinButtons implement that interface. But that didn't work - although the Class object for the instantiated Bean swore blind that it implemented ActionEventSource, it refused to let me cast the Component to ActionEventSource. I noticed later that java.beans.Beans has a method called getInstanceOf, so for some reason it seems that casting doesn't work on Beans. Hmmm.
Fortunately there is an obscenely intrusive process call Introspection and Reflection in which a Bean tells you anything you want to know about it, including calling any public method you like. So this method uses reflection to call addActionListener in any object which implements it, no matter what class it is. Gosh, doesn't the code look like calling an IDispatch method in DCOM?!
public static void becomeActionListener(Object o, ActionListener me) { Class c = o.getClass(); Method m = null; try { Class[] argClasses = { Class.forName("java.awt.event.ActionListener") }; m = c.getMethod("addActionListener",argClasses); Object[] args = { me }; Object ret = m.invoke(o,args); } catch (Exception e) { System.out.println(e); } }I packaged these two useful functions together into a class called BeanBroker.
So now I can use BeanBroker to worry about actually getting to the Beans, while I just write the code. Here's the body of addNotify() for the application:
super.addNotify(); setLayout(new GridLayout(1,2,4,4)); add(val = new Label()); add(spin = BeanBroker.getSpinButtons()); BeanBroker.becomeActionListener(spin,this); val.setText(""+i);It creates some SpinButtons and makes itself an ActionListener of them. In its actionPerformed() method, it increments or decrements i according to whether the up or down button was pressed.
public void actionPerformed(ActionEvent evt) { if (evt.getActionCommand().equals("UP")) { i++; } else if (evt.getActionCommand().equals("DOWN")) { i--; } val.setText(""+i); }Here's UseBeans.java and MainWindow.java which is a generic Frame that holds whatever you like and responds to the window kill button.