top

Hello JavaFX! Part 2

Hello JavaFX! Part 1 << 前情

使用Java技術設計一個桌面應用程式的使用者介面,以前都是使用Swing和AWT提供的套件,或許還會搭配一些額外的套件,來加強畫面的設計。在JavaFX 2發佈以後,這些工作都可以使用它來代替舊有的套件,而且有更多的元件和更好的效率。

不論使用哪一種技術,對Java技術開發人員來說,使用者介面的設計方式,應該就會是一大堆的程式碼,尤其是比較複雜一點的應用程式畫面,光是建立與設定各種畫面元件的程式碼,還真的是一大堆,程式碼一多了,對開發人員來說,都是一個很沉重的負擔。

現在的使用者對應用程式畫面的需求是非常高的,除了功能性、效率都要很好以外,畫面最好也要很好看。現在也很流行一種畫面的設計,同樣的應用程式,不同的使用者登入以後,會有不一樣的畫面樣式,可能是顏色或字體,甚至是畫面的排列都可以呈現不同的外觀。對Java應用程式來說,要完成這樣的需求,也不是做出來,只不過應該會讓開發人員更加辛苦了。

JavaFX技術在初期的時候,就已經採用網頁設計已經很普遍應用的CSS技術;到了JavaFX 2,也把CSS技術套用使用者介面設計,對Java技術開發人員來說,接下來的內容,會帶你進入這個全新的設計方式。

JavaFX CSS

先在NetBeans中建立一個JavaFX應用程式,名稱可以取為「SignInCSS」,把主程式「SignInCSS.java」的「start」方法改成下面的樣子:

GridPane grid = new GridPane();
grid.setAlignment(Pos.CENTER);
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(25, 25, 25, 25));

Text scenetitle = new Text("Welcome");
grid.add(scenetitle, 0, 0, 2, 1);
Label userName = new Label("User Name");
grid.add(userName, 0, 1);
final TextField userTextField = new TextField();
grid.add(userTextField, 1, 1);
Label pw = new Label("Password");
grid.add(pw, 0, 2);
final PasswordField passwordField = new PasswordField();
grid.add(passwordField, 1, 2);
Button btn = new Button("Sign in");
HBox hbBtn = new HBox(10);
hbBtn.setAlignment(Pos.BOTTOM_RIGHT);
hbBtn.getChildren().add(btn);
grid.add(hbBtn, 1, 4);
final Text actiontarget = new Text();
grid.add(actiontarget, 1, 6);

btn.setOnAction(new EventHandler() {
    @Override
    public void handle(ActionEvent e) {
        String userNameValue = userTextField.getText();
        String passwordValue = passwordField.getText();
        String result = "Incorrect!";
        if (userNameValue.equals("simon") && passwordValue.equals("javafx") ) {
            result = "Welcome!";
        }
        actiontarget.setFill(Color.FIREBRICK);
        actiontarget.setText(result);
    }
});

Scene scene = new Scene(grid, 300, 275);
primaryStage.setScene(scene);
primaryStage.show();

執行這個應用程式,你會看到一個可以讓使用者執行登入工作的畫面;接下來的工作,就是要使用JavaFX CSS的技術,把原來的畫面改成像右圖的樣子:
hello-javafx-part2-1hello-javafx-part2-2

建立JavaFX CSS檔案

JavaFX CSS可以讓你為畫面指定一個CSS檔案,在檔案中設定的樣式,都會套用在畫面的元件,這樣就可以不用在程式碼中執行設定元件的工作了。而且只要修改CSS檔案,就可以改變畫面的外觀。你可以按照下面的步驟,為應用程式建立一個CSS檔案:

  1. 在專案的主要套件目錄「signincss」上點選滑鼠右鍵,選擇「New -> Other…」
  2. 在新增檔案的對畫框,選擇「Other」目錄後,選擇「Cascading Style Sheet」,然後選擇「Next」
  3. 輸入檔案名稱「SignIn.css」後選擇「Finish」

建立好需要的CSS檔案後,回到主程式中,在「primaryStage.show();」敘述前加入下面的程式:

scene.getStylesheets().add(SignInCSS.class.getResource("SignIn.css").toExternalForm());

這一行程式碼為「Scene」物件指定一個名稱為「SignIn.css」的CSS資源檔案。使用「Scene」物件呼叫「getStylesheets」方法,傳回的物件是「ObservableList」,它可以包含多個連結到樣式資源的檔案。

