All posts by amaranathallampati

Simple Drag and Drop Tutorial

I wanted to play around drag and drop API in JavaFX. Here is a sample application implemented using DnD API. This example comprises 2 Listviews i.e. Players and Team. Players are dragged from source list view and dropped inside target list view.

I have come across a couple of issues while working with DnD API:

1) I was not able to drag and drop multiple cells initially using a custom data format because dropping multiple cells was resulting in a serialization related exception as listed in the following ticket:

https://forums.oracle.com/thread/2596807

One of the developers posted a solution that worked i.e. Change the type from ObservableList to ArrayList while storing the content. The exceptions were thrown because ObservableLists cannot be serialized.

2) I was not sure how to store multiple rows/cells inside clipboard content. After all, there is an easy way to do so by creating an instance of DataFormat and assign a name as follows:


// Create a custom data format which acts as a key and give it a name

private DataFormat listDataFormat = new DataFormat("listOfPlayers");

// Store the selected items as the value for the key when multiple rows are dragged

ClipboardContent content = new ClipboardContent();

content.put(listDataFormat, new ArrayList<Player>(playersListView.getSelectionModel().getSelectedItems()));

// Retrieve the value by key when the drag is dropped on target.

List<Player> players = (List<Player>) dragEvent.getDragboard().getContent(listDataFormat);

Notes:

– A list cell factory is used to render the player name in list cells.

– A BlendMode is used to differentiate when an active drag is over the target. Using a blend mode is not really ideal though.

– Move Transfer mode is used and players are removed from source list view after successful drop on to the target.


import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.SelectionMode;
import javafx.scene.effect.BlendMode;
import javafx.scene.input.*;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class JavaFXDnDApplication extends Application
{

private static final ListView<Player> playersListView = new ListView<Player>();

private static final ObservableList<Player> playersList = FXCollections.observableArrayList();

private static final ListView<Player> teamListView = new ListView<Player>();

private static final GridPane rootPane = new GridPane();

public static void main(String[] args)
 {
 launch(args);
 }

@Override
 public void start(Stage primaryStage)
 {
 primaryStage.setTitle("Drag and Drop Application");

initializeComponents();

initializeListeners();

buildGUI();

populateData();

primaryStage.setScene(new Scene(rootPane, 400, 325));
 primaryStage.show();
 }

 private void initializeListeners()
 {
 playersListView.setOnDragDetected(new EventHandler<MouseEvent>()
 {
 @Override
 public void handle(MouseEvent event)
 {
 System.out.println("setOnDragDetected");

Dragboard dragBoard = playersListView.startDragAndDrop(TransferMode.MOVE);

ClipboardContent content = new ClipboardContent();

content.putString(playersListView.getSelectionModel().getSelectedItem().getName());

dragBoard.setContent(content);
 }
 });

playersListView.setOnDragDone(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragDone");

// This is not the ideal place to remove the player because the drag might not have been exited on the target.
// String player = dragEvent.getDragboard().getString();
// playersList.remove(new Player(player));
 }
 });

teamListView.setOnDragEntered(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragEntered");

teamListView.setBlendMode(BlendMode.DIFFERENCE);
 }
 });

teamListView.setOnDragExited(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragExited");

teamListView.setBlendMode(null);
 }
 });

teamListView.setOnDragOver(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragOver");

dragEvent.acceptTransferModes(TransferMode.MOVE);
 }
 });

teamListView.setOnDragDropped(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragDropped");

String player = dragEvent.getDragboard().getString();

teamListView.getItems().addAll(new Player(player));

playersList.remove(new Player(player));

dragEvent.setDropCompleted(true);
 }
 });
 }

 private void buildGUI()
 {
 rootPane.setPadding(new Insets(10));
 rootPane.setPrefHeight(30);
 rootPane.setPrefWidth(100);
 rootPane.setVgap(10);
 rootPane.setHgap(20);

Label playersLabel = new Label("Players");
 Label teamLabel = new Label("Team");

rootPane.add(playersLabel, 0, 0);
 rootPane.add(playersListView, 0, 1);
 rootPane.add(teamLabel, 1, 0);
 rootPane.add(teamListView, 1, 1);
 }

 private void populateData()
 {
 playersList.addAll(
 new Player("Adam"), new Player("Alex"), new Player("Alfred"), new Player("Albert"),
 new Player("Brenda"), new Player("Connie"), new Player("Derek"), new Player("Donny"),
 new Player("Lynne"), new Player("Myrtle"), new Player("Rose"), new Player("Rudolph"),
 new Player("Tony"), new Player("Trudy"), new Player("Williams"), new Player("Zach")
 );

playersListView.setItems(playersList);
 }

