Wednesday 9 March 2016

Drag and Drop from and to a File

The usual way of loading or saving data involves clicking a menu, opening a dialog box, navigating trough directories and choosing a file or typing a name. It wouldn't be more easy to just drag a file into my application and have it's contents loaded or the other way round, drag an image and have it saved?
So I started coding, building an application, JavaFX, just to prove the concept. The application consists of just a pane with an image. Dragging a file into it is accepted if the file contains a valid image and the image is loaded and displayed. Dragging from it to a folder creates a new file saving the previously loaded image to it.
To save the image to a file two events are caught. First on drag detected a temporary file is created with the image's contents. Then on drag done either nothing needs to be done if the image was already moved by the OS or the file is deleted if the drag was canceled to avoid cluttering the temporary folder.
To load the image other two events are caught. First on drag over the file is validated, then on drag dropped the file is read into an Image and the Image is displayed.


import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;

import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.ClipboardContent;
import javafx.scene.input.DragEvent;
import javafx.scene.input.Dragboard;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

/**
 * Test drag and drop in both directions from a file to an image.
 * 
 * @author António Raposo
 */
public class DragDropJavafx extends Application {

@Override
public void start(Stage primaryStage) {
ImageView image = new ImageView();
StackPane myPane = new StackPane();
myPane.getChildren().add(image);

// starting drag and drop from the pane
myPane.setOnDragDetected(new EventHandler<MouseEvent>() {
@Override
public void handle(MouseEvent event) {
// create a new file object representing a temporary file and
// put it in the dragboard. create the temporary file and save
// the image to it.
Dragboard db = myPane.startDragAndDrop(TransferMode.ANY);
ClipboardContent content = new ClipboardContent();
File tempFile = new File(System.getProperty("java.io.tmpdir"), "test_image.png");
BufferedImage bImage = SwingFXUtils.fromFXImage(image.getImage(), null);
try {
ImageIO.write(bImage, "png", tempFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
content.putFiles(Arrays.asList(tempFile));
db.setContent(content);
event.consume();
}
});

// completing drag and drop from the pane
myPane.setOnDragDone(new EventHandler<DragEvent>() {
@Override
public void handle(DragEvent event) {
// if the transfer is not accepted delete the temporary file.
if (!event.isAccepted()) {
Dragboard db = event.getDragboard();
List<File> files = db.getFiles();
if (files != null && !files.isEmpty()) {
files.get(0).delete();
}
}
event.consume();
}
});

// accepting drag and drop to the pane
myPane.setOnDragOver(new EventHandler<DragEvent>() {
@Override
public void handle(DragEvent event) {
// the first step is when the mouse enters the target. accept
// copy or move of one file into the target
if (event.getGestureSource() != myPane && event.getDragboard().hasFiles()
&& event.getDragboard().getFiles().size() == 1) {
if (!new Image(event.getDragboard().getFiles().get(0).toURI().toString()).isError()) {
event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
}
event.consume();
}
});

// accepting drag and drop to the pane
myPane.setOnDragDropped(new EventHandler<DragEvent>() {
@Override
public void handle(DragEvent event) {
// complete the drag and drop event reading the dropped file.
Dragboard db = event.getDragboard();
boolean success = false;
if (db.hasFiles()) {
List<File> files = db.getFiles();
if (files != null && !files.isEmpty()) {
image.setImage(new Image(files.get(0).toURI().toString()));
}
}
event.setDropCompleted(success);
event.consume();
}
});

Scene myScene = new Scene(myPane);
primaryStage.setScene(myScene);
primaryStage.setWidth(400);
primaryStage.setHeight(300);
primaryStage.show();
}

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

}

Saturday 26 December 2015

Properties pane (I)

As I want to use JavaFX on already existing applications I am in the process of investigating how to improve those applications with pieces of JavaFX.

One improvement is handling properties. JavaFX allows to create property panels in a neat way. But let's start from the beginning.

For the sake of the example I am going to use an text property, a boolean property and a filename property.

The FXML file 'properties.fxml' is as follows.
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<GridPane hgap="10.0" vgap="10.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller">
  <columnConstraints>
    <ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" />
    <ColumnConstraints hgrow="SOMETIMES" />
    <ColumnConstraints hgrow="SOMETIMES" />
  </columnConstraints>
  <rowConstraints>
    <RowConstraints vgrow="SOMETIMES" />
    <RowConstraints vgrow="SOMETIMES" />
    <RowConstraints vgrow="SOMETIMES" />
  </rowConstraints>
   <children>
     <Label text="Text property" />
     <Label text="Boolean property" GridPane.rowIndex="1" />
     <Label text="File property" GridPane.rowIndex="2" />
     <TextField fx:id="textProperty" prefHeight="25.0" prefWidth="74.0" GridPane.columnIndex="1" GridPane.columnSpan="2" />
     <RadioButton fx:id="boolProperty" minHeight="25.0" mnemonicParsing="false" GridPane.columnIndex="1" GridPane.rowIndex="1" />
     <TextField fx:id="fileProperty" GridPane.columnIndex="1" GridPane.rowIndex="2" />
     <Button mnemonicParsing="false" onAction="#onSelect" text="Select" GridPane.columnIndex="2" GridPane.rowIndex="2" />
    </children>
   <padding>
     <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
   </padding>
</GridPane>
For this pane the properties are aligned in rows inside a grid. The labels are in the first column aligned to the right and the fields are in the second column. For the filename field a Select button is provided splitting the second column in two.

Then the main class is created in file "Properties.java"
import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.Parent;
import javafx.stage.Stage;

/**
 * @author António Raposo
 */
public class Properties extends Application {

   @Override
   public void start(Stage primaryStage) throws IOException {
      Parent root = FXMLLoader.load(getClass().getResource("properties.fxml"));
      Scene myScene = new Scene(root);
      primaryStage.setScene(myScene);
      primaryStage.show();
   }

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

} 
It's very similar to the previous Hello example. But for this example there is also a controller in file "Controller.java"
import java.io.File;
import javafx.event.ActionEvent; import javafx.fxml.FXML; import javafx.scene.control.RadioButton; import javafx.scene.control.TextField; import javafx.stage.FileChooser; /** * @author António Raposo */ public class Controller { @FXML private TextField textProperty; @FXML private RadioButton boolProperty; @FXML private TextField fileProperty; @FXML void onSelect(ActionEvent event) { FileChooser chooser = new FileChooser(); chooser.setTitle("Select File"); File file = new File(fileProperty.getText()); chooser.setInitialDirectory(file.getParentFile()); File newfile = chooser.showOpenDialog(fileProperty.getScene().getWindow()); if (newfile != null) { fileProperty.setText(newfile.getAbsolutePath()); } } }
And that's it. Just compile and run. Here is how it looks.


For the moment there is no way to load and save the properties. That will be the next step.

Friday 18 December 2015

Hello JavaFX (8)

Introduction

Once upon a time, there was a blog called "A Cup of Java". Until one day the service where it was hosted shut down.

JavaFX ended up being the trigger that send me to find another blog service and start writing again.

I've been using Java and Swing for ages, but I always felt awkward building user interfaces in code, mixing them with event handlers and ending up with either an entire constellation of small classes or a hipper huge unmanageable class. Sometimes it worked well, others it became close to unmanageable.

Of course, JavaFX is not going to replace Swing but has some features I like. Of course, it is still possible to build an entire JavaFX user interface programmatically and assign a multitude of controllers to it. But the beauty of it is that it is possible to entirely separate the user interface from the code. Writing the interface in FXML allows one to easily design the user interface without the temptation of starting to code. It also has some new and very useful containers and as a bonus finally brings charts!

Having that itching in my hands to try it I started to re-write a small app I had made using Swing and a third-party charts library. The result was good, becoming much simpler than the original and working equally well but better looking.

Overview

In this article, I'll explain how to start writing applications using JavaFX and try a very simple example.

How to create a JavaFX application

My recipe for JavaFX is as follows:
  • Build the user interface in FXML.
  • Create a controller class like MyAppController.
  • Create the main class like MyApp extending Application.
Let's do a hello world example

First step: create the FXML and save it as "hello.fxml":
<?xml version="1.0" encoding="UTF-8"?>

<?import java.net.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.text.*?>

<StackPane>
   <Label text="Hello World"/>
</StackPane>
Second step: this example has no interactions so there is no need for a controller, skip this step.
Third step: create the main class Hello and save it as "Hello.java":
import java.io.IOException;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.Parent;
import javafx.stage.Stage;

/**
 * @author António Raposo
 */
public class Hello extends Application {

   @Override
   public void start(Stage primaryStage) throws IOException {
      Parent root = FXMLLoader.load(getClass().getResource("hello.fxml"));
      Scene myScene = new Scene(root);
      primaryStage.setScene(myScene);
      primaryStage.setWidth(400);
      primaryStage.setHeight(300);
      primaryStage.show();
   }

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

}
Now compile and run. Here is how it looks.

Conclusion

If you followed these steps you just created your first JavaFX application.