JavaFX or JFX -The Basics and The Essentials-

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
Hi

History

As the hype about MVC (Model-View-Controller) started to make noise and Microsoft loudly clamored its C# plus .NET SUN responded with JavaFX. If you so will, JFX is the counterpart of C# with .NET. As you already guessed JFX can be developed with or without the most-touted MVC. Of course, there are a lot of arguments pro and con MVC. In this tutorial here I don't intend to discuss about MVC, but about the basics of JFX. With the basics you are able to develop and to work with JFX. When you have acquired enough experiences on JFX you could start to work with JFX-MVC in conjunction with FXML (Java FX -> FX XML -> FXML) and CSS (Cascading Style Sheets).

What is JFX? From the basic view Java FX is a new GUI development along the SWING package. It is not the SWING replacement, but an adaption to the new GUI styles which were used by MS and Apple at that time. Hence, JFX looks more Apple-alike (or Windows) than Java SWING. But the main purpose is the Desktop application development and RIAs (Rich Internet Applications). Rich Internet? Yes. With the javafx.scene.web.WebEngine, for example, you can build an app that could play Youtube or displays the Wikipedia, etc.

Desktop Applications

Desktop application means GUI application. The more attractive the GUI appearance is, the more people like it. Compared to SWING JFX appearance is clearer, smoother, more fluid and more colorful. Furthermore is that JFX is more dynamic and livelier than SWING. To create an animation is a simple work compared to SWING.

Let's start with the basics. JavaFX Application deviates from "normal" Java App:

  1. A JavaFX App is ALWAYS an extension of the API Application. Different to SWING a SWING app must NOT an extension of JFrame. And that is the 1st deviation.
  2. JavaFX App must NOT include the "static void main()" but it can have. That is the 2nd deviation.
  3. External parameters are passed by different way than Java App with the main(). That is the 3rd deviation.
  4. JavaFX application requires a "Scene" to play the "Stage" as SWING with the JFrame.
  5. API Application provides 4 essential methods that can be overwritten for your specific needs. And that is the 5th deviation. These methods are:
PHP:
public void init()
public void launch()
public void start()
public void stop()
The first deviation is easy to understand:
PHP:
import javafx.application.Application;
public MyJFX extends Application {
  ...
}
The second deviation is usually ignored by most developers. Probably due to their old habit they start a JFX app with a main().

The "new" way to start a JFX app
PHP:
import javafx.stage.Stage;
import javafx.application.Application;
public MyJFX extends Application {
  public void start(Stage stage) {
    ...
  }
}
The old habit with main(). Note: see the redundancy!
PHP:
import javafx.stage.Stage;
import javafx.application.Application;
public MyJFX extends Application {
  public void start(Stage stage) {
    ...
  }
  ...
  public static void main(String... args) {
    launch(args);
  }
}
As you see, the main() starts the launch() of Application and that is! Some argue that it's for safety.

The third deviation is also a problem for the newbies who start to learn JavaFX without a proper guidance: the external parameters. Even those who live with the old habit don't know how to pass the "args" array to the start() method. The using of a global static String array is awkward and amateurish.
PHP:
import javafx.stage.Stage;
import javafx.application.Application;
public MyJFX extends Application {
  public void start(Stage stage) {
    // access the parms here
    ...
  }
  ...
  private static String[] parms;
  public static void main(String... args) {
    parms = args; // <<---pass the parameters
    launch(parms);
  }
}
The correct way of passing parameters is as following:
PHP:
import java.util.*;
import javafx.stage.Stage;
import javafx.application.Application;
public MyJFX extends Application {
  public void start(Stage stage) {
    // Accessing the passing parameters
    Application.Parameters params = getParameters();
    java.util.List<String> plist = params.getRaw(); // List<String> instead of String[]

    ...
  }
}
To run a JavaFX Application JVM always needs a "Scene" for the "Stage". It's like in Hollywood. Without Scene, no Stage.

Finally the fifth deviation is to overwrite the provided methods to suit your own specific requirements.
  • lauch() is used by those who won't trust the new way and stay with the old habit. No need for any further explanation (see the example of the 2nd deviation). It's an optional method.
  • init() is used to initialize some works (e.g. open DB connection, etc.) before JavaFX starts to run. It's an optional method. If you have nothing to initialize you can omit it.
  • start() is the starting point of a JavaFX application (if main() is omitted). Start always requires ONE default parameter "javafx.stage.Stage".
  • stop() is the "clean-up" method. What you initialized in init() can be cleaned up here (e.g. close DB connection, etc.) It's an optional method. If you have nothing clean up you can omit it.

Now we can build our 1st "runnable" JavaFX Application using the knowledge about the 5 deviations:
PHP:
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.application.Application;
import javafx.scene.control.TextArea;
import javafx.util.*;
// Joe Nartca (C)
public class MyJavaFX extends Application {
  public void init() {
    System.out.println("I'm doing initialization...");
  }
  public void start(Stage stage) {
    // load passing arguments
    Application.Parameters params = getParameters();
    java.util.List<String> pList = params.getRaw();
    //
    TextArea ta = new TextArea();
    ta.textProperty().addListener(c->{
      ta.setScrollTop(Double.MAX_VALUE);
    });
    ta.setWrapText(true);
    ta.setEditable(false);
    ta.setPrefRowCount(20);
    ta.setPrefColumnCount(42);
    ta.setScrollTop(Double.MAX_VALUE);
    ta.setPrefSize(300, 200);
    int sz = pList.size();
    ta.setText("You have passed: "+sz+" parameter"+(sz > 1?"s":""));
    if (sz > 0) for (int i = 0; i < sz; ++i)
      ta.appendText("\n"+(i+1)+". parameter:"+pList.get(i));
    Scene scene = new Scene(ta, 300, 200);
    stage.setScene(scene);
    stage.show();
  }
  public void stop() {
    System.out.println("I'm doing the Clean-Up...");
  }
}
MyJavaFX.png
After exit you get this message from the method stop()
Code:
C:\links\java\jfx>javac -g:none -d ./classes MyJavaFX.java

C:\links\java\jfx>java MyJavaFX "Hello CongdongJava"
I'm doing initialization...
I'm doing the Clean-Up...

C:\links\java\jfx>
Because We don't have anything to initialize, nor to clean up MyJavaFX could shorten to only the method start().
PHP:
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.application.Application;
import javafx.scene.control.TextArea;
import javafx.util.*;
// Joe Nartca (C)
public class MyJavaFX extends Application {
    // load passing arguments
    Application.Parameters params = getParameters();
    java.util.List<String> pList = params.getRaw();
    //
    TextArea ta = new TextArea();
    ta.textProperty().addListener(c->{
      ta.setScrollTop(Double.MAX_VALUE);
    });
    ta.setWrapText(true);
    ta.setEditable(false);
    ta.setPrefRowCount(20);
    ta.setPrefColumnCount(42);
    ta.setScrollTop(Double.MAX_VALUE);
    ta.setPrefSize(300, 200);
    int sz = pList.size();
    ta.setText("You have passed: "+sz+" parameter"+(sz > 1?"s":""));
    if (sz > 0) for (int i = 0; i < sz; ++i)
      ta.appendText("\n"+(i+1)+". parameter:"+pList.get(i));
    Scene scene = new Scene(ta, 300, 200);
    stage.setScene(scene);
    stage.show();
  }
}
For a comparison with SWING
PHP:
import javax.swing.*;
// Joe Nartca (C)
public class MySWING extends JFrame {
  public MySWING(String[] pList) {
    JTextArea ta = new JTextArea(20, 42);
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    ta.setText("You have passed: "+pList.length+" parameter"+(pList.length > 1?"s":""));
    if (pList.length > 0) for (int i = 0; i < pList.length; ++i)
      ta.append("\n"+(i+1)+". parameter:"+pList[i]);
    add(ta);
    setSize(300, 200);
    setVisible(true);
  }
  public static void main(String... args) {
    new MySWING(args);
  }
}
If you want to have the same functionality of JavaFX TextArea you have to wrap the JTextArea in a JScrollPane and that will complicate the matter more than you, as a newbie, could imagine. And that is the superiority of JavaFX over SWING.


(cont.)
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JFX Threads

Before we start to work with JFX objects let me continue the basics which are in some cases helpful when our codes won't work or seem to be ignored or missed. It's about the Event Threads and the GUI threads (the working threads in the JFX environment and in SWING as well). It is so: in JFX environment events (e.g. mouse or button click, etc.) are driven by threads. These threads distinguish from the working threads that run the app. If the synchronization went wrong exceptions would be thrown. In some cases you get such "cryptic" exceptions saying that "Thread XYZ isn't a JFX Thread", or worst: "NullPointerException". What Thread is NOT the JFX Thread? Where is the NullPointer? Nobody knows because it happened inside the JVM?

