Category Archives: Enterprise

Introduction by Example: JavaFX 8 Printing

I‘ve not blogged in awhile, and I miss sharing with others about all things JavaFX (My day job and family are likely excuses). For those who are new to this blog, I am the author of JavaFX 2 Introduction by Example (JIBE), co-author of Java 7 Recipes, and technical reviewer of the Pro JavaFX 2 books from Apress publishing. For those who already know me, I’d like to thank you for supporting me and the other authors by purchasing these books. More importantly, my hope is to reach out to  Java enthusiasts and share ideas.

The book JavaFX 2 Introduction by Example, was published in Nov. 2011 and many more APIs were added since then. During the writing of the book, I was working on the early editions of JavaFX 2.0 up until the announcement at JavaOne Oct. 2011. It was pretty crazy trying to update the book based on API changes as things were almost set in stone. I thought it was amazing how it even got out the door. However, I was pretty pleased. Some of you who have read the beginning of the book (JIBE) understand that the chapters of JIBE are also found in the book Java 7 Recipes (actually it is originally taken from Java 7 recipes). This little fact explains why the book, JavaFX 2 Introduction by Example, is reminiscent of recipe or cookbook style technical books. My intent was to help the reader get introduced quickly without a lot of tech blather. Instead of trying to convince people about the JavaFX platform, I’d rather demonstrate things with useful examples. I find it counter productive discussing  deep philosophical debates regarding why one particular technology is superior to the other (cheesy 80’s Highlander reference).

After the release of JavaFX 2.0, there came subsequent versions such as JavaFX 2.1, 2.2 and the upcoming release of JavaFX 8 (January 2014). In this blog entry, I will provide a recipe for the JavaFX 8’s Printing API. Similar to my book (JIBE), I will follow the same pattern as before where I present a problem, solution, code, and a “How it Works” section.

Declaimer: In this blog you will encounter Java Functional Interfaces using Lambda expressions. I will not be discussing them here, but will refer you to Oracle’s tutorials on Project Lambda .

Prerequisite software:

JDK 8 – https://jdk8.java.net/download.html

 

Problem

You want to create a JavaFX application that prints out a visited website.

Solution

Use the JavaFX 8 PrintJob and Printer APIs to print any JavaFX scene graph node. Also, use the WebView and WebEngine APIs to display a Website or Web page.

Instructions

Assuming you’ve compiled and have run the application, follow the instruction below:

  1. Enter website address or url into the text field.
  2. Hit the enter key
  3. After the page is loaded, click on the “Print” button
  4. Go to the printer to get the printed web page

Code

package org.carlfx;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker.State;
import javafx.print.*;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.transform.Scale;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;

/**
 * Demo to use JavaFX 8 Printer API.
 *
 * @author cdea
 */
public class PrintDemo extends Application {
    @Override
    public void start(Stage primaryStage) {

        final TextField urlTextField = new TextField();
        final Button printButton = new Button("Print");
        final WebView webPage = new WebView();
        final WebEngine webEngine = webPage.getEngine();

        HBox hbox = new HBox();
        hbox.getChildren().addAll(urlTextField, printButton);
        BorderPane borderPane = new BorderPane();
        borderPane.setTop(hbox);
        borderPane.setCenter(webPage);
        Scene scene = new Scene(borderPane, 300, 250);
        primaryStage.setTitle("Print Demo");
        primaryStage.setScene(scene);

        // print button pressed, page loaded
        final BooleanProperty printButtonClickedProperty = new SimpleBooleanProperty(false);
        final BooleanProperty pageLoadedProperty = new SimpleBooleanProperty(false);

        // when the a page is loaded and the button was pressed call the print() method.
        final BooleanProperty printActionProperty = new SimpleBooleanProperty(false);
        printActionProperty.bind(pageLoadedProperty.and(printButtonClickedProperty));

        // WebEngine updates flag when finished loading web page.
        webEngine.getLoadWorker()
                 .stateProperty()
                 .addListener( (ChangeListener) (obsValue, oldState, newState) -> {
                    if (newState == State.SUCCEEDED) {
                        pageLoadedProperty.set(true);
                    }
                 });

        // When user enters a url and hits the enter key.
        urlTextField.setOnAction( aEvent ->  {
            pageLoadedProperty.set(false);
            printButtonClickedProperty.set(false);
            webEngine.load(urlTextField.getText());
        });

        // When the user clicks the print button the webview node is printed
        printButton.setOnAction( aEvent -> {
            printButtonClickedProperty.set(true);
        });

        // Once the print action hears a true go print the WebView node.
        printActionProperty.addListener( (ChangeListener) (obsValue, oldState, newState) -> {
            if (newState) {
                print(webPage);
            }
        });

        primaryStage.show();

    }

    /** Scales the node based on the standard letter, portrait paper to be printed.
     * @param node The scene node to be printed.
     */
    public void print(final Node node) {
        Printer printer = Printer.getDefaultPrinter();
        PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT);
        double scaleX = pageLayout.getPrintableWidth() / node.getBoundsInParent().getWidth();
        double scaleY = pageLayout.getPrintableHeight() / node.getBoundsInParent().getHeight();
        node.getTransforms().add(new Scale(scaleX, scaleY));

        PrinterJob job = PrinterJob.createPrinterJob();
        if (job != null) {
            boolean success = job.printPage(node);
            if (success) {
                job.endJob();
            }
        }
    }

    /**
     * The main() method is ignored in correctly deployed JavaFX application.
     * main() serves only as fallback in case the application can not be
     * launched through deployment artifacts, e.g., in IDEs with limited FX
     * support. NetBeans ignores main().
     *
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }
}
Print Demo using JavaFX 8

Print Demo using JavaFX 8

How it Works

The code begins by creating a TextField, a Button, and a WebView control to be placed into a BorderPane. When using the BorderPane layout, you will be able to place controls into the following regions: Top, Right, Left, Bottom, and Center.

Similar to a web browser, the text field allows the user to enter a website url. Once the url is entered, the user will hit the enter key to load the web page into the WebView node. When placing controls on any of the side regions, the BorderPane layout will take on the preferred height of any controls that are added. The center region will allow a node to take up the available space minus the remaining space taken up by the width and height of the bordering side regions. In other words, if the side regions contain no nodes (empty), a node in the center region has the opportunity to take all the available width and height space provided by its parent (Scene). Since the WebView node will occupy the center region, it will take all the available width and height (minus top region) when the web page is fully loaded. You’ll also notice scroll bars allowing the user to view pages larger than the current view port.

After laying out all the components for the UI, you will need to wire things up. Here you will simply create three boolean property
(javafx.beans.property.SimpleBooleanProperty) instances. The first property variable printButtonClickedProperty is a flag indicating when the print button was clicked. The second property pageLoadedProperty is a flag indicating that the web page was finished loading. Lastly, you will want to note the printActionProperty which binds the printButtonClickedProperty and the pageLoadedProperty by using the fluent API. As they evaluate, the printActionProperty will be true if both the printLoadedProperty and the printLoadedProperty are true values.

// print button pressed, page loaded
final BooleanProperty printButtonClickedProperty = new SimpleBooleanProperty(false);
final BooleanProperty pageLoadedProperty = new SimpleBooleanProperty(false);

// when the a page is loaded and the button was pressed call the print() method.
final BooleanProperty printActionProperty = new SimpleBooleanProperty(false);
printActionProperty.bind(pageLoadedProperty.and(printButtonClickedProperty));

Continuing the wiring up of the UI, I took an event driven approach where the handler code will respond to events and property changes. Starting with the WebView node, I attached handler code to the statePropery instance (ChangeListener) in order that the pageLoadedProperty will be set to true once the web page is loaded successfully.

  // WebEngine updates flag when finished loading web page.
  webEngine.getLoadWorker()
           .stateProperty()
           .addListener( (ChangeListener) (obsValue, oldState, newState) -> {
               if (newState == State.SUCCEEDED) {
                    pageLoadedProperty.set(true);
               }
           });

Next, you will see the text field’s ‘setOnAction‘ method containing handler code that resets the pageLoadedProperty and printButtonClickedProperty objects. Also, the code will initiate the loading of the page via the WebView‘s WebEngine load() method.


  // When user enters a url and hits the enter key.
  urlTextField.setOnAction( aEvent ->  {
     pageLoadedProperty.set(false);
     printButtonClickedProperty.set(false);
     webEngine.load(urlTextField.getText());
  });

After the TextField control’s action code is wired up, the print button will also need handler code to set the printButtonClickedProperty flag to true. Lastly, the printActionProperty property will need a ChangeListener to respond when its state evaluates to true. When this evaluates to true, my print() method is invoked.

        // When the user clicks the print button the webview node is printed
        printButton.setOnAction( aEvent -> {
            printButtonClickedProperty.set(true);
        });

        // Once the print action hears a true go print the WebView node.
        printActionProperty.addListener( (ChangeListener) (obsValue, oldState, newState) -> {
            if (newState) {
                print(webPage);
            }
        });

Finally, the print() method takes a JavaFX Node object to be printed. The Printer object has a method which returns the default printer your computer is set to. Before actually printing, we can derive a default page layout to scale the node before printing the node. If you don’t do this, only part of the web page will be printed. With the default printer obtained, the createPrinterJob() method is invoked to return a PrinterJob instance that does the actual printing. To print a JavaFX displayable type node, you simply invoke the PrinterJob object’s printPage() method by passing in the Node instance as a parameter.

    /** Scales the node based on the standard letter, portrait paper to be printed.
     * @param node The scene node to be printed.
     */
    public void print(final Node node) {
        Printer printer = Printer.getDefaultPrinter();
        PageLayout pageLayout = printer.createPageLayout(Paper.NA_LETTER, PageOrientation.PORTRAIT, Printer.MarginType.DEFAULT);
        double scaleX = pageLayout.getPrintableWidth() / node.getBoundsInParent().getWidth();
        double scaleY = pageLayout.getPrintableHeight() / node.getBoundsInParent().getHeight();
        node.getTransforms().add(new Scale(scaleX, scaleY));

        PrinterJob job = PrinterJob.createPrinterJob();
        if (job != null) {
            boolean success = job.printPage(node);
            if (success) {
                job.endJob();
            }
        }
    }

In conclusion, I find that the APIs are simpler to use compared to Java Swing/AWT APIs. I would like to mention that there are many features you can play around with since this blog entry only scratches the surface on the APIs currently available.

