Tag Archives: bind

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

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 – 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 2

Full Name Form with Validation

Full Name Form with Validation

Introduction

This is the second installment of a series of blog entries relating to a proof of concept for a JavaFX Forms Framework. Before I specify the requirements and a simple design of the FXForms Framework, I want to follow-up on comments about tough issues relating to enterprise application development and JavaFX. If you recall in Part 1 we discussed about MVC form based technologies. I listed many technologies that may only provide a means to separate content from presentation, but they don’t necessarily deal with real plumbing problems such as Threading, Binding, Validation and Transactions.  I believe these issues are one of the most important areas that must be understood in the JavaFX world, thus positioning itself as a premiere enterprise development platform (not just rich client a hint to Oracle).  As Richard Obaldeston mentions in the comments regarding enterprise level support for JavaFX controls similar to Swinglabs / SwingX. I cannot speak of the future concerning Swing & Swinglabs, but I believe the JavaFX teams are well aware of them, (very much so) due to the fact that the same project owners of the SwingX-WS are leads on the JavaFX teams (I don’t mean to put them on the spot).  Many of these difficult issues are mostly solved in the Browser based Web Application Development world and can also be done in the JavaFX world (Check out Exadel). Of course I can’t answer all the hurdles faced in just one blog entry especially because every enterprise application has varied types of architectures such as (Corba, RMI, EJB, Soap, RESTful, JSON, Jini, JMS, DAO, JDO, JDBC, JNI, etc). It would literally take a book or two. But, I will try to address some of them later in this blog as we discuss JavaFX and Java interoperability. One should always re-evaluate one’s architecture to adhere to best practices or industry standards and conventions. However, some standards seem to stall or are slow to be accepted (JSR-296 or JSR-295) with many reasons of course. It is often the nature of the beast when it comes to new technology solutions and refactoring that subsequently boils down to when and how you want to pick your battles. As readers of this blog for the first time who might be new to JavaFX or Java Swing I strongly encouraged you to explore and learn about the pitfalls of thread safety, asynchronous behaviors, best practices, and UI concepts like (UI blocking, disabling, progress bar, etc.) . So, let’s get started with requirements and the analysis phase of our FXForms Framework.

Requirements

My main goal of these series of blog entries are to share with others my experience with creating an application called iSF86 which hopefully will end up in the Java App Store and make tons of cash so I can quit my day job like Ethan (wishful thinking). iSF86 is an application which contains many form elements to capture a person’s information to apply for a job doing classified work for the United States Government. When dealing with so many forms I thought of building a mini Forms Framework where domain objects can be mapped to forms. The application will save a person’s SF86 information locally as an XML file. This is purely a client-side application which doesn’t involve any server-side communications. Each form would be bound to domain objects in a bi-directional way. A domain object that is bound to an existing form can be swapped out with a different domain object, thus making the old bean ready to be garbage collected and unbound to the form. Important Note: One requirement is that the domain objects will be JavaBeans (see disclaimer below).

Disclaimer: For brevity, the requirements are for JavaBeans (change support) and not POJOs (there is a difference)! Yes, I know I said POJOs in Part 1, because I wanted to tackle the problem using byte code engineering for boiler plate code for property change support on POJOs, but it would be beyond the scope of the blog series’ goal. So, this tutorial is really geared for people migrating Swing/SWT applications to JavaFX applications.

FX Forms Framework Requirements:

  1. It shall bind JavaFX Forms to JavaBeans as domain objects
  2. It shall enable JavaBeans to be swapped in a JavaFX Form
  3. It shall provide validation on property values
  4. It shall provide Action bindings to controls

To keep the framework simple shown below is a list of features that the framework does not provide.  Interesting features will later be discussed in Part 4 Enhancements.

What the FX Forms Framework does NOT provide:

  • Application Framework Features (like JSR-296)
    • Application Lifecycle
    • Resource injection
    • Task Monitoring
    • Saved Preferences
    • Thread Pool
  • Forms Building
  • RCP Features
    • Window Docking
    • Menu bar
    • Etc.

Design

