HƯỚNG DẪN CSS for JFX app and its POP-UP Dialogs

Joe

Thành viên VIP
21/1/13
3,068
1,343
113
Hi,

JFX app has already a quite "appetizing" appearance as you see in this Example JFXCalculator

1657889466765.png

But JavaFX is purposed for Desktop application and that means the appearance of a JFX app can be beautified (or customized) with CSS elements (CSS: Cascading Style Sheets). I show you how to beautify the above-mentioned example JFXCalculator with the following CSS file using JavaFX Reference Guide

calculator.css
Java:
/* Joe Nartca (C)                             */
/* root: see declaration in JFXCalculator app */
.root {
    -fx-font-size: 10pt;
    -fx-font-family: "Times";
    -fx-base: CORNSILK;
}
/* .label */
.label {
    -fx-font-family: "Veranda";
    -fx-text-fill: black;
    -fx-font-size: 11pt;;
}
/* .text-field */
.text-field {
    -fx-text-fill: black;
    -fx-background-color: silver;
    -fx-font-family: "Veranda";
    -fx-font-size: 13;
}
.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 );
}
.button:hover {
    -fx-background-color: bisque;
}
Of course, you have to modify slightly the JFXCalculator as following (see the modification):
  1. modification 1: adding the 3rd parameter for CSS file
  2. modification 2: add the style sheets to Scene
  3. modification 3: add the style sheets to the Alert dialog. To do that we have to load the CSS file into its DialogPane using the method getDialogPane()