NOTE: JavaFX 8 printer API is still in the early stages and there are still outstanding issues (Jira issues).

Please visit to vote and help test functionality at https://javafx-jira.kenai.com

Outstanding Tickets: http://bit.ly/1dw7Vc8

Advertisement

A Case Study: A JavaFX Widget Framework API

A prototype of a widget container

A prototype of a widget container

Building a better mousetrap

In short I want to revisit the idea of a cross platform widget framework using JavaFX. In this article I’d like to share some ideas, concepts and prototypes that might invigorate this old but ingenious idea for the desktop and embedded world. An old idea you say? Does anyone remember WidgetFX?

In the early days of JavaFX there were a few Java Champions and community members who decided to create a cross platform JavaFX 1.x widget framework for the desktop (year 2008). I was fortunate enough to know many of those contributors involved.  I grew to love the capabilities the framework and widgets were able to provide.  However, as we all know JavaFX is now redone as Java APIs as opposed to the JavaFX script language. This eventually became apparent regarding to the future of most frameworks back then. Before I begin I would like to give credit to the folks who contributed to the WidgetFX framework project.  I want to thank them for their enthusiasm and their tireless efforts on building the community to what it is today. To see some of their cool widgets please visit the site http://widgetfx.org (catch them before they’re gone). Two of the original founders of the project are Stephen Chin and Keith Combs. Even though I did not partake in the fun during the height of the project I was quite inspired.

Inception phase

To fast forward just last year at the JavaOne 2012 conference I was able to catch up with Keith Combs (Silicon Valley JavaFX User Group organizer) and Stephen Chin (Java Technology Evangelist at Oracle) after his keynote speaking to inquire about the WidgetFX project now that JavaFX 1.x is no more. Steve kindly asked if anyone would be interested in giving it a reboot on the JavaFX 2.x platform (As he peered over towards Keith and me). I believe I said, “I don’t think, so… “. I thought it might be too much to bite off than I could possibly chew. So I politely declined the offer. During the conference I was later enamored by all things Java embedded which later fueled to some of my home automation project ideas using JavaFX with (Arduino, Raspberry Pi, and Beagle Board).

After returning home I was not only excited about the things I’ve learned at the conference but, I also was in the middle of transitioning to a new job and company. Working as a UI and Visualization developer for cloud based monitoring software I often encountered stakeholders/SMEs mentioning the term “SPOG” or single pane of glass.

What does the term “SPOG” mean?

According to Floyd Strimling a cloud technical evangelist at Zenoss explains how so many organizations will create desperate (silos) systems and he goes on to say, “The Holy Grail is a single pane of glass that provides IT managers with a single access point to make sense of their entire underlying infrastructure in real time”. (Searching for the Mythical Single Pane of Glass)

Isn’t “SPOG” an interesting concept for large operation centers? So, I continued to rethink of the old idea of a flexible widget framework that would provide richer interfaces that would allow better desktop integration, decoupled services, higher performance, and capable of empowering users with many of the modern UI metaphors that they’d expect on their favorite OS platform. I believe such a framework should also work on embedded devices for home automation.  Actually I got the idea of a widget framework for embedded devices from my good friend Gerrit Grunwald (Java Champion and author of the popular Steel Series API) while exchanging ideas about graphics performance and launching JavaFX applications on embedded devices.

Wow, with so many different problem domains and so many perspectives could such a widget framework truly exist? Could such a widget framework be able to make everyone happy? Is it possible to create a simple widget framework to rule them all? I believe it is possible.

eWidgetFX is born

In the back of my mind I noticed similar use cases with many customers wanting widget frameworks having this concept of a “SPOG”, but also provide a framework that I could use myself on my own desktop or touch device. So, I kept going back and forth on the possibility of rebooting the old project WidgetFX. Instead I wanted to throw a monkey wrench into the whole business of widget frameworks. Since there are so many kinds of ways to launch, display, and manage widgets it would also be a good idea to build a single widget framework SPI (service provider interface API) which would enable developers to build their own widget containers thus allowing widget developers to run their widgets in any widget container. There would basically be two developer perspectives: Container Developers and Widget Developers. So, I’d like to propose a new API called eWidgetFX (core) that would allow the developers to create widget containers and widgets.

For now I will defer the work of designing the core APIs and make it a future post. So, in this article I’ve decided to generate some prototypes of widget containers that would basically depend on the future eWidgetFX framework API.

Prototyping Widget Containers

Prototyping is probably the most fun part during any software development activity. Shown below are videos I recently uploaded to Youtube.com to kick some ideas around for different kinds of widget containers (Icon App tray) on a desktop environment.

Conclusion

One of the main issue I found in UI development is that customers will often know what they want, but aren’t able to communicate what they ultimately want. It’s almost like playing a game of Charades or a game of Pictionary where guessing is a sought after skill in software design.  We are often faced with providing prototypes and end up settling on one particular view (one view, one way, one silo). The problem became pretty apparent where everyone had an opinion or philosophical stance on a particular GUI interface, usability aspect or UI metaphor. Sometimes it’s based on subjective personal preferences that can go into long discussions. No one person in the room was completely happy, but at the end of the day we compromised and we settled.

I believe that by creating a widget API it would allow organizations to build different single pane of glass instances (widget containers) and capable of sharing all widgets across other panes.

So, imagine seeing different types of widget containers in the wild. Some future widget containers could look like interfaces similar to the following movies: Avengers, Tron, or Minority Report

A final note: Many of of the ideas expressed and proposed are a collective effort from some of my friends (team members) of the JFXtras.org project (Mark Heckler, Hendrik Ebbers, and Gerrit Grunwald).

Would you like to see a talk regarding widget frameworks at JavaOne?

As always comments are welcome.

Carl 😉

Resources

JavaFX: http://www.oracle.com/technetwork/java/javafx/overview/index.html
Java Champions: https://java.net/website/java-champions/bios.html
WidgetFX: http://widgetfx.org
Keith Combs from the Silicon Valley JavaFX user group: http://www.svjugfx.org/member/10394895/?op=&memberId=10394895
Steve on Java by Stephen Chin: http://steveonjava.com
Arduino : http://www.arduino.cc
Raspberry Pi: http://www.raspberrypi.org
Beagle Board: http://beagleboard.org
Guest Post: Searching for the Mythical Single Pane of Glass: http://siliconangle.com/blog/2012/02/29/guest-post-searching-for-the-mythical-single-pane-of-glass
Anti-patterns: http://en.wikibooks.org/wiki/Introduction_to_Software_Engineering/Architecture/Anti-Patterns
Google Images of Operation centers: http://bit.ly/106p3h0
Avengers UI design: http://cargocollective.com/jayse/Avengers
Tron UI design: http://dlew.me/Tron-Legacy-Boardroom
Minority Report UI designer demos his tech at TED (video): http://www.engadget.com/2010/06/03/minority-report-ui-designer-demos-his-tech-at-ted
The Java Jungle by Mark Heckler: https://blogs.oracle.com/javajungle/entry/welcome_to_the_jungle
Gui Garage by Hendrik Ebbers: http://www.guigarage.com
Harmonic Code by Gerrit Grunwald: http://harmoniccode.blogspot.de
JFXtras.org : http://jfxtras.orgLeap Motion: https://www.leapmotion.comLeap Motion: https://www.leapmotion.com

I’ll be back…

Hey everyone!