private void initializeComponents()
 {
 initializeListView(playersListView);

initializeListView(teamListView);
 }

 private void initializeListView(ListView<Player> listView)
 {
 listView.setPrefSize(250, 290);
 listView.setEditable(false);
 listView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
 listView.setCellFactory(new StringListCellFactory());
 }

 class StringListCellFactory implements Callback<ListView<Player>, ListCell<Player>>
 {
 @Override
 public ListCell<Player> call(ListView<Player> playerListView)
 {
 return new StringListCell();
 }

class StringListCell extends ListCell<Player>
 {
 @Override
 protected void updateItem(Player player, boolean b)
 {
 super.updateItem(player, b);

if (player != null)
 {
 setText(player.getName());
 }
 }
 }
 }
}
public class Player
{
 private String name;

public Player(String name)
 {
 this.name = name;
 }

public String getName()
 {
 return name;
 }

public void setName(String name)
 {
 this.name = name;
 }

@Override
 public boolean equals(Object o)
 {
 if (this == o) return true;
 if (o == null || getClass() != o.getClass()) return false;

Player player = (Player) o;

if (name != null ? !name.equals(player.name) : player.name != null) return false;

return true;
 }

@Override
 public int hashCode()
 {
 return name != null ? name.hashCode() : 0;
 }
}

In case of dragging and dropping multiple cells, use the following implementation:

playersListView.setOnDragDetected(new EventHandler<MouseEvent>()
 {
 @Override
 public void handle(MouseEvent event)
 {
 System.out.println("setOnDragDetected");

Dragboard dragBoard = playersListView.startDragAndDrop(TransferMode.MOVE);

ClipboardContent content = new ClipboardContent();

content.put(listDataFormat, new ArrayList<Player>(playersListView.getSelectionModel().getSelectedItems()));

dragBoard.setContent(content);
 }
 });

teamListView.setOnDragDropped(new EventHandler<DragEvent>()
 {
 @Override
 public void handle(DragEvent dragEvent)
 {
 System.out.println("setOnDragDropped");

List<Player> players = (List<Player>) dragEvent.getDragboard().getContent(listDataFormat);

teamListView.getItems().addAll(players);

playersList.removeAll(players);

dragEvent.setDropCompleted(true);
 }
 });

Untitled

Advertisements

Table View Tutorial and Questions

Why is there an extra column displayed in a Table View by default?

When we write code to add certain number of columns to a Table View (for example 4), there is always 5 columns displayed by default (5 in this case). If we do not want to see the extra column, we need to change the column re-size policy to CONSTRAINED RESIZE POLICY by invoking the following method on Table View.

setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);

However, please note that there are some differences between using Constrained and Un-Constrained re-size policies (UCRP) which can be observed while re-sizing a given column. If we use UCRP, width of all the columns to the right will be adjusted upon re-sizing a column. Otherwise, the right most column followed by second right most column’s width will be adjusted.

Steps involved in creating a Table View and populate the data:

While creating a table to render tabular data, we need to follow the following steps to show the data appropriately:

– Create an instance of Table View class and initialize various attributes such as re-size policy, preferred height, whether or not it is editable and to hide/show table menu button etc.

– Define a data model class with appropriate properties. These properties are similar to how we define plain data transfer objects but the datatypes of these properties are part of Java FX API.

– We must provide the get/set methods for all the properties defined inside the data model class. If a get/set method does not contain the exact property name, the column to which this property is mapped will not be rendered.

– Define various table columns and add them to Table View. In addition, set a value factory on all the columns with appropriate properties. In addition, it is always a good idea to provide the preferred width for all table columns.

– Initialize the data and set the records on the table view.

Why is the data not populated in one or more table view columns?

If a get/set method for a property does not contain the exact property name, the column to which this property is mapped will not be rendered. 

If a table column is not mapped to a property correctly while setting a value factory, the data may not show up in the table as we expect.

How to convert a read only table view to editable table view? 

– Make sure to set the editable property to true on table view.

– Set the appropriate cell factory on columns which must be editable. Depending on the cell factory set, appropriate editor will be provided while editing the table cells.

– Lastly, implement an event handler to access the new value and to update the data model.

How to change the label “No content in table” which is visible in a Table View?

When there are no rows visible in the table view, a label “No content available” is shown in the middle of the table view. This can be changed by using the following method: setPlaceHolder(Node node);

