Carsten Zerbst's WeblogCarsten Zerbst's Weblog |
Friday Oct 23, 2009
Craftsmann's Guide to Swing Application, JGoodies Binding II The last blog introduced the concept of JGoodies Binding with the chain from domain model, value model and JComponent specifc view adapter. The examples shown so far used the BasicComponentFactory as convinient way to directly create bounded Swing components. This Blog will show you how to handle the most common binding cases using either the BasicComponentFactory or explicitly creating the binding chain. All examples are based on this domain model. Check- and Radiobuttons
Check- and Radiobuttons are a standard way to offer selection for ValueModel vm = new PropertyAdapter( model, "on", true ); JCheckBox checkBox = BasicComponentFactory.createCheckBox( vm, "binding to boolean value" ); vm = new PropertyAdapter( model, "oneFromThree", true ); JRadioButton radioButton = BasicComponentFactory.createRadioButton( vm, "A", " possibility A" ); radioButton = BasicComponentFactory.createRadioButton( vm, "B", " possibility B" );For the radio button you have to provide a value (A and B in the example). When this value is matched by the bound property the button is selecteda and is used when a user selects the button. Selection in JComboBox and JListJList and JComboBoxes are typically used to select one item out of a collection. Therefore they handle two variables, one contains the possibilities to select from, one contains the selected item(s). In most cases you know the possible items to choose from in advance and could use simple solution using the SelectionInList class. For the JComboBox this requires 3 three lines of code, the same ways is available for JLists. ValueModel vm = bena.getValueModel( "selectionInPossibilities" ); SelectionInList selection = new SelectionInList( DomainModel.THREE_POSSIBILITIES, vm ); JComboBox box = BasicComponentFactory.createComboBox( selection ); JSpinnerThe JSpinner is pretty similar to a JComboBox, it offers to select one out of many possible values. Using the SpinnerAdapterFactory it is possible to bind it to your domain model. While the JSpinner itself is not restricted to certain value, the binding framework supports only Date and Number items. Usually you provide the minimal, maximal and default value plus the number of steps alongside your value to create a binding: ValueModel vm = ... ;// type of ValueModel (date, number) has to match created SpinnerModel !!! SpinnerNumberModel spinnerModel = SpinnerAdapterFactory.createNumberAdapter(vm, 42, 0,100, 2); JSpinner levelSpinner = new JSpinner(spinnerModel);This creates a spinner for a range from 0 to 100, stepwidth 2 and the default value 42. Changing CollectionsMany domain models contain collection style attributes which vary over the time. As discussed in the last blog, attributes have to be bound to work correct with any kind of binding. It is pretty simple to bind the collection style value itself in your domain model: public class DomainModel { private List collection; private PropertyChangeSupport chsngeSupport; public void setCollection( List collection ) { Object ov = this.collection; this.collection = collection; firePropertyChange("collection", ov, collection); } }But in most cases one will not exchange the complete collection but simply add or remove items. As the standard collections (ArrayList, HashSet, ...) do not fire an event in those cases, you find yourself modifiying the collection but as no event is fired no updates happen. If you have a closer look into the Swing APIs you find the ListModel interface, and JGoodies Binding contains the needed ArrayListModel implementation. This is a 1:1 replacement for the standard ArrayList with the added functionality to fire events when the model is changed. Of course you should not provide a setter any more to avoid replacing the complete list. The ArrayListModel could be used directly with the JList. (If you need to support changing the list AND changing the list content have a look at the IndirectListModel). public class DomainModel { private final ListModel changingList = new ArrayListModel(); public ListModel getChangingList() { return changingList; } } ... JList list = new JList( domainModel.getChangingList());The same pattern applies for JComboBoxes and JList used to select an instance. By providing an ArrayListModel as variable to contain the possible values both the selection AND the objects to select from are bound variables and could change over the time. ConclusionThis two blogs should cover most cases you need to bind Swing components to your domain model for business style applications. There are more things to detect in JGoodes Binding, e.g. binding of JTables or colour choosers, but based on this blog you'll find them pretty straight forward. And off course the binding framework is not only restricted to bind your domain model to a Swing component, but could be used to bind any bean to another. The PropertyConnector just gets both beans and attribute names and you are done: DomainModel m1 = ...; OtherModel m2 = ...; PropertyConnector( m1, "attrName1", m2, "attrName2");
JGoodies Binding to bind standard Java Beans to Swing components is a mature solution. Other solutions like the Beans Binding efforts are more or less stuck or depend on special annotations.
Content Posted at 02:35PM Oct 23, 2009 by Carsten Zerbst in Java | Comments[2]
Thursday Oct 15, 2009
Craftsmann's Guide to Swing Application, JGoodies Binding I According to the common Model View Controller (MVC) Pattern, applications with a GUI have three different parts:
In SWING application the model part is typically implemented using a Java Bean or POJO, the view part is implemented using the components provided by SWING or based on the SWING framework. But as of today there is no solution shipped with Java which is used to bind the model to the view and vice versa.
If you have a close look many application use a simple copy and paste solution, the data is copied
from the bean into the SWING components and later on copied back from those into the bean (e.g. when a user presses the Save button). But this approach has it's limits, especially if the data has it's own life dictated by other source or you want to validate the user input. That's where a real There is life outside the SWING world, e.g. the scripting language Tcl/Tk knows a special bind command for nearly two decades, and so does JavaFX. But as of today there is no solution available in the Java core language for the binding. There have been efforts under their way in JSR 295 Beans Binding to standardize such a binding. Unfortunately these efforts failed, the Beans Binding JSR did not come to an end (like the SWING Application Framework) and in the end multiple forks exists which try to create a usuble Beans Binding. As with many real world problems in the SWING area you find a solution by Karsten Lentzsch, he offers the JGoodies Binding framework, a full featured solution to bind typicall SWING components like JTextFields, JComboBoxes etc. to models. JGoodies BindingThe JGoodies Binding is available from JGoodies, currently in version 2.0.6. In case you are still bound to Java 1.4.2 (yes, there a still such companies) you could use version 1.5 from archive.
The idea behind JGoodies Binding is that of an universal model access to the data found in the domain model. The universal model access is called a Model Requirements
To keep the Model via the Value Model to the Swing component in synch, one needs to ensure that every change in the model is propagated to the view layer and vice versa. Unlike simple POJOs this neccesitates With the simple P(lain) O(ld) J(ava) O(bject) approach, a typical model is implemented like the following Bean public class Customer { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } But for binding other components need to follow changes in to attributes. According to the Java Beans this is achieved by bounded values, which fire an PropertyChangeEvent to interested listeners. This necessitates a bean implementation like this: public class Customer { private final ExtendedPropertyChangeSupport propChangeSupport = new ExtendedPropertyChangeSupport(this); private String name; // methods needed to add, remove listeners public void addPropertyChangeListener(PropertyChangeListener listener) { propChangeSupport.addPropertyChangeListener( listener); } public void removePropertyChangeListener(PropertyChangeListener listener) { propChangeSupport.removePropertyChangeListener(listener); } public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { propChangeSupport.removePropertyChangeListener(propertyName, listener); } public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { propChangeSupport.removePropertyChangeListener(propertyName, listener); } // getter as used public String getName() { return name; } // Setter fires a PropertyChangeEvent using the property change support public void setName(String name) { String oldValue = this.name; this.name = name; propChangeSupport.firePropertyChange("name", oldValue, name); } } Every change to the name attribute in the bean triggers firing a PropertyChangeEvent. Implementing the creation of the PropertyChangeEvent and sending it to all registered PropertyChangeListeners is pretty simple, but in most cases simply use the existing implementation in com.jgoodies.binding.beans.ExtendedPropertyChangeSupport or java.beans.PropertyChangeSupport and delegate the task. All you need to do is to enhance your setters to keep the old value and call the property change support. Simple BindingBinding a Swing component to such a bound property is simple. As first step you create a ValueModel which encapsulates your Bean, then use the BasicComponentFactory to create an already bound Swing Component: ValueModel vm = new PropertyAdapter(cust, "name", true); JTextField text =BasicComponentFactory.createTextField(vm);With just this two lines, you get a Swing component which is bound to your model property. Every time the model property changes, the contents of the JTextField is updated and vice vera. You do not have copy the value from bean to Swing back and forth, everything is handled automatically by the JGoodies Binding. The ValueModel is the generic glue between a Java Bean Property and any kind of View Adapter. There are two ways to create a Value Model in JGoodies Bindings. One way is to create the ValueModel directly using the PropertyAdapter. It gets the encapsulated bean and the name of the desired property as input. In most cases you want to have bidirectional update support, so you add the additional true to the constructor: ValueModel vm = new PropertyAdapter(cust, "name", true);When you use a wrong property name, you get an error at runtime, so real production code should use static identifiers. Unlike template engines like JSP or Freemarker the property name do not support chained identifiers ! If you use many properties from the same domain model, it could be usefull to use the BeanAdapeter. This registers only on PropertyChangeListener instead one per adapted property: BeanAdapter bad = new BeanAdapter( cust); ValueModel vm1 = bad.getValueModel("name"); ValueModel vm2 = bad.getValueModel("surname"); There are several possibilities to connect the ValueModel to a Swing component. The most convinient way is to use the BasicComponentFactory. It has many convinience function to directly create an already bound JComponent. Typicall cases are :
The next Blog demonstrates how to handle the more complex cases using JGoodies Binding like lists, comboboxes, attributes with differences between representation (e.g. String) and model type (e.g. java.util.Date). Content Posted at 11:47PM Oct 15, 2009 by Carsten Zerbst in Java |
Saturday Oct 03, 2009
Craftsmann's Guide to Swing Application, Form Layout II
In the previous entry I presented the first steps in designing a typical business style form.
As result we had a sketch of the form with the entries and labels arranged in a grid:
If you have a look at the layouts available in SWING, there is only one candidate, which is more or less able to create such a GUI: the GridBagLayout. The GridBagLayout is very flexible and after long experience and even more tinkering around you could create nearly every layout with it. BUT it takes a tremendous time and frustration tolerance to get there. As a start look at Totally Gridbag. So please let me introduce Karsten Lentzsch's JGoodies Forms. This layout framework offers all you need to create forms and button bars in time and with a superb quality. With the form layout you define a design grid for rows and columns in terms of width/height, alignment and resizing behavior. The definition is done using a human readable description (or domain specific language to use that buzzword). After you defined that grid you place your components. It is even possible to override the global grid definition on a per component base. The complete code to create the form you designed using the JGoodies Form Layout is in the code snippet below: public JPanel createComponent() { FormLayout layout = new FormLayout( "4dlu,right:max(50dlu;pref), 4dlu, 75dlu:grow, 4dlu, pref,4dlu", "4dlu,pref,3dlu,pref,3dlu,pref,fill:4dlu:grow" ); // rows PanelBuilder builder = new PanelBuilder( layout ); CellConstraints cc = new CellConstraints(); int row = 2; JLabel label = new JLabel("Label 1:"); builder.add( label, cc.xy( 2, row ) ); JTextField field1 = new JTextField(); builder.add( field1, cc.xyw( 4, row, 3 ) ); row += 2; builder.addLabel( "Longer Label :", cc.xy( 2, row ) ); JTextField field2 = new JTextField(); builder.add( field2, cc.xyw( 4, row, 3 ) ); row += 2; builder.addLabel( "Label 3:", cc.xy( 2, row ) ); JTextField field3 = new JTextField(); builder.add( field3, cc.xy( 4, row ) ); JButton select = new JButton( "..." ); builder.add( select, cc.xy( 6, row ) ); return builder.getPanel(); } Define the LayoutIn the first lines you define the global grid of your form, in this case with the column specification 4dlu,right:max(50dlu;pref), 4dlu, 75dlu:grow, 4dlu, pref,4dlu and the row specification 4dlu,pref,3dlu,pref,3dlu,pref,fill:4dlu:grow. Human readable ?? Yes, I think so, but just after some explanation. Each column and row is specified with a string separated from the next one using the comma (,). Each specification may have up to three parts which are separated using the colon (:), the alignment, the width and the resizing behavior. A definition like 4dlu reads as
You may know pixels, centimeters and points, but dlu ? Using pixels or centimeters to define a size is possible in form layout as well, but they definitively have a drawback. A pixel is not a fixed size, as the resolution of the display varies. And most forms do contain a lot off text and the size of the text is defined e.g. in the systems preferences. What looks good on your screen will look pretty bad if someone uses a different font size. Therefore forms layout offers the Fixed size defined in dlu or pixels are easy, but forms layout offers more. Using min or pref you could specify the size to be the minimum or preferred size of all components placed in that row or column. This is used to specify the height of all rows containing labels and entries. As you could see in the definition of the second column, there are even possibilities to have a bound on the resulting size. The column specification right:max(50dlu;pref) results in a width defined by the largest label, but using 50 dlu as upper bound. If your labels are longer, they are truncated and your layout is not corrupted. Depending on your needs the size off the columns and rows may either be constant or uses additional space if the user resizes the gui. This behaviour is given using the grow keyword, so in the example the width of the entries is enlarged with the size off the frame. The user documentation of JGoodies Form Layout explains all these possibilities in depth, this article should be enough for a start. Place the componentsAfter you defined the FormLayout, there are two more layout specific lines in the example: PanelBuilder builder = new PanelBuilder( layout ); CellConstraints cc = new CellConstraints();With the first one you create a PanelBuilder which is more or less a convenience class used to minimize the code you type. The second one is an object you could use to define the cell in which you place a component. Existing components like the first label are simply added to the design grid using the cell location in x (column) and y (row). Both column and row count start with 1 !. The design grid has more columns as you expected, as the last line sports both a text entry and button. Therefore the first both entries have to span three columns. This is simply described using the xyw(idth) method of the CellConstraint. As you could see with the second row, the builder already has a convinience method to create and place a label with one shot. After you put all your components into the layout, you could asked the builder to return the created panel and you are done. There is of course much more available in forms layout than demonstrated in this small example. E.g. you could define groups of columns and rows which always have the same width or height. Or you define special growing behavior where additional space is divided in an arbitrary ratio. And if you run into a problem, e.g. by placing components into the wrong cell or using an inappropriate layout description. Forms layout has already a simple help build in, the FormDebugPanel. Using PanelBuilder builder = new PanelBuilder( layout, new FormDebugPanel() );will give you red lines painted on your form so you could see your design grid.
RésuméUsing Karsten Lentzsch's JGoodies Form Layout enables you to design forms in no term. The resulting forms are independant off the screen solution (as long as you use dlu !) and resize like you like and not like You Know Who (GridBagLayout). An alternative to Form Layout is the MiG Layout, but I never used it so far. Posted at 10:34PM Oct 03, 2009 by Carsten Zerbst in Java | Comments[1] Craftsmann's Guide to Swing Application, Form Layout I
No matter whether your application will consist of multiple pages or a simple dialog style GUI, user interaction is typically done by one or more forms. Theses forms are made from several labels and active components like text fields, buttons or sliders. This is the primary interface your users are going to see and have to work with. Given that the type and number off attributes to edit is already known, you have several task to be done BEFORE you open your editor:
As experience with mine and other people application tell me, don't do this alone unless you are a real user experience crack. It is typically better to show your sketches other colleagues. If you already have working code, then its typically to late, so please use either a sheet of paper for you first ideas. If drawing does not match your style use GUI mock up tool like Pencil, but paper is typically faster. And please refrain from creating "art". The mantra off business style applications is to not surprise you users. Every label, every text field should have it's place just where a user expects it to be. Your form may look a bit boring, but this is better then user searching around for the right entries. And boring is not the same as ugly. If you are in need for good examples have look at applications made by Apple or some GNOME application like Evolution. A look at the Apple Human Interface Guidelines is usefull as well. After you and your colleagues and you are satisfied, DO NOT open your editor. Now take your sketch and draw the design grid into the sketch. Then decide the width/height, alignment and resize behaviour for the rows and columns. Content Posted at 08:31PM Oct 03, 2009 by Carsten Zerbst in Java |
Sunday Sep 27, 2009
Craftsmann's Guide to Swing Application, Introduction I was asked by a colleague off mine to help him create a simple application. He just starts creating rich client application and needs someone to shepard him through the scrub off Swing application development until he finds his own way to the meadows. After some meetings filled with discussion and pair-programming, I decided to write down some of my ideas how to create small to medium sized Swing applications. The applications in mind are typical business style application with one to several forms to create or edit data. If you need to create a filthy rich applications you may find some sensible hints for the structure of you application as well, but then you have to consult other guys like Chet Haase or Romain Guy for the eye candy. There will be several articles each dedicated to the different disciplines needed to build an application:
The articles are based on my experience and make heavy use of the libraries offered by Karsten Lentzsch. Posted at 10:31PM Sep 27, 2009 by Carsten Zerbst in Java | Comments[1]
Thursday May 22, 2008
Extract U3D from PDF Starting with PDF 1.6 the Adobe Reader supports real 3D models in the U3D format. The embedding of U3D files into a PDF document is more or less covered, but so far there is no tool to extract U3D from PDFs. The following code is based on Bruno Lowagies iText library, a real nice Java library to create and modify PDF files. It searches through the PDF objects from a file and puts all found U3D streams into files.
import com.lowagie.text.pdf.PRIndirectReference; import com.lowagie.text.pdf.PRStream; import com.lowagie.text.pdf.PdfName; import com.lowagie.text.pdf.PdfObject; import com.lowagie.text.pdf.PdfReader; import com.lowagie.text.pdf.PdfStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.net.MalformedURLException; import java.net.URL; import java.util.Iterator; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.CommandLineParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.OptionBuilder; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import org.apache.commons.cli.PosixParser; import org.apache.log4j.BasicConfigurator; import org.apache.log4j.PropertyConfigurator; import org.apache.log4j.Logger; /** * * @author cz */ public class ExtractU3D { private static final Logger log = Logger.getLogger(ExtractU3D.class); public static final String PDF_NAME_3D = "3D"; public static final String PDF_NAME_U3D = "U3D"; public static void main(String[] args) throws Exception { BasicConfigurator.configure(); Option helpO = new Option("h", "help", false, "display help"); Option logO = OptionBuilder.withLongOpt("log").hasArg().withDescription( "url for log4j.properties file, e.g. file:./log4j.properties").withValueSeparator('=').create("log"); Option fileO = OptionBuilder.withArgName("f").withLongOpt("file").hasArg().isRequired( true).withDescription("the name of the PDF file").create("f"); Options options = new Options(); options.addOption(helpO); options.addOption(fileO); options.addOption(logO); // create the parser CommandLine line = null; try { CommandLineParser parser = new PosixParser(); line = parser.parse(options, args); } catch (ParseException exp) { // oops, something went wrong HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("ExtractU3D", options); System.exit(66); } if (line.hasOption("h")) { HelpFormatter formatter = new HelpFormatter(); formatter.printHelp("ExtractU3D", options); System.exit(0); } if (line.hasOption("log")) { try { URL propsURL = new URL(line.getOptionValue("log")); PropertyConfigurator.configure(propsURL); log.info("using log4j configuration from " + propsURL.toExternalForm()); } catch (MalformedURLException ex) { System.err.println("invalid properties url " + line.getOptionValue("log")); System.err.println("use standard configuration"); BasicConfigurator.resetConfiguration(); } } String pdfFileName = line.getOptionValue("f"); File pdfFileIn = new File(pdfFileName); if (!(pdfFileIn.isFile())) { System.err.println("could not find file '" + pdfFileIn.getAbsolutePath() + "'"); System.exit(66); } if (!(pdfFileIn.canRead())) { System.err.println("unable to read file '" + pdfFileIn + "'"); System.exit(66); } FileInputStream fis = new FileInputStream(pdfFileIn); PdfReader reader = new PdfReader(fis); int numExport = 0; for (int i = 0; i < reader.getXrefSize(); i++) { PdfObject pdfobj = reader.getPdfObject(i); log.info("pdfObj " + pdfobj); if (pdfobj == null || !pdfobj.isStream()) { continue; } PdfStream stream = (PdfStream) pdfobj; log.info(" length " + stream.getRawLength()); log.info(" keys " + stream.getKeys()); log.info(" class " + stream.getClass()); for (Iterator it = stream.getKeys().iterator(); it.hasNext();) { PdfName name = (PdfName) it.next(); PdfObject pdobj = stream.get(name); log.info(" " + PdfName.decodeName(name.toString()) + "\t\t: pdobj " + pdobj + ", " + pdobj.getClass()); if (pdobj instanceof PRIndirectReference) { PRIndirectReference inref = (PRIndirectReference) pdobj; log.info(" indirect type " + inref.type()); if (PRIndirectReference.STREAM == inref.type()) { log.info("#bytes " + inref.getBytes().length); } } } PdfObject pdfsubtype = stream.get(PdfName.SUBTYPE); log.info(" subtype " + pdfsubtype); if (PDF_NAME_U3D.equals(PdfName.decodeName(pdfsubtype.toString()))) { byte[] u3d = PdfReader.getStreamBytesRaw((PRStream) stream); File out = new File("extract" + ( numExport++) + ".u3d"); log.info("extracting " + u3d.length + " bytes U3D to " + out); FileOutputStream fos = new FileOutputStream(out); fos.write(u3d); fos.close(); } } } } Posted at 10:51PM May 22, 2008 by Carsten Zerbst in PDF |
Tuesday Mar 25, 2008
Schliemann Tutorial I wrote a full blown Tutorial on the Netbeans WIKI how to extend the Netbeans IDE to support a new language. The tutorial explains the Generic Language Support (codename Schliemann) using ISO 10303 Part21 (STEP Physical File) as example. Posted at 05:22PM Mar 25, 2008 by Carsten Zerbst in NetBeans |
Friday Sep 16, 2005
Using JTree with SwingWorker Often the datasource for JTrees are not that fast and data is only retrieved when a certain node is opened. If the retrieval of children takes longer than several tenth of seconds the GUI does not feel snappy. Now take the |