設定背景圖片

為應用程式的畫面設定背景,可以讓畫面上的元件更加清楚,如果設定好看一點的背景圖片,那整個應用程式畫面就更美觀了。你可以在「background.png」上按滑鼠右鍵,然後把背景圖片檔案儲存到專案的「signincss」套件中。

開啟專案中的「SignIn.css」檔案,把內容改為下面的樣子:

.root {
     -fx-background-image: url("background.png");
}

背景圖片的設定是套用在「.root」樣式,這表示裡面的設定會套用在Scene中最頂端的「root」容器元件,在這個範例中,指的是「GridPane」容器;目前在「.root」樣式中只有執行背景圖片的設定,JavaFX CSS的設定名稱,最前面都會加上「-fx」。執行應用程式,看看出現的畫面,如果一切正確的話,畫面應該會有背景圖片了。

設定文字樣式

JavaFx中的元件都可以透過CSS為它們指定樣式,接下來為這個畫面中的Label和Text元件加入樣式的設定。開啟「SignIn.css」檔案,加入下面的內容:

.label {
    -fx-font-size: 12px;
    -fx-font-weight: bold;
    -fx-text-fill: #333333;
    -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );
}

加入的樣式會設定所有文字的大小、字體與顏色,也加入陰影的效果,讓文字看起來比較立體一些。

除了設定元件共同的樣式外,你也可以設定某一個特定元件使用的樣式,開啟「SignIn.css」檔案,加入下面的內容:

#welcome-text {
   -fx-font-size: 32px;
   -fx-font-family: "Arial Black";
   -fx-fill: #818181;
   -fx-effect: innershadow( three-pass-box , rgba(0,0,0,0.7) , 6, 0.0 , 0 , 2 );
}

#actiontarget {
  -fx-fill: FIREBRICK;
  -fx-font-weight: bold;
  -fx-effect: dropshadow( gaussian , rgba(255,255,255,0.5) , 0,0,0,1 );  
}

為了讓歡迎的訊息文字可以擁有自己特別的樣式,所以加入一個名稱為「welcome-text」的樣式,把文字加大一些,也設定了比較不一樣的效果;顯示登入成功或失敗訊息的文字,也為它加入名稱為「actiontarget」的樣式,讓它可以有自己特殊的外觀。加入這兩個元件專用的樣式以後,就要在程式碼中執行相關的設定,開啟主程式,在「primaryStage.show();」敘述前加入下面的程式:

scenetitle.setId("welcome-text");
actiontarget.setId("actiontarget");

JavaFX元件物件的「setId」方法,用來指定元件使用的特殊樣式名稱,顯示歡迎訊息的元件設定為「welcome-text」,顯示登入結果的元件設定為「actiontarget」,這些名稱是在之前加入CSS檔案中的設定。執行應用程式,看看出現的畫面,如果一切正確的話,文字的樣式應該跟原來不一樣了。

設定按鈕樣式

最後再為按鈕設定樣式,讓它比較好看一些,在使用者操作時也可以即時的反應。開啟「SignIn.css」檔案,加入下面的內容:

.button {
    -fx-text-fill: white;
    -fx-font-family: "Arial Narrow";
    -fx-font-weight: bold;
    -fx-background-color: linear-gradient(#61a2b1, #2A5058);
    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.6) , 5, 0.0 , 0 , 1 );
}