Why does a Table View show more rows than a table model has?

As per current implementation, Table View always shows more rows that what is defined inside the model if preferred height is not set to match the set number of rows. I think this behavior needs to be turned off by default. There is a way to control this behavior from CSS files but not sure if there is a way to control this behavior from code. If there isn’t one, we will definitely need one soon. After all, who wants to show/see the blank rows in a table view?

First Java FX Application

Prerequisites for starting to develop JavaFX Application:

– Make sure that java 1.7 is specified as project SDK in your IDE.

– If you are using java 1.6, add jfxrt.jar as a dependency to the project.

Mandatory steps involved in creating an application:

– Create a Java class and extend it with Application abstract class.

– Implement start method which gets invoked when the application is started.

– Implement a main method and invoke launch method

– Provide the implementation inside start method

Implementation:

– Create the nodes which need to displayed on user interface

– Create a layout manager such as Border Pane

– Add the nodes to the layout manager

– Create a scene and specify layout manager as the root of scene graph

– Set the scene on primary stage provided by application

– Show the stage such that the stage is visible with computed preferred size

Example:


public class JavaFXApplication extends Application
{
   public static final String TITLE = "Java FX Application";
   private Button closeButton = new Button("Close");

   @Override
   public void start(Stage stage) throws Exception
   {
      initializeStage(stage);

      closeButton.setPrefSize(50, 25);

      BorderPane root = new BorderPane();
      root.setCenter(closeButton);

      stage.setScene(new Scene(root, 400, 300));
      stage.show();
    }

    private void initializeStage(Stage stage)
    {
       stage.setTitle(TITLE);
    }

    public static void main(String[] args)
    {
       launch(args);
    }
 }

Important web sites related to JavaFX

Java FX Experience: http://fxexperience.com/

Java FX Tutorials: http://docs.oracle.com/javafx/2/ui_controls/jfxpub-ui_controls.htm

Java FX Overview: http://docs.oracle.com/javafx/

Java FX Documentation: http://docs.oracle.com/javafx/2/api/index.html

Java FX Charts Tutorials: http://docs.oracle.com/javafx/2/charts/jfxpub-charts.htm

Getting started with Java FX: http://docs.oracle.com/javafx/2/get_started/jfxpub-get_started.htm

New features in next major release – Java FX 8.0

Please find the list of new features which will be released in Java FX 8.0. The developer preview release should be out in 1st week of September 2013. The final release candidate should be out sometime in January 2014.

Introduction of Swing Node:

This node allows us to integrate Java Swing components in side Java FX. As of now, the opposite is possible i.e. integrating Java FX components inside Java Swing using JFXPanel container. Introduction of this node facilitates the smooth migration of applications from Java Swing to Java FX.

Event Dispatch Thread and JavaFX Application Thread will merge:

Java FX Thread and Event Dispatch Thread will be merged in JavaFX 8.0 . However, this feature is going to be experimental and must be turned on explicitly. Definitely, this will reduce the amount of coding required when application is built with Swing and FX components.

New Look and Feel – Modena:

A new look and feel will be introduced called Modena with significant look and feel changes. This look and feel has stunning borders, colors, shades and highlights. Overview of these look and feel changes can be read here:  http://fxexperience.com/2013/03/modena-theme-update/

Rich Text Support:

Introduction of rich text support allows the styling of text associated with FX UI components. Styling includes applying several effects and transforms to words. In addition, text attributes can be controlled form CSS files. A sample tutorial can be found here: https://wikis.oracle.com/display/OpenJDK/Rich+Text+API+Samples

Printing Support in FX:

A simpler printer API will be released as part of 8.0 compared to Swing printing API. Java FX printing API is capable of printing any Java FX node, includes Web View nodes, with appropriate scaling. A detailed tutorial can be found here:  http://carlfx.wordpress.com/2013/07/15/introduction-by-example-javafx-8-printing/

Tree Table Control:

A new control Tree Table will be released as part of FX API in 8.0. This control is really helpful when a data model needs to be displayed in a Tree Table format i.e. Trade Book, Position Keeper etc. This view control can be viewed as functional as a combination of TableView and a TreeView. Detailed tutorial can be found here: https://wikis.oracle.com/display/OpenJDK/TreeTableView+API+Examples  https://wikis.oracle.com/display/OpenJDK/TreeTableView+User+Experience+Documentation

Miscellaneous:

In addition to these, there are a lot of other enhancements to 3D API, Web View,  Date Picker controls, and support for Audio & Video recording etc. This page will be updated as we find more features.