JavaFX 2.0 Introduction by Example book

19 12 2011

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 2.0 Roadmap

28 02 2011
Road to tommorow

Road to Tomorrow

In case folks did not notice the JavaFX Script Language has been converted to pure Java APIs for JavaFX 2.0. I’ve recently been fortunate to be able to take things out for a spin (JavaFX 2.0 EA). To my surprise I am very impressed by the work of the JavaFX teams and the amount that had been done so far. In a little over a four month period, they have managed to take on most if not all the features from 1.0 to 1.3.x, added many new controls and increased performance. Kudos Guys and Gals at Oracle!

There is much more to do and if you want to help out please be on the look out for JavaFX 2.0 Beta release and then head on over to http://javafx-jira.kenai.com for tracking bugs and features. To see what tomorrow brings for JavaFX here is the official Roadmap: http://javafx.com/roadmap/

I believe the road to the Java Desktop is getting brighter again.





JavaFX – Tabbed Pane & Tab Panel

21 07 2010
JavaFX - Tabbed Pane & Tab Panel

JavaFX - Tabbed Pane & Tab Panel

Introduction

With each release of JavaFX, more components and controls are becoming available for developers to use when building applications. However, what if you needed a component that wasn’t included in the current JavaFX release? Well, you have a few options: you could Wait, Find, Pay or Build it yourself (or suck your thumb and rock back and forth in a fetal position underneath your desk).  The current version of JavaFX 1.3.x did not include a Tabbed Pane class, so I decided to create my own. My intent was to see how rapidly I could create a Tabbed Pane and Tab Panel that looked pretty decent.

Disclaimer: This implementation does not follow best practices (API design). So use at your own risk. I encourage the reader to take the code apart and to improve it as they see fit.
To launch the Tab Demo click button (Java Webstart App).

Tab Demo

Screen Shots

Tab Demo - Tab 0

Tab Demo - Tab 0

Tab Demo - Tab 1

Tab Demo - Tab 1

Tab Demo - Tab 2

Tab Demo - Tab 2

Source Code

Main.fx

Below is the main application which assembles a TabbedPane to be displayed in a Scene on the Stage.

package tabdemo;

import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.effect.DropShadow;
import javafx.scene.paint.Color;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Circle;
import javafx.scene.paint.RadialGradient;
import javafx.scene.layout.VBox;
import javafx.scene.layout.HBox;
import javafx.scene.control.Label;
import javafx.scene.control.TextBox;
import javafx.animation.Timeline;
import javafx.animation.Interpolator;
import javafx.scene.shape.Rectangle;
import javafx.stage.StageStyle;
import javafx.scene.input.MouseEvent;
import javafx.scene.Group;
import javafx.scene.text.Text;
import javafx.scene.text.Font;
import tabdemo.tabs.TabPane;
import tabdemo.tabs.TabPanel;
import javafx.scene.paint.LinearGradient;
import javafx.scene.control.Button;

/**
 * @author cdea
 */
var stageWidth:Float = 450;
var stageHeight:Float = 400;

var tabPane:TabPane = TabPane {
  translateY: 20
};

var tab0:TabPanel = TabPanel {
  index:0
  title: "Tab 0"
  parentTabPane:tabPane
  content:[Circle {
            centerX: 100, centerY: 100
            radius: 40
            fill: RadialGradient {
                    centerX: 5
                    centerY: 5
                    focusX: 0.1
                    focusY: 0.1
                    radius: 10
                    stops: [
                      Stop {
                        color : Color.BISQUE
                        offset: 0.0
                      },
                      Stop {
                        color : Color.BLUE
                        offset: 1.0
                      },
                    ] // stops
           } // radial gradient
           effect: DropShadow {
              offsetX: 10
              offsetY: 10
              color: Color.BLACK
              radius: 10
           } // effect
         } // Circle
  ] // content
}

var firstNameRow = HBox {
  spacing:7
	content: [Label {
    text: "First Name:"
  },
  TextBox {
    selectOnFocus: true
  }
  ]
}
var lastNameRow = HBox {
  spacing:7
	content: [Label {
    text: "Last Name:"
  },
  TextBox {
    selectOnFocus: true
  }
  ]
}

var formView = VBox {
  spacing:7
  translateX:20
  translateY:100
	content: [firstNameRow,lastNameRow
  ]
}

// all widgets or controls to be laid out.

