使用JavaFX開發桌面程序
注:我也是JAVA FX的初學者之一,自己在學習的時候踩了許多的坑,中文英文的資料查了不少,但是覺得FX技術和其他熱門技術相比,教程還是太少了。這里就盡量做一點微小的貢獻吧
使用環境
注:寫這個只是為了說明我的環境,使用和我的不一樣的環境在理解這篇教程的時候並沒有什么問題,例如使用Windows平台、使用Oracle JDK(這樣就不需要再單獨安裝FX組件了,可以不用MAVEN)、使用Oracle的SceneBuilder。可能唯一一個比較影響體驗的就是不使用IDEA而是使用eclipse了
- Ubuntu18.04LTS
- OpenJDK 1.8
- IDEA(with MAVEN):使用MAVEN安裝FX環境(OpenJDK不附帶FX環境)
- SceneBuilder(glounhq):這是一個fxml可視化設計環境,使用上不如C#,但起碼比純命令設計強一百倍
搭建JAVA FX環境
-
下載IDEA、OpenJDK1.8、SceneBuilder(glounhq).
SceneBuilder下載地址:https://gluonhq.com/products/scene-builder/#download
-
在IDEA中關聯SceneBuilder.關聯的目的是為了之后可以從IDEA快速打開SceneBuilder來設計頁面
IDEA->File->Settings->Language->Java FX->輸入SceneBuilder的路徑
如果是Linux環境,你會發現這個路徑還不好找,我是使用
locate SceneBuilder
命令找到的,路徑是:/opt/SceneBuilder/SceneBuilder
-
因為OpenJDK沒有FX環境,需要我們自己安裝。為了便於管理,我們在這里使用MAVEN
-
在IDEA中創建一個Java FX項目
-
在項目名上右鍵,選擇'Add framework support',選擇MAVEN
-
在pom.xml文件中加入以下依賴:
<dependencies> <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-controls --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-controls</artifactId> <version>13</version> </dependency> <!-- https://mvnrepository.com/artifact/org.openjfx/javafx-fxml --> <dependency> <groupId>org.openjfx</groupId> <artifactId>javafx-fxml</artifactId> <version>13</version> </dependency> </dependencies>
設計流程
這里只寫一些我已經探索出來的設計流程,如果有不對的請指出~
-
先在Resources中創建fxml文件(之所以放在Resources文件夾下,是為了加載的時候方便,之后能看到),創建完成后在文件名上右擊,選擇'Open in SceneBuilder',之后就可以在SceneBuilder中進行可視化設計了。設計時要注意,對有響應的元素要在code欄下的fx:id中設置id,以便於之后的調用。設計完成后Ctrl+s保存文件
-
設計第一個加載的界面。這個可以放在入口的java類的main方法下,舉個例子:
package sample; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class Main extends Application { @Override public void start(Stage primaryStage) throws Exception{ Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("Entry.fxml"));//從Resources中獲取資源 primaryStage.setTitle("Course Registration System"); primaryStage.setScene(new Scene(root, 800, 600)); primaryStage.show(); }
-
設計觸發器:
對於每一個Panel,我們要指定一個觸發器類,這個是放在該fxml文件中的,例如IDEA中默認創建的就是AnchorPane對象,在它那一行就能找到:
fx:controller="sample.MainController"
,這個MainController就是我創建的一個類之后,我們可以對該panel下各個控件設計觸發事件后的反映,這個可以在SceneBuilder中填寫,在Code那一欄下面。設計了之后,它就會到我們指定的那個觸發器類下尋找這個方法,如果沒有的話IDEA會提示你創建
注意,觸發器類可以創建多個,這樣更便於管理,降低耦合度
-
在觸發器中獲取fxml中的控件對象
有時候,我們需要在事件相應中獲取對象的值,例如設計登錄頁面時點擊'提交'的按鈕,我們需要知道輸入框的字符串。這時候我們可以在觸發器中獲取這些元素,前提是我們為這些控件輸入了fx:id,它是全局性的,不允許重復。例如我們可以通過聲明:
@FXML private TextField username; @FXML private TextField password;
獲取兩個TextField對象下的值:
usernameString=username.getText(); passwordString=password.getText();
-
頁面跳轉
我們需要為每一個頁面設計一個Java類,例如我設計了一個SignIn_Student.java:
package sample; import javafx.application.Application; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.scene.Scene; import javafx.stage.Stage; public class SignIn_Student extends Application{ private String usernameString; private String passwordString; @Override public void start(Stage stage) throws Exception{ Parent root = FXMLLoader.load(getClass().getClassLoader().getResource("SignIn_Student.fxml"));//加載頁面 Scene anotherScene=new Scene(root); stage.setTitle("Please log in"); stage.setScene(anotherScene); stage.show(); } }
-
TableView的使用
這個控件用起來着實有點麻煩。折騰了好久。
-
我們肯定需要在某一個fxml頁面中加入了這個TableView,並且輸入了Table和它每一個TableColumn的fx:id.
-
我們需要為有TableView的fxml文件單獨創建一個控制器類,之后會說為什么
-
我們需要創建一個類來表示要儲存的數據,例如我這里創建了一個Courses.class:(下面的get和set方法是IDEA自動生成的)
package sample; import javafx.beans.property.*; import java.time.LocalDate; import java.time.LocalTime; public class Courses { private final StringProperty department; private final StringProperty lecturer; private final ObjectProperty<LocalDate> Time; private final StringProperty location; private final IntegerProperty ID; public Courses(String name, String department, String lecturer, LocalDate time, String location, Integer ID) { this.name = new SimpleStringProperty(name); this.department = new SimpleStringProperty(department); this.lecturer = new SimpleStringProperty(lecturer); this.Time = new SimpleObjectProperty<LocalDate>(time); this.location = new SimpleStringProperty(location); this.ID = new SimpleIntegerProperty(ID); } //String,String,String, Date,String,Integer private final StringProperty name; public String getName() { return name.get(); } public StringProperty nameProperty() { return name; } public void setName(String name) { this.name.set(name); } public String getDepartment() { return department.get(); } public StringProperty departmentProperty() { return department; } public void setDepartment(String department) { this.department.set(department); } public String getLecturer() { return lecturer.get(); } public StringProperty lecturerProperty() { return lecturer; } public void setLecturer(String lecturer) { this.lecturer.set(lecturer); } public LocalDate getTime() { return Time.get(); } public ObjectProperty<LocalDate> timeProperty() { return Time; } public void setTime(LocalDate time) { this.Time.set(time); } public String getLocation() { return location.get(); } public StringProperty locationProperty() { return location; } public void setLocation(String location) { this.location.set(location); } public int getID() { return ID.get(); } public IntegerProperty IDProperty() { return ID; } public void setID(int ID) { this.ID.set(ID); } }
-
我們需要實現的效果是,在加載這個頁面時,表格中自動加載數據。填寫我們創建的控制器類如下:
package sample; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.TextField; import java.time.LocalDate; public class MainController { @FXML private TextField username; @FXML private TextField password; @FXML private TableView<Courses> allCoursesTable; @FXML private TableColumn<Courses,String> CourseNameAttribute; @FXML private TableColumn<Courses,String> DepartmentAttribute; @FXML private TableColumn<Courses,String> LectureAttribute; @FXML private TableColumn<Courses, LocalDate> TimeAttribute; @FXML private TableColumn<Courses,String> LocationAttribute; @FXML private TableColumn<Courses,Number> CourseIDAttribute; @FXML private void initialize() { ObservableList<Courses> data= FXCollections.observableArrayList(new Courses("MACHINE LEARNING","COMPUTER","ZHANGYI",LocalDate.of(2012,01,01),"A101",4011));//創建ObservableList對象,將數據裝進去 CourseNameAttribute.setCellValueFactory(cellData->cellData.getValue().nameProperty()); DepartmentAttribute.setCellValueFactory(cellData->cellData.getValue().departmentProperty()); LectureAttribute.setCellValueFactory(cellData->cellData.getValue().lecturerProperty()); TimeAttribute.setCellValueFactory(cellData->cellData.getValue().timeProperty()); LocationAttribute.setCellValueFactory(cellData->cellData.getValue().locationProperty()); CourseIDAttribute.setCellValueFactory(cellData->cellData.getValue().IDProperty()); allCoursesTable.setItems(data);//加載數據 } }
這就是為什么要用單獨的控制器類了,否則initialize方法會在每次創建頁面的時候都加載一次,而只有某一個頁面有我們說的這些Tabel和Column對象,會報錯的。
-
寫一個方法來跳轉到這個頁面。
-
如何實現頁面之間的傳參呢?
對於要傳參的頁面,我們就不能直接獲取parent對象了,而是先要獲取FXMLLoader對象:
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getClassLoader().getResource("MainPanel.fxml")); Parent root = fxmlLoader.load(); MainController mc=fxmlLoader.getController();
注意這個MainController是我為這個頁面寫的控制器類
獲取了Controller對象后,我們就可以調用方法,將參數傳進去了:
mc.setPassword(pass); mc.setUsername(user); mc.handleAllCourses();
我在MainController這個類中是這樣寫的:
public void setUsername(String username){ usernameString=username; } public void setPassword(String password){ passwordString=password; }
-
-
-
我畫了個流程圖來表示傳參過程:
-
Controller的初始化
java @FXML private void initialize(){ /* 對控制器中注冊了的控件進行初始化 */ }
-
退出當前頁面
```java Stage stage = (Stage)exitButton.getScene().getWindow(); stage.close(); ``` 這里是以一個id為exitButton的Button舉例的。
-
如何從前向后傳遞參數?
意思就是,如果我們通過一個頁面的事件進入了一個新頁面,如果把在這個頁面中得到的參數不借助文件等傳遞回來呢?當然,既然我們說的是javafx,那么一般就是兩個控制器之間的參數傳遞。一個辦法就是,再后一個頁面的控制器上定義一個控制器對象和set方法,而前一個頁面的控制器對象在創建后一個頁面的時候通過set方法將自己傳遞進去。這樣后面的控制器就可以使用前一個控制器的public和protected方法了
-
創建監聽器
界面編輯器中內置了一些監聽方法,但是這是不夠用的,我們還可以自己創建監聽器。例如,如何根據音量條的值控制視頻音量?
volumeSD.valueProperty().addListener(new ChangeListener<Number>() { @Override public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) { mediaPlayer.setVolume(newValue.doubleValue() / 100); } });
注意監聽器的注冊要在頁面創建之后、使用之前
這就是入門的FX教程了,有了這些基本的方法,相信設計一個稍微復雜一點的桌面應用程序已經不是問題了。