Before going into the design I want to mention other technologies which provide beans to forms binding or RIA MVC capable. The mentioned technologies are primarily Java based. Some of my classes will use similar names because I’ve taken a lot of ideas from these frameworks in the past.  Here are just a few that come to mind:

As a reminder regarding rapid application development that there are two main areas on building applications quickly ‘Forms building’ and ‘Forms binding’. We will only focus on ‘Forms binding’. Ok, already! Let’s get to the designing.

User of the API

At times when it comes to API development I’m a fan of user driven design and the user of the API is who I’m targeting. So, I pretend the FXForms Framework is ready to be used. Below is the developer obtaining a JavaBean and setting it into some Form.

// normally loaded from external source
var personBean1:PersonBean = new PersonBean(); // change support enabled
personBean1.setFirstName("SpongeBob");
.
.
.

// generate a form and bind to bean. Req #1
var personForm:NameForm = NameForm{
jBean:personBean1
};

// subsequent time another domain object is loaded Req #2
var personBean2:PersonBean = … // loaded from external source.
personForm.jBean = personBean2; // old person bean is no longer attached

// set property to an invalid value. Req #3
personBean2.setFirstName(“#$”);
// validator will fire to output error message

// create navigation panel area Req #4
var mainButtonForm:PresentationModel = MainButtonForm {
jBean:personBean2
};
var nextButton:Button = mainButtonForm.getControl(‘nextButton’);
// simulate a mouse press to test action binding

Let’s step through the code to see what is happening and what the FXForms Framework will provide.

Line 1: Creating domain object or loading from external source. Notice the new on the PersonBean (Java Object).

Line 8: Instantiating a Form to use the current JavaBean loaded. All the properties will be bound to form elements bi-directionally. Changes in the GUI will affect properties and changes to the bean properties will change the GUI fields. (Requirement #1)

Line 14: Swapping a domain object with the existing form. Old bean is disconnected from the form and replaced with new bean. Presentation Model Pattern (Requirement #2)

Line 17: Validation of a property when set with invalid data. The user can type into the textbox to fire the validator code. ValueModel updated (Requirement #3)

Line 20-25: A button panel form has buttons mapped to actions. Command Pattern. Yes, this is strange but I will explain later. (Requirement #4)

You will notice the comment on line 25 where you could test a control such as obtaining a button independent of the application being launched (testing individual forms).  This brings up a good point when using MVC Frameworks.  In  the Swing world I highly recommend using FEST Swing a tool to test Swing applications with ease. Also, the team is working on FEST for JavaFX with a joint effort with the JFXtras team.

JavaFX and Java Interoperability

While the user of the API appears simple, it is the behind-the-scenes work that some may consider unorthodox when using a combination of Triggers, Bindings, Reflection, Threading, etc. Until the JavaFX teams expose (with JavaFX blessed code) better interoperability against Java and JavaFX, I will resort to techniques that are known and official. When using triggers on properties this enables us to follow the basic proxy pattern: where a property is changed you will have an opportunity to intercept the change and update value models, which also updates GUI widgets mapped to them. One concern is that object-based memory leaks could occur with inverse bind (Maybe we need a unbind keyword). Another problem is properly populating a form on or off the JavaFX’s main process thread (EDT on the desktop environment) without blocking the GUI thread or causing some race condition.  What follows are the necessary classes to implement the FXForms framework.

Class Design

// Java
DomainModel <abstract Java class>
pcs:PropertyChangeSupport

// Java
PersonBean extends DomainModel <JavaBean>
firstName:String
middleName:String
lastName:String
suffixName:String

// JavaFX
FieldSetter <mixin class> Adapter
valueModel:ValueModel
boundValue:String

// JavaFX
MyTextBox extends TextBox, FieldSetter
updateField(obj:Object):Void

// JavaFX
ValueModel <adapter class>
propertyName:String
guiControls:FieldSetter[]
boundValue:Object
jBean:Object

// JavaFX
PresentationModel extends PropertyChangeListener <mixin class>
propertyValues: Map <String, ValueModel>
propertyValidatorMap: Map <String, List<Validator>
jBean: DomainModel // java class javabean inherit
getModel(propertyName:String):ValueModel
getControl(propertyName:String):Control
propertyChange(changeEvent: PropertyChangeEvent):Void

//JavaFX
Form
guiControls:[] // Scene.lookup(id) is currently a bug in jfx 1.2
presentationModel:PresentationModel

// JavaFX
NameForm extends CustomNode, Form, PresentationModel <JavaFX node>

// JavaFX
Validator
validate():ValidationResult

// JavaFX
ValidationResult
messages:[]
getMessages():Message

// JavaFX
Message

key:String
propertyName:String
errorType:[“warn”,”err”]
text:String

You will notice the NameForm inherits the PresentationModel mixin class which provides the controller functionality mixed in with the view. I’ve done this to make the example short Sometimes I’m not a big fan of inheritance especially in this case and decided to refactor in order to separate the “Presentation Model” from the “View Form” for form reuse.  There are situations such as reusing the same GUI form for “Adding” and “Editing“, but the validation of the form data is different. Developers may also create a concrete presentation model that inherits from the mixin class to be independent of the view so that it decouples the Form and the Presentation Model. An EditPresentationModel and an AddPresentationModel should be created to have two different validations using the same GUI form.  Again for a slim down version I’ve omitted the ability to consume an XML file detailing the form metadata and mappings such as actions, mandatory fields, validators, and custom bindings; the named property is the same name as the ‘id’ attribute on the JavaFX GUI form element (TextBox). I will discuss in Part 4 Enhancements. Relating to validation at some point I would like to provide a way to display warnings and error icons beside fields whenever a Validation Result is updated. The validation results will contain the error messages to be displayed. One last thing to mention is GUI component factories aren’t in the design. You will also notice when creating a TextBox I needed additional behavior, so I simply created a MyTextBox instead of something like var field1 = WidgetFactory.create(valueModel, “TextBox”) to wrap, return and put it into form.

Conclusion

This is surely not a thorough or complete design, but an attempt to demonstrate how to make a simple forms framework from scratch. Hopefully, I’ve captured the requirements and articulated the object relationships within the textual design (No UML sorry). Next, we will look into how to implement the FXForms Framework in Part 3. Please be advised that this design is subject to change as any development shop would.

Personal Note: Sometimes I wonder if Sun had focused on finishing the often highly controversial JSR 295 Beans Binding and Java Properties before JavaFX would it render this blog entry totally useless.

Any comments are welcome. Thanks!

-Carl

References

JavaFX w/JMS 307-311 JavaFX RIA book Server Call Back- James Clarke updates code for JavaFX – Developing RIA book regarding Code Recipe for JMS pages 307-3011 http://tinyurl.com/lzej5x

Best practices: http://www.greggbolinger.com/blog/2008/11/11/1226423940000.html

Fuse http://weblogs.java.net/blog/joconner/archive/2007/11/index.html

Exadel, http://mkblog.exadel.com/ria/javafx-and-spring-crud/

Swing worker / swing/ http://java.sun.com/javase/6/docs/api/javax/swing/SwingWorker.html

Beans Binding: https://beansbinding.dev.java.net/

RESTful : http://jnb.ociweb.com/jnb/jnbAug2009.html

jfxtras JFXWorker http://jfxtras.googlecode.com/svn/site/javadoc/release-0.5/org.jfxtras.async/org.jfxtras.async.JFXWorker.html

Java Properties: http://tech.puredanger.com/java7#property

Fxbuilder: http://www.jroller.com/aalmiray/entry/griffon_gfxbuilder_fxbuilder_side_by

Disable Swing Container:http://weblogs.java.net/blog/alexfromsun/archive/2007/06/_enablingdisabl_1.html

Java Properties without Getters and Setters: http://www.artima.com/forums/flat.jsp?forum=270&thread=247837

Beans Binding (JSR 295) & Properties on JDK7: http://www.javalobby.org/java/forums/t101998.html

JSR 295: http://jcp.org/en/jsr/detail?id=295

RCP Spring rich- http://spring-rich-c.sourceforge.net/1.0.0/index.html

SAF:https://appframework.dev.java.net/intro/index.html

Oracle buys Sun : http://www.oracle.com/us/corporate/press/018363