Nobody? NO. They are the event threads that "touch" the working threads (e.g. update the GUI such as Label, ListView, etc.). Events to a certain JFX object should be "brought" for the appropriate processing. In other words: the event thread for a certain GUI component must be switched to the corresponding working Thread for this GUI component. Regardless of JFX or SWING. Otherwise the weirdness arises when an event for a GUI update arrives, but the active working thread has NOTHING to do with the GUI. The outcome is that the event was either missed or rejected with a weird exception (Not JFX, NullPointer, etc.)

Is it then the dead end? Again: NO. It's the question of synchronization and prioritization. If you are an experienced SWING developer you would know this API SwingUtilities method invokeLater(). An example:
PHP:
public class MySWING extends JFrame implements ActionListener {
  public MySWING(String[] pList) {
    ...
    JButton but = new JButton("GO");
    but.addActionListener(this);
    ...
    setVisible(true);
  }
  public void actionPerformed(ActionEvent e) {
    // Synchronize the (executing) queue....
    SwingUtilities.invokeLater(() ->  {                      // <<---invokeLater()
        ...// do the long-lasting work
    });
  }
  public static void main(String... args) {
    new MySWING(args);
  }
}
Similar to "invokeLater()" we have to run in JFX the method runLater() of the API Platform to synchronize the event threads (by queueing) with the corresponding working Threads. Example:
PHP:
public class MyJavaFX extends Application {
  public void start(Stage stage) {
    // load passing arguments
    Application.Parameters params = getParameters();
    java.util.List<String> pList = params.getRaw();
    //
    ta = new TextArea();
    // wait for MouseEvent.....
    ta.setOnMouseReleased(e->load());
    ...
    Scene scene = new Scene(ta, 300, 200);
    stage.setScene(scene);
    stage.show();
  }
  private void load( ) {
    // update TextArea with a looooong text
    javafx.application.Platform.runLater(() -> {                                //<<--- runLater()
      byte[] buf = Files.readAllBytes((new File("bigFile.txt")).toPath());
      ta.appendText(new String(buf));
    });
  }
  private TextArea ta;
}
The loading of "bigFile.txt" could take very long time so that the update of TextArea would be missed or unaccepted. The invocation of runLater() ensures that the update (here: appendText) will be made by the corresponding working thread.


(cont.)
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JFX Objects

He who worked with the "oldest" JAVA GUIs would remember the "names" of AWT GUI components (AWT stands for Abstract Window Toolkit) when he learns the names of JFX GUI components. But the structural architecture is different. JFX is rich with GUI components and that allows developers to create fancy appearances with the less effort in JFX than in AWT or in SWING. The basic JFX structure bases on:
  • Stage: the outer frame
  • Scene: the "scene (or canvas)" of the frame if you so will
  • Pane: or usually called "root" as the bearer
  • VBox for the vertical arrangement of JFX components (e.g. Button, Label, etc.)
  • HBox for the horizontal arrangement of JFX components (e.g. Button, Label, etc.)

JFX_Layout.png

An example:
PHP:
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.text.Font;
import javafx.scene.control.*;   // TextArea, TexField, Button, CheckBox
import javafx.scene.layout.*;    // HBOX, VBOX
import javafx.util.*;
// Joe Nartca (C)
public class JavaFX_GUI extends Application {
  public void start(Stage stage) {
    VBox vbox = new VBox(5); // spacing 5
    TextField tf = new TextField("TextField");
    tf.setFont(Font.font ("Verdana", 18));
    Label lab = new Label("Label");
    lab.setFont(Font.font ("Verdana", 18));
    Button but = new Button("Button");
    but.setFont(Font.font ("Verdana", 18));
    but.setPrefSize(210, 20);
    CheckBox cb = new CheckBox("CheckBox");
    cb.setFont(Font.font ("Verdana", 18));
    TextArea ta = new TextArea("TextArea");
    vbox.getChildren().addAll(tf, lab, but, cb, ta);
    // Insets(top, right, bottom, left)
    vbox.setPadding(new Insets(4, 4, 4, 4));
    Scene scene = new Scene(vbox, 213, 235);
    stage.setScene(scene);
    stage.show();
  }
}
Explanation:
  • VBox (Vertical Box) is used to arrange VERTICALLY the JFX components (over another). HBox is used to arrange the JFX components HORIZONTALLY.
  • TextField, Label, Button, CheckBox and TextArea are JFX components which are arranged upon each other in VBox
  • Insets create the padding of the components inside VBox
  • Because VBox is an extension of Pane we don't need a carrier (e.g. AnchorPane, etc.)
  • Scene is set on VBox, and
  • is put on the Stage

Further JFX components are, for example, TilePane, StackPane, FlowPane, etc. and Color (different to SWING Color), Insets, BackgroundImage, etc. They all together with the Animation and Transformation package we can create not only fancy images (e.g. SVG), but also lively animations.

The following picture shows you the JFX Appearence compared to AWT and SWING Appearance

JFX.png
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
(cont. of JFX Objects)

Beside the JFX "moldings" (VBox, HBox, Pane, etc.) you can, of course, "free-style" the layout like in SWING, too. To do that you need only to have an artistic imagination and have only to work with two methods of the JFX Node which is usually the base of ALL JFX objects (e.g. TextArea, VBox, Pane, etc.)
  1. setLayoutX()
  2. setLayoutY()
Example:
PHP:
import javafx.stage.Stage;
import javafx.scene.Scene;
//
import javafx.scene.image.*;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.application.Application;
import javafx.scene.control.TextArea;
import javafx.scene.paint.Color;
// Joe Nartca (C)
public class FreeStyleJFX extends Application {
  public void start(Stage stage) {
    stage.setTitle("Free Styling");
    TextArea ta = new TextArea();
    ta.textProperty().addListener(c->{
      ta.setScrollTop(Double.MAX_VALUE);
    });

    ta.setLayoutX(10);     // <<-- Coordinate X
    ta.setLayoutY(5);      // <<-- Coordinate Y

    ta.setPrefSize(100,150);
    ta.setEditable(false);
    ta.setWrapText(true);
    ta.setPrefRowCount(20);
    ta.setPrefColumnCount(42);
    ta.setScrollTop(Double.MAX_VALUE);
  
    Image earth = new Image(getClass().getResourceAsStream("earth.gif"));
    Button but = new Button("Earth", new ImageView(earth));

    but.setLayoutX(120);    // <<-- Coordinate X
    but.setLayoutY(50);     // <<-- Coordinate Y

    but.setOnAction(a -> {
      ta.appendText("You kicked me\n");
    });
  
    Pane pane = new Pane();
    pane.getChildren().addAll(ta, but);
    Scene scene = new Scene(pane, 230, 180);
    stage.setScene(scene);
    stage.show();
  }
}
earth.gif the image for theJFX Button

FreeStyle.png

(Next: JFX Graphics with SVG and JFX Animation)
 

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JFX Graphics

JFX Graphics is abundant. However JFX Graphics can be categorized into three areas:

  • build-in Graphics
  • Imaging
  • Scalar Vector Graphics (or SVG).

The build-in Graphics is for example the API Circle or Rectangle or Ellipse, etc. which can be found in the package javafx.scene.shape.* and the GraphicsContext (derived from API Canvas). The question is when I should use the built-in and when I should use the GraphicsContext? The answer is unambiguous: GraphicsContext is for freestyle (like an artist paints on a canvas). The built-in graphics is like a stencil that can be used to paint on anything. Examples:

Freestyle (with Imaging):
PHP:
import javafx.application.*;
import javafx.stage.Stage;
import javafx.scene.canvas.*;
import javafx.scene.shape.*;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.layout.*;

public class JFXGraphics extends Application {
  public void start(Stage stage) {
    stage.setTitle( "JFX Graphisc" );
    Canvas screen = new Canvas(500, 500);
    GraphicsContext gc = screen.getGraphicsContext2D();
    // external parameter
    Application.Parameters params = getParameters();
    java.util.List<String> pList = params.getRaw();
    //
    String S;
    if (pList.size() == 0) S = "circle";
    else S = pList.get(0).toLowerCase();
    //
    gc.setFill(Color.BLACK);
    gc.fillRect(0,0,500,500);
    switch (S) {
    case "circle":
      gc.setFill(Color.CYAN);
      gc.fillOval(200,200, 100, 100);
      break;
    case "oval":
      gc.setFill(Color.LIGHTGREEN);
      gc.fillOval(200,100, 100, 200);
      break;
    case "rectangle":
      gc.setFill(Color.LIGHTBLUE);
      gc.fillRect(200,200, 100, 100);
      break;
    default:
      Image img = new Image(getClass().getResourceAsStream(S));
      double h = img.getHeight();
      double w = img.getWidth();
      gc.drawImage(img, (500-w)/2, (500-h)/2);
      break;
    }
    Pane pane = new Pane();
    pane.getChildren().add(screen);
    Scene scene = new Scene(pane);
    stage.setScene(scene);
    stage.show();
  }
}
The invocation is: java JFXGraphics xyz where xyz: circle, oval, rectangle and any vaild image filename (jpg, png, gif).