I’m sorry to inform you that I will be taking a break from the JavaFX Game Tutorials to prepare for my upcoming presentation at the Montgomery county (Maryland) Java user group (http://www.mcjug.org) on July 18, 2012. If any of you Java fans are in town stop by between 6:00-8:00pm EST. My talk will be about an introduction to building JavaFX 2 enterprise applications using Grails.

I look forward to resuming where we left off in the JavaFX Game Tutorial series. 😉

Code well my friends!

Carl

JavaFX 2.0 Introduction by Example book

I recently finished writing a book on the new release of the JavaFX 2.0 SDK and it has already been placed on the shelves at a bookstore (Amazon) near you. The book will walk you through, step-by-step, giving you the ins and outs of JavaFX 2.0. When you encounter a chapter you will be presented recipes which will pose a problem (use case scenario) that will have an associated solution. After a proposed solution you will be shown an example source code listing and its display output after the program has been executed. Lastly you will be shown a section called “How it works” which will explain and discuss the examples and their details. To see more about this book, such as the source code and errata, please visit Apress Publishing (http://www.apress.com/9781430242574).

In this blog entry I also want to give you a sneak preview of a Java Webstart example of chapter 3 recipe 3-1 (JavaFX 2.0 MP3 Player). To launch the JavaFX MP3 player just jump down to Chapter 3 below.

Below is a brief overview of the chapters in the book:

Chapter 1: JavaFX Fundamentals

I begin by instructing you on how to get your environment set-up to rapidly develop rich internet applications using Java and JavaFX.

JavaFX Swiss army knife

After reading this chapter you will be able to answer questions such as:

  • How do I create GUI applications?
  • What is the Scene graph?
  • How do I display text onto the scene graph?
  • How do I incorporate UI controls into my application?
  • How do I bind expressions?
  • How do I draw shapes?
  • How do I generate a background process?
  • How do I associate keyboard sequences to applications?

Chapter 2: Graphics with JavaFX
In chapter 2 there are recipe examples which delve into JavaFX’s graphics and animation capabilities. I begin by explaining some of the basics of JavaFX’s Graphics such as rendering images and animating nodes. I then go on to more advanced topics such as animating with transitions, manipulating layouts, and enhancing nodes using JavaFX CSS.
The following picture, taken from Recipe 2-2, depicts an image viewer application with a custom news ticker control at the bottom.

Photo Viewer and News Ticker application

Photo Viewer and News Ticker application

Chapter 3: Media with JavaFX
Chapter 3 covers all things media related. Here I reveal JavaFX’s media APIs which allows you to integrate audio and video into your applications. I begin by showing you how to create a JavaFX MP3 player and a video player. Then I will walk you through the different ways to enhance the video player with additional features to control media actions and events, mark positions in a video, and synchronize animations.

The illustration below, taken from Recipe 3-1, depicts a JavaFX 2.0 MP3 player with a graphical visualization (using the AudioSpectrumListener API). Before launching the example you will need to know the requirements and instructions to run the demo example.

JavaFX 2.0 MP3 Player

JavaFX MP3 Player

Simple requirements and instructions to run the JavaFX 2.0 MP3 Player:

Requirements

  • Java 6 update 24 JRE or greater (Java 7 is preferred)
  • JavaFX 2.0 or greater (JavaFX 2.0.2 is preferred)
  • Windows XP SP 3 or greater. (I will update the jnlp as JavaFX becomes available on other OSes)

Instructions

  1. Click the Webstart launch button below.
  2. By using your file explorer on your host operating system “drag and drop” a music mp3 file onto the surface of the application.
  3. Use the controls to the bottom right of the application to pause, play, and stop the music.
  4. You may use your mouse to drag the application around your desktop.
  5. To close the application click on the ‘X’ in the upper right hand corner.

To launch the application click on the Java Webstart button below:

Demo JavaFX 2.0 MP3 Player

Chapter 4: JavaFX on the Web
In chapter 4 you will be able to take advantage of the interoperability between JavaFX and HTML5.

For starters I will cover how to embed JavaFX applications into a Web page. I then will demonstrate JavaFX’s powerful WebView and WebEngine APIs. Below are the recipe examples from chapter 4 which utilize the  WebView and WebEngine APIs:

  • Displaying HTML5 Content (animated analog clock application)
  • Loading data from a Web service (weather application)
  • Handling Web events
  • Storing and displaying data using an embedded database (RSS reader application)

Depicted below is an animated analog clock application, taken from Recipe 4-2, demonstrating the ability to render HTML5 content.

JavaFX 2.0 Analog Clock

JavaFX 2.0 Analog Clock (HTML5)

I assume you know the Java programming language and some web development concepts. I hope you will enjoy these examples which can be used freely in your own projects. I’ve tested the examples with the latest Java 7 update 2 and JavaFX 2.0.2 runtime and SDK. If you have any questions or comments feel free to ask them here or on my Twitter account @carldea .

Thanks!

Carl

JavaFX – Java Applets Making a Comeback?

Pedal to the metal

Petal to the metal

Introduction

In 2008 JavaWorld had written a great article by Jeff Friesen called “Are applets making a comeback?“. Jeff Friesen poses this question to leading thinkers and doers in the Java developer community. Although the question posed to readers may seem rhetorical, and difficult to answer with an emphatic “Yes“.  I want to try to answer this question in the year 2010.  Right after Oracle has finalized its acquisition of Sun Microsystems, Larry Ellison CEO of Oracle had  an Oracle + Sun Product Strategy Webcast announcing it would invest heavily in JavaFX. So, currently there are lots of things going on with the JavaFX platform. As the JavaFX platform matures the community should mature. So, hopefully I can encourage you to stay more involved in helping the JavaFX community grow.

Are Applets Making a Comeback?

Many Java Developers who have used Applets in the past, have often felt inadequate when competing with the new Web Order due to all the AJAX and Adobe Flash craze.  One of the main complaints about Java Applets was its start-up time. In my opinion ‘Perception‘ is key and when it comes to user experience, it is often said that “If it looks and feels slow, the application is probably not worth running“.  I believe that our society has this urge of instant gratification, so being patient is a hard thing to do these days. During years past Java developers have been longing for that day when Java Applets will make its big comeback. While many Sun engineers are working tirelessly solving this problem it always seems on going (It should).  Sometimes I’ll come across Java developers still feeling unconvinced whenever an update is released. I also have heard people get quite flustered and a little threatening.  I don’t mind if you complain, but do something about it. Then some will say, “What can I do about it?“. Well, the obvious things are forums, blogs and filing bugs. I normally am an early adopter when it comes to Java/JavaFX updates relating to performance increases. Just like folks who decide to buy a new game (Like StarCraft 2) off the shelf they’ll take a look at the graphics requirements such as video ram, etc in hopes their computer can handle it. If not they will likely upgrade their video card or buy a new computer all together. So, upgrading your Java Runtime Environment (JRE) is a snap. So, you are probably wondering where is the part where I encourage you? Well, let me just say with the recent release of JRE 1.6 update 18, I was pretty impressed  with the start-up and reload performance increases. To really see how fast Applets start-up I recommend installing the latest JRE (1.6 update 18), and to please visit Oracle/Sun engineer Rakesh Menon‘s blog entry called “JavaFX – Applet Startup Time“. On his site you will see a list of many folks taking some time to record metrics and describing their browser, OS, and hardware (Please note: make sure your Java Console is set to display so that a text dump outputting  the metrics can later be cut and pasted). I also suggest posting your results if you have a different configuration than the others on the list.

*Note: When clearing the cache (from the Java Control Panel) while having Firefox open be sure to restart Firefox or close tab before re-running the applet.

Conclusion

Once you will notice the start-up time increased you should be able to answer the question yourself  “Are Applets Making a Comeback?“. I know I can see the difference and I’m sure you will too. The Java/JavaFX community with tons of libraries and very good boosts in performance, I truly believe it is safe to say with an emphatic “Yes, Applets are making a comeback!”.  I also want to mention about two very cool sites to test drive your new JRE w/start-up/reload enhanced:

Lastly, I want to applaud and thank the Java/JavaFX engineers (Rakesh and company) for making these great strides.

References

Are applets making a comeback? by Jeff Friesen: http://www.javaworld.com/javaworld/jw-05-2008/jw-05-applets.html
Larry Ellison CEO of Oracle: http://en.wikipedia.org/wiki/Larry_Ellison
Interactive Pulp – JavaFX, startup time, and security dialogs : http://www.interactivepulp.com/blog/2008/12/javafx-startup-time-and-security-dialogs
Oracle invests in JavaFX: http://learnjavafx.typepad.com/weblog/2010/01/oracle-we-will-invest-heavily-in-javafx.html
Oracle/Sun strategy Webcast: http://www.oracle.com/events/productstrategy/index.html
New Applet Experience by Jeff Friesen: http://www.javaworld.com/javaworld/jw-05-2008/jw-05-newapplet.html
JavaFX forums: http://forums.sun.com/category.jspa?categoryID=132
The JavaFX Journey filing bugs: http://piliq.com/javafx/?p=1144
The Vancouver 2010 Olympics: http://www.vancouver2010.com/olympic-medals/geo-view/
JavaFX – Applet Startup Time: http://rakeshmenonp.wordpress.com/2009/08/31/javafx-applet-startup-time/
Star Cannon: http://www.freearcade.com/StarCannon.jav/StarCannon.html

JavaFX – A Poor Man’s Form Designer

Person Form - Using Poor Man's Form Designer

Person Form

Introduction (Updated 2)

NOTE: If you’ve been here before and found this blog entry useful please note that I have updated it again (for good reason).  Let me explain why and what I’ve updated. Since I have found that I was becoming quite productive generating forms, I wanted a more decoupled approach, so I re-factored the source code to be more reusable and easily up-datable. To use the form designer for your applications you can skip down to the section called “Using the Form Designer” to help you create your own forms in no time at all! (I also had a crack at making the form a little more attractive too).

What is a Form Designer?

Software developers who build form based applications will often struggle with positioning controls dynamically while a window resizes. This is better known as “Layout Management”. Many developers will rely on GUI builders that provide a “WYSIWYG” ability allowing a designer to drag and drop components onto a canvas area. On the other hand some developers prefer to hand code GUI form screens. There are pros and cons to using both strategies; however in this article I will strike a balance by building a form designer to allow a developer to assist them in hand coding GUI form screens (sound strange, just read on). While the JavaFX community has anticipated for GUI designer tools, I couldn’t wait. So, I decided to create the “Poor Man’s Form Designer”. This simple designer tool uses the popular MigLayout by Mikael Grev and was ported over to the JavaFX platform by Dean Iverson (an author of the book “Pro JavaFX Platform”) into the JFXtras project. The PMFD‘s source code consists of one file Main.fx and just at around 370 lines of code including comments. After the Conclusion section of this article you will see the full source code to the demo and form designer. Simply click on the source code links to expand and your on your way to developing beautiful looking forms!

Poor Man's Form Designer Constraints Window

Poor Man's Form Designer Constraints Window

Note: You will notice I forgot to add column constraints. The designer now has the ability to adjust the column constraints.

Demo

Demo

Instructions:

  1. Click Launch button above
  2. Go to the MigLayout cheat sheet at http://migcalendar.com/miglayout/cheatsheet.html to keep handy when tweaking the “Form Design Constraint Editor” window.
  3. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  4. Go to the “Form Design Constraint Editor” window under the Component Constraint area is a combo box please select firstNameLabel, then tab to the constraint text field (just right of combo box) and type “align left“. Click Apply button to update changes.
  5. Be sure to observe the Form View window and you’ll notice the “First Name:” label is left justified beside the first name text field.
  6. Play around with other constraint areas. Cut and paste from the cheat sheet. The cheat sheet is divided in three sections: Layout Constraints, Column & Row Constraints and Component constraints. Don’t forget to hit the apply button.
  7. You’ll notice in the component constraint area when selecting a control in the combo box the previous constraint is preserved and displayed in the constraint text box.
  8. Go to the “Form Design Constraint Editor” and click the dump button. Here it will dump all the current constraints for the following areas: Layout, Column/Row and Components constraints.
  9. Copy and paste those dumped constraints to assist in your hand coded GUI Form. (See below “Using the Form Designer” section, Steps 3 & 4)

Disclaimer: While the coding details below seem strange,  you may want to visit Dean’s Pleasing Software blog entry “MigLayout for JavaFX Reloaded” regarding how to Get started with MigLayout and JavaFX. http://pleasingsoftware.blogspot.com/search?q=layout

Using the Form Designer

Step 1: Create a new JavaFX project / setup to include JFXtras libraries and MigLayout jar files

Step 2: Copy Source code poormans_form_designer.fx expand put into your project. (check package statement)

Step 3: Create a form (class) which extends MigLayout from the JFXtras project. Simply add two attributes:

  1. componentsConstraints – Sequence of String objects
  2. nodesToLayout – Sequence of Node objects

Although this seems odd, I plan to possibly create a Mixin class or a derived MigLayout class to carry these attributes. Internally each Node contains a layoutInfo which contains a MigNodeLayoutInfo object (see http://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout for details). You should notice that the nameform.fx does not have dependencies to the designer library code, thus allowing the form developer to use CustomNode if they choose.

Create a Form (see nameform.fx for full source code)

public class NameForm extends MigLayout {
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[] = [];

  init {

    // ... other controls
    var firstNameLabel:Label = Label {
      id: "firstNameLabel"
      text: "First Name"

      font : Font {
          size: 18
      }
    };

    var firstNameField:TextBox = TextBox {
      id:"firstNameField"
    };
    // ... other controls

   nodesToLayout = [
      sectionFullName,  // 0
      instructionsText, // 1
      firstNameLabel,   // 2
      firstNameField,   // 3
      // ... all controls
   ];
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes below
    // +---------------------------------------------------
    // [BEGIN]

    constraints = "";
    columns = "[pref]10[fill]";
    rows = "[pref]10[pref]";
    componentsConstraints = [
      "span, growx, wrap",                // fullNameTitle 0
      "span, wrap 15px",               // instructionsText 1
      "align right",                     // firstNameLabel 2
      "span, w min:100:300, wrap",       // firstNameField 3
      "align right",                    // middleNameLabel 4
      "growx, w min:100:300, wrap",     // middleNameField 5
      "align right",                      // lastNameLabel 6
      "growx,  w min:100:300, wrap",      // lastNameField 7
      "align right",                    // suffixNameLabel 8
      " w min:100:300, wrap 15",        // suffixNameField 9
      "span, wrap",                           // dobTitle 10
      "align right",                          // dobLabel 11
      " w min:100:pref, wrap 15",             // dobField 12
      "span, wrap",                           // pobTitle 13
      "align right",                      // pobCityLabel 14
      "span, growx,  w min:100:300, wrap",  // pobCityField 15
      "align right",                    // pobCountyLabel 16
      "span, growx,  w min:100:300, wrap",  // pobCountyField 17
      "align right",                     // pobStateLabel 18
      "span, w pref:100:pref + 10, wrap",  // pobStateField 19
    ];

    // [END]
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes above
    // +---------------------------------------------------
  }

  // IMPORTANT - This will generate mig nodes to be put
  // into your content to be displayed in the scene.
  postinit {
    content = for (i in [0.. sizeof nodesToLayout -1]) {
      migNode(nodesToLayout[i], componentsConstraints[i]);
    }
  }

}

Note: Next, will be Step 3, Launching your newly created MigLayout form into the designer. Once you are happy with the constraints you may press the “Dump” button to output the code that you will cut and paste into your form above. Place code between your [BEGIN] and [END] comment tags (Between lines 32 & 60)

Step 3: Launching your newly created MigLayout form into the designer (see Main.fx for full source code below conclusion)

Create a Main.fx to launch form into the Poor Man’s Form Designer tool. (see poormans_form_designer.fx for full source code below conclusion )

  // http://pleasingsoftware.blogspot.com/search?q=layout
  // Create name form
  def nameForm:nameform.NameForm = NameForm{
  };

  // Remove content, due to nodes placed in the migNameForm later
  delete nameForm.content;

  // Create a miglayout designer object
  def designer:MigLayoutDesigner = MigLayoutDesigner{
      layoutConstraint: nameForm.constraints
      columnsConstraint: nameForm.columns
      rowsConstraint: nameForm.rows
      componentsConstraints: nameForm.componentsConstraints
      nodesToLayout: nameForm.nodesToLayout
  };

  // launch designer
  poormans_form_designer.launch(designer, null, null);

Troubleshooting

  • Missing imports or resolving – NetBeans: Control-Shift-I . Eclipse: Control-Shift-O . In NetBeans when using script level functions or classes inside a script file just use the ‘*’ wild card. ie: import poormans_form_designer.*;
  • Script file does not contain correct package name. ie: package xyz;
  • Form does not have two attributes called nodesToLayout and componentsConstraints.
  • If Form is extending from MigLayout it should set its content using the two known attributes. ie: content = for (i in [0.. sizeof nodesToLayout -1]) {
    migNode(nodesToLayout[i], componentsConstraints[i]);
    }

Enhancements

Above you will see hand coded controls that eventually get displayed onto the Form View window (Person Form). Since the the GUI control code elements are simply sequential, I believe it would be quite easy to add, insert and remove controls dynamically onto the Form View window area.  Also, possibly a property sheet window for controls, skins and behavior swapping.

Conclusion

The main goal is to properly lay out components in order to create a nice looking forms and also to learn the popular layout framework library MigLayout with it’s constraints language syntax. Using JavaFX‘s binding allows the form designer tool to enable the user to easily tweak constraints on the fly without having to rerun an application GUI for every adjustment made during the layout process in a form view. Another interesting thing to note is that since we are in the JavaFX world we can layout shapes, custom nodes and graphics (not just regular form controls). As a reminder regarding the use of JFXtras MigLayout, users should read about the known issues, please go to JFXtras MigLayout wiki at: http://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout . Well, by the time you read this blog entry I’m sure a nice GUI form designer/builder tool will be available.

The source code is listed below (click source code link to expand):

Note: I am in the process of putting the zipped up project onto the JFXtras.org for easy downloading.

Requirements:

  • Java 6 JDK or greater
  • JavaFX 1.2.1 SDK
  • JFXtras version 0.5 jars
  • latest Miglayout jar

Main.fx –

/*
 * Main.fx
 * @author Carl Dea
 * Created on Jan 9, 2010, 9:06:36 PM
 */
package migtest3;

import javafx.stage.Stage;
import migtest3.nameform.*;
import javafx.scene.Scene;
import org.jfxtras.scene.ResizableScene;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.paint.Color;
import migtest3.poormans_form_designer.*;

function run(__ARGS__ : String[]) {

  // Create name form
  def nameForm:nameform.NameForm = NameForm{
  };

  // Remove content, due to nodes placed in the migNameForm later
  delete nameForm.content;

  // Create a miglayout designer object
  def designer:MigLayoutDesigner = MigLayoutDesigner{
      layoutConstraint: nameForm.constraints
      columnsConstraint: nameForm.columns
      rowsConstraint: nameForm.rows
      componentsConstraints: nameForm.componentsConstraints
      nodesToLayout: nameForm.nodesToLayout
  };

  // Create a custom scene. Note: this overrides default
  // scene inside launch PMFD script function.
  var newDesignerViewScene:Scene = ResizableScene {
    fill: LinearGradient {
      startX : 0.0
      startY : 0.0
      endX : 1.0
      endY : 0.0
      stops: [
        Stop {
          color : Color.rgb(251, 251, 251)
          offset: 0.0
        },
        Stop {
          color : Color.rgb(240, 240, 240)
          offset: 1.0
        },
     ]
    }
    content: [designer.dynamicMigLayout] // use the dynamicMigLayout
  }; // scene

  // create a custom Stage. Note: this overrides default
  // Stage inside launch PMFD script function.
  var newDesignerStageView = Stage {
      y:150
      x:100
      width: 490
      height: 600
      scene:newDesignerViewScene
      visible:true
  };

  // launch designer
  poormans_form_designer.launch(designer, newDesignerViewScene, newDesignerStageView);
}

nameform.fx –

/*
 * nameform.fx
 *
 * @author Carl Dea
 * Created on Jan 9, 2010, 11:03:57 AM
 */

package migtest3;

import javafx.ext.swing.SwingComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
import javafx.scene.Node;
import javafx.scene.CustomNode;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.scene.Group;
import javafx.scene.shape.Rectangle;
import org.jfxtras.scene.layout.MigLayout;

public var US_STATES = [
  "AL","AK","AS","AZ","AR","CA","CO", "CT","DE","DC","FM","FL","GA","GU","HI","ID","IL","IN","IA","KS",
  "KY","LA","ME","MH","MD","MA","MI","MN","MS","MO","MT","NE","NV","NH","NJ","NM","NY","NC","ND","MP",
  "OH","OK","OR","PW","PA","PR","RI","SC","SD","TN","TX","UT","VT","VI","VA","WA","WV","WI","WY"
];

public function comboItemSeqCreator(names:String[]):SwingComboBoxItem[]{
  for( n in names) SwingComboBoxItem{ text:n };
}

public class SectionTitleHeader extends CustomNode {
  public-init var text:String;
  public var height;
  public var width;
  public var rectangleColor:Color on replace {
    if (not FX.isInitialized(rectangleColor)){
      rectangleColor = Color.rgb(0,130,171);
    }
  };

  public override function create():Node{
    var title = Group {
      content: [
        Rectangle {
          x: 10,
          y: height - 85,
          arcHeight: 5,
          arcWidth: 5,
          width: bind width - 30,
          height: 30,
          stroke:Color.BLACK
          fill: rectangleColor
          opacity: .5
        },
        Text {
          x: 15,
          y: height - 65,
          content: text
          fill: Color.WHITE
          font: Font {
            name: "Arial Bold"
            letterSpacing: .20
            size: 20
          }
        }
      ]
    }; // title
    return title;
  }

}

public class NameForm extends MigLayout {
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[] = [];

  init {

    var sectionFullName:SectionTitleHeader = SectionTitleHeader {
      id:"fullNameTitle"
      text:"1  Full Name"
      width:bind sectionFullName.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }

    def instructions:String =
      "- If you have only initials in your name, use them "
      "and enter (I/O) after the initial(s). \n"
      "- If you have no middle name, enter \"NMN\".\n"
      "- If you are \"Jr.,\" \"Sr.,\" etc. enter this in the box after your middle name.\n";

    var instructionsText:Text = Text {
      id: "instructionsText"
      content: instructions
      fill:Color.BLACK
      font : Font {
        embolden:true
        size: 14
      }
    };

    var firstNameLabel:Label = Label {
      id: "firstNameLabel"
      text: "First Name"

      font : Font {
          size: 18
      }
    };

    var firstNameField:TextBox = TextBox {
      id:"firstNameField"
    };

    var middleNameLabel:Label = Label {
      id: "middleNameLabel"
      text: "Middle Name"
      font : Font {
        size: 18
      }
    };

    var middleNameField:TextBox = TextBox {
      id:"middleNameField"
    };

    var lastNameLabel:Label = Label {
      id: "lastNameLabel"
      text: "Last Name"
      font : Font {
        size: 18
      }
    };

    var lastNameField:TextBox = TextBox {
      id:"lastNameField"
    };

    var suffixNameLabel:Label = Label {
      id: "suffixNameLabel"
      text: "Suffix"
      font : Font {
        size: 18
      }
    };

    var suffixNameField:TextBox = TextBox {
      id:"suffixNameField"
    };

    var sectionDob:SectionTitleHeader = SectionTitleHeader {
      id:"dobTitle"
      text:"2  Date of Birth"
      width: bind sectionDob.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }

    var dobLabel:Label = Label {
      id: "dobLabel"
      text: "DOB"
      font : Font {
        size: 18
      }
    };

    var dobField:TextBox = TextBox {
      id:"dobField"
    };

    var sectionPlaceOfBirth:SectionTitleHeader = SectionTitleHeader {
      id: "pobTitle"
      text: "3  Place of Birth"
      width: bind sectionPlaceOfBirth.scene.stage.width
      height:100
      rectangleColor:Color.BLUE
    }
    var pobCityLabel:Label = Label {
      id: "pobCityLabel"
      text: "City"
      font : Font {
        size: 18
      }
    };

    var pobCityField:TextBox = TextBox {
      id:"pobCityField"
    };
    var pobCountyLabel:Label = Label {
      id: "pobCountyLabel"
      text: "County"
      font : Font {
        size: 18
      }
    };

    var pobCountyField:TextBox = TextBox {
      id:"pobCountyField"
    };

    var pobStateLabel:Label = Label {
      id: "pobStateLabel"
      text: "State"
      font : Font {
        size: 18
      }
    };
    var statesComboItems = comboItemSeqCreator(US_STATES);
    var pobStateField:SwingComboBox = SwingComboBox {
       id: "pobStateField"
       items: [statesComboItems]
       visible: true
    }

    var changeFieldsOpacity:Node[] = [
      firstNameField,
      middleNameField,
      lastNameField,
      suffixNameField,
      dobField,
      pobCityField,
      pobCountyField,
      pobStateField,
    ];
    changeOpacity(changeFieldsOpacity, .80);

   nodesToLayout = [
      sectionFullName,  // 0
      instructionsText, // 1
      firstNameLabel,   // 2
      firstNameField,   // 3
      middleNameLabel,  // 4
      middleNameField,  // 5
      lastNameLabel,    // 6
      lastNameField,    // 7
      suffixNameLabel,  // 8
      suffixNameField,  // 9
      sectionDob,       // 10
      dobLabel,         // 11
      dobField,         // 12
      sectionPlaceOfBirth,// 13
      pobCityLabel,     // 14
      pobCityField,     // 15
      pobCountyLabel,   // 16
      pobCountyField,   // 17
      pobStateLabel,    // 18
      pobStateField,    // 19
    ];

    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes below
    // +---------------------------------------------------
    // [BEGIN]

    constraints = "";
    columns = "[pref]10[fill]";
    rows = "[pref]10[pref]";
    componentsConstraints = [
      "span, growx, wrap",                // fullNameTitle 0
      "span, wrap 15px",               // instructionsText 1
      "align right",                     // firstNameLabel 2
      "span, w min:100:300, wrap",       // firstNameField 3
      "align right",                    // middleNameLabel 4
      "growx, w min:100:300, wrap",     // middleNameField 5
      "align right",                      // lastNameLabel 6
      "growx,  w min:100:300, wrap",      // lastNameField 7
      "align right",                    // suffixNameLabel 8
      " w min:100:300, wrap 15",        // suffixNameField 9
      "span, wrap",                           // dobTitle 10
      "align right",                          // dobLabel 11
      " w min:100:pref, wrap 15",             // dobField 12
      "span, wrap",                           // pobTitle 13
      "align right",                      // pobCityLabel 14
      "span, growx,  w min:100:300, wrap",  // pobCityField 15
      "align right",                    // pobCountyLabel 16
      "span, growx,  w min:100:300, wrap",  // pobCountyField 17
      "align right",                     // pobStateLabel 18
      "span, w pref:100:pref + 10, wrap",  // pobStateField 19
    ];

    // [END]
    // +---------------------------------------------------
    // ! Poor Man's designer constraints info goes above
    // +---------------------------------------------------
  }

  // IMPORTANT - This will generate mig nodes to be put
  // into your content to be displayed in the scene.
  postinit {
    content = for (i in [0.. sizeof nodesToLayout -1]) {
      migNode(nodesToLayout[i], componentsConstraints[i]);
    }
  }

}
public function changeOpacity(nodes:Node[], opacityLevel:Float):Void {
  for (n in nodes) {
    n.opacity = opacityLevel;
  }

}

poormans_form_designer.fx –

/*
 * poormans_form_designer.fx
 *
 * @author Carl Dea
 * Created on Jan 2, 2010, 8:05:06 PM
 */
package migtest3;

import javafx.ext.swing.SwingComboBox;
import javafx.ext.swing.SwingComboBoxItem;
import javafx.ext.swing.SwingComponent;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.text.Font;
import javafx.stage.Stage;
import javafx.util.Sequences;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import org.jfxtras.scene.ResizableScene;
import org.jfxtras.scene.layout.MigLayout;
import org.jfxtras.scene.layout.MigLayout.*;
import org.jfxtras.stage.JFXDialog;

/** Title of the designer */
var designViewTitle:String = "Poor Man's Form Designer ver 0.2";

/**
 * This makes a node MigLayout capable.
 *
 */
public class MigLayoutDesigner {
  public var layoutConstraint:String = "";
  public var columnsConstraint:String = "";
  public var rowsConstraint:String = "";
  public var componentsConstraints:String[] = [];
  public var nodesToLayout:Node[];
  public var migNodesToLayout:Node[] = bind generateMigNodes(nodesToLayout, componentsConstraints);
  protected var dynamicMigLayout:MigLayout;

  postinit {
    dynamicMigLayout = createBindableMigLayout();
  }

//  public abstract function createMigLayoutNode():Void;
  /**
   * Creates a bound MigLayout.
   */
  bound public function createBindableMigLayout() {
    MigLayout {
        constraints: bind layoutConstraint
        columns: bind columnsConstraint
        rows: bind rowsConstraint
        content: bind migNodesToLayout
    }
  }
}

/**
 * Generates a sequence of Nodes wrapped with migNode function.
 */
bound public function generateMigNodes(nodes:Node[], componentConstraintStrings:String[]){
    for (i in [0.. sizeof nodes -1]) {
      migNode(nodes[i], componentConstraintStrings[i]);
    }
}

/**
 * @param migForm - a MigLayoutCapable form. Inherits from the mixin MigLayoutCapable.
 * @param designerViewScene - The designer View of the Scene. If null one will be created.
 * @param designerViewStage - The designer View of the Stage (Window). If null one will be created.
 */
public function launch(migForm:MigLayoutDesigner, designerViewScene:Scene, designerViewStage:Stage) {

  // List Comprehensions - Strings representing nodes' id
  var listIds = for( n in migForm.nodesToLayout) n.id;

  // List comprehensions - SwingComboBoxItems
  var comboboxItems = for( n in listIds) SwingComboBoxItem{ text:n };

  // Combo Box control with ids of each widget on form.
  var idListBox:SwingComboBox = SwingComboBox {
    text : bind componentIdSelection with inverse;
    items: [comboboxItems]
  }

  // select first one as default
  idListBox.selectedIndex = 0;

  ////////////////////////////////////////////////////
  // Layout constraints section
  ////////////////////////////////////////////////////
  var layoutConstraintLabel:Label = Label {
    text: "Layout Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }
  var layoutConstraintTextBox:TextBox = TextBox {
    text: migForm.layoutConstraint
    selectOnFocus: true
  }

  // Updates the layout constraint when pressed
  var layoutConstraintApplyButton:Button = Button {
    text: "Apply"
    action: function() {
        migForm.layoutConstraint = layoutConstraintTextBox.text;
    }
  }

  ////////////////////////////////////////////////////
  // Column constraints section
  ////////////////////////////////////////////////////
  var columnsConstraintLabel:Label = Label {
    text: "Columns Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }

  // Constraint textbox
  var columnsConstraintTextBox:TextBox = TextBox {
    text: migForm.columnsConstraint
    columns: 30
  };

  // Updates the columns constraint when pressed
  var columnsConstraintApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        migForm.columnsConstraint = columnsConstraintTextBox.text;
     }
  };

  ////////////////////////////////////////////////////
  // Row constraints section
  ////////////////////////////////////////////////////
  var rowsConstraintLabel:Label = Label {
    text: "Rows Constraint"
     font: Font {
        embolden:true
        size: 20
     }
  }

  // Constraint textbox
  var rowsConstraintTextBox:TextBox = TextBox {
    text: migForm.rowsConstraint
    columns: 30
  };

  // Updates the rows constraint when pressed
  var rowsConstrainApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        migForm.rowsConstraint = rowsConstraintTextBox.text;
     }
  };

  ////////////////////////////////////////////////////
  // Component constraints section
  ////////////////////////////////////////////////////
  var componentsConstraintLabel:Label = Label {
     text: "Nodes Constraints"
     font: Font {
        embolden:true
        size: 20
     }
  };

  // Constraint textbox
  var componentConstraintTextBox:TextBox = TextBox {
      columns: 30
  };

  var componentIdSelection: String on replace {
      componentConstraintTextBox.text = migForm.componentsConstraints [
        Sequences.indexByIdentity(listIds, componentIdSelection)
      ];
  };

  var componentConstraintApplyButton:Button = Button {
     text: "Apply"
     action: function() {
        for (i in [0.. sizeof migForm.nodesToLayout -1]) {
           if (migForm.nodesToLayout[i].id.equalsIgnoreCase(componentIdSelection)) {
              migForm.componentsConstraints[i] = componentConstraintTextBox.text;
              break;
           }
        }
     } // action
  };

  ////////////////////////////////////////////////////
  // MigLayout Dump constraints section
  ////////////////////////////////////////////////////
  var miglayoutDumpLabel:Label = Label {
     text: "MigLayout Constraints Dump"
     font: Font {
        embolden:true
        size: 20
     }
  };

  var constraintsDumpButton:Button = Button {
     text: "Dump"
     action: function() {
        //var miglayout:MigLayout = personFormScene.content[0] as MigLayout;
        var buffer:String;
        var migStructure:String;
        buffer = "constraints = \"{migForm.layoutConstraint}\";\n"
           "columns = \"{migForm.columnsConstraint}\"; \n"
           "rows = \"{migForm.rowsConstraint}\";\n"
           "componentsConstraints = [\n";

        for (i in [0.. sizeof listIds -1]) {
          //buffer2 = "{buffer2}\"  id: {listIds[i]} - {migForm.componentsConstraints[i]}\n";
          var strElement = "\"{migForm.componentsConstraints[i]}\", // {listIds[i]} {i} \n";
          var spaces = generateSpaces(55 - strElement.length());
          buffer = "{buffer}  \"{migForm.componentsConstraints[i]}\", {spaces} // {listIds[i]} {i} \n";
        }
        buffer = "{buffer}];\n";
        constraintsDumpTextArea.setText("{buffer}");
     }
  };

  // set up a swing wrapped text area box to hold dumped constraints.
  var constraintsDumpTextArea:JTextArea = new JTextArea();
  constraintsDumpTextArea.setColumns(40);
  constraintsDumpTextArea.setRows(20);
  constraintsDumpTextArea.setAutoscrolls(true);
  var sp = new JScrollPane(constraintsDumpTextArea);
  var constraintsDumpTextAreaJfx = SwingComponent.wrap(sp);

  // constrains for the constraint configure window.
  var editorComponentsConstraints = [
     "wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "wrap",
     "span, newline 15px, wrap",
     "growx",
     "grow",
     "",
     "newline 20px",
     "span, align right, wrap",
     "span, growx"
  ];

  var inputPanelToLayout = [
      layoutConstraintLabel,    // 0
      layoutConstraintTextBox,  // 1
      layoutConstraintApplyButton,// 2
      columnsConstraintLabel,   // 3
      columnsConstraintTextBox, // 4
      columnsConstraintApplyButton,// 5
      rowsConstraintLabel,      // 6
      rowsConstraintTextBox,    // 7
      rowsConstrainApplyButton, // 8
      componentsConstraintLabel,// 9
      idListBox,                // 10
      componentConstraintTextBox,// 11
      componentConstraintApplyButton, // 12
      miglayoutDumpLabel,       // 13
      constraintsDumpButton,    // 14
      constraintsDumpTextAreaJfx,// 15
    ];

// convert to miglayout type nodes wrapped.
var editorMigNodesToLayout:Node[] = bind generateMigNodes(inputPanelToLayout, editorComponentsConstraints);

// check if user of the api sent in a Scene or not.
var newDesignerViewScene:Scene = designerViewScene;
  if (designerViewScene == null) {
    newDesignerViewScene = ResizableScene {
      fill:  LinearGradient {
        startX : 0.0
        startY : 0.0
        endX : 1.0
        endY : 0.0
        stops: [
          Stop {
            color : Color.DARKTURQUOISE
            offset: 0.0
          },
          Stop {
            color : Color.WHITE
            offset: 1.0
          },
        ] // stops
      } // fill
      content: [migForm.dynamicMigLayout] // content
    }; // scene
  }

  // check if user of the api sent in a Stage or not.
  var newDesignerStageView:Stage = designerViewStage;

  if (designerViewStage == null) {
    // Form View (Person Form)
    newDesignerStageView = Stage {
      y:150
      x:100
      width: 500
      height: 500
      title: designViewTitle
      scene: newDesignerViewScene
      visible:true
      opacity: .94
    }
  } else {
    newDesignerStageView.scene = newDesignerViewScene;
    if ("".equals(newDesignerStageView.title.trim())) {
      newDesignerStageView.title = designViewTitle;
    }
  }

  // Generate Scene for design view if one doesn't exist
  var constraintInputWindowScene:Scene = ResizableScene {
     fill: Color.GRAY

     content: [
        MigLayout {
           content:bind inputPanelToLayout
        },
     ] // content
  };

  // Form Design Constraint Editor.
  var constraintInputWindow:JFXDialog = JFXDialog{
     title: "Form Design Constraints Editor"
     owner:newDesignerStageView
     x: newDesignerStageView.x + newDesignerStageView.width
     y: 150
     modal:false
     visible:true
     scene:constraintInputWindowScene
     height:500
     width: 600
  }
}

/** Assisting dump button to display constraints nicely
 * to ease cut and paste in to Form for the user.
 * @param numSpaces
 */
function generateSpaces(numSpaces:Integer):String {
  var spaces:String = "";
  for (i in [1..numSpaces]) {
    spaces = "{spaces} ";
  }
  return spaces;
}

References

MigLayout by Dean Iverson – Pleasing Softwarehttp://pleasingsoftware.blogspot.com/search?q=layout

JavaFX and Layouts by Amy Fowlerhttp://weblogs.java.net/blog/aim/archive/2009/09/10/javafx12-layout

JFXtras & MigLayouthttp://jfxtras.googlecode.com/svn/site/javadoc/release-0.5/org.jfxtras.scene.layout/org.jfxtras.scene.layout.MigLayout.html

MigLayout Cheatsheet http://migcalendar.com/miglayout/cheatsheet.html

JFXtras – Miglayout wikihttp://jfxtras.org/portal/core/-/wiki/JFXtras/MigLayout

Swing GUI Builder (formerly Project Matisse)http://netbeans.org/features/java/swing.html

Abeille Form Designerhttps://abeille.dev.java.net/

JForm Designerhttp://www.formdev.com/

WindowBuilderhttp://www.instantiations.com/windowbuilder/

Swing tutorialhttp://java.sun.com/docs/books/tutorial/uiswing/

SWThttp://www.eclipse.org/swt/

Swing layouthttp://java.sun.com/docs/books/tutorial/uiswing/layout/using.html

Swing GUI Builder (formerly Project Matisse)

JavaFX Forms Framework Part 5

Introduction

Tilghman Island

Tilghman Island

This is the final part in a five part series of entries creating a proof of concept for a JavaFX forms framework.  Here, I will conclude with some thoughts relating to using JavaFX to build a forms framework. If you are here for the first time I recommend going to the beginning: Part 1 “What is a Forms Framework?.  By now we should know why it is very important to understand the MVC architectural pattern and how it applies when building applications (especially form type applications). The forms framework helps provide manageable code and clear boundaries for both the designer role and developer role. This allows a designer to focus on the usability (look and feel) of the application meanwhile allowing the developer to focus on the service components and business logic. Next, I want to talk about the popular and ambiguous acronym ‘RIA’ (better known as rich internet application).  You are probably wondering what does RIA have anything to do with a MVC forms framework?  Well, let me try to explain. To some people ‘RIA’ is a buzzword and to some people its actually a tangible software development platform. I have a hunch that some people are not convinced they should jump on the bandwagon or not.  But whatever you choose to agree with, I believe as we slowly become convinced what RIA is and how it fits into our domain an MVC forms framework should always be considered when building larger scale forms based applications. I want to begin by discussing RIA from a Swing/Web/Enterprise developer point of view (considering that’s my background).

Swing/Web/Enterprise Developer

Like most of us who had previously come from an enterprise background noticed the Web 2.0 looking applications getting lots of attention.  Whenever I hear the word RIA, I really think that it should be called  “Rich Applications” and drop the ‘I‘ because all clients should look and feel great regardless if they were connected to the Internet or not. So, usability is key to making client side applications connect with users. This year I was fortunate to go to JavaOne 2009 and experience all things rich client (some enterprise) and one of my last sessions was about Craftsmanship in Software from Ben Galbraith. He mentions “the most important aspect of software creation: crafting an amazing user experience.” This makes me realize that as applications become richer, consumers will demand richer content (a vicious cycle).  But,  I still believe enterprise-y looking applications (not so flashy)  using Swing will still have its place in the world of rich client. When developing rich clients I’ve found Swing GUI development to be quite pleasant to work with, however at times certain things  seem pretty hard to do such as animation, binding, and validation.

Martin Jet Pack

Martin Jet Pack


JavaFX

If you are like me who enjoys software development will learn over time some good proverbs and principles that surface. One saying I’ve heard was “Letting Go of Your Bananas”, which can be applied to your life and not just software. In terms of software I feel its OK to let go of some things and embrace other things. So, it’s healthy to be early adopters, but always keeping in the back of your mind past concepts that are tried and true.  An example would be “Swing/Web Frameworks” where everyone created their own or used the vast number of open-source libraries out there.

Rocket man - melbourne show (Fir0002/Flagstaffotos)

Rocket man - melbourne show (Fir0002/Flagstaffotos)

Conclusion

As the JavaFX community matures I believe forms based frameworks, GUI tools and application frameworks should start appearing, making many developers’ jobs much easier. I think there will be a day where Java developers around the world will not think ‘Swing is Hard‘, but say ‘JavaFX is extremely Easy‘. I encourage you as the reader to explore and learn as much as you can about JavaFX because it’s not every day that you can start from the beginning to learn a soon-to-be-popular language. Enjoy!

JavaFX Forms Framework Part 4

Introduction

(Updated)** Added JavaFX Script code to represent a display panel mapping instead of the XML format example in the ‘User of the API (Application Developer)’ section.

(Thanks! Andres Almiray)


This is the forth installment of a series of blog entries relating to a proof of concept for a JavaFX Forms Framework.

Chairman and Co-Founder, Sun Microsystems. (person on the left)

Chairman and Co-Founder, Sun Microsystems. (person on the left)

If you missed the beginning of the series you may go to Part 1, Part 2 and Part 3. Before we discuss this section I want to recap for those who have been following along. Part 1 explains “What is a Form based framework?” and a demo application. Part 2 describes the requirements and design. Part 3 are the implementation details. As we near the end of the series in Part 4 we will discuss enhancements that could be added to improve the FXForms framework. Below is a summary of the features from two user perspectives, first being the ‘User of the API’ and secondly the ‘User of the application’. I will only explain in detail the ‘User of the API‘ perspective.

Features

User of the API (Application Developer)

  • Mapping files (forms to beans)
  • GUI Component factory – make use of other controls

User of the application (User experience)

  • Icon error, warn, info indicators overlay
  1. Override images to load and position when validation occurs.
  2. Error / Exceptions / Global errors
  3. Tooltips
  • Transitions to forms (fading, sliding, etc)
  • Focus Policy
  • User feedback (block gui visually) busy, indeterminate state.
  • Skinning and Themes for Forms
  • Context sensitive help
  • Sound effects

User of the API (Application Developer)

The goal here is to make life easier for the Application developer developing JavaFX Forms.

The features are as follows:

  • Mapping Files – This provides custom bindings to field elements and bean properties. Also provides mappings for actions and controls.
  • GUI Component factories – Display panel managers (form manager) to assemble and wrap components to provide presentation model pattern behavior.

1) Mapping files (forms to beans) – Display panels (forms) will be mapped to a particular Java Bean class. Each field will be mapped based on the bean property ‘name’ and the GUI widget’s ‘id’ attribute. When defining a display panel in XML it is a good idea to create a DTD or Schema, but for brevity I will not create them, and I will describe each XML element in detail such as default values, enumerated types, etc. an object representing fields and their corresponding bindings and actions will be called a ‘Display Panel Metadata’ object.

