|
|
|||
Scott Violet's BlogCommunity: JDK ArchivesEase of Swing Development - Beans BindingPosted by zixle on May 23, 2006 at 03:25 PM | Permalink | Comments (42)With few exceptions (Web Start and Beans Persistence) there have been very few desktop related JSRs. Well, we're going to change that. Beans Binding, or JSR 295, has just passed inception ballot and the expert group is now forming. Wahoo! Beans BindingA big part of what we, desktop developers, have to write every day is component wiring code, or glue code. Glue code is the code that connects components to your application model. For example, connecting a slider's value propety to a property of the selected object in a table requires glue code. Here's an example.
slider.addValueListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
selectedObject.setFoo(slider.getValue());
}
});
selectedObject.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChanged(PropertyChangeEvent e) {
if (e.getPropertyName() == null || "foo".equals(e.getPropertyName())) {
slider.setValue(selectedObject.getFoo());
}
}
});
Anyone that's written this code, and we all have, knows it's painful,
tedious and error prone. YUCK!
Beans Binding aims to make it easy to bind two properties of two objects together. Taking the slider example, you might be able to bind the two together with the following code. bind(slider, "value", selectedObject, "foo");This would bind the slider's value property to the selectedObject's foo property, such that changes to the slider are reflected in the selectedObject, and vice versa. Take this as just an example, the JSR was just filed, so no API has been decided upon. Of course this is a simplified view of things, beans binding will likely accomodate converters, and possibly validators. To be successful it will also need to accomodate renderers. At this years JavaOne conference Hans and myself did a presentation on beans binding with a handful of demos. You can view the presentation here. The source for the first demo can be found here. Notice the source contains two variants, one implemented in terms of binding (BindingCaricatureController), the other with typical listeners (NoBindingCaricatureController). You can run the demo by clicking on the launch button below.
We also demoed how beans binding addresses more traditional database apps at JavaOne, but being a database app, it requires a bit more setup so I'm holding off on publishing it now. I'll return to it later on. Data BindingHow does this relate to data binding, and specifically JSR 227: A Standard Data Binding & Data Access Facility for J2EE?WARNING: I am not on the expert group for 227, and do not speak for Mike De Groot, the spec lead for 227. Mike was gracious enough to meet with me on various occasions to help me understand where he believes 227 is going. The following is based on those conversations, but as 227 is not finalized, it may change. JSR 227 aims to make it easy to bind to any data, be it a ResultSet an XML service, you name it. 227 will not care about the type of your data, there will be an intermediary class used to access the data that knows the specific of your data. In this manner, assuming you have the intermediary layer, 227 will work with any data. Beans Binding is much narrower than that, it assumes you have your data in a well known type, a bean and possibly a Map. A big part of 227 includes declaratively describing the bindings and data, Beans Binding will not include a delcarative XML file describing the bindings and data. So, why care about beans binding? If you're a good OO citizen you've already created some mapping layer from your database (or other backend) to real objects. Hibernate, Mustang's DataSet, and EJB 3 all do this; they provide a way for you to get real object graphs. Once you've done that, it'll just work with beans binding and the world will be a happier place. Ok, perhaps that's a bit grand, but at the least beans binding will simplify wiring your application objects to components. What can you do?As I mentioned, beans binding will be done as a JSR. If you are interested in helping shape the API, and have time to be on an expert group, head over to the home page for beans binding and click on the I would like to join this JSR link. For those that are interested, but can't commit the time, watch the home page for 295 for updates. Within the next month or so we'll settle on a java.net project for the prototype including public forums and what not.Architecting Applications 3: the ControllerPosted by zixle on March 06, 2006 at 04:24 PM | Permalink | Comments (16)This is the third blog in a series on architecting applications. In the first blog I discussed the application I'm going to develop, how it would be architected, and briefly went over the model. In the second blog I motivated the need for an Application class that is suitable for typical Swing based Apps, as well as the functionality it should provide. In this third installment I'll go over the role of the controller as used in the MVC architecture. As promised, this blog has a runnable demo. I was strongly tempted to redo the model a bit for this installment of the application architecture series, but I had to hold back lest there be no demo for this article. So, without further ado, here's the demo.
The demo is far from complete. In particular most of the menus are not wired up and are disabled. The only menu items that work is creating a new account (control-n). I'll flesh out the implementation of the remaining menu items in future articles. Complete source code is available at the end of the article. To keep the app from being signed, but be able to store information between sessions I'm using the persistence service provided by webstart. The persistence service allows an app to save state in a secure way, similar to cookies. If you run the app from the command line it'll save the file into your home directory. ControllerAs early explained this app is built using the model-view-controller pattern. As the name implies the pattern dictates you design three distinct areas: the model, for modelling your data; the view, for the graphical view the user will interact with; lastly, the controller which orchestrates the two, keeping them in sync. For the purposes of this demo the Controller will perform the following operations:
Creating the UICreating and assembling the view is pretty much route Swing programming. As earlier mentioned I'm not going to have any external dependencies, so I wrote this code by hand using GridBagLayout. I honestly don't recommend hand coding UI creation or layout, it's painful, error prone, and a maintenance burden. Use a builder. Of course I recommend Matisse and once done will will publish a followup blog that's written in terms of Matisse.JList WiringThe model consists of a List of PasswordEntrys. This will be rendered as a JList. JList itself requires a model which implements the ListModel interface. To display the List of PasswordEntrys a custom ListModel implementation will be used that talks to the List of PasswordEntrys from the model. Here's the code.
private PasswordModel model;
private class PasswordListModel extends AbstractListModel {
public int getSize() {
return model.getPasswordEntries().size();
}
public Object getElementAt(int index) {
return model.getPasswordEntries().get(index);
}
}
Notice the ListModel returns a PasswordEntry for each of the
elements. JList's default cell renderer will use toString on this,
which isn't all that helpful here. As such we'll need a custom
ListCellRenderer. I'm not going to go over the code for the
CellRenderer, it's pretty simple, if you're curious look to the
source.
SynchronizationKeeping the model and view in sync is one of the more tedious aspects of an application of this sort. To reinforce that, consider what happens when the user types in a text field. As the user types in a text field the model must be updated. As I wanted the model to be updated immediately I'm using a DocumentListener. DocumentListeners are notified immediately of any changes to a text component. Here's the code.
// Install a DocumentListener on the host text field
hostTF.addDocumentListener(new DocumentHandler());
private class DocumentHandler implements DocumentListener {
public void insertUpdate(DocumentEvent e) {
edited(e);
}
public void removeUpdate(DocumentEvent e) {
edited(e);
}
public void changedUpdate(DocumentEvent e) {
// TextFields can ignore this one.
}
private void edited(DocumentEvent e) {
// The host text field was edited, update the model
hostChanged();
}
}
// A valid edit has occured, update the model.
private void hostChanged() {
getSelectedEntry().setHost(hostTF.getText());
}
The real source code is slightly different in so far as I've coalesced
all the listeners into a single class, which leads to a switch like
statement in the edited method. None-the-less the core idea is the
same. As the document is edited the model is immediately updated.
The Controller must also track changes made to the model by other parts of the app. For example, an Action might directly update the model. To track changes to the model the Controller installs a PropertyChangeListener on each PasswordEntry. Here's the code.
// Install a listener on each entry
entry.addPropertyChangeListener(new PropertyChangeHandler());
private class PropertyChangeHandler implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
entryChanged((PasswordEntry)e.getSource(), e.getPropertyName());
}
}
private void entryChanged(PasswordEntry passwordEntry,
String propertyChanged) {
// Notify the list of the change
listModel.fireContentsChanged(model.getPasswordEntries().
indexOf(passwordEntry));
// If the selected entry was changed, update the appropriate text field
if (getSelectedEntry() == passwordEntry) {
if (propertyChanged == "host") {
hostTF.setText(passwordEntry.getHost());
} else ...
}
}
A PropertyChangeListener is attached to
every entry in the model. When an entry is changed, ListModel notification
is dispatched (listModel.fireContentsChanged), which triggers the JList to
update itself. Similarly if the contents of the selected entry have
changed the appropriate text field is updated.
The following graphically depicts changes originating from the text field.
The following depicts changes originating from the model.
What's wrong with the previous code? Turns out there is a subtle bug. When the text field changes, we update the model, which sends notification, which triggers a reset of the text field. JTextField (actually javax.swing.text.Document) does not allow a listener to modify the contents, so that this code triggers Swing to thrown an exception. Here's a graphical depiction of this.
Notice the textfield on both sides. This is happening because the Controller doesn't realize that it initiated the change to the model and doesn't need to update the display. There are a couple of schemes for dealing with this. You can disable and reenable listeners, check the contents of the text fields, or keep a property around. I've settled for keeping a property around that tracks if the Controller initiated a change to the model. Here's the revised code for the edited method that is invoked when the text is edited.
private void hostChanged() {
if (!changingHost) {
changingHost = true;
getSelectedEntry().setHost(hostTF.getText());
changingHost = false;
}
}
Now the hostChanged method knows who originated the code. If
changingHost is true it indicates the change was initiated by the
Controller and the Controller need not do anything.
The entryChanged method is similarly updated.
private void entryChanged(PasswordEntry passwordEntry,
String propertyChanged) {
// Notify the list of the change
listModel.entryChanged(model.getPasswordEntries().
indexOf(passwordEntry));
if (getSelectedEntry() == passwordEntry) {
if (propertyChanged == "host" && changingHost) {
changingHost = true;
hostTF.setText(passwordEntry.getHost());
changingHost = false;
} else ...
}
}
This is most definitely one of the more tedious aspects of GUI
programming. One forgotten !changingXXX or changingXXX=true, and
you'll either get an exception, or you'll stop updating the
model or UI. Ugh!
The last thing worth mentioning is that as List has no notification the Controller can not listen to it. Instead all mutations to the list itself are done in Controller which will update the ListModel appropriately. I will revist this later on. SourceI've broken the source into two chunks: fabric.zip contains the generally useful stuff, and passwordStore.zip contains the source specific to PasswordStore.SummarySo, where are we? I've gone through the model, I've touched on the Application class, and went through the bulk of the controller. At this point the app is usable, but by no means a complete app. Next time around I'll either go over undo, or cut/copy/paste. Which would you prefer?Mustang: the little thingsPosted by zixle on February 15, 2006 at 08:08 AM | Permalink | Comments (6)I'm taking a brief hiatus from my series of blogs on application architecture to join in the mustang blog carnival extravaganza that is celebrating the beta release of mustang. The bulk of the major features have already been covered. Chet and myself have covered true double buffering, Shannon has covered the dnd work, SwingWorker was covered in an article, table's sorting and filtering API was discussed as well. I've even covered some of the smaller stuff, like the action improvements. So, what to talk about? After struggling with what to cover, I realized that some times it's not just the big things that make a release matter, but a culmination of both the big things and the little things. So, in this blog I'll cover the little bugs and features. The things that by themselves aren't that big, but undoubtedly make the release that much better. ButtonGroup.clearSelectionAnyone that has seriously used ButtonGroup undoubtedly stumbled over the inability to clear the selection of the group. For example, lets say you have two toggle buttons in a button group and you need to clear the selection to an initial state. Previously there was no way to do this! Sure, there are some ugly workarounds, but thankfully we've addressed this problem now. In mustang we've added the method clearSelection to ButtonGroup. Funny enough this is the oldest bug we've fixed for Swing in mustang. Justified TextOne of Swing's most voted on bugs is support for fully justified text in Swing's text components. What's doubly bad about this feature is that we've always had API that implies it would work, but it never had. Well, thankfully it now does. Here's a screen shot of Stylepad in mustang with fully justified text:
YIKES! I know, it turns out we justified the last line of the text which obviously doesn't look good. Thankfully this bug will be fixed before the final release of mustang. Here's a screenshot with the fix:
Window.setMinimumSizeAnother long time sore point is the inability to enforce a minimum size for Windows. Being a long time NeXT/Apple user I've always thought it a good idea to enforce a minimum window size. That doesn't appear to be too common on Windows boxs though. None-the-less the API now works and you can set a minimum size. Here's an internal app that can't enforce a minimum size:
And that same app with mustang when it could enforce a minimum size:
Nice! Painting offscreen componentsGetting into the more niche arena, Swing's painting code has always ignored painting child components if the Component is not contained in a Window or an Applet. Why would you ever do this? Perhaps you want to render a JComponent, and it's children, to an image. This came up a number of times on the forum. Finally it's fixed! I know, a bit obscure but none-the-less interesting. See 6215148 for details. JTextComponent printingThe last thing I'll mention is certainly not a small change. Following the JTable printing support we added in 1.5 we've now made it trivial to print text component. JTextComponent now has a print method that allows you to specify various attributes of the print job, such as header, footer... This means it's now trivial to print a text component and have the output reformatted for the page, just like a user would expect! So anyway, if you haven't yet tried mustang I highly recommend you give it a whirl. As an incentive we announced a free t-shirt for any regressions that are found as well as the chance at winning an Ultra 20, sadly this offer isn't availble to Sun engineers;) Next time around, more application architure. -Scott Architecting Applications 2: the Application classPosted by zixle on January 30, 2006 at 03:03 PM | Permalink | Comments (36)This is the second blog in a series on architecting applications. In the first blog I discussed the application I'm going to develop, how it would be architected, and briefly went over the model. In this second article I'll motivate the need for an Application class that is suitable for typical Swing based Apps, as well as the functionality it should provide. Swing is an incredibly rich toolkit, but to date we haven't provided much guidance on how to structure your app. How should I start my app? Where should I place resource bundles in my project? How should I use preferences? Logging? ... The list goes on. While I'm not going to address all of these issues I'm going to focus on the first issue. How should I start my app? The first thing to comes to mind is something along the lines of:
public class Main {
public static void main(String[] args) {
JFrame frame = new JFrame(...);
frame.pack();
frame.show();
}
}
Unfortunately this code can be problematic. Swing is not thread safe
and unless otherwise mentioned in the javadoc all methods must be
invoked on the event dispatch thread. As it's name implies, the
main method is invoked on the main thread. The preceeding code
calls into Swing widgets from this thread - the main thread. As
such, it is entirely likely that wierd errors or exceptions can
occur. Why you ask? Invoking various Swing/AWT methods can result
in events getting posted to the event dispatch thread. If the
thread scheduler happens to schedule processing of those events
while the main thread is still invoking methods you'll have two
threads interacting with the same Swing component at the same
time - a definite no-no. Best case, it all works and you're fine,
worst case you get random deadlocks!
We currently recommend an approach along the following lines:
public class Main {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
But this gets to be a bit painful to remember every time
through. Ideally there would be a class that isolates you from the
various Swing threading vagaries. Furthermore, it would be great
if this class were seamlessly integrated with builders.
Here's how I would like to write main:
public class Main {
public static void main(String[] args) {
new MyApplication().start();
}
}
And MyApplication would have methods intended to be overriden that
are called on the event dispatch thread. This way I don't have to
do the invokeLater everytime, it's handled for me.
Beyond handling proper initialization there are a number of other
features common across Swing apps. In no particular order they
are:
I suspect anyone that's developed a number of Swing apps has already developed a similar class. You can find the version used in this article here, or peruse the doc of Application and ApplicationListener. Application itself is an abstract class, a typical subclass, and the one for this blog series, looks like:
public class PasswordStoreApplication extends Application {
public String getName() {
return "PasswordStore";
}
protected void init() {
JFrame frame = new JFrame(getName());
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
exit();
}
});
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
new PasswordStoreApplication().start();
}
}
Notice main, it's now short and sweet. While not obvious, init is
invoked on the event dispatch thread and I needn't worry about
using invokeLater, it's all done for me! Similarly the look and
feel is automatically set to the system look and feel for me from
the preInit method.
Those that look at the code will notice a couple of hard code strings. Bad Scott, bad Scott! I did this to make it easier for folks to download the source and use, but this most definitely needs to come from a resource bundle. You've been warned. I've tried to keep Application simple. It doesn't know about Frame or any other application specific components, that's up to subclasses. In fact it probably makes sense to have a subclass that knows about a single frame. Such a subclass would further reduce the code of PasswordStoreApplication as registering the window listener, packing and all that would be done for you. One of the biggest open questions with a class like Application, is should it do dependency injection? That is, should Application contain meta information about your application that can be used in configuring it. For example, database style apps need to know what database to connect to. Does it make sense to have Application configure your connection settings so that there is a standard place for this meta information? I'm not sure, but I'm certainly interested in feedback from the community. A related issue I mentioned is how this class would work with builders. Ideally one would be able to create a 'Swing Application' which in turn would configure things like the name of the application and perhaps a class to create on the EDT. In this way developers needn't subclass Application directly, the builders would do the wiring for you. More research is needed on this. Hans suggested rather than using listeners you have the ability to register Runnables given a name, with the Runnables processed at well known points in the app life cycle. This would make for a more loosely specified init sequence, but one that allows for a bit more than listeners. I'll explore this in more detail later. In the interest of keeping each blog short and sweet I'm going to wrap this one up here. I had hoped to have something running by this point, but I'll leave that to the next blog. In the next blog I'll create a controller class along with the UI that ties the model together and is started with the Application subclass. I promise that by the end of the next article you'll be able to click on a web start link to see the app running. Architecting Applications 1: the modelPosted by zixle on January 18, 2006 at 04:33 PM | Permalink | Comments (18)While in Russia (see Shannon's blog) I wrote a long blog on undo, actions and various other things. At the time I felt this a bit long for a blog and so it languishes in my unposted blog queue (with many others, but that's a different story). I really liked the idea of writing a series of blogs centered around a real app though, hence this blog and hopefully more to follow. As part of the series I'll cover architecting the app (this blog) including using beans persistence as a way to save the model, Actions, Undo, the need for an application class, data binding and various other things that come up. In writing a series of blogs around an app I thought it important that the app be compelling enough, but not too big such that you become bogged down in all the implementation details. So, I needed something small but not too small as to be trivial. I may have erred on the side of trivial, none-the-less the concepts and points are still worth while to explore. If you're like me you have a ton of passwords for various machines and web sites that you're expected to remember. As an added wrinkle each password should be unique and unreadable, ugh! Added together this makes it nearly impossible to remember all your passwords. For this series I'm going to stick with a simple app that manages passwords. A Java based app that stores passwords has the added bonus of being able to run from a USB memory stick on the three major platforms: Windows, OS X and Linux. I realize there are loads of commercial apps in this space that undoubtedly go way beyond what I'm going to do. None-the-less such an app is at least interesting and not too complicated (at least as far I'm going to take it). Here's roughly where I'll end up when done with the series:
A password store will need to be encrypted, I'm going to leave that out as I don't want to worry about the legal issues of such a blog. There are various approaches to designing apps. Some of the more popular patterns are model-view-presenter , presentation model and model-view-controller. I'm a fan of the latter, and especially for an app of this size. In creating any app you'll need to make the desicision as to which approach is applicable to your design space and requirements. Model-View-Controller dictates that you have a clear model for your application. The Controller coordinates keeping the view and model in sync. The model for Password Store is going to be trivial. It'll consist of a List of PasswordEntrys. Each PasswordEntry will have the following properties: host, user and password. PasswordEntry will be a proper bean in that it'll communicate changes to listeners by way of a PropertyChangeListener notification. That means any time you change a property all PropertyChangeListeners are notified of the change. Here's the code for setting the host property:
public void setHost(String host) {
String oldHost = this.host;
this.host = host;
if (propertySupport != null) {
propertySupport.firePropertyChange("host", oldHost, host);
}
}
The methods for setting the user and password follow the same pattern. This may seem like a lot of boilerplate code for something so trivial, and I would agree. I'll try and address that in a later blog.
The List of PasswordEntrys will be managed by a PasswordModel. PasswordModel will initially expose the List as modifiable. I suspect this is a bad long term decision, but I'm going to stick with it in hopes of keeping the API simple and not getting bogged down in details. Here's the code:
public class PasswordModel {
private List
The biggest downside with choosing List is that it doesn't notify listeners of changes to the contents. This is key, and something I'll touch on later.
An added bonus of making the PasswordEntrys proper beans is that I can save and restore the PasswordEntrys using beans persistence. Here's the code:
public void load(String file) throws IOException {
BufferedInputStream inputStream = new BufferedInputStream(
new FileInputStream(file));
XMLDecoder decoder = new XMLDecoder(inputStream);
List entries = (List)decoder.readObject();
decoder.close();
passwordEntries.clear();
passwordEntries.addAll(entries);
}
public void save(String file) throws IOException {
BufferedOutputStream outputStream = new BufferedOutputStream(
new FileOutputStream(file));
XMLEncoder encoder = new XMLEncoder(outputStream);
encoder.writeObject(passwordEntries);
encoder.close();
}
At this point we have the model, which by itself isn't all that interesting. Next time around by the end of the blog we should have something that you can at least run.
Variations of JPasswordFieldPosted by zixle on December 05, 2005 at 03:37 PM | Permalink | Comments (2)We've been gathering input from customers on various features they would like to see us implement. At such a meeting one customer requested a variation of JPasswordField. I figured it would be interesting to blog on how you could modify JPasswordField to vary the level of feedback it gives you. I'll cover various pieces of Swing's text architecture in implementing one variation, as well as a demo. If you want to cut to the chase, and have 1.5 installed, here's the demo. Variation 1: no feedbackJPasswordField allows you to change the character it uses as the echo character. That is, regardless of what character the user types you'll see the echo character in it's place (1). A simple variation on JPasswordField that makes it harder for folks to guess at what you're typing is to change the echo character to a character that takes up no space. You can use '\u200b' for this. This means that someone watching you type at a password field won't be able to tell how many characters are in your password. Of course they can still watch you type at the keyboard, and the user gets very little feedback that they're actually typing. Here's the code for this:
passwordField.setEchoCharacter('\u200b');
Variation 2: variable amount of spaceFor those that remember NeXT's login dialog this'll be familiar. Rather than using a single echo character a random amount of white space will be used for each character. This has the benefit over the previous approach in that you can't readily determine how many characters are in the password, but you get some level of visual feedback as you type your password. Apple's latest login dialogs don't use this scheme, which would seem to indicate many folks didn't like it.All of Swing's text components, be it JPasswordField, JTextField, JEditorPane share the same architecture. What applies to one equally applies to the others. The text component use an instance of View to do the rendering of text. In some ways a View can be thought of as a lightweight Component, for example, View has methods like getPreferredSpan, getMinimumSpan, paint... Just as Containers are typically nested Views are usually nested as well. Views are responsible doing the following:
private static final int MIN_WIDTH = 2;
private static final int MAX_WIDTH = 15;
private List<Integer> widths;
private Random widthGenerator;
private final int generateCharacterSize() {
return widthGenerator.nextInt(MAX_WIDTH - MIN_WIDTH) + MIN_WIDTH;
}
Swing's text components use Document for modeling the text. As you
type at a text component the text is inserted into the Document. As
the Document is mutated the Views are notified (as long as the
document is associated with a JTextComponent). This allows the Views
to stay in sync with the model. VariableWidthPasswordView will
override the two View methods insertUpdate and removeUpdate, which are
invoked as text is inserted or removed. The implementation of these
two methods is roughly the same, they will update the cache of widths
and trigger a repaint. Here's the implementation of insertUpdate:
// Invoked when text has been inserted. Updates the width List appropriately
public void insertUpdate(DocumentEvent changes, Shape a, ViewFactory f) {
for (int i = 0; i < changes.getLength(); i++) {
// Notice the autoboxing of int to Integer here.
widths.add(i + changes.getOffset(), generateCharacterSize());
}
// Invoke super to update the visibility appropriately.
super.insertUpdate(changes, a, f);
}
Invoking insertUpdate on super is done to update JPasswordFields
visibility model. It will also signal the preferred size of the View
has changed and repaint.
VariableWidthPasswordView doesn't need to paint, so it no-ops the appropriate paint methods. The primary View method for painting is paint. VariableWidthPasswordView is a subclass of FieldView which implements paint to invoke drawSelectedText and drawUnselectedText. VariableWidthPasswordView will override drawSelectedText and drawUnselectedText to do nothing. This is done rather than overriding paint as paint is also responsible for invoking the appropriate methods to render the selection. By overriding drawUnselectedText and drawSelectedText to do nothing the selection is still rendered appropriately by FieldView. To map from a position of text in the model to that of the view VariableWidthPasswordView overrides modelToView. modelToView uses the List of widths to determine the position. Here's the implementation:
public Shape modelToView(int pos, Shape a, Position.Bias b)
throws BadLocationException {
// JPasswordField and JTextField implement their own virtual
// coordinate system. This does the necessary mapping.
Rectangle bounds = adjustAllocation(a).getBounds();
bounds.x += modelToView(pos);
bounds.width = 1;
return bounds;
}
// Returns the view x coordinate in the views space for the specified
// model coordinate.
private final int modelToView(int p1) {
int offset = 0;
int end = Math.min(widths.size(), p1);
for (int i = 0; i < end; i++) {
// Notice the auto unboxing here.
offset += widths.get(i);
}
return offset;
}
viewToModel does the inverse mapping:
public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) {
a = adjustAllocation(a);
Rectangle bounds = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds();
int x = (int)fx - bounds.x;
for (int i = 0; i < widths.size(); i++) {
// More auto unboxing.
x -= widths.get(i);
if (x <= 0) {
return i;
}
}
return getEndOffset();
}
The last thing to mention is calculating the preferred size for the
View. That's done in terms of the widths field as well:
public float getPreferredSpan(int axis) {
if (axis == View.X_AXIS) {
int size = 0;
for (Integer width : widths) {
size += width;
}
return size;
}
return super.getPreferredSpan(axis);
}
Now that we have the View implementation we need to modify
JPasswordField to use it. Views are created by a ViewFactory, which is
obtained from the EditorKit. It's up to the TextUI implementation to
provide the EditorKit and ViewFactory. Unfortunately this means
creating a custom UI to plug-in
VariableWidthPasswordView. BasicPasswordFieldUI is itself a
ViewFactory, so that by subclassing BasicPasswordFieldUI we can
plug-in VariableWidthPasswordView. Here's the code for it:
// We generally discourage using UIs from one look and feel
// with another look and feel. When using metal this will be fine,
// that may not be true when using other look and feels.
JPasswordField myPasswordField = new JPasswordField(10) {
public void updateUI() {
setUI(new BasicPasswordFieldUI() {
public View create(Element elem) {
return new VariableWidthPasswordView(elem);
}
});
}
};
The complete source for VariableWidthPasswordView can be found here. And the demo showing the various takes on
password field can be found here. For more
information on Swing's text component take a look at the Swing
tutorial section on Using
Text Components or Tim's article on Using
the Swing Text Package.
1: As this demo shows it's possible to write a UI that ignores the
echo character. All of Sun's UIs honor the echo character, but third
party look and feels may not.
Changes to Actions in 1.6Posted by zixle on November 21, 2005 at 08:16 AM | Permalink | Comments (15)In my last two blogs I've covered various aspects of Actions. I had originally wanted to write about the changes to Actions in 1.6 but felt the background would make interesting blogs. I promise this is the last blog on Actions for a while;) In 1.6 we've overhauled Actions adding new features and fixing a handful of annoying bugs. For this blog I'm going to cover the new features and when you might use them.
Action.SELECTED_KEYThe single biggest request for Actions was to add support for Actions to represent selected state. In particular when an Action is attached to a JRadioButton, JCheckBox, JToggleButton and the like there was no way to change the selected state of the component from the Action and have it reflected in the component. The new key Action.SELECTED_KEY takes care of that. All components that visually represent a selection will now listen for changes to the SELECTED_KEY and reflect that in the component. The following code creates a JRadioButton from an Action that is initially selected: Action action = new MyAction(); // Make the action selected. action.putValue(Action.SELECTED_KEY, Boolean.TRUE); JRadioButton selectedRadioButton = new JRadioButton(action);The SELECTED_KEY property is a bit different than other Action properties. In particular the SELECTED_KEY property is both read and set from the component; none of the other Action properties have this characteristic. For example, if the user clicks on the radio button to make it selected, the SELECTED_KEY property is set on the Action from the component. The following example shows how an Action could customize it's behavior based on the selected state:
class BoldAction implements Action {
public void actionPerformed(ActionEvent e) {
toggleBold((Boolean)getValue(Action.SELECTED_KEY));
}
}
Action.LARGE_ICON_KEYAll buttons and menu items have the ability to show an Icon. When a button or menu item is associated with an Action the button or menu item uses the Icon from the Action. This is problematic because typically you want two different icons for these two components. To address this in 1.6 we added LARGE_ICON_KEY. Menu items will only use the Icon from the SMALL_ICON property. Buttons will first look for the LARGE_ICON_KEY property, if it's value is non-null it will be used, otherwise the value from SMALL_ICON is used. This enables you to customize the Icon for the two different components. The following example creates an Action with two different icons. The menu item will use smallIcon and the button largeIcon: Action action = new MyAction(); action.putValue(Action.SMALL_ICON, smallIcon); action.putValue(Action.LARGE_ICON_KEY, largeIcon); // menu item will use smallIcon new JMenuItem(action); // button will use largeIcon new JButton(action);Note: the term button is a bit overloaded. AbstractButton is the superclass of both menu items and classes such as JButton, JToggleButton, JRadioButton ... In this context when referring to button I mean all non JMenuItem subclasses that extend AbstractButton. Action.DISPLAYED_MNEMONIC_INDEX_KEYThis is useful for the rare cases where you don't want the first occurence of a character in the text to be underlined. For example, a menu item with the text 'Save As' typically has the second 'a' underlined. The following code shows how to do this:
Action action = new MyAction(); action.putValue(Action.NAME, "Save As"); // Notice the autoboxing here. action.putValue(Action.MNEMONIC_KEY, KeyEvent.VK_A); action.putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, 5); new JMenuItem(action);Be careful when using DISPLAYED_MNEMONIC_INDEX_KEY. In particular if you change the mnemonic you should be sure you change the DISPLAYED_MNEMONIC_INDEX_KEY as well. AbstractButton.setHideActionTextThis property allows you to specify whether or not AbstractButton should show the NAME property from the Action as the buttons text. Consider if you have a menu item and toolbar button attached to the same Action. To have non-empty text on the menu item the Action will have a non-null value for it's NAME property. If you don't want the toolbar button to show the text you would invoke setHideActionText(true). The following code illustates this:Action action = new MyAction(); action.putValue(Action.NAME, "Save As"); new JMenuItem(action); JButton toolbarButton = new JButton(action); // We don't want the button to mirror the text from the Action. toolbarButton.setHideActionText(true); System property swing.actions.reconfigureOnNullThe beans specification indicates that anull property
name can be used to indicate multiple values have changed. By default
Swing components that take an Action do not handle such a
change. To indicate that Swing should treat null
according to the beans specification set the system property
swing.actions.reconfigureOnNull to the
String value true.
This property is useful for cases where you want to do a bunch of changes to an Action without sending individual property changes, and then have all listeners refetch properties when done. I honestly can't say I have ever had a need for this functionality, but it's now there. The javadoc for Action now details all this information as well as how the various components support the different Action properties.
As usual, if you want to try out these changes (and the rest of the
goodness in 1.6) download the source and binaries from mustang snapshot release.
The Usefulness of ActionsPosted by zixle on November 01, 2005 at 07:58 AM | Permalink | Comments (10)I originally intended to blog on the usefullness of WeakReferences for An ActionListener encapsulates application specific functionality that
private class GoBackActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
browserComponent.show(getLastURL());
}
}
backButton.addActionListener(new GoBackActionListener());
backButton.setIcon(...);
backButton.setEnabled(false);
If you've written any Swing/AWT application chances are you have code Consider what happens if you want to conditionally enable the button The following code shows configuring a menu item and wiring it to a backMenuItem.addActionListener(new GoBackActionListener()); backMenuItem.setText(...); backMenuItem.setMnemonic(...); backMenuItem.setDisplayedMnemonicIndex(...); backMenuItem.setAccelerator(...); backMenuItem.setEnabled(false); The Action interface is meant to address these problems. Action not
private class GoBackAction extends AbstractAction {
public GoBackAction() {
putValue(Action.NAME, "...");
putValue(Action.LARGE_ICON_KEY, ...);
putValue(Action.SMALL_ICON, ...);
putValue(Action.MNEMONIC_KEY, ...);
putValue(Action.DISPLAYED_MNEMONIC_INDEX_KEY, ...);
putValue(Action.ACCELERATOR_KEY, ...);
putValue(Action.SHORT_DESCRIPTION, ...);
setEnabled(false);
}
public void actionPerformed(ActionEvent e) {
browserComponent.show(getLastURL());
}
}
Action goAction = new GoBackAction();
backButton.setHideActionText(true);
backButton.setAction(goAction);
backMenuItem.setAction(goAction);
You'll notice a number of differences with this example and the Those that haven't followed the latest changes in 1.6 will notice some When using ActionListener we had to explicitly update the enabled backButton.setEnabled(...); backMenuItem.setEnabled(...); With Action the code becomes: goBackAction.setEnabled(...); Any user interface components attached to the Action will update The ability to change the Action directly and have it reflected If this is the first time you've read about Actions you should read Matisse: one step closer to cross platform layout nirvanaPosted by zixle on June 10, 2005 at 11:07 AM | Permalink | Comments (2)Yow, it's been more than a month since my last blog! I intended to write a blog once a week, but, well, finishing up table sorting, JavaOne and Matisse have kept me unbelievably busy in the past month:( I'll blog later about table sorting and other Mustang goodness. In the mean time I wanted to blog about Matisse. Matisse shows work that the NetBeans and Swing teams have been deeply involved in for close to a year (YOW!) now. For those interested there is a long thread on javalobby talking about Matisse. In this blog I'll cover some of the new JDK features that make Matisse possible. Baseline supportIn visual designers paradise all components are aligned nicely along their baseline. For example, labels should be aligned along their baseline with textfields. To date this has been problematic because we haven't provided an API for getting this information, meaning lots of tricks and assumptions. Thankfully we've added this API and it's now part of Mustang. For details read an older blog on baseline layout. Matisse makes extensive use of this API to allow aligning components along their baselines. For details on the API see JComponent.getBaseline and JComponent.getBaselineResizeBehavior .Preferred GapEach of the platforms (JLF, Windows, Gnome and OS X) provides guidelines as to how far apart various widgets should be placed from each other. We wanted the ability to make it easy for developers to honor these guidelines. You can see this in the Matisse demo, as the components gets close enough they snap to a location based on the guidelines of the current look and feel. The API is in the works for mustang and was discussed in a thread on the swing forums.GroupLayoutSo how does all this tie together? Yes, a new LayoutManager called GroupLayout. The LayoutManager offers the ability to align components along their baseline as well as to easily place components using the preferred gap API. GroupLayout itself is, in some ways, a combination of BoxLayout and SpringLayout. It's more constrained than SpringLayout and we are investigating moving it on top of SpringLayout. Later on I'll provide more details of GroupLayout. For those coming to JavaOne we'll cover it the layout roundup talk.Do we really need to wait for Mustang to use this?No! We realize it's important for developers to use this on pre 1.6, even pre 1.5 JREs. As such, we've rolled these three pieces into an open source project that runs on 1.4.2, and newer, JREs. The project is under the swing-labs umbrella and is called swing-layout. For more information, including source, head over to its home page. | |||
|
|