Oval.png

For the image: sun.jpg

Built-in Graphics:
PHP:
import javafx.application.*;
import javafx.stage.Stage;
import javafx.scene.canvas.*;
import javafx.scene.shape.*;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.layout.*;

public class JFXBuiltIn extends Application {
  public void start(Stage stage) {
    stage.setTitle( "JFX Graphisc" );
    // external parameter
    Application.Parameters params = getParameters();
    java.util.List<String> pList = params.getRaw();
    //
    String S;
    Shape obj = null;
    if (pList.size() == 0) S = "circle";
    else S = pList.get(0).toLowerCase();
    //
    switch (S) {
    case "circle":
      obj = new Circle(100, Color.CYAN);
      ((Circle)obj).setCenterX(250);
      ((Circle)obj).setCenterY(250);
      break;
    case "oval":
      obj = new Ellipse(100, 150);
      obj.setFill(Color.LIGHTGREEN);
      ((Ellipse)obj).setCenterX(250);
      ((Ellipse)obj).setCenterY(250);
      break;
    case "rectangle":
      obj = new Rectangle(100, 200, Color.LIGHTBLUE);
      ((Rectangle)obj).setX(250);
      ((Rectangle)obj).setY(250);
      break;
    }
    Pane pane = new Pane();
    pane.getChildren().add(obj);
    Scene scene = new Scene(pane, 500, 500, Color.BLACK);
    stage.setScene(scene);    
    stage.show();
  }
}
Oval_2.png


At Begin of this tutorial I have mentioned about JFX as Desktop Applications and RIAs (Rich Internet Applications). One of the RIAs is the ability to process and to display the Scalable Vector Graphics or SVG (more: HERE). What is SVG? Let look at Wikipedia:
Scalable Vector Graphics (SVG) is an Extensible Markup Language (XML)-based vector image format for two-dimensional graphics with support for interactivity and animation. The SVG specification is an open standard developed by the World Wide Web Consortium (W3C) since 1999.
SVG images and their behaviors are defined in XML text files. This means that they can be searched, indexed, scripted, and compressed. As XML files, SVG images can be created and edited with any text editor, as well as with drawing software.
The first phrase is crystal-clear enough that I have no need to explain more about SVG. An example: the following string in SVG codes represents a triangle:
Code:
M 100 100 L 300 100 L 200 300 z
Quite cryptic, isn't it? But if you work with SVG for a while you'll get familiar with it and they won't appear "cryptic" anymore. An Example:
PHP:
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
//
import javafx.scene.shape.*;
import javafx.scene.layout.*;
import javafx.application.Application;
import javafx.animation.PathTransition.*;
import javafx.util.*;
import javafx.animation.*;
// Joe Nartca (C)
// More about SVG: see https://www.w3.org/TR/SVG/expanded-toc.html
public class SVGview extends Application {
    public void start(Stage stage) throws Exception {
        stage.setTitle("Scalar Vector Graphics");
        Application.Parameters params = getParameters();
        java.util.List<String> pList = params.getRaw();
        //
        String svg; 
        if (pList.size() > 0) svg = pList.get(0);
        // create a Triangle in SVG codes with JavaFX
        else svg = "M 100 100 L 300 100 L 200 300 z";
        //
        SVGPath path = new SVGPath();  // <<---the base for SVG     
        path.setContent(svg);          // <<---loading SVG codes
        path.setStrokeWidth(2);        // <<---set the border "thickness"
        path.setStroke(Color.RED);     // <<---border color
        path.setFill(Color.YELLOW);    // <<---content color
        
        // JFX Graphic CIRCLE that can be used as SVG Circle as well
        Circle circle = new Circle(10, Color.BLUE);
        circle.setLayoutX(170);
        circle.setLayoutY(150);
                    
        Pane pane = new Pane();
        pane.getChildren().addAll(path, circle);
        
        // Animation
        PathTransition pt = new PathTransition();
        pt.setDuration(Duration.millis(10000));  // 10 sec.
        pt.setNode(path);                        // node is the SVG
        pt.setPath(circle);                      // around the Path Circle

        // set animation art: tangential around
        pt.setOrientation(OrientationType.ORTHOGONAL_TO_TANGENT);
        pt.setCycleCount(Animation.INDEFINITE);
        pt.setInterpolator(Interpolator.LINEAR);
        // Animation
        pt.play();
        
        stage.setScene(new Scene(pane, 350, 350));
        stage.show();
    }
}
SVGview allows you to display a "small" SVG coded graphics. The invocation is as following
Code:
java SVGview SVG-Coded-String
Note: the SVG-Coded-String must be embedded by double quotes ("...") if it contains spaces. The following SVG-coded-Strings let you see how JFX work with SVG:
Code:
1) m5 20 L 15 10 S 45 12 75 10 L 85 20z
2) m5 0 S 15 10 45 12
3) M 19.8022,0 L 26.8921,24.105 33.9819,0 39.1157,0 L 27.3809,39.604 26.8921,39.604 19.5576,15.0596 L 12.2236,39.604 11.7349,39.604 0,0 5.13379,0 L 12.2236,24.105 17.0151,7.87262 14.6685,0z
4) M107,88c100,140 50,140 -8,160
Just try and see how it works...with the "anmination".

SVG.png
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
(cont.)

JFX Animation

Hi

As you have already seen in the SVGview example, JavaFX provides a lot of Animation APIs (click HERE for more details about the package) which can be used to produce some fancy animation such as Car-Racing, etc. The 2 following examples show you how the API AnimationTimer is used to make some beautiful animations (Note: I have downloaded the example SunEarth.java years ago from the WEB, beautified it with the new SUN & new Earth, and amended it so that they could run with up JDK 7. I'm terribly sorry that I've forgotten the link to the original.)

Clock.java: This animation shows you how to create a free-styled Clock on canvas without having to involve any Image. All with the GraphicsContext API.
PHP:
import javafx.application.*;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.paint.Color;
import javafx.scene.canvas.*;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.text.Font;
//
import java.time.LocalDateTime;
// Joe Nartca
public class Clock extends Application {

  public void start(Stage stage) {
    stage.setTitle( "CLOCK" );

    Canvas screen = new Canvas(580, 580);
    GraphicsContext gc = screen.getGraphicsContext2D();

    double rad  = 0.0174533;
    minute = LocalDateTime.now().getMinute();
    hour = LocalDateTime.now().getHour() + minute/180;
    //starting time with LocaLDateTime
    startTime = System.nanoTime();
    new AnimationTimer() {
      public void handle(long currentTime) {
        second = (currentTime - startTime) / 10000000000.0;
        if (second >= 6.28319) {
          startTime = currentTime;
          minute = LocalDateTime.now().getMinute();
          hour = LocalDateTime.now().getHour() + minute/180;
        }
        gc.setFill(Color.BLACK);
        gc.fillRect(0,0,580,580);
        //
        gc.setLineWidth(1.5);
        gc.setFill(Color.LIGHTGREEN);
        gc.fillOval(40, 40, 500, 500);
        gc.setFont(Font.font("Verenda", 25));
        gc.setStroke(Color.RED);
        gc.strokeText("12", 272.5, 70);
        gc.strokeText("6", 282, 530);
        gc.strokeText("3", 512, 293);
        gc.strokeText("9", 48, 293);
        gc.setStroke(Color.CYAN);
        gc.strokeText("Joe's animated Clock", 175, 30);
        //
        double x1 = 290 + 180 * Math.sin((hour*30 + minute*0.3333)*rad);
        double y1 = 290 - 180 * Math.cos((hour*30 + minute*0.3333)*rad);
        gc.setLineWidth(10);
        gc.setStroke(Color.YELLOW);
        gc.strokeLine(290, 290, x1, y1); // hour

        x1 = 290 + 200 * Math.sin(minute*6*rad);
        y1 = 290 - 200 * Math.cos(minute*6*rad);
        gc.setLineWidth(7);
        gc.setStroke(Color.RED);
        gc.strokeLine(290, 290, x1, y1); // Minute

        gc.setLineWidth(2);
        x1 = 290 + 220 * Math.sin(second);
        y1 = 290 - 220 * Math.cos(second);
        gc.setStroke(Color.BLUE);
        gc.strokeLine(290, 290, x1, y1); // second
      }
    }.start();
    Group root = new Group();
    root.getChildren().add(screen);
    Scene scene = new Scene(root);
    stage.setScene(scene);
    stage.show();
  }
  private long startTime;
  private double hour, minute, second;
  // quick Exit
  public void stop() {
    Platform.exit();
    System.exit(0);
  }
}
clock.png

The Clock drives itself with the currentTime after the initial LocalDateTime.

And the beautiful Animation with the Sun and the Earth
PHP:
import javafx.application.*;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.canvas.*;
import javafx.animation.*;
import javafx.scene.*;