.button:hover {
    -fx-background-color: linear-gradient(#2A5058, #61a2b1);
}

加入的內容包含給所有按鈕使用的兩個設定,「.button」用來設定按鈕基本的外觀,包含按鈕文字的樣式,還有按鈕填充的顏色設定為線性的漸層填充,也為它加入陰影的效果;「.button:hover」用來設定使用者把滑鼠游標移到按鈕時的樣式,這裡只有設定按鈕填充的顏色。執行應用程式,應該會是像這樣的畫面:
hello-javafx-part2-3hello-javafx-part2-4

JavaFX FXML

在設計比較大型應用程式的時候,很普遍的作法是採用MVC的設計架構,把應用程式不同的工作分開來,讓程式元件清楚一些。只不過在View的部份,光是為了設計需要的畫面,就是一大堆程式碼了,所以JavaFX加入全新的設計方式,讓畫面的設計比較清楚,也更容易一些。直接試試看這種全新的設計方式:

  1. 在NetBeans中選擇建立一個專案
  2. 在建立新專案的對畫框中,選擇「JavaFX」目錄後,選擇「JavaFX FXML Application」,然後選擇「Next」
  3. 專案名稱輸入「SignInFXML」,FXML name輸入「SignIn」,最後選擇「Finish」

建立好這個JavaFX FXML應用程式以後,除了自動開啟的主程式「SignInFXML.java」外,再開啟其它兩個自動建立好的檔案:

  • SignIn.fxml:一個XML格式的檔案,設計畫面的工作會在這個檔案中執行
  • SignInController.java:Java原始程式碼,與FXML檔案對應的Controller元件,事件的控制會在這裡執行設計的工作

指定FXML檔案

既然畫面的設計已經不在主程式中,而且不是採用傳統程式設計的方式,所以主程式就會變得很精簡。使用FXML設計畫面的JavaFX應用程式,主程式要執行的工作就不多了。開啟主程式「SignInFXML.java」,「start」方法中的內容會像這樣:

Parent root = FXMLLoader.load(getClass().getResource("SignIn.fxml"));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();

在「start」方法中建立一個畫面元件的頂端容器物件,指定載入使用的FXML檔案,JavaFX會幫你把在FXML檔案中設計的畫面,轉換為物件放到這個容器物件中,剩下的就是與一般JavaFX應用程式一樣的工作了。你可以在「stage.show();」加入下面的敘述,為視窗設定標題:

stage.setTitle("Sign In FXML");

JavaFX FXML與「import」

使用XML格式的檔案來設計畫面,這對Java應用程式來說,可是從沒有過的事情。開啟「SignIn.fxml」檔案,清除原來NetBeans預先加入的東西,再加入下面的內容:

<?xml version="1.0" encoding="UTF-8"?>

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

JavaFX FXML雖然是一個XML格式的檔案,不過它跟Java程式碼有一點類似的地方,設計畫面時要用的API,也要使用「import」標籤,方式跟在Java程式碼中使用「import」敘述一樣。

建立GridPane容器

開始在FXML檔案中設計畫面了,這個範例要設計的畫面跟之前討論的一樣,也是一個讓使用者執行登入工作的視窗。所以最頂端的容器物件同樣採用「GridPane」。在「SignIn.fxml」檔案中加入下面的內容:

<GridPane fx:controller="signinfxml.SignInController"
          alignment="center" hgap="10" vgap="10"
          xmlns:fx="http://javafx.com/fxml" >
   <padding>
        <Insets top="25" right="25" bottom="10" left="25"/>
    </padding>

</GridPane>

加入的內容決定這個畫面採用「GridPane」的配置方式,同時也設定邊界與元件的間隔。如果你已經測試過之前討論過的內容,你會留意到這些標籤的名稱,會跟JavaFX API的類別名稱一樣,還有各種設定的名稱也是符合JavaBean的規則,所以開發人員在瞭解程式怎麼寫以後,就可以很快的進入這樣的設計方式。

FXML檔案可以搭配一個用來執行事件控制的Controller類別,在「GridPan」標籤中使用「fx:controller」設定一個使用完整名稱的Java類別,在後面會瞭解它在應用程式中的角色。

加入需要的元件

接下來就會在「GridPane」標籤的範圍中,加入這個畫面所有需要的元件。在「SignIn.fxml」檔案中,把下面的內容加入「」之前:

<Text text="Welcome"
      GridPane.columnIndex="0" GridPane.rowIndex="0"
      GridPane.columnSpan="2"/>
<Label text="User Name"
       GridPane.columnIndex="0" GridPane.rowIndex="1"/>
<TextField fx:id="userTextField"
    GridPane.columnIndex="1" GridPane.rowIndex="1"/>
<Label text="Password"
       GridPane.columnIndex="0" GridPane.rowIndex="2"/>
<PasswordField fx:id="passwordField" 
               GridPane.columnIndex="1" GridPane.rowIndex="2"/>
<HBox spacing="10" alignment="bottom_right" 
      GridPane.columnIndex="1" GridPane.rowIndex="4">
    <Button text="Sign In"     
            onAction="#handleSignInButtonAction"/>
</HBox>
<Text fx:id="actiontarget"
      GridPane.columnIndex="1" GridPane.rowIndex="6"/>

這裡加入的元件跟之前討論的登入畫面一樣,標籤的名稱就是JavaFX API的類別名稱,設定的名稱也採用JavaBean的規則。要特別注意的是元件的「fx:id」設定,如果只是顯示在畫面上,不會在程式碼中使用的的元件,就不用加入這個設定;可是像帳號、密碼和顯示登入結果的元件,需要在程式碼中執行讀取和設定的工作,所以要加入「fx:id」設定,為元件取一個名稱;按鈕元件也有一個特別的設定,「onAction」用來設定元件的事件控制和指定一個方法名稱。

完成畫面的設計了,可以先執行應用程式看看,出現的畫面應該會像這樣:
hello-javafx-part2-5

事件控制

最後再為登入按鈕加入事件控制,執行讀取帳號、密碼與判斷是否正確的工作。開啟「SignInController.java」檔案,把這個類別改為下面的內容:

public class SignInController implements Initializable {
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // TODO
    }    
}