var tab1:TabPanel = TabPanel {
  index:1
  title: "Tab 1"
  parentTabPane:tabPane
  content:[formView]
}

def spinRect = Rectangle {
	translateX: 100,
  translateY: 160
	width: 80, height: 80
	fill: Color.BLUE
}

var animateThing = Timeline {
	repeatCount: 4
  autoReverse:true
	keyFrames: [
		at (1s) { spinRect.rotate => 100.0 tween Interpolator.EASEBOTH }
	];
}
animateThing.play();

var activateSpin = Button {
  translateX: 30
  translateY: 60
	text: "Click to Spin"
	action: function() {
    animateThing.playFromStart();
	}
}

var tab2:TabPanel = TabPanel {
  index:3
  title: "Tab 2"
  parentTabPane:tabPane
  content: [spinRect, activateSpin]
}

tabPane.content = [tab0, tab1, tab2];

var currentX:Number;
var currentY:Number;

var closeButton:Circle;

var titleGrabClose = Group {
  content : [
      // the window background
      Rectangle {
      x: 0, y: 0
      arcHeight:15
      arcWidth:15
      width: stageWidth, height: stageWidth
      fill: Color.WHITE
      opacity: 0.85
    },
    // grabby area
    Rectangle {
      x: 0, y: 0
      arcHeight:15
      arcWidth:15
      width: stageWidth, height: 20
      fill: LinearGradient {
        startX : 0.0
        startY : 0.0
        endX : 1.0
        endY : 0.0
        stops: [
          Stop {
            color : Color.LIGHTBLUE
            offset: 0.0
          },
          Stop {
            color : Color.BLUE
            offset: 1.0
          },

        ]
      }

      opacity: 80
      onMousePressed: function(e: MouseEvent): Void {
        currentX = e.screenX - stage.x;
        currentY = e.screenY - stage.y;
      }

      onMouseDragged: function(e: MouseEvent): Void {
        stage.x = e.screenX - currentX;
        stage.y = e.screenY - currentY;
      }
    },

    // window title text
    Text {
      font : Font {
        size: 18
        embolden:true
      }
      x: 10, y: 15
      content: "Tabs Demo"
    }

    // mouse close area
    closeButton = Circle {
      centerX: stageWidth - 10 , centerY: 10
      radius: 8
      fill: Color.WHITE
      onMousePressed: function(e: MouseEvent): Void {
        javafx.lang.FX.exit();
      }
    },

    // X marks the spot
    Text {
      font : Font {
        size: 14
      }
      x: (2+ stageWidth - closeButton.radius * 2)
      y: 15
      content: "X"
   }
  ] // content
} // titleGrabClose

var stage: Stage;
stage = Stage {
    style: StageStyle.TRANSPARENT
    title: "Slide Tab Demo"
    width: stageWidth
    height: stageHeight
    scene: Scene {
        width: stage.width
        height: stage.height
        content: [titleGrabClose, tabPane]
        fill:null
    };
    resizable:false
    opacity: 0.85
}

Code walk through of Main.fx

  • Lines 30-31 : initialize Stage
  • Lines 33-35: Create a TabbedPane really a JavaFX Stack
  • Lines 37-69: Create Tab 0 which contains a Circle
  • Lines 71-98: Create a simple form with Labels and controls
  • Lines 102-107: Create Tab 1 which contains the simple form
  • Lines 109-114: Create a Blue Rectangle to be put in Tab 2
  • Lines 116-122: Create a animator to control Blue Rectangle
  • Line 123: Start the animation
  • Lines 125-132: Create a button to reanimate the Blue Rectangle when pressed
  • Lines 134-139: Create Tab 2 which will contain the button and Blue Rectangle
  • Line 141: Put all tab panels into the tabbed pane object.
  • Lines 143-225: Create a drag-able title area with a close button on the upper right.
  • Lines 227-241: Display Stage containing TabPane and drag-able title.

tabs.fx

Below is the library that contains a TabPanel and TabbedPane classes.

package tabdemo;

import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.effect.DropShadow;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Stack;
import javafx.scene.paint.Color;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.ShapeSubtract;
import javafx.scene.text.Font;
import javafx.scene.text.FontWeight;
import javafx.scene.text.Text;
import javafx.scene.text.TextOrigin;

/**
 * @author cdea
 */