public class SunEarth extends Application {

  public void start(Stage stage) {
    stage.setTitle( "The Sun and the Earth" );

    Image earth = new Image(getClass().getResourceAsStream("./pic/earth.gif"));
    Image sun   = new Image(getClass().getResourceAsStream("./pic/sun.jpg"));
    Image space = new Image(getClass().getResourceAsStream("./pic/all.jpg"));

    Canvas screen = new Canvas(580, 570);
    GraphicsContext gc = screen.getGraphicsContext2D();

    // Create an Animation with the Sun and the Earth
    final long startTime = System.nanoTime();
    new AnimationTimer() {
      public void handle(long currentTime) {
        double t = (currentTime - startTime) / 10000000000.0;
        // elliptical path around the Sun
        double x = 260 + 240  * Math.cos(t);
        double y = 250 + 90 * Math.sin(t);
        gc.drawImage(space, 0, 0);
        gc.drawImage(sun, 200, 205);
        gc.drawImage(earth, x, y);
      }
    }.start();

    Group root = new Group();
    root.getChildren().add(screen);
    Scene scene = new Scene(root);
    stage.getIcons().add(sun);
    stage.setScene(scene);
    stage.show();
  }
  // quick Exit
  public void stop() {
    Platform.exit();
    System.exit(0);
  }
}
I have include the stop() method to make sure that NOTHING is still running when the app is closed. And to show you how to make a "safe" exit with JavaFX application.

SunEarth.jpg

Note: for the SunEarth.java you need to download the sunearth.zip

(next: JavaFX with the WEB)
 

Attachments

Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
(cont.)

JavaFX with the WEB

Hi

I hope that you are now familiar with JavaFX up to this tutorial: JFX with the Internet. With SVGPath we are already able to access ALL SVG on the Web without having to create a SVG-Engine. The question is how far can we access the WEB beside the SVG ? On the web swirls a lot of fancy things such as Videos (YouTube), documents (Wikipedia), musics (classic, beat rap, opera, etc.) and so on. Well, in theory JavaFX provides us the WebEngine that could do that almost as good as a REAL Browser (Chrome, Firefox, Opera, etc.) would do.

Why "in theory"? Well, the answer is more philosophical than practical. Browser is a full-fledged BIG application which contains all kinds of "engines" (e.g. SVG) and "interpreters" (e.g. JavaScript) while WebEngine is merely an API that allows you to access the Web. Let's start with an example: we build a WebBrowser with JFX WebEngine. The Task is a straight forward work.

  1. First we design a download Thread that does the downloading in background,
  2. then we design a simple WebBrowser that bases on JFX WebEngine (in next tutorial I will show you how to bridge "this" WebBrowser to, for example, the TOR network or a VPN/Proxy).

Let's start with the Download Thread. The downloading process could take a while. So, we decide to run it with a Java Runnable in the ForkJoinPool.commonPool(). The WebDownload.java

PHP:
import java.io.*;
import java.net.URL;
import javafx.concurrent.Task;
import javafx.scene.control.ProgressIndicator;
// Joe T. Nartca
//
public class WebDownload implements Runnable {

  public WebDownload(ProgressIndicator pInd, String url, String dir) {
    pInd.setVisible(true);
    this.pInd = pInd;
    this.url  = url;
    this.dir  = dir;
  }
  public void run() {
    try (InputStream inp = (new URL(url)).openStream()) {
      byte[] buf = new byte[65536];
      int b = url.lastIndexOf("/");
      String path = dir+File.separator+url.substring(b+1);
      FileOutputStream fout = new FileOutputStream(path, false);
      for(b = inp.read(buf); b > 0; b = inp.read(buf)) fout.write(buf, 0, b);
      fout.flush();
      fout.close();
      inp.close();
    } catch (Exception e) {e.printStackTrace();}
    pInd.setVisible(false);
  }
  private ProgressIndicator pInd;
  private String url, dir;
}
The WebBrowser. It sounds complicated and difficult, but it isn't complicated, nor difficult at all. Again, a straight forward implementation with JFX WebEngine and some "bling-bling" things like Home button, For-/Backward button, etc. Here you can see how JavaFX components are used:
  • Button
  • HBox
  • VBox
  • TabPane
  • ProgressIndicator
  • CookieManager
And the Clean-Up action in stop() method (here Windows, to do is in the Linux Part). If an input is NOT an URL WebBrowser goes GOOGLE-Search (see google() method). You may notice the method getCSS(). Well, this CSS and FXML part will be explained in the next tutorials. The codes: WebBrowser.java