Java:
// Joe Nartca (C)
public class JFXCalculator extends Application {
   // Start the JavaFXCalculator
   public void start(Stage primaryStage) {
      java.util.List<String> lst = getParameters().getRaw();
      x = 0d; y = 0d;

      // Modification 1 for using CSS: include thte 3rd parameter
      if (lst.size() > 1) {
        x = Double.parseDouble(lst.get(0));
        y = Double.parseDouble(lst.get(1));
        if (lst.size() > 2) {
          java.net.URL u = getClass().getResource(lst.get(2));
          if (u != null)  url = u.toExternalForm();
        }
      } else if (lst.size() == 1) {
        java.net.URL u = getClass().getResource(lst.get(0));
        if (u != null)  url = u.toExternalForm();
      }
      // end modification 1

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

      // Setup a GridPane for 4x4 Buttons
      int numCols = 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]);
      }
      result = 0d;
      // Setup 16 Buttons and add to GridPane; and event handler
      Button[] btns = new Button[16];
      for (int i = 0; i < btns.length; ++i) {
         btns[i] = new Button(btnLabels[i]);
         btns[i].setOnAction(evt -> {
            if (clear) cleanUp();
            char b = ((Button)evt.getSource()).getText().charAt(0);
            tfDisplay.appendText(""+b);
            dField = tfDisplay.getText();
            pos = dField.length();
            switch (b) {
               case '+': case '-': case '*': case '/':
                  if (dField.length() == 1 || beg == pos) {
                    tfDisplay.setText("");
                    return;
                  }
                  if (isDuplicate( )) {
                    cleanUp();
                    return;
                  } else if (!compute(dField.substring(beg, pos-1))) {
                    tfDisplay.appendText(": Exception");
                    clear = true;
                  }
                  beg = pos;
                  op = b;
                  return;
               case '=':
                  if (dField.length() == 1) {
                    tfDisplay.setText("");
                    return;
                  }
                  if (compute(dField.substring(beg, pos-1))) tfDisplay.appendText(""+result);
                  else tfDisplay.appendText("Exception");
                  clear = true;
                  return;
               case 'C':
                  cleanUp();
                  return;
               default:
                 if (beg > 0) return;
                 if (pos > 1) { // remove leading 0
                   while (dField.charAt(0) == '0') dField = dField.substring(1);
                   tfDisplay.setText(dField);
                 }
                 return;
            }
         });
         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
      Label logo = new Label("CongDongJava");
      // Setup up the scene graph rooted at a BorderPane (of 5 zones)
      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

      Scene scene = new Scene(root, 320, 230);

      // Modification 2: adding the CSS style to scene
      if (url != null) scene.getStylesheets().add(url);
      // end moddification 2

      primaryStage.setScene(scene);
      primaryStage.setTitle("JFX Calculator - Joe Nartca -");
      primaryStage.show();
   }
   //
   private boolean isDuplicate( ) {
     int p = dField.length()-1;
     char op1 = dField.charAt(p-1), op2 = dField.charAt(p);
     boolean boo = ((op1 == '+' || op1 == '-' || op1 == '*' || op1 == '/') &&
                    (op2 == '+' || op2 == '-' || op2 == '*' || op2 == '/'));
     if (boo) warning("Invalid operations: "+dField);
     return boo;
   }
   //
   private boolean compute(String s) {
     int iOp = Integer.parseInt(s);
     if (op == ' ') result = iOp;
     else if (op == '+') result += iOp;
     else if (op == '-') result -= iOp;
     else if (op == '*') result *= iOp;
     else if (op == '/') {
       if (iOp == 0) {
         warning("Divide by ZERO: "+dField.substring(0, dField.length()-1));
         return false;
       } else result /= iOp;
     }
     return true;
   }
   //
   private void warning(String msg) {
     Alert alert = new Alert(AlertType.ERROR);
     if (x > 0d && y > 0d) {
        Bounds pos = root.localToScreen(root.getBoundsInLocal());
        alert.setX(pos.getMinX() + pos.getWidth() / x);
        alert.setY(pos.getMinY() + pos.getHeight() / y);
     }
     // modification 3: add style to dialog
     //DialogPane diaPane = alert.getDialogPane();
     //if (url != null) diaPane.getStylesheets().add(url);
     // end modification 3

     alert.setContentText(msg);
     alert.showAndWait(); // wait...
   }
   //
   private void cleanUp() {
     tfDisplay.setText("");
     clear = false;
     result = 0d;
     op = ' ';
     beg = 0;
     pos = 0;
   }
   //
   private String[] btnLabels = {  // Labels of 16 buttons
      "7", "8", "9", "+",
      "4", "5", "6", "-",
      "1", "2", "3", "*",
      "0", "C", "=", "/"
   };
   //
   private boolean clear = false;
   private TextField tfDisplay;
   private double result, x, y;
   private String dField, url;
   private BorderPane root;
   private char op = ' ';
   private int pos, beg;
}
First of all we comment the modification 3 and see how the Alert Dialog reacts...
1657890696518.png

The result is visible: JFXapp gets the new style, BUT the Alert Dialog stays with its default. Now we UNcomment the 3rd modification
Java:
   private void warning(String msg) {
     Alert alert = new Alert(AlertType.ERROR);
     if (x > 0d && y > 0d) {
        Bounds pos = root.localToScreen(root.getBoundsInLocal());
        alert.setX(pos.getMinX() + pos.getWidth() / x);
        alert.setY(pos.getMinY() + pos.getHeight() / y);
     }
     // Modification 3: add style to dialog
     DialogPane diaPane = alert.getDialogPane();
     if (url != null) diaPane.getStylesheets().add(url);
     // end modification 3

     alert.setContentText(msg);
     alert.showAndWait(); // wait...
   }
and recompile the codes and run the app
1657891079666.png

As you see, a little work for a big show of beauty...=))
Joe
 
Sửa lần cuối:

Joe