public class TabPanel extends CustomNode {
  public-init var index:Integer;
  public-init var title:String;
  public var parentTabPane:Stack;
  public var content:Node[] = [];
  // The clickable tab area
  def tabRect = Rectangle {
        x: 10 + ((4 + 50) * index), y: 10
        arcHeight:7
        arcWidth:7
        width: 50, height: 25
        opacity:0
        onMousePressed: function( e: MouseEvent ):Void {
          var tabs:Node[] = parentTabPane.content;
          for (tab in tabs) {
            var tabPanel:TabPanel = tab as TabPanel;
            if (index == tabPanel.index) {
              delete tab from parentTabPane.content;
              insert tab into parentTabPane.content;
              break;
            } // found so make it the top of stack [add to end]
          } // loop through all tab panels
        }
        onMouseEntered: function(e: MouseEvent): Void {
          println("Entered {title}");
        }
        onMouseExited: function(e: MouseEvent): Void {
          println("Exited {title}");
        }

  }

  // tab text
  def tabText = Text {
    content: title
    textOrigin: TextOrigin.TOP
    font: Font.font("Verdana", FontWeight.BOLD, 10);
    x: 15 + ((4 + 50) * index)
    y: 15
  }

  // merge or blend the bottom of tab with the main content region
  def blendBottomOfTab = Rectangle {
    x: 10 + ((4 + 50) * index), y: 10 + 20
    width: 50, height: 25
  }

  // content area background area
  def tabPanelContentArea = Rectangle {
    x: 10, y: 10 + 25
    arcHeight:15
    arcWidth:15
    width: 350, height: 300
  }

  def tabArea = ShapeSubtract {
    a: [  tabRect,
          blendBottomOfTab,
          tabPanelContentArea
    ] // union tab and content area

    fill: LinearGradient {
            startX: 0.0, startY: 0.0, endX: 1.0, endY: 1.0
            proportional: true
            stops: [
                Stop { offset: 0.0 color: Color.WHITE }
                Stop { offset: 1.0 color: Color.GRAY }
            ]
    } // fill
    stroke: Color.BLACK
    effect: DropShadow {offsetX: 2 offsetY: 4}
  }

  public override function create(): Node {
    return Group {
      content: [tabArea, tabRect, content, tabText]
    }
  }
}

public class TabPane extends Stack {}

Code walk through of tabs.fx

  • Line 22: Create a custom node representing a single Tab Panel
  • Line 23: Index is the position and order of a tab from left to right on a Tabbed Pane
  • Line 24: Tabs title text
  • Line 25: The owning Tabbed Pane
  • Line 26: The contents that would be displayed on this Tab Panel
  • Lines 28-52: The tab’s rectangular click region surrounding the tab title text region
  • Lines 55-61: The Text containing the title text to be overlaid on the click-able tab area
  • Lines 63-67: This is a simple rectangle to make the bottom of the click-able tab area connect smoothly to the main Tab Panel content region
  • Lines 70-75: This is the main Tab Panel content region
  • Lines 77-93: Using the ShapeSubtract on attribute ‘a’ union the three rectangles (Lines 28-75) to create a smooth looking Tab Panel
  • Lines 95-100: Creating and assembling the Tab Panel
  • Line 102: Class representing a Tabbed Pane

Conclusion

With many types of application contexts having variations of tabbed panes (Look-n-Feel) and numerous features (functionality), one might be hard pressed to find the right one for their application.  I find that it is very important in a GUI toolkit to be able to quickly prototype a control or container component before investing heavily on designing an advanced component with a good API.  JavaFX allows me to rapidly create a TabbedPane while adequately providing simple functionality.

Any feedback is welcome.

References





JavaFX – Java Applets Making a Comeback?

15 02 2010

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

30 12 2009

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 in Action – Book Review

15 11 2009

JavaFX in Action

I have been fortunate enough to read the e-book version of the book JavaFX in Action by Simon Morris through the Manning MEAP program. I subsequently did a detailed book review on the DZone‘s “IT Book Zone” area. Book Review: http://www.dzone.com/links/javafx_in_action.html . I highly recommend this book if you are a beginner or an experienced developer from any language background to create engaging Rich Internet Applications (RIA) ranging from game programming to business applications. It is an easy read and I’m very impressed with many bonus sections included in most chapters. This book is another great book to add to my JavaFX library.

JavaFX Library