When mapping fields and controls here are situations you will encounter:

  • Nested properties – Nested objects ie: Person references an object Address with a property called city. (use  ‘address.city‘)
  • Mapping bean properties with different GUI component ids. ie: Person bean property is ‘fName‘ and the TextBox’s id is ‘firstName
  • Custom bindings (list models single select, multi-select and contains all). ie: ListBox containing all objects in a collection owned by Person.
  • Action mappings to events and controls. ie: Buttons are mapped to functions in modules (script level functions).

Below is an example of an XML mapping file might look like:

Please skip this mapping file and go to JavaFX script code right after it. XML mapping file is replaced by JavaFX script code.


<displayPanels>
  <!--
  *************************************************************************
  **** NO LONGER IN USE, DEPRECATED,                                  *****
  **** Please DISREGARD.
  **** REPLACED WITH JavaFX DisplayPanelMetadata class                *****
  *************************************************************************
  --------------------------------
  - Full Name Form Display Panel -
  --------------------------------
  This describes all gui widgets and actions.
    * Text control bound to a different named bean property 'fName'
    * Radio buttons bound to bean property 'likesFood'
    * Spinner control bound to bean property 'dateOfBirth'
    * Spinner control bound to bean property 'shoeSize'
    * List or combo control bound to nested bean property 'address.state'
    * List box control bound to nested bean property 'contacts' (collection)
    * List box control bound to nested bean property 'relatives' (collection)
    * Button control bound to action handler 
  -->
  <displayPanel id="FullNameForm" 
    className="com.company.forms.FullNameForm" 
    beanClass="com.company.domain.model.PersonBean" 
    presentationModel="com.company.domain.model.personpresentationmodel.EditPersonPM" 
    reuseForm=”true”>

    <!--First Name Field -->
    <textControl id="firstName" name="fName"/>

    <!-- Likes Food (yes or no)-->
    <radioButton id=”no”
      name=”likesFood”
      selectedValue=”true”
      bindingType=”BOOLEAN_TYPE”/>
    <radioButton id=”yes”
      name=”likesFood”
      selectedValue=”false”
      bindingType=”BOOLEAN_TYPE”/>

    <!--Date of Birth Spinner control-->
    <spinner id=”dob”
      name=”dateOfBirth”
      defaultValue=”null”
      bindingType=”DATE_TYPE”/>

    <!--Shoe Size Spinner control-->
    <spinner id=”shoeSize”
      name=”shoeSize”
      defaultValue=”5”
      minimumValue=”2”
      maxValue=”18”
      stepValue=”1”
      bindingType=”NUMBER” />

    <!--US State Drop Down List control-->
    <listModel id="address.state" 
      uniqueElementProperty="code" 
      displayName="${name}" />

    <!--Contacts List Box control-->
    <listModel id="contacts" 
      uniqueElementProperty="id" 
      displayName="${firstName} ${lastName} – (${id})" 
      bindingType="MULTIPLE_SELECT" />

    <!--Relatives List Box control-->
    <listModel id="relatives"
      name="relatives" 
      uniqueElementProperty="id"
      displayName="${firstName} ${lastName} – (${id})" 
      bindingType="CONTAINS_ALL" />

    <!--Save Button control-->
    <action id=”saveAction” 
      name="saveAction" 
      module="com.company.domain.forms.personactions" 
      function="updatePerson" 
      listener="java.awt.event.ActionListener"/>

  </displayPanel>