Thành viên VIP
21/1/13
3,068
1,343
113
Nevertheless, if you want to have a different CSS style for the popup dialogs you need only to tell the DialogPane where to take the CSS style. Let's modify the 3rd modification for the (Alert) Dialog (see the comment "look at this "popup" tag and take the style"):
Java:
   private void warning(String msg) {
     Alert alert = new Alert(AlertType.ERROR);
     if (x > 0d && y > 0d) {
        Bounds pos = root.localToScreen(root.getBoundsInLocal());
        alert.setX(pos.getMinX() + pos.getWidth() / x);
        alert.setY(pos.getMinY() + pos.getHeight() / y);
     }
     // Modification 3: add style to dialog
     DialogPane diaPane = alert.getDialogPane();
     if (url != null) {
       diaPane.getStylesheets().add(url);
       diaPane.getStyleClass().add("popup");   // <-- look at this "popup" tag and take the style
     }
     // end modification 3

     alert.setContentText(msg);
     alert.showAndWait(); // wait...
   }
and the calculator.css includes the expansion of the popup styles
PHP:
/* Joe Nartca (C)                                            */
.root {
    -fx-font-size: 10pt;
    -fx-font-family: "Veranda";
    -fx-base: CORNSILK;
}
....
.button:hover {
    -fx-background-color: bisque;
}
/* for the Dialog */
.popup {
    -fx-background-color: azure;
}

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

.popup .label {
    -fx-font-family: Veranda;
    -fx-text-fill: darkblue;
    -fx-font-weight: bold;
    -fx-font-size: 15;
}

.popup .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 );
}
...and you get this beautiful Dialog popup with different background color, text color and button shape.

1657955885204.png
 

Joe

Thành viên VIP
21/1/13
3,068
1,343
113
The other CSS-Styling possibility is to "hard-code" the CSS instructions into JFX components which base on the JavaFX Node. To do that you can circumvent a CSS file and work directly with the JFX components. The class Node provides the methods
Java:
ObservableList<String>    getStyleClass() ; // get the Styling class
void setStyle(String value);  // set the CSS instruction (here: value)
they allow you to fill the ObservableList<String> with CSS instructions like in the CSS file, or to set directly a chain of CSS instructions to the JFX component. For example: the Alert Dialog with CSS file using getStyleClass() and setStyle():
Java:
/* CSS file */
.popup .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 );
}
and its CSS loading
Java:
   private void warning(String msg) {
     Alert alert = new Alert(AlertType.ERROR);
     if (x > 0d && y > 0d) {
        Bounds pos = root.localToScreen(root.getBoundsInLocal());
        alert.setX(pos.getMinX() + pos.getWidth() / x);
        alert.setY(pos.getMinY() + pos.getHeight() / y);
     }
     // Modification 3: add style to dialog
     DialogPane diaPane = alert.getDialogPane();
     if (url != null) {
       diaPane.getStylesheets().add(url);
       diaPane.getStyleClass().add("popup");   // <-- look at this "popup" tag and take the style
     }
     // end modification 3

     alert.setContentText(msg);
     alert.showAndWait(); // wait...
   }
and the display is
1658044142675.png

Now we want to OVERWRITE the ".popup .button { .... }" for the Alert-Button with our hard-coding CSS instructions using the Node provided method setStyle() as following:
Java:
   private void warning(String msg) {
     Alert alert = new Alert(AlertType.ERROR);
     if (x > 0d && y > 0d) {
        Bounds pos = root.localToScreen(root.getBoundsInLocal());
        alert.setX(pos.getMinX() + pos.getWidth() / x);
        alert.setY(pos.getMinY() + pos.getHeight() / y);
     }
     DialogPane diaPane = alert.getDialogPane();
     if (url != null) {
       diaPane.getStylesheets().add(url);
       diaPane.getStyleClass().add("popup");  // <-- either is overwritten OR can be dropped (or commented)
   
       // extract the Alert Button and (over)write the CSS style with our
       // hard-coding CSS instruction: rounded shape button with bold green text
       Button but = (Button) diaPane.lookupButton(ButtonType.OK);
       but.setStyle(" -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: green;"+
                    "  -fx-font-weight: bold;"+
                    " -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 3, 0.0 , 0 , 1 );"
                   );

     }
     alert.setContentText(msg);
     alert.showAndWait(); // wait...
   }
...and the display is as expected a rounding button with bold green text

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