JavaFX Library

Any feedback is welcome!





30 Lines of JavaFX Coding Challenge Theme – Time

1 10 2009

Space Time (updated!)

My entry is called ‘Space Time‘. A 3D cube clock. The full source code that is readable is below the article.

Wow, it is amazing at what you can you do in 30 lines of JavaFX code or 3000 characters.   JFXStudio is running a contest and the theme has to do with “TIME”. Although I posted my entry very last minute, there will be other chances for you to enter. Please visit http://jfxstudio.wordpress.com/2009/08/31/jfxstudio-challenge-small-is-the-new-big/

Space Time

Space Time

My entry including the import statements are just at 30 lines with each line containing less than or equal to 100 characters.  Click on the image to launch the demo.

Instructions:

  • Observe the cube rotating as each tick a second.
  • Observe the Space Time Title’s Distant lighting effect as seconds go by
  • Use mouse drag to rotate 3D cube
  • import javafx.animation.*;import javafx.scene.*;import javafx.scene.paint.Color;import javafx.scene
    .effect.*;import javafx.scene.text.*;import javafx.scene.shape.*;import javafx.scene.input.
    MouseEvent;import javafx.util.Math;class P{var x:Number;var y:Number;var z:Number;var sX:Number;var
    sY:Number;}class E{var a:Integer;var b:Integer;}var w:Number=800;var h:Number=600;var a:Number=30;
    var e:Number=30;var vs:P[]=[];function av(x:Integer,y:Integer,z:Integer){insert P{x:x,y:y,z:z}into
    vs;}av(-1,-1,-1);av(-1,-1,1);av(-1,1,-1);av(-1,1,1);av(1,-1,-1);av(1,-1,1);av(1,1,-1);av(1,1,1);var
    es:E[]=[];function aE(a:Integer,b:Integer){insert E{a:a,b:b}into es;}aE(0,1);aE(0,2);aE(0,4);aE(1,3
    );aE(1,5);aE(2,3);aE(2,6);aE(3,7);aE(4,5);aE(4,6);aE(5,7);aE(6,7);var ps:Circle[]=[];var ls:Line[]=
    [];function mps(c:Boolean):Void{var th:Number=bind 0.017453*a;var ph=0.017453*e;var cT=Math.cos(th)
    ;var sT=Math.sin(th);var cP=Math.cos(ph);var sP=Math.sin(ph);var cTcP=cT*cP;var cTsP=cT*sP;var sTcP
    =sT*cP;var sTsP=sT*sP;var sf=bind w/4;for(i in[0..sizeof vs-1]){var x0=vs[i].x;var y0=vs[i].y;var
    z0=vs[i].z;var x1=cT*x0+sT*z0;var y1=-sTsP*x0+cP*y0+cTsP*z0;var z1=cTcP*z0-sTcP*x0-sP*y0;x1=x1*3/(
    z1+4.5);y1=y1*3/(z1+4.5);vs[i].sX=x1;vs[i].sY=y1;if(c)insert Circle{centerX:bind w/2+sf*vs[i].sX+.5
    ,centerY: bind h/2-sf*vs[i].sY+.5,radius:2 fill:Color.BLUE}into ps;}if(c)for(e in es){insert Line{
    startX:bind ps[e.a].centerX startY:bind ps[e.a].centerY endX:bind ps[e.b].centerX endY:bind ps[e.b]
    .centerY strokeWidth:2,stroke:Color.BLUE}into ls;}}mps(true);var mx:Number;var my:Number;var g:
    Rectangle=Rectangle{x:0,y:0 width:bind w,height:bind h fill:Color.BLACK onMousePressed:function(me:
    MouseEvent):Void{mx=me.x;my=me.y;}onMouseDragged:function(me:MouseEvent):Void{var new_mx=me.x;a-=
    new_mx-mx;var new_my=me.y;e+=new_my-my;mx=new_mx;my=new_my;mps(false);}}var ct:String=new java.util
    .Date().toString();var tz:Integer=-125;var t=Group{content:[Text{effect:Lighting{light:javafx.scene
    .effect.light.DistantLight{azimuth:bind tz elevation:40}surfaceScale:5} x:10 y:50 content:"Space "
    "Time" fill:Color.RED font:Font{name:"Arial Bold" letterSpacing:0.20 size:50}},Text{x:10 y:80
    content:bind ct fill:Color.BLUE font:Font{name:"Arial Bold" letterSpacing:0.20 size:20}}]}var bc=
    Group{content:[Rectangle{x:10,y:bind h-85,arcHeight:5,arcWidth:5,width:bind w-30,height:30,fill:
    Color.RED,opacity:.5},Text{effect:DropShadow{offsetX:10,offsetY:10,color:Color.BLACK,radius:10}
    opacity:.5 x:15,y:bind h-65,content:"by Carl Dea - carlfx.wordpress.com"fill:Color.WHITE font:Font{
    name:"Arial Bold"letterSpacing:.20 size:20}}]}var scene:Scene=Scene{content:bind[g,ps,ls,t,bc]};var
    anim=Timeline{keyFrames:[KeyFrame{time:1s action:function():Void{a-=1;mps(false);ct=new java.util.
    Date().toString();if(tz>125){tz=-125;}tz+=2;}}]repeatCount:Timeline.INDEFINITE};anim.play();javafx.
    stage.Stage{width:bind w with inverse height:bind h with inverse scene:scene}

    Enjoy!
    Please vote for me…
    Let me know what you think. :)

    Here is the readable source code below:

    /**
     * Main.fx
     * 
     * Check out http://www.comp.brad.ac.uk/research/GIP/tutorials/project1.html 
     * for all the math.
     * Created on Dec 20, 2009, 11:06:15 PM
     */
    
    package bigsmall.part8;
    
    import javafx.animation.*;
    import javafx.scene.*;
    import javafx.scene.paint.Color;
    import javafx.scene.effect.*;
    import javafx.scene.effect.light.DistantLight;
    import javafx.scene.text.*;
    import javafx.scene.shape.*;
    import javafx.scene.input.MouseEvent;
    import javafx.stage.*;
    import java.util.Date;
    import javafx.util.Math;
    
    class Point3D {
       var x: Number;
       var y: Number;
       var z: Number;
       var screenX: Number;
       var screenY: Number;
    }
    
    class Edge {
       var a: Integer;
       var b: Integer;
    }
    var width: Number = 800;
    var height: Number = 600;
    var azimuth: Number = 30;
    var elevation: Number = 30;
    var vertices: Point3D[] = [];
    
    function addVertex(x: Integer, y: Integer, z: Integer) {
       insert 
          Point3D {
             x: x, 
             y: y, 
             z: z
          } 
       into vertices;
    }
    
    addVertex(-1, -1, -1);
    addVertex(-1, -1, 1);
    addVertex(-1, 1, -1);
    addVertex(-1, 1, 1);
    addVertex(1, -1, -1);
    addVertex(1, -1, 1);
    addVertex(1, 1, -1);
    addVertex(1, 1, 1);
    
    var edges: Edge[] = [];
    
    function addEdge(a: Integer, b: Integer) {
       insert 
          Edge {
             a: a, 
             b: b
          } 
       into edges;
    }
    
    addEdge(0, 1);
    addEdge(0, 2);
    addEdge(0, 4);
    addEdge(1, 3);
    addEdge(1, 5);
    addEdge(2, 3);
    addEdge(2, 6);
    addEdge(3, 7);
    addEdge(4, 5);
    addEdge(4, 6);
    addEdge(5, 7);
    addEdge(6, 7);
    
    var points: Circle[] = [];
    var lines: Line[] = [];
    
    function movePoints(createFirstTime: Boolean): Void {
       var theta: Number = bind 0.017453 * azimuth;
       var phi = 0.017453 * elevation;
       var cosTheta = Math.cos(theta);
       var sinTheta = Math.sin(theta);
       var cosPhi = Math.cos(phi);
       var sinPhi = Math.sin(phi);
       var cosThetaCosPhi = cosTheta * cosPhi;
       var cosThetaSinPhi = cosTheta * sinPhi;
       var sinThetaCosPhi = sinTheta * cosPhi;
       var sinThetaSinPhi = sinTheta * sinPhi;
       var scaleFactor = bind width / 4;
       for (i in [0..sizeof vertices - 1]) {
          var x0 = vertices[i].x;
          var y0 = vertices[i].y;
          var z0 = vertices[i].z;
          var x1 = cosTheta * x0 + sinTheta * z0;
          var y1 = -sinThetaSinPhi * x0 + cosPhi * y0 + cosThetaSinPhi * z0;
          var z1 = cosThetaCosPhi * z0 - sinThetaCosPhi * x0 - sinPhi * y0;
          x1 = x1 * 3 / (z1 + 4.5);
          y1 = y1 * 3 / (z1 + 4.5);
          vertices[i].screenX = x1;
          vertices[i].screenY = y1;
          if (createFirstTime) {
             insert 
                Circle { 
                   centerX: bind width / 2 + scaleFactor * vertices[i].screenX + 0.5,
                   centerY: bind height / 2 - scaleFactor * vertices[i].screenY + 0.5,
                   radius: 2 
                   fill: Color.BLUE
                } 
             into points;
          }
       }
    
       if (createFirstTime) {
          for (edge in edges) {
             insert 
             Line {   
                startX: bind points[edge.a].centerX
                startY: bind points[edge.a].centerY
                endX: bind points[edge.b].centerX
                endY: bind points[edge.b].centerY
                strokeWidth: 2,
                stroke: Color.BLUE
             } 
             into lines;
          }
       }
    }
    
    // create the first time; draw cube.
    movePoints(true);
    
    var mouseX: Number;
    var mouseY: Number;
    
    // Space region that changes the azimuth and recalculates the cube 
    var galaxy: Rectangle = Rectangle { 
       x: 0,
       y: 0
       width: bind width,
       height: bind height
       fill: Color.BLACK
       onMousePressed: function (me: MouseEvent): Void {
          mouseX = me.x;
          mouseY = me.y;
       }
       onMouseDragged: function (me: MouseEvent): Void {
          var new_mx = me.x;
          azimuth -= new_mx - mouseX;
          var new_my = me.y;
          elevation += new_my - mouseY;
          mouseX = new_mx;
          mouseY = new_my;
          movePoints(false);
       }
    } // galaxy
    
    var currentTime: String = new Date().toString();
    var initialAzimuth: Integer = -125;
    
    // Title with current time seconds.
    var titleDisplay = Group { 
       content: [
          Text {    
             effect: Lighting {  
                light: DistantLight {
                   azimuth: bind initialAzimuth
                   elevation: 40
                }
                surfaceScale: 5
             }
             x: 10
             y: 50
             content: "Space Time"
             fill: Color.RED
             font: Font {    
                name: "Arial Bold"
                letterSpacing: 0.20
                size: 50
             }
          },
          Text {    
             x: 10
             y: 80
             content: bind currentTime
             fill: Color.BLUE
             font: Font {    
                name: "Arial Bold"
                letterSpacing: 0.20
                size: 20
             }
          }
       ]
    }; // titleDisplay Group
    
    
    // This is the bottom display credits
    var byCarl = Group {    
       content: [
          Rectangle {   
             x: 10,
             y: bind height - 85,
             arcHeight: 5,
             arcWidth: 5,
             width: bind width - 30,
             height: 30,
             fill: Color.RED,
             opacity: .5
          },
          Text {    
             effect: DropShadow {    
                offsetX: 10,
                offsetY: 10,
                color: Color.BLACK,
                radius: 10
             }
             opacity: .5
             x: 15,
             y: bind height - 65,
             content: "by Carl Dea - carlfx.wordpress.com"
             fill: Color.WHITE
             font: Font {
                name: "Arial Bold"
                letterSpacing: .20
                size: 20
             }
          }
       ] // byCarl contents Group
    }; // by Carl
    
    var scene: Scene = Scene {  
       content: bind [
          galaxy, 
          points, 
          lines, 
          titleDisplay, 
          byCarl]
    };
    
    // this is the ticking motion of the cube.
    var anim = Timeline {   
       keyFrames: [
          KeyFrame {
             time: 1s
             action: function (): Void {
                azimuth -= 1;
                movePoints(false);
                currentTime = new Date().toString();
                if (initialAzimuth > 125) {
                  initialAzimuth = -125;
                }
                initialAzimuth += 2;
             }
          }
       ] // keyFrames
       repeatCount: Timeline.INDEFINITE
    }; // animation
    
    anim.play();
    
    Stage { 
       title: "Space Time"
       width: bind width with inverse
       height: bind height with inverse
       scene: scene
    }
    
    





    JavaFX Forms Framework Part 5

    4 09 2009

    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

    29 08 2009

    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

    20 08 2009

    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








    Follow

    Get every new post delivered to your Inbox.