| Welcome back tip readers. I'm hoping all of you are ready to read more about the JGoodies binding framework. If you missed my last tip, too bad. Just kidding - you can find it
here - JGoodies: Understanding Binding - Part 1
. Yesterday I briefly ran through the subject of JGoodies binding, with the hope I could show you enough to whet your appetite, but I didn't show a whole lot about the usage - I pretty much showed how to bind a text field and a checkbox, and that was it. There is so much more, however.
A Little More Detail
So you are feeling a lack of control because right now you can only use checkboxes and text fields? It seems to me it is surely possible to build an entire application out of just those. Surely you don't expect to be able to use sliders, radio buttons, lists and combo boxes - that'd just be greedy. While I suppose that would be an interesting study (building an application composed entirely of two component types), it doesn't sound very user friendly.
One of the things I always want to avoid with my tips is being to wordy. Let's face it - it's hard for me; I obviously can find more than enough to say about a subject, and manage to often do so incoherently on top of that. Thankfully, the binding framework speaks volumes just by being implemented and used as I did in the previous tip. The fact is, the transparency it provides is so apparent (isn't that an oxy-moron - 'apparent transparency'?) that it is hard not to understand the value it can provide. In that sense, this tip should be a good one, as I plan to mostly use code examples to get through the real world examples of the different bindings that it can perform.
Beyond a Trivial Example
Yesterday's tip was, unfortunately, a little trivial. I covered binding a couple simple bean properties to a few view components - but when you get into real world applications, that just doesn't cut the metaphorical mustard. So what I want to do now is cover other components in either a cursory glance, or if the situation merits, some more detailed examples to show the different patterns of usage. Also, I will commonly be using the BasicComponentFactory and other similar classes in this example. I will NOT be exercising all possible methods available - many of these methods/constructors take many variations of the parameters I pass - so do some digging. The best way to do that digging is to look at the API documentation for each adapter for view elements - and while I'm at it, before I move forward, a comprehensive list of adaptations for view elements can be found in /docs/guide/viewadapters.html in your downloaded binding framework folder... what do you mean you didn't download the binding framework yet?
GO GET IT!
.
Many of the bindable components can be approached in the exact same way as our textfield and checkbox from yesterday. For instance, JToggleButton and JCheckBoxMenuItem are handled exactly the same as JCheckBox elements. Likewise, JTextArea and JPasswordField are handled exactly the same as JTextField. Because of that, I don't see it being worth my time or yours to detail how to create/bind them here - shouldn't be too hard to get from A->B if you catch my drift. There are some wierd cases however, so keep reading.
Binding a Radio Button to a Field
Ok - wierd item one - radio buttons. Radio buttons turn out to be quite easy to bind. Multiple radio buttons in the same button group are assigned to a single value model, but each one is given a unique value to set on the bean property when selected. Ok sorry, again there I go with the words. Here is a code example that uses our existing
MyBean.java
class, and provides two choices for a value on the
stringValue
field (note, the exact same is possible for JRadioButtonMenuItem elements):
public class Binding {
public static void main(String[] args) {
MyBean bean = new MyBean();
// Bean adapter is an adapter that can create many value model objects for a single
// bean. It is more efficient than the property adapter. The 'true' once again means
// we want it to observe our bean for changes.
BeanAdapter adapter = new BeanAdapter(bean, true);
// set the string value to one of the radio button's associated values
bean.setStringValue("value1");
ValueModel booleanModel = adapter.getValueModel("booleanValue");
ValueModel stringModel = adapter.getValueModel("stringValue");
// creates a JCheckBox with the property adapter providing the underlying model.
JCheckBox box = BasicComponentFactory.createCheckBox(booleanModel, "Boolean Value");
// create two radio buttons... 1st one binds 'value1' to 'stringValue'
// 2nd one binds 'value2'.
JRadioButton button1 = BasicComponentFactory.createRadioButton(stringModel, "value1", "Value 1");
JRadioButton button2 = BasicComponentFactory.createRadioButton(stringModel, "value2", "Value 2");
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(2,1));
frame.getContentPane().add(box);
frame.getContentPane().add(button1);
frame.getContentPane().add(button2);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
Pretty easy, right? I'm impressed with the simplicity of this setup, and I'm also impressed that all is required to ensure that a radio button is selected from the group at initialization is simply setting that radio button's value on the bean itself (note this will also work once the components are actually bound together).
Binding a Bounded Slider to a Field
Woah, wait a minute. Sliders have always been a pain in the tropal tailbone to interact with effectively in Java. Not anymore. While for whatever reason the
BasicComponentFactory
and
Bindings
factories don't seem to have entries for the slider, all that is required is using a
BoundedRangeAdapter
object as the
BoundedRangeModel
on the slider, and setting it up for our bean's value model. Here is an example that sets up a slider with allowed values between 0 and 500.
Notice in this example I dropped our
booleanValue
on the
MyBean
bean in favor of an
intValue
(as integers work just a *little* bit better with sliders).
// MyBean.java
import java.beans.PropertyChangeListener;
import com.jgoodies.binding.beans.*;
public class MyBean {
// Note you don't HAVE to use this class - you can use
// java.beans.PropertyChangeSupport if you want.
private ExtendedPropertyChangeSupport changeSupport = new ExtendedPropertyChangeSupport(
this);
private int intValue;
private String stringValue;
public int getIntValue() {
return intValue;
}
public void setIntValue(int newValue) {
System.out.println("Boolean value set: " +newValue);
int oldValue = intValue;
intValue = newValue;
changeSupport.firePropertyChange("intValue", oldValue, newValue);
}
public void addPropertyChangeListener(PropertyChangeListener x) {
changeSupport.addPropertyChangeListener(x);
}
public void removePropertyChangeListener(PropertyChangeListener x) {
changeSupport.removePropertyChangeListener(x);
}
public String getStringValue() {
return stringValue;
}
public void setStringValue(String newValue) {
System.out.println("String value set: "+ newValue);
String oldValue = stringValue;
this.stringValue = newValue;
changeSupport.firePropertyChange("stringValue", oldValue, newValue);
}
}
// Binding.java
import java.awt.GridLayout;
import javax.swing.*;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.BoundedRangeAdapter;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.value.ValueModel;
public class Binding {
public static void main(String[] args) {
MyBean bean = new MyBean();
// Bean adapter is an adapter that can create many value model objects for a single
// bean. It is more efficient than the property adapter. The 'true' once again means
// we want it to observe our bean for changes.
BeanAdapter adapter = new BeanAdapter(bean, true);
ValueModel intModel = adapter.getValueModel("intValue");
ValueModel stringModel = adapter.getValueModel("stringValue");
// creates a slider and a new bounded range adapter.
// the range adapter is given our integer value model, and a range of 0-500.
JSlider slider = new JSlider(new BoundedRangeAdapter(intModel, 0, 0, 500));
JRadioButton button1 = BasicComponentFactory.createRadioButton(stringModel, "value1", "Value 1");
JRadioButton button2 = BasicComponentFactory.createRadioButton(stringModel, "value2", "Value 2");
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(2,1));
frame.getContentPane().add(slider);
frame.getContentPane().add(button1);
frame.getContentPane().add(button2);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
Once again, a walk in the park.
Ok, so what about the less conventional widgets. What do I mean by conventional? Well, two widgets that for one reason or another don't implement an underlying model are the
JLabel
and the
JFormattedTextField
. What do we do if there is no model to adapt? Well, this is where we introduce the
Property Connector
.
Property Connectors
Property connectors are an interesting tool. The most common usage in the binding framework is for connecting non-modeled widgets to bean properties. Interestingly enough, however, it can be used more generally - simply to connect and synchronize multiple beans. Before I get ahead of myself though, let me explain what a property connector is. Property connectors differ from property adapters because they use the word connector instead of the word adapter. Ok, I can feel you pulling away from the hideousness of that joke. Seriously though, instead of connecting a widget's model to a bean, property connectors simply connect two bean properties. Thankfully, since most Swing elements follow (to at least some extent) the Javabean specification, any of the widgets that don't have a model, can be connected by using property connectors. This includes the
setText
on a JLabel and JFormattedTextField. Be aware, however, that PropertyConnectors make no attempt to convert types from one bean to another. That means that if you are using a JFormattedTextFields for dates, it better be bound to a field of
java.util.Date
type (and labels better be strings). Here is how a label is bound:
import java.awt.GridLayout;
import javax.swing.*;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.adapter.BoundedRangeAdapter;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.value.ValueModel;
public class Binding {
public static void main(String[] args) {
MyBean bean = new MyBean();
BeanAdapter adapter = new BeanAdapter(bean, true);
ValueModel stringModel = adapter.getValueModel("stringValue");
JLabel label = BasicComponentFactory.createLabel(stringModel);
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(2,1));
frame.getContentPane().add(label);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
Now you may be saying, "But JLabels aren't editable - binding them to a bean is a waste of time", and you'd be right. Just kidding - don't forget that these bindings are
BI
-directional. While the user can't edit the JLabel - some other behavior in the application (such as changing a JTextField) may have updated the bean. Because the JLabel is bound to the bean, it will automagically be updated! JFormattedTextFields are created in much the same way - re-listing the entire contents of the bean and the binding test would largely be repetitive here; just remember that the fields must be of the correct type!
Lists of Stuff
A-ha! I know what the binding framework doesn't support.
JList
s and
JComboBox
s. Well, no, actually it does. To start, here is how you can bind our existing
MyBean
instance to a single-selection JList (yes, I'm using the same old worn out bean, once again):
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.adapter.BoundedRangeAdapter;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ValueModel;
public class Binding {
public static void main(String[] args) {
MyBean bean = new MyBean();
BeanAdapter adapter = new BeanAdapter(bean, true);
ValueModel intModel = adapter.getValueModel("intValue");
ValueModel stringModel = adapter.getValueModel("stringValue");
List possibleValues = new ArrayList();
possibleValues.add("First Value");
possibleValues.add("Second Value");
possibleValues.add("Third Value");
JList jlist = BasicComponentFactory.createList(new SelectionInList(possibleValues, stringModel, intModel));
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(2,1));
frame.getContentPane().add(jlist);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
Note that while in the above example, I provide the possible values explicitly, it is just as possible to get them directly from the bean (as another value model backed by a
java.util.List
). As best as I can tell, there is (as of yet) no way to automatically bind a mulitple-selection list to a bean. I may be wrong about this, so don't take my word as gold, but short of implementing my own adapter (hah!), I haven't found any way to have a fully supported mulitple-select list. Incidentally, the class that performs adaptation of the selections is the
com.jgoodies.binding.adapter.SingleListSelectionAdapter
.
Combo boxes can be handled in a similar way to single-selection lists, although it can be a little less complicated (given the right constructor usage):
import java.awt.GridLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import com.jgoodies.binding.adapter.BasicComponentFactory;
import com.jgoodies.binding.adapter.Bindings;
import com.jgoodies.binding.adapter.BoundedRangeAdapter;
import com.jgoodies.binding.adapter.ComboBoxAdapter;
import com.jgoodies.binding.beans.BeanAdapter;
import com.jgoodies.binding.list.SelectionInList;
import com.jgoodies.binding.value.ValueModel;
public class Binding {
public static void main(String[] args) {
MyBean bean = new MyBean();
BeanAdapter adapter = new BeanAdapter(bean, true);
ValueModel intModel = adapter.getValueModel("intValue");
ValueModel stringModel = adapter.getValueModel("stringValue");
List possibleValues = new ArrayList();
possibleValues.add("First Value");
possibleValues.add("Second Value");
possibleValues.add("Third Value");
JComboBox comboBox = new JComboBox(new ComboBoxAdapter(possibleValues, stringModel));
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new GridLayout(2,1));
frame.getContentPane().add(comboBox);
frame.pack();
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
}
}
Whew! Well, I'm about beat for this session of JGoodies Bindings. I hope you learned a little about binding various 'real world' widgets to your beans. Don't go scurrying off yet, however. Next on the agenda is to spend more time understanding the more advanced benefits that can be found by using the value model intermediaries. To do some name dropping, this includes usage of the
PresentationModel
class, the
BufferedValueModel
,
Triggers
and a few others. Finally, two tips from now I want to finish out my look at the binding framework by discussing some helpful classes that ship with it, but aren't neccessarily directly needed. If time merits, I'll also discuss some of the extras, and if I'm feeling really persistent I'll also discuss the super-ultra-top-secret-advanced features.
Until next time,
R.J. Lorimer |