</displayPanels>

Line 15-19 Defines a form or display panel.

Line 22 Maps a text field when a bean property and GUI control are named differently. Framework should assume if the name and id are the same and at text control developer doesn’t need to map. This keeps the XML mapping file smaller.

Line 24-32 Custom binding for radio controls. Other scenarios exist, but for a simple boolean value selection the ‘BOOLEAN_TYPE’

Line 34-38 Custom binding for a JSpinner. The bean property is a java.util.Date.

Line 41-47 Custom binding for a JSpinner. The bean property is an int.

Line 49-52 Custom binding for a dropdown list. The bean property is a java.lang.String. Notice the nested properties. When a form displays a nested property on the same form.

Line 54-58 Custom binding for a List box. This is not bound to a bean property but a populated list allowing the user to select multiple items.

Line 60-65 Custom binding for a List box. This is bound to a bean property referencing a collection of objects. Notice the “CONTAINS_ALL” which means all items in the list box are synchronized inside the collection.

Line 67-72 Action mapping for a button on the form. They are named so actions can be reused such as menu or toolbar.

Disregard the XML mapping file above. The same representation is below in JavaFX script code defining a display panel.

**IMPORTANT NOTE: Since JavaFX is a declarative language which can achieve the same thing as a mapping file, I have re done the XML in JavaFX below:

