|
A sophisticated bean will have lots of different kinds of properties that it should expose in a builder tool for a user to set at design time or get at run time. It can also trigger both standard and custom events. Properties can be as simple as the fileName property that you saw in ImageViewerBean and FileNameBean or as sophisticated as a color value or even an array of data pointswe encounter both of these cases later in this chapter. Furthermore, properties can fire events, as you will see in this section. Getting the properties of your beans right is probably the most complex part of building a bean because the model is quite rich. The JavaBeans specification allows four types of properties, which we illustrate by various examples. Simple PropertiesA simple property is one that takes a single value such as a string or a number. The fileName property of the ImageViewer is an example of a simple property. Simple properties are easy to program: Just use the set/get naming convention we indicated earlier. For example, if you look at the code in Example 8-1, you can see that all it took to implement a simple string property is the following: public void setFileName(String f) { fileName = f; image = . . . repaint(); } public String getFileName() { if (file == null) return null; else return file.getPath(); } Notice that, as far as the JavaBeans specification is concerned, we also have a read-only property of this bean because we have a method with this signature inside the class public Dimension getPreferredSize() without a corresponding setPreferredSize method. You would not normally be able to see read-only properties at design time in a property inspector. Indexed PropertiesAn indexed property is one that gets or sets an array. A chart bean (see below) would use an indexed property for the data points. With an indexed property, you supply two pairs of get and set methods: one for the array and one for individual entries. They must follow this pattern:
Here's an example of the indexed property we use in the chart bean that you will see later in this chapter. public double[] getValues() { return values; } public void setValues(double[] v) { values = v; } public double getValues(int i) { if (0 <= i && i < values.length) return values[i]; return 0; } public void setValues(int i, double value) { if (0 <= i && i < values.length) values[i] = value; } . . . private double[] values; The
method cannot be used to grow the array. To grow the array, you must manually build a new array and then pass it to this method:
NOTE
Bound PropertiesBound properties tell interested listeners that their value has changed. For example, the fileName property in FileNameBean is a bound property. When the file name changes, then ImageViewerBean is automatically notified and it loads the new file. To implement a bound property, you must implement two mechanisms.
The java.beans package has a convenience class, called PropertyChangeSupport, that manages the listeners for you. To use this convenience class, your bean must have a data field of this class that looks like this: private PropertyChangeSupport changeSupport = new PropertyChangeSupport(this); You delegate the task of adding and removing property change listeners to that object. public void addPropertyChangeListener(PropertyChangeListener listener) { changeSupport.addPropertyChangeListener(listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { changeSupport.removePropertyChangeListener(listener); } Whenever the value of the property changes, use the firePropertyChange method of the PropertyChangeSupport object to deliver an event to all the registered listeners. That method has three parameters: the name of the property, the old value, and the new value. For example, changeSupport.firePropertyChange("fileName", oldValue, newValue); The values must be objects. If the property type is not an object, then you must use an object wrapper. For example, changeSupport.firePropertyChange("running", false, true); TIP
Other beans that want to be notified when the property value changes must implement the PropertyChangeListener interface. That interface contains only one method: void propertyChange(PropertyChangeEvent event) The code in the propertyChange method is triggered whenever the property value changes, provided, of course, that you have added the recipient to the property change listeners of the bean that generates the event. The PropertyChangeEvent object encapsulates the old and new value of the property, obtainable with Object oldValue = event.getOldValue(); Object newValue = event.getNewValue(); If the property type is not a class type, then the returned objects are the usual wrapper types. For example, if a boolean property is changed, then a Boolean is returned and you need to retrieve the Boolean value with the booleanValue method. Thus, a listening object must follow this model: class Listener { public Listener() { bean.addPropertyChangeListener(new PropertyChangeListener() { void propertyChange(PropertyChangeEvent event) { Object newValue = event.getNewValue(); . . . } }); } . . . } Constrained PropertiesA constrained property is constrained by the fact that any listener can "veto" proposed changes, forcing it to revert to the old setting. The Java library contains only a few examples of constrained properties. One of them is the closed property of the JInternalFrame class. If someone tries to call setClosed(true) on an internal frame, then all of its VetoableChangeListeners are notified. If any of them throws a PropertyVetoException, then the closed property is not changed, and the setClosed method throws the same exception. For example, a VetoableChangeListener may veto closing the frame if its contents have not been saved. To build a constrained property, your bean must have the following two methods to manage VetoableChangeListener objects: public void addVetoableChangeListener(VetoableChangeListener listener); public void removeVetoableChangeListener(VetoableChangeListener listener); Just as there is a convenience class to manage property change listeners, there is a convenience class, called VetoableChangeSupport, that manages vetoable change listeners. Your bean should contain an object of this class. private VetoableChangeSupport vetoSupport = new VetoableChangeSupport(this); Adding and removing listeners should be delegated to this object. For example: public void addVetoableChangeListener(VetoableChangeListener listener) { vetoSupport.addVetoableChangeListener(listener); } public void removeVetoableChangeListener(VetoableChangeListener listener) { vetoSupport.removeVetoableChangeListener(listener); } TIP
To update a constrained property value, a bean uses the following three-phase approach:
For example,
It is important that you don't change the property value until all the registered vetoable change listeners have agreed to the proposed change. Conversely, a vetoable change listener should never assume that a change that it agrees to is actually happening. The only reliable way to get notified when a change is actually happening is through a property change listener. We end our discussion of JavaBeans properties by showing the full code for FileNameBean (see Example 8-2). The FileNameBean has a constrained filename property. Because FileNameBean extends the JPanel class, we did not have to explicitly use a PropertyChangeSupport object. Instead, we rely on the ability of the JPanel class to manage property change listeners. Example 8-2. FileNameBean.java1. package com.horstmann.corejava; 2. 3. import java.awt.*; 4. import java.awt.event.*; 5. import java.beans.*; 6. import java.io.*; 7. import javax.swing.*; 8. 9. /** 10. A bean for specifying file names. 11. */ 12. public class FileNameBean extends JPanel 13. { 14. public FileNameBean() 15. { 16. dialogButton = new JButton("..."); 17. nameField = new JTextField(30); 18. 19. chooser = new JFileChooser(); 20. 21. chooser.setFileFilter(new 22. javax.swing.filechooser.FileFilter() 23. { 24. public boolean accept(File f) 25. { 26. String name = f.getName().toLowerCase(); 27. return name.endsWith("." + defaultExtension) || f.isDirectory(); 28. } 29. public String getDescription() 30. { 31. return defaultExtension + " files"; 32. } 33. }); 34. 35. setLayout(new GridBagLayout()); 36. GridBagConstraints gbc = new GridBagConstraints(); 37. gbc.weightx = 100; 38. gbc.weighty = 100; 39. gbc.anchor = GridBagConstraints.WEST; 40. gbc.fill = GridBagConstraints.BOTH; 41. gbc.gridwidth = 1; 42. gbc.gridheight = 1; 43. add(nameField, gbc); 44. 45. dialogButton.addActionListener( 46. new ActionListener() 47. { 48. public void actionPerformed(ActionEvent event) 49. { 50. int r = chooser.showOpenDialog(null); 51. if(r == JFileChooser.APPROVE_OPTION) 52. { 53. File f = chooser.getSelectedFile(); 54. try 55. { 56. String name = f.getCanonicalPath(); 57. setFileName(name); 58. } 59. catch (IOException e) 60. { 61. } 62. } 63. } 64. }); 65. nameField.setEditable(false); 66. 67. gbc.weightx = 0; 68. gbc.anchor = GridBagConstraints.EAST; 69. gbc.fill = GridBagConstraints.NONE; 70. gbc.gridx = 1; 71. add(dialogButton, gbc); 72. } 73. 74. /** 75. Sets the fileName property. 76. @param newValue the new file name 77. */ 78. public void setFileName(String newValue) 79. { 80. String oldValue = nameField.getText(); 81. nameField.setText(newValue); 82. firePropertyChange("fileName", oldValue, newValue); 83. } 84. 85. /** 86. Gets the fileName property. 87. @return the name of the selected file 88. */ 89. public String getFileName() 90. { 91. return nameField.getText(); 92. } 93. 94. /** 95. Sets the defaultExtension property. 96. @param s the new default extension 97. */ 98. public void setDefaultExtension(String s) 99. { 100. defaultExtension = s; 101. } 102. 103. /** 104. Gets the defaultExtension property. 105. @return the default extension in the file chooser 106. */ 107. public String getDefaultExtension() 108. { 109. return defaultExtension; 110. } 111. 112. public Dimension getPreferredSize() 113. { 114. return new Dimension(XPREFSIZE, YPREFSIZE); 115. } 116. 117. private static final int XPREFSIZE = 200; 118. private static final int YPREFSIZE = 20; 119. private JButton dialogButton; 120. private JTextField nameField; 121. private JFileChooser chooser; 122. private String defaultExtension = "gif"; 123. } java.beans.PropertyChangeListener 1.1
java.beans.PropertyChangeSupport 1.1
java.beans.PropertyChangeEvent 1.1
java.beans.IndexedPropertyChangeEvent 5.0
java.beans.VetoableChangeListener 1.1
java.beans.VetoableChangeSupport 1.1
javax.swing.JComponent 1.2
java.beans.PropertyVetoException 1.1
|
|