接触Swing有一段时间,搜索到JSR295项目,苦于没有中文资料,所以翻译了国外的一片文章,英文不好,有语法错误请指正。
We'll compare getting and setting bean properties in JBoss Seam with Swing, and show how easy it is in Seam, how painful it is in Swing, and how Beans Binding can save Swing from its misery. We'll end with some discussion of how easy it is to fit Beans Binding into your applications, and future explorations, and how this pattern relates to the Action
pattern we discussed earlier.
The ubiquitous Person
bean will be the backing bean in our examples:
1public class Person {
2 private String firstName;
3 public String getFirstName() { return firstName; }
4 public void setFirstName(String firstName) { this.firstName = firstName; }
5
6 // also a lastName property, and a int yearOfBirth property,
7 // with getters and setters
8}
We want to create user interface elements which can get and set properties of this bean.
JBoss Seam is great. Set up the component by adding a @Name
annotation:
1@Name("person") public class Person {
and then you can use it in your JSF pages:
1Please enter your first name:
2 <h:inputText value="#{person.firstName}"/>
and Seam handles it.
In a Swing application, the code would like like this:
1JTextField text = new JTextField();
2
3Person person = new Person();
4
5// Note the anonymous inner class, a common
6// pattern for a listener
7DocumentListener textListener = new DocumentListener() {
8 void changedUpdate(DocumentEvent e) {
9 person.setFirstName(firstNameField.getText()); }
10 void insertUpdate(DocumentEvent e) {
11 person.setFirstName(firstNameField.getText()); }
12 void removeUpdate(DocumentEvent e) {
13 person.setFirstName(firstNameField.getText()); }
14};
15
16// Call this before displaying
17void initComponents() {
18 firstNameField.getDocument().addDocumentListener(textListener);
19}
That's painful, and that idiom must be used for every single binding. Why is Swing so much more painful than a Web-based interface with JBoss Seam? Why does Swing require implementing and binding a listener (usually an anonymous inner class) for every single property? The reason is that JBoss Seam is, in essence, a sophisticated properties binding system. To be as easy as Seam, Swing needs something similar.
Swing has languished since its introduction with Java 1.2 in December of 1998. One of the reasons for Swing's doldrums was this need to write tedious listener code to bind front-end components to beans. Beans binding solves this problem. Straight to the code. I'll replace
DocumentListener
with a beans binding like this:
1final Property textProperty = BeanProperty.create("text");
2
3// Now do a simple binding of the UI elements to the
4// Person bean for firstName and lastName properties
5Binding binding =
6 Bindings.
7 createAutoBinding(AutoBinding.UpdateStrategy.READ, // one-way binding
8 firstNameField, // source of value
9 textProperty, // the property to get
10 person, // the "backing bean"
11 BeanProperty.create("firstName") // property to set
12 );
13binding.bind();
The code is shorter and clearer.
Note that
Property
objects are immutable, so you don't need to keep on creating them. The text property can be used to get the
text
property of any bean (ie, any bean with a
getText()
method).
Beans binding gets even better. You can use Expression Language (EL) to create properties:
1binding =
2 Bindings.
3 createAutoBinding(AutoBinding.UpdateStrategy.READ,
4 person,
5 ELProperty.create("${firstName} ${lastName}"),
6 fullName,
7 textProperty
8 );
Note that this is full EL, so dot notation can be used to traverse objects. If every Person had a Pet, we could use:
1ELProperty.create("${pet.name} the ${pet.species}")
to bind a property to display a helpful string about the pet.
All Swing components are beans, with change listeners, so Beans Binding "just works" on them. Even better, NetBeans 6 comes with great support for beans binding. Right click on the component you want to use as the
target and you can bind it to a source without writing any code.
Unfortunately, in NetBeans, only Swing components can be targets. That's great if you want to link a slider to a text field, so the user could slide from 0 to 100, and the text field will show the corresponding number. However, the most common real-world use for beans binding will be not linking UI components to other UI components, but rather, linking UI components to "backing beans". NetBeans 6 doesn't have a point-and-click way of doing that.
But it's easy to do by hand. To use your data bean as a
target, just use it. To use it as a
source, you'll need to add one more thing: a support for
PropertyChangeListener
s. Let's say some UI component is bound to the Person's firstName. The UI component changes. Under the hood, the Beans Binding works by registering itself as a
PropertyChangeListener
. Swing components fire
PropertyChangeEvent
s when properties change, so the whole thing happens invisibly when the
source is a Swing component.
When the source is a bean written for the application, you'll need to add your own support for
PropertyChangeListener
s. This is easy to do, using the
PropertyChangeSupport
class. Look at the source code of our
Person.java class.
The essential change is the addition of the addPropertyChangeListener()
and removePropertyChangeListener()
methods. The beans binding framework uses introspection to look for classes with those two methods. If both of those methods are found, beans binding registers the binding as a
PropertyChangeListener
, so it will be aware of changes to the property. Implementing these two methods is trivial; see the source code.
Now the Person can be both the source and target for bindings, so our application looks like this:
In this example, I'm using a GridLayout
. It's ugly. In any real application, I would use NetBeans Matisse, to create beautiful resizable platform-independent interfaces. However, I wanted to keep this code short and simple to focus on the beans binding, without cluttering the example with layout concerns.
The savings in lines of code is not great, but the savings in readability, clarity, and verifiability is great.
Now we move on to some more advanced features: adding a validator for that year-of-birth field. Our validation rule is simple: the year of birth can't be in the future.
First write the validator:
YearValidator.java. This class extends the
Validator
abstract class, which has one method:
1public abstract Validator.Result validate(Object argument)
If validation succeeds, return null. Otherwise return a result with a descriptive message.
Attaching this validator to the binding requires a twist. It's natural to think of the UI element as the "source" of the data and the bean as the "target" of the data. However, validation occurs on the write-back from the target to the source. Therefore, to use a validator, we must reverse the arguments in the binding, and set the mode to
READ_WRITE
, as follows:
1binding =
2 Bindings.
3 createAutoBinding(AutoBinding.UpdateStrategy.READ_WRITE, // two-way binding
4
5 person,
6 BeanProperty.create("yearOfBirth"), // property to set
7
8 yearOfBirthField, // source of value
9 textProperty, // the property to get
10
11 "yearOfBirth");
12// note that the converter and validators must be installed
13// before calling bind()
14binding.setConverter(new PositiveIntegerConverter());
15binding.setValidator(new YearValidator());
16// this allows us to get sync failure notifications,
17// so they can be displayed
18binding.addBindingListener(this);
19binding.bind();
The source and target arguments are reversed. NetBeans 6's Matisse makes it easy to turn any Swing component into a target, making this type of use point-and-click easy.
The call to
binding.bind()
happens after the validator and converter are added. And note that we use
binding.addBindingListener(this);
to add a
BindingListener
. The
PersonFrame
class, which is also the
JFrame
, has been modified to implement the methods of the
BindingListener
interface. The method we need to catch validation errors is the
1public void syncFailed(Binding binding, Binding.SyncFailure failure)
method. This is what it looks like:
Now we have seen how to use beans binding to easily link UI elements with data beans. We've seen how to use EL to create new properties as combinations of or operations on old properties. We've seen how to use a converter and a validator. That's only the beginning of the beans binding framework.
The main advanced features of the beans binding framework is the ability to bind to collection-style components, such as
JTable
,
JList
and
JComboBox
. I won't say more about the collection components in this article. In a future article, I'll build a demo of using those components with beans binding. The advantages of using beans binding for simple component are evident. With
JTable
components, the advantages are even greater. I will also discuss design considerations. How do you build a desktop application to interact with a shared server? The obvious solution, with built-in support in NetBeans, is to bind front-end
JTables
to database tables. This method should not be used in real-world desktop applications, however. I'll explore the alternatives to figure out the right way to do this.
Finally, beans binding is called a "framework". The word "framework" brings to mind heavy-weight containers, extending or implementing special classes, threads, a server, tricky XML configuration files, etc. None of these things are present in beans binding. Beans binding works with plain old classes which follow the bean patterns, including, conveniently, all the Swing UI components. The beans binding framework requires only including a small
JAR file and creating properties. No server, no configuration, no changes to your existing body of code or deployment practices. It's easy to use, too. I hope after seeing this article, you'll try out the beans binding framework in your project.
In a previous post, we discussed the
conventional wisdom for adding functionality to Buttons. The traditional way is to write a
ActionListener
and bind it to the
JButton
with
addActionListener()
. We showed that this approach is cumbersome, and usually wrong, and the right way is to
add an AbstractAction to the JButton. Writing listeners on input components is also cumbersome. Beans binding could be thought of as the correct alternative to change listeners on input UI components, just like
AbstractAction
s are the correct alternative to
actionPerformed
listeners on command UI elements.
I expect that EL and the beans binding framework (JSR 295) will find their way into a future Java release, but there's no reason to wait to start using it.
Download the full sourcecode for this article: example.zip