var displayPanelMetadata:DisplayPanelMetadata =  DisplayPanelMetadata {
  id: "FullNameForm"
  className: "com.company.forms.FullNameForm"
  reuseForm: true

  // Custom bindings to field elements
  customBindings: [
    // First Name Field 
    TextControlMetadata{
        id: "firstName"
        name: "fName"
    },

    // Likes Food (yes or no)
    RadioButtonMetadata {
        id: "no"
        name: "likesFood"
        selectedValue: "false"
        bindingType:"BOOLEAN_TYPE"
    },

    RadioButtonMetadata {
        id: "yes"
        name: "likesFood"
        selectedValue: "true"
        bindingType:"BOOLEAN_TYPE"
    },

    // Date of Birth Spinner control
    SpinnerMetadata {
        id: "dob"
        name: "dateOfBirth"
        defaultValue:"null"
        bindingType: "DATE_TYPE"
    },

    // shoe size
    SpinnerMetadata {
        id: "shoeSize"
        name: "shoeSize"
        defaultValue: "5"
        minimumValue: "2"
        maxValue: "18"
        stepValue: "1"
        bindingType: "NUMBER_TYPE"
    },

    // state selection
    ListModelMetadata {
        id: "address.state"
        uniqueElementProperty:"code"
        displayName:"${name}"
    },

    // contacts List Box control (not bound to bean)
    ListModelMetadata {
        id: "contacts"
        uniqueElementProperty:"id"
        displayName: "${firstName} ${lastName} – (${id})"
        bindingType: "MULTIPLE_SELECT"
    },

    // relatives(family) List Box control
    ListModelMetadata {
        id: "relatives"
        name: "relatives"
        uniqueElementProperty:"id"
        displayName: "${firstName} ${lastName} – (${id})"
        bindingType: "CONTAINS_ALL"
    }
  ] // custom bindings sequence

  // Save Button control
  actions:[
      ActionMetaData {
      name: "saveAction"
      module: "com.company.domain.forms.personactions"
      functionReference: "updatePerson"
    }

    ] // actions

} //displayPanel