PHP:
// Java
import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;
// Java FX
import javafx.application.*;
import javafx.stage.*;
import javafx.scene.*;
import javafx.scene.text.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.control.*;
import javafx.scene.effect.*;
import javafx.scene.image.*;
import javafx.scene.media.*;
import javafx.scene.web.*;
import javafx.geometry.*;
import javafx.event.*;
import javafx.beans.*;
import javafx.beans.value.*;
import javafx.collections.*;
import javafx.scene.web.*;
import javafx.util.Callback;
import javafx.concurrent.*;
import javafx.concurrent.Worker.State;
import javafx.scene.web.WebHistory.*;
// Joe Nartca (C)
public class WebBrowser extends Application {
  public void start(Stage stage) {
    this.stage = stage;
    stage.setTitle("WebBrowser");
    stage.getIcons().add(new Image(getClass().getResourceAsStream("./pic/browser.jpg")));
    System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
    List<String> lst = getParameters().getRaw();
    String home = lst.size() > 0? lst.get(0):"https://congdongjava.com/forum/";
    // TabList, EngineList, UserList
    tList = new ArrayList<Tab>();
    uList = new ArrayList<String>();
    eList = new ArrayList<WebEngine>();
    ArrayList<String> hTabs = new ArrayList<String>();
    //
    pDload = new ProgressIndicator();
    pInd = new ProgressIndicator();
    loc = new TextField(home);
    loc.setOnAction(a -> {
      String url = google(loc.getText().toLowerCase());
      if (forDownload(url)) loc.setText(uList.get(Itab));
      else {
        tList.get(Itab).setText(getSite(url));
        eList.get(Itab).load(url);
        uList.set(Itab, url);
      }
    });
    // set caret to TextField
    Platform.runLater(() -> {
      loc.requestFocus();
    });
    tabPane = new TabPane();
    tabPane.getTabs().add(getTab(home));
    tabPane.getSelectionModel().selectedItemProperty().addListener((obs, oldTab, newTab)-> {
      if(oldTab != null && newTab != null) {
        Iold = tList.indexOf(oldTab);
        Itab = tList.indexOf(newTab);
        loc.setText(uList.get(Itab));
      }
    });
    // create additional TABs
    if (hTabs.size() > 0) for (String tmp:hTabs) tabPane.getTabs().add(getTab(tmp));
    tabPane.getSelectionModel().select(tList.get(Itab));
    // 3 buttons: back, home, fore
    Button back = new Button("<");
    back.setOnAction(a -> {
      Platform.runLater(()-> {
        try {
          eList.get(Itab).getHistory().go(-1);
        } catch(Exception e){
          eList.get(Itab).load(loc.getText());
        }
      });
    });
    Button Home = new Button("H O M E");
    Home.setOnAction(a -> {
      eList.get(Itab).load(home);
      uList.set(Itab, home);
      loc.setText(home);
    });
    Button fore = new Button(">");
    fore.setOnAction(a -> {
      Platform.runLater(()-> {
       try {
          eList.get(Itab).getHistory().go(+1);
        } catch(Exception e){
          eList.get(Itab).load(loc.getText());
        }
      });
    });
    Button bTab = new Button("NEW TAB");
    bTab.setOnAction(a -> {
      Platform.runLater(() -> {
        loc.requestFocus();
      });
      Itab = tList.size();
      Tab tab = getTab(null);
      tabPane.getTabs().add(tab);
      tabPane.getSelectionModel().select(tab);
    });
    pDload.setVisible(false);
    // Upper HBox
    HBox hboxU = new HBox(7);
    // Insets(top, right, bottom, left)
    hboxU.setPadding(new Insets(5, 5, 1, 5));
    hboxU.getChildren().setAll(Home, back, loc, fore, bTab);
    hboxU.setHgrow(loc, Priority.ALWAYS);
    hboxU.setAlignment(Pos.CENTER);
    // Left VBox
    VBox vboxL = new VBox(10);
    vboxL.getChildren().setAll(pInd, pDload);
    vboxL.setAlignment(Pos.CENTER);
    // Lower HBox
    HBox hboxL = new HBox(10);
    hboxL.setPadding(new Insets(1, 5, 5, 5));
    hboxL.getChildren().setAll(vboxL, tabPane);
    hboxL.setHgrow(tabPane, Priority.ALWAYS);
    hboxL.setAlignment(Pos.CENTER);
    // All VBox
    VBox vbox = new VBox(10);
    vbox.setAlignment(Pos.CENTER);
    vbox.setPadding(new Insets(5, 5, 5, 5));
    vbox.getChildren().setAll(hboxU, hboxL);
    vbox.setVgrow(hboxL, Priority.ALWAYS);
    // GO....
    CookieManager cookies = new CookieManager();
    Scene scene = new Scene(vbox, 1210, 700);
    getCSS(scene);
    stage.setScene(scene);
    Platform.setImplicitExit(true);
    // clear all Cookies on CLOSE
    stage.setOnCloseRequest(e -> {
      cookies.getCookieStore().removeAll();
    });
    stage.show();
  }
  // quick Exit
  public void stop() {
    for (WebEngine wN : eList) wN.load(null);
    if (cssDef != null) (new File(cssDef)).delete();
    String path = null; // Windows or other Operating?
    if (System.getProperty("os.name").charAt(0) == 'W') {
      path = "C:\\Users\\"+System.getProperty("user.name")+
             "\\AppData\\Roaming\\WebBrowser\\webview\\localstorage\\";
    } else { // e.g. linux....
      // to do.......
    }
    if (path != null) {
      File[] all = (new File(path)).listFiles();
      for (File file: all) file.delete();
    }
    Platform.exit();
    System.exit(0);
  }
  //
  private Tab getTab(String url) {
    WebView wView = new WebView();
    WebEngine engine = wView.getEngine();
    Tab tab = new Tab(getSite(url));
    tab.setContent(wView);
    eList.add(engine);
    loc.setText(url);
    engine.load(url);
    tList.add(tab);
    uList.add(url);
    // CloseListener
    tab.setOnClosed(e -> {
      if (!tabPane.getTabs().isEmpty()) {
        uList.remove(Iold);
        tList.remove(Iold);
        eList.remove(Iold).load(null);
      } else tabPane.getTabs().add(getTab(home));
    });
    // start to show up with ProgressIndicator
    engine.getLoadWorker().progressProperty().addListener((obs, oldVal, newVal) -> {
        pInd.setVisible(true);
        if (newVal.doubleValue() >= 0.95) pInd.setVisible(false);
    });
    // reload after correction (e.g. insert www., etc.)
    engine.locationProperty().addListener((obs, oldUrl, newUrl) -> {
      if (newUrl != null && oldUrl != null && !newUrl.equals(oldUrl)) {
          if (forDownload(newUrl)) {
            newUrl = uList.get(Itab);
            eList.get(Itab).load(newUrl);
          } else {
            if (Itab >= tList.size()) Itab = tList.size()-1;
            if (Itab < 0) return;
            tabPane.getSelectionModel().select(tList.get(Itab));
            uList.set(Itab, newUrl);
            tList.get(Itab).setText(getSite(newUrl));
          }
          loc.setText(newUrl);
        }
    });
    // refresh URL state (display field)
    engine.getLoadWorker().stateProperty().addListener((ov, oldState, newState) -> {
       if (newState == Worker.State.SUCCEEDED) {
         String tmp = eList.get(Itab).getLocation();
         if (!tmp.equals("about:blank")) {
           tList.get(Itab).setText(getSite(tmp));
           loc.setText(tmp);
           uList.set(Itab, tmp);
         }
       }
    });
    return tab;
  }
  // URL validation
  private String google(String url) {
    String u = url.indexOf("://") > 0? url:"http://" + url;
    if (url.contains(".")) try { // is it a valid URL?
      (new java.net.URL(u)).openConnection();
      return u;
    } catch (Exception e) { }
    if ((new File(url)).exists()) return "file://" + url;
    // Ask Uncle Google for advice & service...
    return "https://www.google.com/search?client=webengine&q="+
           url.replace(" ", "+").replace("&","%26").
           replace("/","%2F").replace("\\","%5C").
           replace("^","%5E").replace("%","%25").
           replace("?","%3F").replace("=","%3D").
           replace("$","%24").replace(":","%3A").
           replace(".","%2E").replace(",","%2C")+
           "&sourceid=webengine&ie=UTF-8&oe=UTF-8";
  }
  //
  private String getSite(String url) {
    if (url != null) {
      int b = url.indexOf("//")+2;
      if (b < 2) b = 0;
      int e = url.indexOf("/", b);
      if (e < 0) e = url.length();
      String tag = url.substring(b, e);
      if (tag.startsWith("www.")) tag = tag.substring(4);
      return (tag.length() > 16)? tag.substring(0, 16):tag;
    }
    return null;
  }
  // is for Downloading ?
  private boolean forDownload(String url) {
    int p = url.lastIndexOf(".");
    if (p > 0) {
      String suf = url.substring(p);
      if (suf.equals(".exe") || suf.equals(".msi") ||
          suf.equals(".mp3") || suf.equals(".mp4") ||
          suf.equals(".deb") || suf.equals(".zip") ||
          suf.equals(".pdf") || suf.equals(".dmg") ||
          suf.equals(".tar") || suf.equals(".gz")) {
        DirectoryChooser dChooser = new DirectoryChooser();
        dChooser.setTitle("New Directory");
        dChooser.setInitialDirectory(new File(System.getProperty("user.dir")));
        String dir = dChooser.showDialog(stage).getAbsolutePath();
        ForkJoinPool.commonPool().submit(new WebDownload(pDload, url, dir));
        return true;
      }
    }
    return false;
  }
  // private CSS cssDef
  private void getCSS(Scene scene) {
    cssDef = System.getProperty("user.dir") + File.separator +
                 "D"+Math.abs((new Random()).nextInt())+".css";
    try (FileOutputStream fout = new FileOutputStream(cssDef, false)) {
      fout.write((".root { -fx-font-size: 11pt;  -fx-base: silver; }\n"+
            ".button {  -fx-background-color:   #c3c4c4,  linear-gradient(#d6d6d6 50%, white 100%),"+
            " radial-gradient(center 50% -40%, radius 200%, #e6e6e6 45%, rgba(230,230,230,0) 50%); "+
            " -fx-background-radius: 30; -fx-background-insets: 0,1,1; -fx-text-fill: black; "+
            " -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 3, 0.0 , 0 , 1 ); }\n"+
            ".button:hover { -fx-background-color: bisque; }\n"+
            ".text-field { -fx-background-color: snow; -fx-font-size: 14; }"+
            ".combo-box .list-cell { -fx-background:  #c3c4c4, linear-gradient(#d6d6d6 50%, white 100%),"+
            " radial-gradient(center 50% -40%, radius 200%, #e6e6e6 45%, rgba(230,230,230,0) 50%);"+
            " -fx-background-color: transparent; -fx-text-fill: black;"+
            " -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 3, 0.0 , 0 , 1 ); }\n"+
            ".combo-box-base:focused { -fx-background-radius: 7, 4, 5, 3; -fx-background-insets: -1, 0, 1, 2; }\n"+
            ".combo-box-popup .list-view .list-cell:hover {  -fx-text-fill: blue; }\n"+
            ".combo-box .cell:selected {  -fx-text-fill: red; }").getBytes());
      fout.flush();
      fout.close();
      scene.getStylesheets().add("file:///" + cssDef.replace("\\", "/"));
    } catch (Exception ex) { }
  }
  //
  private Stage stage;
  private TextField loc;
  private int Iold, Itab;
  private TabPane tabPane;
  private boolean cookies;
  private ArrayList<Tab> tList;
  private ArrayList<String> uList;
  private String home, DLoad, cssDef;
  private ArrayList<WebEngine> eList;
  private ProgressIndicator pInd, pDload;
}
browser_1.jpg

browser_2.jpg

To run this WebBrowser you have to download this following image: browser.jpg

(next: JavaFX with CSS and FXML)
 

Attachments

Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JavaFX with CSS -Cascading Style Sheets-

Hi

Three words: Cascading, Style and Sheets. What does each word mean?
  • Cascading: from to cascade and that means "falling step-wise" like a waterfall
  • Style: is a fashion, a way of presentation
  • Sheets: some pieces of paper.
Together they tell us the step-wise Styling sheet by sheet. In plain English for the newbies: Decoration using some styling sheets. And what does the CSS creator say? W3 says
Cascading Style Sheets (CSS) is a simple mechanism for adding style (e.g., fonts, colors, spacing) to Web documents. These pages contain information on how to learn and use CSS and on available software.
Every browser supports CSS. WebEngine, too. If you don't believe me you could try with the "WebBrowser" (in the previous section). There are many ways to CSS in JavaJFX:
  • internal with CSS line by line (inline) or a goup of lines.
  • external CSS file or line by line.
Before I go into some details I strongly recommend you to study this JFX-CSS Tutorial. It's old, but still valid and quite actual. And if you want to dig more you could consult this W3-CSS.