與FXML搭配使用的Controller類別,是一個繼承自「Initializable」的子類別,類別中必需要實作「initialize」方法,執行初始化的工作,在這個範例中先不要管它。接下來在類別中加入下面的程式碼:

@FXML 
private TextField userTextField;
@FXML 
private PasswordField passwordField;
@FXML 
private Text actiontarget;

這三行敘述看起來像是一般的欄位變數宣告,可是在前面都加了「@FXML」的標記,搭配FXML檔案的設計,欄位變數的名稱要跟元件設定的「fx:id」一樣,JavaFX會自動初始化這些欄位變數。繼續在類別中加入下面的程式碼,執行事件控制的工作:

@FXML 
protected void handleSignInButtonAction(ActionEvent event) {
    String userNameValue = userTextField.getText();
    String passwordValue = passwordField.getText();

    String result = "Incorrect!";

    if (userNameValue.equals("simon") && passwordValue.equals("javafx") ) {
        result = "Welcome!";
    }

    actiontarget.setFill(Color.FIREBRICK);
    actiontarget.setText(result);
}

這是一個加入「@FXML」的標記的方法宣告,方法名稱與按鈕元件使用「onAction」的設定一樣,在方法中先讀取使用者輸入的帳號與密碼,判斷是否正確後,設定顯示登入結果元件的內容。

完成Controller類別的設計,可以執行應用程式並操作看看:
hello-javafx-part2-6hello-javafx-part2-7

JavaFX CSS 與 FXML

在之前的討論中,要讓應用程式的畫面套用CSS檔案中設定的樣式,需要在程式碼中執行相關的工作,把畫面改為使用FXML設計以後,這個工作也會在FXML檔案中設定了。你可以把前一個專案「SignInCSS」中的檔案複製到「signinfxml」套件中:

  • SignIn.css
  • background.png

開啟「SignIn.fxml」檔案,參考下面的內容,在頂端容器標籤中,加入指定CSS檔案的設定:

<GridPane fx:controller="signinfxml.SignInController"
          stylesheets="signinfxml/SignIn.css"
          alignment="center" hgap="10" vgap="10"
          xmlns:fx="http://javafx.com/fxml" >

在「SignIn.fxml」檔案中,找到顯示歡迎和登入結果的元件,參考下面的內容,加入指定id的設定:

<Text text="Welcome"
      id="welcome-text" 
      GridPane.columnIndex="0" GridPane.rowIndex="0"
      GridPane.columnSpan="2"/>
<Text fx:id="actiontarget"
      id="actiontarget"
      GridPane.columnIndex="1" GridPane.rowIndex="6"/>

完成CSS樣式相關的設定了,執行應用程式看看最後的結果:
hello-javafx-part2-8hello-javafx-part2-9

在下一篇「Hello JavaFX! Part 3」的內容中,會試試看JavaFX提供的繪圖、動畫與特殊效果。最後也會討論如何發佈一個已經開發完成的JavaFX應用程式。

資料來源與參考資訊

後續 >> Hello JavaFX! Part 3

留言

留言請先。還沒帳號註冊也可以使用FacebookGoogle+登錄留言

關於作者

從事資訊工作二十幾年,曾任台灣昇陽教育訓練中心Java課程講師,並於Java TWO與Java認證日負責Java FX與ADF專題演講,目前主要工作是在電腦補習班教課,負責Java程式設計、資料庫、Android和iOS等課程。除了賴以為生的Java之外,這幾年也在Android技術花費許多心力,致力於Android課程設計、推廣和書籍寫作。目前最感興趣也持續在研究的是Java FX技術,想要好好推廣這個Java技術的新朋友給大家認識。

熱門論壇文章

熱門技術文章