Line 0-3 Defines a form or display panel.

Line 8-11 Maps a text field when a bean property and GUI control are named differently. Framework should assume if the name and id are the same and at text control developer doesn’t need to map. This keeps the metadata mapping info smaller.

Line 14-19 Custom binding for radio controls. Other scenarios exist, but for a simple boolean value selection the ‘BOOLEAN_TYPE’

Line 29-34 Custom binding for a JSpinner. The bean property is a java.util.Date.

Line 37-45 Custom binding for a JSpinner. The bean property is an int.

Line 48-52 Custom binding for a dropdown list. The bean property is a java.lang.String. Notice the nested properties. When a form displays a nested property on the same form.

Line 55-60 Custom binding for a List box. This is not bound to a bean property but a populated list allowing the user to select multiple items.

Line 63-69 Custom binding for a List box. This is bound to a bean property referencing a collection of objects. Notice the “CONTAINS_ALL” which means all items in the list box are synchronized inside the collection.

Line 74-77 Action mapping for a button on the form. They are named so actions can be reused such as menu or toolbar.

2) GUI Component factory – make use of other controls

Forms can be retrieved and populated using convenient factory functions. When assembling a form and mapping properties the underlying implementation may have a configuration file to have modular ways to specify what controls will be used for binding controls.

var personForm = FormManager.retrieveForm(personBean, 'FullNameForm');

Conclusion

While some features of the ‘User of the Application‘ perspective may seem debatable, I believe a forms framework should at least have all the features described in the ‘User of the APIs‘ perspective. As a framework implementer one should design a plug-able architecture for custom components. Components should be wrapped and registered when assembling a form. I’m sure there are many features I have not mentioned.  As JavaFX matures I believe GUI builders providing WYSIWYG development will eventually allow third party GUI controls to be added to their control palette. Next, I will discuss Part 5 Concluding thoughts.

Like always, any comments are welcome!

JavaFX Forms Framework Part 3

Introduction

Matthew 7:13-14

Matthew 7:13-14

This is the third installment of a series of blog entries relating to a proof of concept for a JavaFX Forms Framework. If you missed the beginning of the series you may go to Part 1 and Part 2. We will take a look at code snippets relating to how the FXForms Framework was implemented. If you want to jump right into the code you may download it here or browse the source code here from the JFXtras Samples area. To those who are following the series will notice similarities to JGoodies Bindings and Validation libraries, it is because of those libraries and presentations which basically inspired me to create this MVC forms framework in JavaFX.

Disclaimer: Most of the code snippets will deal with the user of the API’s perspective as opposed to the implementer of the API’s perspective in order to keep the blog entry short. I will try my best to touch on areas regarding the framework’s underlying implementation. I advise people to check the code project out, review it and run it.

A thing I’d like to bring to your attention is that I decided to refactor the code a little and add a new feature to the FXForms Framework. I refactored the form to reference an instance of a presentation model instead of inheriting from it. As I mentioned in Part 2 the form will be independent of the presentation model. This provides different validation contexts while reusing the same form. An example of this situation is when a user uses a form to ‘Add’ information versus an ‘Edit’ of the form information. The new feature added is the ability to validate a field as the user is typing into the text box and positions an icon to indicate an error, warning or information to the user. Another feature might be to add tool tips when the mouse hovers over the icon similar to JGoodies’ IconFeedbackPanel behavior in Java Swing. Before going further into the implementation details you may want to launch the demo to get a feel for the behavior of the entry form with validation and icon indicators.