Internal CSS
Every JFX object can be decorated with CSS. It's only the question that how far you could do it. Every JFX object that has (or inherits) the method setStyle() can be "decorated" with an inline CSS. Example: JFX Button
PHP:
   Button but = new Button("START");
   but.setStyle("-fx-font-size: 13pt;"+
                "-fx-text-fill: green;"+
                "-fx-font-weight: bold;");
Explanation:
  • "-fx-font-size: 13pt;" this string says the Font will be 13 points. Each CSS expression must be terminated by a "semicolon" ( ; )
  • "-fx-text-fill: green;" indicates that the text gets the GREEN color
  • "-fx-font-weight: bold;" and the text is written in BOLD letters
Or the 3 strings can be grouped in a single line as following:
PHP:
   Button but = new Button("START");
   but.setStyle("-fx-font-size: 13pt; -fx-text-fill: green; -fx-font-weight: bold;");
External CSS
Maybe you have already anticipated that the "external CSS" is no other thing than a file that contains the CSS Strings. Yes it is. But under some syntactic rules. How do the rules look like you have to consult the above mentioned JFX-CSS Tutorial. Example:
PHP:
.button {
    -fx-font-size: 13pt;
    -fx-text-fill: green;
    -fx-font-weight: bold;
}
Explanation: the syntax .button { ... } signifies ALL JFX buttons used in the App. If you want to specify a specific Button you have to precede it with a HASH (#). Example:
PHP:
Button me = new myButton("ME");
me.setID("thisButton");  // <<---set an ID to me
PHP:
#thisButton {  /* <-- HASH with the ID set above */
    -fx-color:blue;
    -fx-padding:4px;
    -fx-background-color:#34c669;
    -fx-background-radius: 10px;
}
Enough for today. Let's start with a "view-able" example JavaFXCalculator that I have downloaded from the Web, modified it so that it could run with an external CSS file.
PHP:
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.ColumnConstraints;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
//
import java.util.List;
// Source: https://www3.ntu.edu.sg/home/ehchua/programming/java/Javafx1_intro.html
// modified by Joe Nartca for CongDongJava
// - to remove "warning: [rawtypes]...." and
// - to include CSS
public class JavaFXCalculator extends Application {
   // Start the JavaFXCalculator
   public void start(Stage primaryStage) {
      List<String> lst = getParameters().getRaw();
      String css = lst.size() > 0? lst.get(0):null;

      // Setup the Display TextField
      tfDisplay = new TextField("0");
      tfDisplay.setEditable(false);
      tfDisplay.setAlignment(Pos.CENTER_RIGHT);

      // Setup a GridPane for 4x4 Buttons
      int numCols = 4;
      int numRows = 4;
      GridPane paneButton = new GridPane();
      // insets(top, right, bottom, left)
      paneButton.setPadding(new Insets(10, 0, 5, 0));
      paneButton.setVgap(5);  // Vertical gap between nodes
      paneButton.setHgap(5);  // Horizontal gap between nodes
      // Setup 4 columns of equal width, fill parent
      ColumnConstraints[] columns = new ColumnConstraints[numCols];
      for (int i = 0; i < numCols; ++i) {
         columns[i] = new ColumnConstraints();
         columns[i].setHgrow(Priority.ALWAYS) ;  // Allow column to grow
         columns[i].setFillWidth(true);  // Ask nodes to fill space for column
         paneButton.getColumnConstraints().add(columns[i]);
      }

      // Setup 16 Buttons and add to GridPane; and event handler
      btns = new Button[16];
      for (int i = 0; i < btns.length; ++i) {
         btns[i] = new Button(btnLabels[i]);
         btns[i].setOnAction(handler);  // Register event handler
         btns[i].setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);  // full-width
         paneButton.add(btns[i], i % numCols, i / numCols);  // control, col, row
      }

      // setup the logo
      logo = new Label("CongDongJava");

      // Setup up the scene graph rooted at a BorderPane (of 5 zones)
      BorderPane root = new BorderPane();
      root.setAlignment(logo, Pos.BOTTOM_CENTER);
      // insets(top, right, bottom, left)
      root.setPadding(new Insets(10, 15, 0, 15));
      root.setTop(tfDisplay);     // Top zone contains the TextField
      root.setCenter(paneButton); // Center zone contains the GridPane of Buttons
      root.setBottom(logo);       // Botton zone contains the LOGO CongdongJava

      // get the CSS file and setup the CSS
      Scene scene = new Scene(root, 300, 230);
      // get the CSS file and setup the CSS
      Scene scene = new Scene(root, 300, 230);
      if (css != null) {
        java.net.URL url = getClass().getResource(css);
        if (url != null) scene.getStylesheets().add(url.toExternalForm());
      }
      // Set up scene and stage
      primaryStage.setScene(scene);
      primaryStage.setTitle("JavaFX Calculator");
      primaryStage.show();
   }
   //
   private Label logo;             // Logo label
   private TextField tfDisplay;    // display textfield
   private Button[] btns;          // 16 buttons
   private String[] btnLabels = {  // Labels of 16 buttons
      "7", "8", "9", "+",
      "4", "5", "6", "-",
      "1", "2", "3", "x",
      "C", "0", "=", "/"
   };
   // For computation
   private int result = 0;      // Result of computation
   private String inStr = "0";  // Input number as String
   // Previous operator: ' '(nothing), '+', '-', '*', '/', '='
   private char lastOperator = ' ';

   // Event handler for all the 16 Buttons
   EventHandler<ActionEvent> handler = evt -> {
      String currentBtnLabel = ((Button)evt.getSource()).getText();
      switch (currentBtnLabel) {
         // Number buttons
         case "0": case "1": case "2": case "3": case "4":
         case "5": case "6": case "7": case "8": case "9":
            if (inStr.equals("0")) {
               inStr = currentBtnLabel;  // no leading zero
            } else {
               inStr += currentBtnLabel; // append input digit
            }
            tfDisplay.setText(inStr);
            // Clear buffer if last operator is '='
            if (lastOperator == '=') {
               result = 0;
               lastOperator = ' ';
            }
            break;

         // Operator buttons: '+', '-', 'x', '/' and '='
         case "+":
            compute();
            lastOperator = '+';
            break;
         case "-":
            compute();
            lastOperator = '-';
            break;
         case "x":
            compute();
            lastOperator = '*';
            break;
         case "/":
            compute();
            lastOperator = '/';
            break;
         case "=":
            compute();
            lastOperator = '=';
            break;

         // Clear button
         case "C":
            result = 0;
            inStr = "0";
            lastOperator = ' ';
            tfDisplay.setText("0");
            break;
      }
   };
   // User pushes '+', '-', '*', '/' or '=' button.
   // Perform computation on the previous result and the current input number,
   // based on the previous operator.
   private void compute() {
      int inNum = Integer.parseInt(inStr);
      inStr = "0";
      if (lastOperator == ' ') {
         result = inNum;
      } else if (lastOperator == '+') {
         result += inNum;
      } else if (lastOperator == '-') {
         result -= inNum;
      } else if (lastOperator == '*') {
         result *= inNum;
      } else if (lastOperator == '/') {
         result /= inNum;
      } else if (lastOperator == '=') {
         // Keep the result for the next operation
      }
      tfDisplay.setText(result + "");
   }
}
The CSS file jfxCSS.css
Code:
.root{
    -fx-font-size: 11pt;
    -fx-font-family: Veranda;
    -fx-base: #DFB951; /*e.g. for scrollbar */
    -fx-background: #191970; /* Midnight blue */
    -fx-focus-color: #FF0000; /* RED for border of TextArea, etc.*/
}

.text-field {
    -fx-font-weight: bold;
    -fx-effect: innershadow(three-pass-box, pink, 12 , 0.5, 1, 1);
}

.label {
    -fx-font-family: Veranda;
    -fx-text-fill: #00ffff;  /* aqua blue */
    -fx-font-weight: bold;
    -fx-font-size: 15;
}

