Menyusun antarmuka lewat kode Java memberi kendali penuh, tetapi ada harga yang tidak tampak: setiap penyesuaian tata letak butuh kompilasi ulang, desainer tidak bisa ikut bekerja, dan file Java memanjang penuh baris new serta set. JavaFX menjawab dengan FXML — format XML yang mendeklarasikan pohon scene graph secara ringkas: satu elemen XML adalah satu node, atribut adalah properti, dan kode Java tinggal menangani perilaku di kelas terpisah bernama controller. Ditambah Scene Builder, alat desain visual yang menyunting FXML lewat tarik-letakkan, pemisahan ini menuntaskan pembagian kerja antara tampilan dan logika. Chapter ini membedah ketiga komponen pola tersebut, lalu membuktikan bahwa struktur FXML bisa dibaca dan dipahami dengan parser XML standar milik JDK.
Antarmuka sebagai Dokumen #
Berikut wujud FXML untuk formulir data siswa — pohon yang sama dengan yang kita bangun lewat kode pada Chapter sebelumnya, kini tertulis sebagai dokumen:
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="AplikasiKontroler"
spacing="10" alignment="CENTER">
<Label fx:id="judul" text="Data Siswa"/>
<TextField fx:id="inputNama" promptText="Nama lengkap"/>
<Button fx:id="tombolSimpan" text="Simpan"
onAction="#handleSimpan"/>
</VBox>Aturan pembacaannya lugas. Elemen <?import ...?> bekerja seperti import pada Java — tanpa baris tersebut, nama pendek VBox harus ditulis javafx.scene.layout.VBox. Elemen bersarang menyatakan anak: Label berada di dalam VBox karena tag-nya ditulis di antara tag buka-tutup VBox. Atribut biasa (spacing="10") memetakan ke method setSpacing(10). Dua atribut berawalan fx: berperan khusus: fx:controller menyebut kelas kontroler untuk dokumen ini, dan fx:id memberi node sebuah nama yang bisa dirujuk dari kontroler.
Kontroler yang Menerima Injeksi #
Kelas kontroler adalah Java biasa — tidak memperluas apa pun dari JavaFX. Yang membuatnya istimewa adalah anotasi @FXML: mesin FXMLLoader mengisi field yang beranotasi itu dengan objek node hasil pembacaan dokumen, lalu mengaitkan onAction="#handleSimpan" pada tombol ke method bernama handleSimpan:
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
public class AplikasiKontroler {
@FXML private Label judul;
@FXML private TextField inputNama;
@FXML private Button tombolSimpan;
@FXML
private void handleSimpan() {
String nama = inputNama.getText();
judul.setText("Data " + nama + " tersimpan");
}
}Alur injeksi berjalan dalam tiga tahap di balik layar. Pertama, FXMLLoader membuat instance kontroler lewat konstruktor tanpa argumen (kelas harus punya konstruktor kosong). Kedua, setiap node yang bertanda fx:id dicocokkan dengan field kontroler bernama sama lalu diisikan — perhatikan bahwa field judul bertipe Label dan elemen FXML <Label fx:id="judul"> memang menghasilkan Label, kecocokan nama dan tipe itulah yang menjalin keduanya. Ketiga, nilai atribut onAction berupa #namaMethod dicari sebagai method kontroler beranotasi @FXML — tanda pagar menandai referensi ke kontroler, bukan nama method di node.
Memuat FXML dari Aplikasi #
Sisi aplikasi tinggal memuat dokumen, mengambil pohon hasilnya, dan memasangnya sebagai Scene:
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class AplikasiUtama extends Application {
@Override
public void start(Stage panggung) throws Exception {
FXMLLoader pemuat = new FXMLLoader(
getClass().getResource("FormSiswa.fxml"));
Parent akar = pemuat.load();
panggung.setTitle("Aplikasi FXML");
panggung.setScene(new Scene(akar, 360, 200));
panggung.show();
}
public static void main(String[] args) {
launch(args);
}
}Method pemuat.load() mengembalikan Parent — akar pohon node hasil penerjemahan XML — sekaligus membuat kontrolernya di belakang. Dari titik ini ke atas, aplikasi berjalan persis seperti aplikasi JavaFX murni-kode yang kita tulis sebelumnya.
Membaca Struktur FXML Tanpa JavaFX #
Karena FXML hanyalah XML, kita bisa membedah pekerjaan FXMLLoader dengan parser DOM standar (javax.xml.parsers) yang tersedia di JDK telanjang. Program berikut mem-parse dokumen FXML yang sama, membaca akarnya, atribut fx:controller, lalu menabulasikan tiap node beserta fx:id, teks, dan onAction-nya:
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/** Membaca struktur FXML dengan parser DOM standar JDK. */
public class BacaFxml {
static String FXML = """
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="AplikasiKontroler"
spacing="10">
<Label fx:id="judul" text="Data Siswa"/>
<TextField fx:id="inputNama" promptText="Nama lengkap"/>
<Button fx:id="tombolSimpan" text="Simpan"
onAction="#handleSimpan"/>
</VBox>
""";
public static void main(String[] args) throws Exception {
DocumentBuilderFactory pabrik = DocumentBuilderFactory.newInstance();
Document dok = pabrik.newDocumentBuilder()
.parse(new org.xml.sax.InputSource(new java.io.StringReader(FXML)));
Element akar = dok.getDocumentElement();
System.out.println("akar = " + akar.getTagName());
System.out.println("controller = " + akar.getAttribute("fx:controller"));
System.out.println("spacing = " + akar.getAttribute("spacing"));
NodeList anak = akar.getChildNodes();
for (int i = 0; i < anak.getLength(); i++) {
if (anak.item(i) instanceof Element e) {
String fxId = e.getAttribute("fx:id");
String aksi = e.getAttribute("onAction");
System.out.printf("%-10s fx:id=%-14s text=%-14s onAction=%s%n",
e.getTagName(),
fxId.isEmpty() ? "-" : fxId,
e.getAttribute("text").isEmpty() ? "-" : e.getAttribute("text"),
aksi.isEmpty() ? "-" : aksi);
}
}
}
}Output yang Dihasilkan:
akar = VBox
controller = AplikasiKontroler
spacing = 10
Label fx:id=judul text=Data Siswa onAction=-
TextField fx:id=inputNama text=- onAction=-
Button fx:id=tombolSimpan text=Simpan onAction=#handleSimpanMari kita bedah baris-baris kuncinya:
- Baris 6-17 (text block
FXML): Java 15+ memungkinkan XML berbaris-baris tertulis apa adanya di dalam"""..."""— tiga tanda kutip mengurangi penuhannya escape karakter. - Baris 19-21 (
DocumentBuilderFactory ... parse): DOM membangun pohon dokumen di memori;getDocumentElement()mengembalikan elemen akar, sama sepertiScenemenjadi akar scene graph. - Baris 28 (
instanceof Element e): node DOM bukan hanya elemen — spasi dan baris baru antar tag juga menjadi node teks. Pattern matching menyaring hanya elemen sungguhan, itulah sebabnya keluaran tidak memuat baris kosong. - Baris 29-33 (
getAttribute("fx:id")dll.): parser tanpa kesadaran namespace menyimpan nama atribut apa adanya, termasuk awalanfx:. Method mengembalikan""bila atribut tak ada — keluaran mencetak-sebagai penanda. - Baris 34-38 (
printfdengan%-10s): perataan kolom lebar tetap membuat tabel keluaran rapi — dan membuktikan seluruh metadata yang dibutuhkanFXMLLoader(jenis node,fx:id,onAction) tersedia bagi pembaca XML mana pun.
Merancang Secara Visual dengan Scene Builder #
Menulis XML tangan-ke-tangan tetap pekerjaan tekun; Scene Builder mengubahnya menjadi aktivitas desain visual. Aplikasi ini — kini dirawat oleh Gluon — menampilkan pustaka komponen di kiri, kanvas desain di tengah, dan pohon dokumen plus inspector properti di kanan; kita menyeret VBox, menjatuhkan Label dan Button, mengatur spacing lewat panel, dan berkas .fxml tersimpan otomatis. Scene Builder juga membaca fx:controller serta fx:id, lalu menawarkan method onAction yang ada pada kontroler sebagai pilihan di dropdown — menjaga tiga sumber kebenaran tetap sinkron.
Dokumen tampilan kini terpisah dari logika; yang belum mekanis adalah bagaimana data berubah dan label ikut berubah sendiri tanpa kita memanggil setText manual. JavaFX menjawabnya dengan properti dan bindings — sistem reaksi yang menjadi topik Chapter berikutnya.
Referensi #
Berkas Sumber
Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.
BacaFxml.java
Unduh berkasimport javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
/** Membaca struktur FXML dengan parser DOM standar JDK. */
public class BacaFxml {
static String FXML = """
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<VBox xmlns:fx="http://javafx.com/fxml"
fx:controller="AplikasiKontroler"
spacing="10">
<Label fx:id="judul" text="Data Siswa"/>
<TextField fx:id="inputNama" promptText="Nama lengkap"/>
<Button fx:id="tombolSimpan" text="Simpan"
onAction="#handleSimpan"/>
</VBox>
""";
public static void main(String[] args) throws Exception {
DocumentBuilderFactory pabrik = DocumentBuilderFactory.newInstance();
Document dok = pabrik.newDocumentBuilder()
.parse(new org.xml.sax.InputSource(new java.io.StringReader(FXML)));
Element akar = dok.getDocumentElement();
System.out.println("akar = " + akar.getTagName());
System.out.println("controller = " + akar.getAttribute("fx:controller"));
System.out.println("spacing = " + akar.getAttribute("spacing"));
NodeList anak = akar.getChildNodes();
for (int i = 0; i < anak.getLength(); i++) {
if (anak.item(i) instanceof Element e) {
String fxId = e.getAttribute("fx:id");
String aksi = e.getAttribute("onAction");
System.out.printf("%-10s fx:id=%-14s text=%-14s onAction=%s%n",
e.getTagName(),
fxId.isEmpty() ? "-" : fxId,
e.getAttribute("text").isEmpty() ? "-" : e.getAttribute("text"),
aksi.isEmpty() ? "-" : aksi);
}
}
}
}