Demo

Demo

Instructions:

  • Enter numbers or symbols into the first, last and middle name field.
  • Click on the check box to swap the JavaBean for the form.
  • Observe the underlying bean values changing.

Next are the steps on how to develop the demo using the FXForms Framework.

Developer Steps

  1. Create a JavaBean representing a domain object.
  2. Create a Presentation Model with validation for a Form.
  3. Create a Form
  4. Associate a Presentation Model to Form
  5. Use the Form in an application

Detailed Steps

Step 1. Create a JavaBean representing a domain object.

// Java
public class PersonBean extends DomainModel{
    public static final String FIRST_NAME_PROPERTY = "firstName";
    public static final String MIDDLE_NAME_PROPERTY = "middleName";
... // more strings naming properties
    private String firstName;
    private String middleName;
... // more attributes

    /**
     * Returns first name of the person.
     * @return
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * Sets the first name of the person.
     * @param firstName
     */
    public void setFirstName(String firstName) {
        String old = this.firstName;
        this.firstName = firstName;
        firePropertyChange(FIRST_NAME_PROPERTY, old, firstName);
    }
... // the rest of the methods.
}

PersonBean.java – A domain object containing property change support.

DomainModel.java – Abstract base class containing property change support.

Step 2. Create a Presentation Model with validation for a Form

As I mentioned earlier about reusing the same form with different presentation models. Below you will see an ‘Add Form’ with validation on the Last Name field. The error icon indicates that the last name may not contain symbols and numbers, but allowing letters, apostrophe or hyphen in the name. You will notice the red error icon beside the ‘last name’ text field.

Add Form validation on last name field.

Add Form validation on last name field.

    // JavaFX
    var personForm:NameForm = NameForm{
        presentationModel:domain.model.personpresentationmodel.AddPersonPM{}
    };
    personForm.presentationModel.jBean = new PersonBean();

Next, you will see an ‘Edit Form’ with no validation on the ‘Last Name’ field. But, there is validation on the ‘First Name’ field. The warning icon indicates that the first name can contain symbols and numbers, letters, apostrophe or hyphen, but isn’t recommended. You will notice the yellow warning icon beside the ‘first name‘ field.

Edit Form no validation for Last Name field

Edit Form no validation for Last Name field

    // JavaFX
    personForm.presentationModel = domain.model.personpresentationmodel.EditPersonPM{}
    personForm.presentationModel.jBean = personBean2;

*Note: The examples above are two hypothetical use cases, I mocked up those forms to help illustrate different validation contexts. The demo app uses an edit presentation model that does validate on the ‘last name‘ field.

Edit Person Presentation Model w/Validation

// JavaFX
public class EditPersonPM extends fxforms.model.model.PresentationModel {

   /** Validate the first name field */
   var validateFirstName =  Validator{
       id:PersonBean.FIRST_NAME_PROPERTY
       public override function validate(value:Object){
           return validateName(value, PersonBean.FIRST_NAME_PROPERTY, "Warning");
       }
   };
... // more validators
   postinit {
       addValidator(validateLastName);
       addValidator(validateFirstName);
       addValidator(validateMiddleName);
   }
}
// Script level function
/**
 * Using regular expression allow letters, apostrophe, hyphen
 */
function validateName(value:Object, propName:String, messageType:String){ // use friendly names, short names, etc.
    var results = ValidationResult{};
    var strValue:String = value as String;
    var found:Boolean = Pattern.matches("^[a-zA-Z,'.\\-\\s]*$", strValue);
    if (not found) {
        var message:FieldMessage = FieldMessage{
            id:propName
            messageType:messageType
            errorId:"123"
            errorMessage:"No symbols in names except - or ' (apostrophe)"
        }
        results.addMessage(message);
    }
    return results;
}

Line 01: Class EditPersonPM extends fxforms.model.model.PresentationModel
Line 04: var validateFirstName is an instance of a Validator
Line 12: Adds all Validators to the presentation model
Line 21: Script level function to be used in each validator
Line 24: Regular expression to allow letters, apostrophe and hyphen characters only.
Line 26: Creation of the message when Validator validates.

personpresentationmodel.fx – The edit presentation model for a person name form.

model.fx – Contains presentation model and value model implementation.

validation.fx – Contains the validator, message, result classes.

Step 3. Create a Form

Edit Person Form (Screen mockup)

0 full name panel
+-1------------------------+ // VBox with 3 things
! +-2--------------------+ ! // HBox with 2 things
! ! [ 3 ] [ 4           ]! ! // Label(section) and Label(title)
! +----------------------+ !
! +-5--------------------+ ! // HBox with 2 things
! ! [ 6 ] [ 7  ]         ! ! // Label(spacer} and Text(instructions)
! !                      ! ! // wrapping text abilities
! +----------------------+ !
! +-8--------------------+ ! // HBox with 2 things
! ! +-9---+ +-10-------+ ! ! // VBox_9(labels) Vbox_10(textbox)
! ! ![11] ! ! [15]     ! ! ! // Label(lastname) TextBox()
! ! ![12] ! ! [16]     ! ! ! // Label(firstName) TextBox()
! ! ![13] ! ! [17]     ! ! ! // Label(mi)  TextBox()
! ! ![14] ! ! [18]     ! ! ! // Label(suffix)  TextBox()
! ! +-----+ +----------+ ! !
! +----------------------+ !
+--------------------------+

NameForm inherits from Form
Form inherits from CustomNode

// JavaFX
public class NameForm extends fxforms.ui.form.Form {
   public override function create():Node{
        // 0 main panel
        var mainPanel:Panel = Panel{
            content:[]
        }
        ... // more layouts and widgets

        var firstLabel:Label = Label {
            text: "First Name"
            hpos:HPos.RIGHT
            font : Font {
                size: 18
            }
            layoutInfo: LayoutInfo { minWidth: 100 width: 150 maxWidth: 200 }
        };
        ... // more code
        var lastNameTextBox:TextBox = fxforms.ui.controls.MyTextBox {
            id:"lastName"
            columns:20
        };
        var miNameTextBox:TextBox = fxforms.ui.controls.MyTextBox {
            id:"middleName"
            columns:20
        };
        ... // more fields

        // *** NOTE: This is for easy lookup. And relating to Scene.lookup(id) bug in 1.2.
        guiFields = [lastNameTextBox, firstNameTextBox, miNameTextBox, suffixNameTextBox];
        presentationModel.addGuiFields(guiFields);
        return mainPanel;
    } // create()
} // NameForm

NameForm.fx – This represents a Form containing a person’s name information.

form.fx – This is the base class which contains the presentation model for forms binding behavior.

controls.fx – This contains all registered GUI controls for observing value model value changes. Currently only one control exists the TextBox control.

*Note: The ideal way to build forms is using the JFXtras MigLayout library. To learn more take a look at Dean Iverson’s blog entry called “MigLayout for JavaFX Reloaded“.

Step 4. Associate a Presentation Model to Form

// JavaFX
var personForm:NameForm = NameForm{
    presentationModel:domain.model.personpresentationmodel.EditPersonPM{}
    translateX: bind slideFormX
};
var personBean2:domain.model.PersonBean = new domain.model.PersonBean();
personBean2.setFirstName("Squidward");
personBean2.setLastName("Tentacles");
personBean2.setMiddleName("Nickelodeon");
personBean2.setSuffixName("Sr.");

// set presentation model with domain object
personForm.presentationModel.jBean = personBean2;

Line 02: Associate presentation model to form
Line 05: Create an instance of a JavaBean
Line 12: Bind bean to presentation model and form

Once the presentation model and form are assembled binding an existing Java object is a snap. In part 4 on enhancing this process would be to create a factory to obtain meta information of the form to retrieve nested properties within a POJO/JavaBean off of the JavaFX main thread (desktop profile is the EDT). This effort will help alleviate from the dreaded Hibernate lazy init exception when using detached objects. So, making sure you don’t block the GUI thread is a big deal when it comes to user experience.

Step 5. Use the Form in an application

    var switchPersonButton:CheckBox = CheckBox {
            text: bind personToSwitchText
            width: 100
            translateX: 5
            translateY: bind mainScene.height - switchPersonButton.height - 5
            allowTriState: false
            selected: false
            override var onMouseReleased = function(e:MouseEvent):Void {
                if (selected){
                   personToSwitchText = "Sponge Bob";
                   personForm.presentationModel.jBean = personBean1;
                } else {
                   personToSwitchText = "Squidward";
                   personForm.presentationModel.jBean = personBean2;
                }
            }
        };

    var mainScene:Scene = Scene {
        fill: LinearGradient {
                    startX: 0
                    startY: 0
                    endX: 0
                    endY: 1
                    stops: [
                        Stop { offset: 0.1 color: Color.ORANGE },
                        Stop { offset: 1.0 color: Color.YELLOW },
                    ]
                }
        content: [personForm, switchPersonButton, backButton, nextButton]
    };

Main.fx – The main application file to launch the application

Value Model

The ‘Value Model‘ is probably the most important aspect of how the Forms binding works. The value model is a model that holds a single value that notifies registered listeners that a value has changed. Registered listeners will likely be GUI controls and JavaBean properties. Bidirectional binding occurs when a bean property value changes, which notifies the value model which updates the  GUI control value. This holds true when going in the other direction too, such as the user changes the value of the GUI control which notifies the value model which updates the JavaBean property value.

model.fx – Contains presentation model and value model implementations.

Conclusion

I feel the implementation of the fxforms framework using JavaFX was extremely easy and is a lot less code compared to a Java equivalent of a Swing/SWT forms framework, also carrying additional overhead using 3rd party libraries for binding and validation. As JavaFX matures with more controls the forms framework would need to be flexible enough to add any controls to handle custom bindings such as list models.  Next we will look at Part 4 Enhancements . As always any feedback is welcome!

References

Validation presentation by Karsten Lentzsch – http://www.jgoodies.com/articles/validation.pdf

JGoodies: http://www.jgoodies.com/

JGoodies Support: http://www.jgoodies.com/products/purchase.html

The Unknown JavaBean by Richard Bair – http://weblogs.java.net/blog/rbair/archive/2006/05/the_unknown_jav.html

JavaFX – JMS Unexpected Null Pointer Exception http://blogs.sun.com/clarkeman/entry/javafx_jms_unexpected_null_pointer

JFXtras Community Site – http://jfxtras.org/portal/home