.button {
    -fx-background-color:
        linear-gradient(#f2f2f2, #d6d6d6),
        linear-gradient(#fcfcfc 0%, #d9d9d9 20%, #d6d6d6 100%),
        linear-gradient(#dddddd 0%, #f6f6f6 50%);
    -fx-background-radius: 8,7,6;
    -fx-background-insets: 0,1,2;
    -fx-font-size: 14;
    -fx-text-fill: darkred;
    -fx-font-weight: bold;
    -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.6), 5, 0.0 , 0 , 1 );
}
calculator.png

And the "Joe's animated Clock" with external CSS line:
PHP:
    List<String> lst = getParameters().getRaw();
    String css = (lst.size() > 0)?lst.get(0):"-fx-background-color: black";
    ...
    canvas.setStyle(css);
    ...
The invocation:
Code:
C\java\jfx>javaw Clock "-fx-background-color: darkblue"
clock.png

(next: JavaFX with FXML)
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JavaFX with FXML

Before I start with JavaFX - FXML I would like to give you an important note about JFX WebEngine. The example WebBrowser bases on the JFX WebEngine and it should run like any browser, but it won't in some cases with the weird hint "this browser is not supported" or so @-) The problem is NOT any bug of WebEngine, but the nature of the Web communication in HTTP. The HyperText Transfer Protocol requires for a connection an ID in UserAgent which is usually the most actual string "Mozilla 5.0.....". WebEngine's UserAgent is "obsolete" and some "evil" site makes a fuss out of this UserAgent.

How to amend the problem? Easy and simple with some extra work which I will show you later at the end of this tutorials. But some words about the "amendment": with a Proxy. WebBrowser (or WebEngine) should be diverted to a Proxy which intercepts the HTTP stream and corrects the string "UserAgent" to the actual UserAgent used by FireFox or Chrome or Opera before it passes this HTTP request to the Web. And the "evil" site will be blindsided and accepts the HTTP request (see following picture).

Mozilla.png

Now, let's go with JFX - FXML. When people "talk" about FXML they have in mind the most modern Application Design and Implementation: the Model-View-Controller or MVC in short. Sping Boot is the most widespread applied MVC package (see, for example, the tutorials of Admin quydtkt), and I have briefly talked about this theme in

- Model View Controller With S W I N G And J F X
- Model-view-controller With Pure Java S W I N G as a simple MVC implementation of MVC in Java SWING.

Nevertheless JavaFX - FXML is an ample, extensive application which bases on three components:
  • JavaFX or JFX
  • XML-based FXLLoader
  • W3-based CSS
In the previous sections I have talked about the basics and the essentials of JavaFX (JFX objects, JFX threads and Synchronization) and CSS (internal/external CSS styling). Today I give you some insight about JFX - MVC with FXML. As mentioned above FXML bases on XML and is used to describe a JFX model with JFX objects in XML Syntax. The conventional rules are relatively simple:
  • JFX Objects are XML elements
  • Attributes of a JFX Object are used like the attributes of a XML element
  • File suffix is .fxml (dot fxml)
Example:
TicTacToe.png

For the above image there are lots of way to "model" the structure in FXML. Some prefer the ArrayList<Button> (e.g. ArrayList[3][3]) and then use the keyword "fx : define" to build an array of buttons as following:
Code:
   <fx:define>
      <!-- create buttons and store them in a list -->
      <ArrayList fx:id="array">
         <Button fx:id="Button_0_0" /> //first row , first column
         <Button fx:id="Button_0_1" />
         <Button fx:id="Button_0_2" />
         <ArrayList fx:id="buttonsArray">
            <Button fx:id="Button_1_0" /> //second row , first column
            <Button fx:id="Button_1_1" />
            <Button fx:id="Button_1_2" />
                    ...
         </ArrayList>
      </ArrayList>
   </fx:define>
   <! now declare the 9 defined buttons -->
      ...
And then each button must declared and assigned (e.g. with events, etc.)

I prefer the Kalashnikov's way: simple and useful. A combination of HBox and VBox for 9 buttons. It is clearer and easier to work with. Further, I can directly include the JFX events in my declarations. And the HBox-VBox fxml is as following:
Code:
   <VBox alignment="center" spacing="5" >
      <HBox spacing="5" >
        <padding><Insets  right="5" left="10"/></padding>
        <children>
          <Button fx:id="b00" onAction="#actionMove"
                  prefHeight="85.0" prefWidth="85.0" />
          <Button fx:id="b01" onAction="#actionMove"
                  prefHeight="85.0" prefWidth="85.0" />
          <Button fx:id="b02" onAction="#actionMove"
                  prefHeight="85.0" prefWidth="85.0" />
        </children>
      </HBox>
      <HBox spacing="5" >
            ...
      </HBox>
      <HBox spacing="5" >
            ...
      </HBox>
   </VBox>
As you see, one stone a lot of birds :D Now we design and implement the "TicTacToe" with "AI AlphaMinMax Algorithm". First, the main part: TicTactoe.java
PHP:
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.fxml.*;
import javafx.scene.layout.*;
// Joe Nartca (c)
public class TicTacToe extends Application {
  public void start(Stage stage) throws Exception {
    stage.setTitle("TicTacToe with AI MiniMax");
    java.util.List<String> lst = getParameters().getRaw();
    String fxml = lst.size() > 0? lst.get(0):"VBox_tictactoe.fxml";
    // invoking FXMLLoader to load the MODEL and to instantiate
    // the Controller TTTController
    FXMLLoader loader = new FXMLLoader(getClass().getResource(fxml));
    VBox root = (VBox)loader.load();
    // set the Scene and go on Stage
    stage.setScene(new Scene(root));
    stage.show();
  }
  // for safety
  public void stop() {
    Platform.exit();
  }
}
Explanation:
  • The usual loading external parameter with List<String> with the default "VBox_tictactoe.fxml"
  • FXMLLoader and retrieve the instantiated VBox (as root which is identified in the CSS file as .root)
  • The usual Hollywood show: Scene, then Stage

Then we start to design the MODEL VBox_tictactoe.fxml. Before you start to study the codes you should firstly study this tutorial JavaFX FXML Tutorial or this tutorial.
Code:
<?xml version="1.0" encoding="UTF-8"?>
<?import java.net.*?>
<?import java.util.*?>
<?import java.lang.*?>
<?import javafx.scene.*?>
<?import javafx.event.*?>
<?import javafx.geometry.*?>
<?import javafx.scene.text.*?>
<?import javafx.scene.paint.*?>
<?import javafx.collections.*?>
<?import javafx.scene.media.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>

<VBox id="VBox" fx:id="root" alignment="center" spacing="5"
    maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
    prefHeight="520.0" prefWidth="325.0" xmlns:fx="http://joenartca.com/fxml"
    fx:controller="TTTController">
        <padding><Insets top="10" right="20" bottom="20" left="20"/></padding>
        <Label fx:id="aLabel" layoutX="5.0" layoutY="10.0" text = "JavaFX MVC_TicTacToe (c)"/>
        <HBox spacing="5" >
              <padding><Insets  right="5" left="10"/></padding>
                <Button fx:id="b00" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b01" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b02" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
        </HBox>
        <HBox spacing="5" >
              <padding><Insets  right="5" left="10"/></padding>
                <Button fx:id="b10" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b11" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b12" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
        </HBox>
        <HBox spacing="5" >
              <padding><Insets  right="5" left="10" bottom="20" /></padding>
                <Button fx:id="b20" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b21" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
                <Button fx:id="b22" onAction="#actionMove"
                        prefHeight="85.0" prefWidth="85.0" />
        </HBox>
        <TextArea fx:id="report" layoutX="15.0" layoutY="30.0" prefHeight="100.0" prefWidth="280.0"
            wrapText="true" editable="false" text="You start..."/>
        <HBox alignment="center" spacing="10" >
           <padding><Insets top="20" right="5" bottom="10" left="5"/></padding>
           <Button fx:id="newGame" onAction="#actionNew" text="newGame"
                   prefHeight="40.0" prefWidth="120.0" />
        </HBox>
   <stylesheets>
       <URL value="@joe.css" />
   </stylesheets>
</VBox>
Explanation:
  • line <VBox id="VBox" fx:id="root" ... fx:controller="TTTController"> declares the VBox used in TictacToe as "root" which is referenced in CSS as ".root" and all the attributes (e.g. height, width, etc.) and the name of Controller (TTTController). Note: each reference always starts with "fx:Reference=StringName" (e.g. fx:controller="TTTController")
  • <padding><Insets top="10" right="20" bottom="20" left="20"/></padding> the insets proportion
  • If you use a "pane" (e.g. AnchorPane) this line will be the <children>. Because we use VBox hence this line starts with a JFX object: Label "JavaFX MVC_TicTacToe (c)"
  • line <HBox ... </HBox> defines the layout of its content: 3 Buttons
  • line <padding><Insets ...>/></padding> see above
  • line <Button fx:id="b00" onAction="#actionMove" ... /> depicts the JFX Button with is reference ID (b00 ... b22) and the action #actionMove in case of event (onAction). The reference "avtionMove" is the declared method materialized in the Controller (TTTController)
  • ...
  • line <stylesheets> ... </stylesheets> indicates the file where the stylesheets are stored (joe.css)
With the MODEL VBox_tictactoe.fml we move to the next implementation: the Controller TTTControler.java
PHP:
import java.net.URL;
import java.util.*;
import javafx.application.*;
import javafx.scene.*;
import javafx.stage.*;
import javafx.fxml.*;
import javafx.scene.layout.*;
import javafx.scene.image.*;
import javafx.scene.paint.Color;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.geometry.*;
import javafx.scene.control.*;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
// Joe Nartca (c)
public class TTTController {//implements Initializable {
    @FXML HBox bBox;
    @FXML Label aLabel;
    @FXML TextArea report;
    @FXML Button newGame, b00, b01, b02, b10, b11, b12, b20, b21, b22;

    private Game game;
    private int gCon, pNo;
    private boolean endGame;
    private ArrayList<Button> but;
    // a mandatory implementation
    //public void initialize(URL location, ResourceBundle resources) {
    @FXML  public void initialize() {
        endGame = false;
        setBoard();
        play();
    }
    // fill the ButtonList
    public void setBoard() {
        but = new ArrayList<Button>();
        but.add(b00); but.add(b01); but.add(b02);
        but.add(b10); but.add(b11); but.add(b12);
        but.add(b20); but.add(b21); but.add(b22);
    }
    //
    public void exit() {
        Platform.exit();
    }
    //
    public void play( ) {
        newGame.setDisable(true);
        game = new Game( );
        endGame = false;
        gCon = 0;
        pNo = 0;
    }
    //
    @FXML public void actionMove(ActionEvent ev) {  
        if (endGame) return;
        Button go = (Button)ev.getSource();
        if (go.getGraphic() != null) return; // wrong MOVE
        int row = getRow(go);
        int col = getCol(go);
        for (int i = 0; i < 2; ++i) {
            // AI move
            if (i == 1) {
                int nxt = game.ai.nextMove(game.gBoard);
                col = nxt % 3;
                row = nxt / 3;
            }
            gCon = game.move(3*row + col);
            pNo = (pNo == 1)? 2 : 1;
            if (pNo == 1) report.appendText("\nYou ");
            else report.appendText("\nAI ");
            report.appendText("played on Button["+(1+row)+", "+(1+col)+"]");
            game.gBoard.paintBoard(but);
            if (gCon != 0) {
                if (gCon == 1 || gCon == 2) game.win(pNo);
                int ai = game.score(2);
                int you = game.score(1);
                if (you > 0) report.appendText("\nYou win.");
                if (ai > 0) report.appendText("\nAI wins.");
                if (you == ai) report.appendText("\nYou're as good as AI.");
                report.appendText("\nAgain? Click \"newGame\" and start to play...");
                newGame.setDisable(false);
                game.cleanUp( );
                endGame = true;
                break;
            }
        }
        Platform.runLater(() -> {
           report.positionCaret(report.getText().length());
        });
    }
    //
    @FXML public void actionNew( ) {
        b00.setGraphic(null); b01.setGraphic(null); b02.setGraphic(null);
        b10.setGraphic(null); b11.setGraphic(null); b12.setGraphic(null);
        b20.setGraphic(null); b21.setGraphic(null); b22.setGraphic(null);
        play();
    }
    //
    private int getRow(Button b) {
        int idx = but.indexOf(b);
        if (idx >= 0 && idx <= 2) return 0;
        if (idx >= 3 && idx <= 5)  return 1;
        return 2;  
    }
    //
    private int getCol(Button b) {
        int idx = but.indexOf(b);
        if (idx == 0 || idx == 3 || idx == 6) return 0;
        if (idx == 1 || idx == 4 || idx == 7) return 1;
        return 2;
    }
}
Explanation:
  • line starting with @FXML refers to the declared JFX object in the MODEL and it should be instantiated or initialized before it could be used elsewhere (e.g. in the start() or any external module). Example: @FXML Label aLabel is in the MODEL: fx:id="aLabel"
  • @FXML public void initialize() indicates the initialization of JFX objects before the MODEL could start. This default method can be either omitted (if nothing to initialize) OR declared as private (safer than public)
  • @FXML public void actionMove(ActionEvent ev) is the action in case of event. In "normal" JFX application it is the Button.setOnAction(...). The MODEL declaration is shortened to "onAction"
The final outcome is:

TicTacToe_2.png

That's all about the JFX Basics and Essentials. Hope to see you in the the next tutorials.

To run the TicTacToe you have to download the mvc.zip for the complete package.
 

Attachments

Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
2,732
1,255
113
JavaFX WebEngine for WebAccess.

As I promised in the last tutorial of "JavaFX or JFX -The Basics and The Essentials-" to explain to you about the weird rejection of some sites against WebEngine. It's about the keyword "User-Agent" of HTTP sent by WebEngine to the Web.

An important note: you should be aware about one thing: it works only with INSECURE sites. Meaning that a site that starts with "https://...." is a secured site and it requires a Public Key to decrypt the sending request. If you don't have the public key you cannot fool this site because you cannot replace the fake User-Agent String with yours.


Normally a browser (WebEngine) sends such a Request to a site, for example Congdongjava.com, as following:
Code:
Header:
GET / HTTP/1.1
Host: congdongjava.com
Connection: keep-alive
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,de;q=0.8,fr;q=0.7,vi;q=0.6
Some sites, probably developed by fearful amateurs, verify their clients by checking for the User-Agent after the "actual" status of the browser and then make a fuss out of the result. Why the sites care about their client browser it is for me a inexplainable riddle. The reason of my wonder is that the term "User-Agent" reveals only the client OS (e.g. Windows NT 10.0; Win64; x64) and the browser origin (e.g. Chrome/80.0.3987.122) and the rest is more or less irrelevant. Probably the "amateurish" developers want to revenge their employer by shooing away his customers (or clients). Whatever it is it's a real riddle.

OKAY, if they shoo me away I blindside them with my fake User-Agent. Tit for tat, isn't it? How? First of all we design a little Proxy Server.
PHP:
// Joe T. Nartca (C)
public class JoeProxy extends Application {
  private int port = 10000;
  public void start(Stage stage) {
    ...
    Label lab = new Label("Port:");
    TextField tf = new TextField("10000");
    //
    Button start = new Button("START");
    start.setOnAction(e -> {
      String p = tf.getText();
      if (p.length() > 0) port = Integer.parseInt(p);
      new Proxy(port).start();
      start.setDisable(true);
      tf.setEditable(false);
    });  
    HBox hbox = new HBox(5);
    hbox.setPadding(new Insets(5, 5, 5, 5));
    hbox.getChildren().addAll(lab, tf, start);

    Scene scene = new Scene(hbox, 250, 100, Color.ANTIQUEWHITE);
    stage.setOnCloseRequest(e -> {
      try { // close JoeProxy
        if (ss != null) ss.close();
      } catch (Exception ex) { }
    });
    stage.setResizable(false);
    stage.setScene(scene);
    stage.show();
  }
  public void stop() {
    Platform.exit();
    System.exit(0);
  }
  ...
  private ServerSocketChannel ss;
  private class Proxy extends Thread {
    public Proxy(int port) {
      this.port = port;
    }
    private int port;
    private ExecutorService exe;
    public void run() {
      try {
        ... 
        ss = ServerSocketChannel.open();
        ss.socket().bind(new InetSocketAddress(port));
        while(true) exe.execute((new Service(ss.accept())));
      } catch (Exception e) {
        System.exit(0);
      }
     }
     private class Service implements Runnable {

      private String userAgent =
        ("User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 "+
        "(KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36");
      private SocketChannel webSoc, soc;  
      public Service(SocketChannel soc) {
        this.soc = soc;
      }
      public void run() {
         ...

      }
      ...
    }
    ...
  }
}
Note: the full source can be downloaded hereunder.

JoeProxy.png

Then we have to "develop" a ProxySetting for our WebBrowser with the API ProxySelector with a little efford:
PHP:
// Joe Nartca (C)
public class WebProxy extends ProxySelector {
    /**
    Constructor
    @param IP    String, IP or ProxyHostname
    @param port  int, the port number
    @param type  int, 0: HTTP, 1: SOCKS)
    */
    public WebProxy(String IP, int port, int type) {
      list.add(type > 0? new Proxy(Proxy.Type.SOCKS, new InetSocketAddress(IP, port)):
                         new Proxy(Proxy.Type.HTTP, new InetSocketAddress(IP, port)));
    }
    /**
    @param uri URI of slected Proxy
    @return list List of Proxy
    */
    public List<Proxy> select(URI uri) {
      return list;
    }
    //
    public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { }
    private ArrayList<Proxy> list = new ArrayList<Proxy>();
}
With the "WebProxy" we can now "plug-in" our WebBrowser as following:
PHP:
public class WebBrowser extends Application {
  public void start(Stage stage) {
    ...
    eList = new ArrayList<WebEngine>();
    ArrayList<String> hTabs = new ArrayList<String>();
    //
    // set Proxy: localhost, port: 30751, type: HTTP
    //
    ProxySelector.setDefault(new WebProxy("localhost", 30751, 0)); // <<---Plug-In
    //
    pDload = new ProgressIndicator();
    pInd = new ProgressIndicator();
    ...
  }
  ...
}
The joe.zip caintains the following sources:
  • JoeProxy.java: the little ProxyServer
  • WebProxy.java: the Proxy-Setting for customized browser
  • WebBrowser.java: the browser with WebEngine (see Tutorial)
  • WebDownload.java: the downloading part (see Tutorial)
Joe
 

Attachments

Sửa lần cuối: