FXML and Controllers (FXML နဲ့ Controller များ)

IA (Internal Assessment) အတွက် tool တစ်ခုပါ၊ စာမေးပွဲ ခေါင်းစဉ် မဟုတ်ပါဘူး: FXML (screen ပုံစံကို ဖော်ပြတဲ့ file) က JavaFX (Java နဲ့ desktop app တည်ဆောက်တဲ့ library) program တစ်ခုရဲ့ screen တွေကို တည်ဆောက်တဲ့ နည်းလမ်း တစ်ခုပါ။ Layout (control တွေကို screen ပေါ်မှာ စီစဉ်ထားပုံ) က သူ့ ကိုယ်ပိုင် file ထဲမှာ ရှိပြီး user ကို တုံ့ပြန်တဲ့ code က Java controller class (အဲဒီ screen ရဲ့ button တွေနဲ့ တခြား control တွေကို ကိုင်တွယ်တဲ့ Java class) ထဲမှာ ရှိပါတယ်။ ဒါကြောင့် ပိုကြီးတဲ့ IA (IB သင်တန်းမှာ ကျောင်းသားတစ်ယောက်ချင်း ကိုယ်တိုင် တည်ဆောက်ရပြီး ကျောင်းက ဆရာက အမှတ်ပေးတဲ့ အပိုင်း) product (ကိုယ်တိုင် တည်ဆောက်ရတဲ့ program) တစ်ခုမှာ အပိုင်း တစ်ခုစီကို ရှာရ၊ ဖတ်ရ၊ ပြင်ရ ပိုလွယ်ပါတယ်။ IB က IA အတွက် JavaFX၊ FXML ဒါမှမဟုတ် screen တည်ဆောက်တဲ့ တခြား library တစ်ခုခုကို မတောင်းပါဘူး။ Python နဲ့ ရေးနေသလား။ tkinter မှာ FXML လို layout file တွေ မရှိပါဘူး: screen တစ်ခုစီကို code ထဲမှာ window class တစ်ခုအဖြစ် တည်ဆောက်ပါတယ် (tkinter Basics ကို ကြည့်ပါ)။


ခြုံငုံ မိတ်ဆက်

JavaFX Basics (JavaFX အခြေခံများ) နဲ့ MVC Architecture (MVC ဖွဲ့စည်းပုံ) မှာ screen တိုင်းကို Java code နဲ့ တည်ဆောက်ခဲ့ပါတယ်: button တွေ၊ label တွေ၊ layout တွေကို code က line တစ်ကြောင်းချင်း ဖန်တီးခဲ့ပါတယ်။ FXML က အဲဒီ screen တွေကိုပဲ တည်ဆောက်တဲ့ နောက်ထပ် နည်းလမ်း တစ်ခုပါ။

FXML က screen တစ်ခုရဲ့ layout ကို သီးခြား file တစ်ခုထဲမှာ ဖော်ပြပါတယ်။ FXML က markup language တစ်ခုပါ: သူ့ရဲ့ tag (< နဲ့ > ကြားမှာ ရေးတဲ့ အမှတ်အသား) တွေက run ရမယ့် ညွှန်ကြားချက်တွေ ပေးတာ မဟုတ်ဘဲ တစ်ခုခုကို ဘယ်လို စီစဉ်ထားလဲ ဆိုတာကို ဖော်ပြပါတယ်။ FXML က XML ကို အခြေခံပါတယ်။ XML ဆိုတာ tag တွေကို တစ်ခုထဲမှာ တစ်ခု ထည့်ပြီး ရေးတဲ့ စာသား ပုံစံ တစ်ခုပါ။ ပြီးတော့ Controller class က screen ရဲ့ event (ဖြစ်ရပ်) တွေကို ကိုင်တွယ်ပါတယ်: event ဆိုတာ button နှိပ်တာ ဒါမှမဟုတ် စာရိုက်တာလို user လုပ်တဲ့ အရာတွေပါ။ Event တွေကို တုံ့ပြန်တဲ့ သူ့ method (သီးခြား အလုပ်တစ်ခု လုပ်ပေးတဲ့ code အပိုင်း) တွေကို handler လို့ ခေါ်ပါတယ်။ FXML က layout ကို ဖော်ပြတဲ့ အပိုင်းကို Java code ထဲကနေ ထုတ်ပေးလိုက်ပါတယ်:

FXML မပါဘဲ FXML နဲ့
UI (user နဲ့ program ကြားက မြင်ကွင်း) layout ကို Java နဲ့ ရေးတယ် UI layout ကို XML နဲ့ ရေးတယ်
Code ကို ကြည့်ပြီး screen ကို မြင်ယောင်ဖို့ ခက်တယ် Tag တွေ တစ်ခုထဲ တစ်ခု ထည့်ထားပုံက screen ရဲ့ layout အတိုင်း ဖြစ်တယ်
Layout ရော event handling ပါ Java နဲ့ ရေးတယ်: class တစ်ခုတည်းထဲမှာ ဒါမှမဟုတ် MVC စာမျက်နှာမှာလို View နဲ့ Controller class သီးခြားစီမှာ Layout က Java မပါတဲ့ .fxml file ထဲမှာ ရှိတယ်၊ event handling က Java controller class ထဲမှာ ရှိတယ်
Control တစ်ခုကို ရွှေ့တာ ဒါမှမဟုတ် Java code နဲ့ ပုံစံ ပြန်ပြင်တာက Java code ကို ပြင်ပြီး compile (ကွန်ပျူတာ run လို့ရအောင် ဘာသာပြန်) ပြန်လုပ်ရတယ် (screen ရဲ့ အရောင်၊ စာလုံး ပုံစံတွေကို သတ်မှတ်တဲ့ သီးခြား CSS file တစ်ခုနဲ့ဆိုရင်တော့ compile ပြန်မလုပ်ဘဲ ပုံစံကို ပြောင်းနိုင်တယ်) Control တစ်ခုကို ရွှေ့တာ ဒါမှမဟုတ် ပုံစံ ပြန်ပြင်တာက .fxml file ကိုပဲ ပြင်ရုံပါပဲ၊ သူ့ fx:id နဲ့ handler နာမည်တွေ မပြောင်းသရွေ့ပါ

FXML File ရဲ့ ဖွဲ့စည်းပုံ

FXML file တစ်ခုက ကိုယ့် window ရဲ့ layout ကို XML tag တွေနဲ့ ဖော်ပြပါတယ်။ Tag အများစုကို <Button> ဒါမှမဟုတ် <VBox> လို သူတို့ ဖန်တီးပေးတဲ့ JavaFX class (object တွေ ဖန်တီးဖို့ ပုံစံခွက်) ရဲ့ နာမည်အတိုင်း ပေးထားပါတယ်။ <center> နဲ့ <padding> လို tag အနည်းငယ်ကတော့ သူတို့ကို အပြင်က ဝိုင်းထားတဲ့ tag ရဲ့ property (object တစ်ခုရဲ့ စာသား၊ အရွယ်အစား စတဲ့ နာမည်ပေးထားတဲ့ တန်ဖိုး) တစ်ခုကို သတ်မှတ်ပါတယ်။

ဥပမာ: student-view.fxml

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

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

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="StudentController">

    <padding><Insets top="10" right="10" bottom="10" left="10"/></padding>

    <center>
        <TextArea fx:id="display" editable="false" prefRowCount="12"/>
    </center>

    <bottom>
        <VBox spacing="10">
            <padding><Insets top="10"/></padding>
            <TextField fx:id="nameField" promptText="Student name"/>
            <TextField fx:id="gradeField" promptText="Grade (0-100)"/>
            <HBox spacing="10">
                <Button text="Add" onAction="#handleAdd"/>
                <Button text="Remove" onAction="#handleRemove"/>
                <Button text="Search" onAction="#handleSearch"/>
            </HBox>
            <Label fx:id="status" text="Ready"/>
        </VBox>
    </bottom>

</BorderPane>

FXML ရဲ့ အဓိက အယူအဆများ

fx:controller: ဒီ FXML file ကို Java Controller class တစ်ခုနဲ့ ချိတ်ပေးပါတယ်။ Root element (အပြင်ဆုံး tag) မှာ ရေးရပါတယ်။ Controller က screen ရဲ့ event တွေကို ကိုင်တွယ်ပါတယ်။

fx:id: Controller က ယူသုံးနိုင်အောင် control တစ်ခုကို မထပ်တဲ့ နာမည် တစ်ခု ပေးပါတယ်။ Variable (တန်ဖိုး သိမ်းထားတဲ့ နာမည်ပေးထားတဲ့ နေရာ) တစ်ခုကို နာမည်ပေးတာနဲ့ ဆင်တူပါတယ်။

onAction="#methodName": Button နှိပ်တာ (ဒါမှမဟုတ် TextField ထဲမှာ Enter နှိပ်တာ) ကို Controller ထဲက method တစ်ခုနဲ့ ချိတ်ပေးပါတယ်။ ရှေ့က # က “controller ထဲက ဒီ method ကို ခေါ်ပါ” လို့ ဆိုလိုပါတယ်။

<?import ...?>: Java ထဲက import statement (code ထဲက ညွှန်ကြားချက်) တွေလိုပဲ JavaFX class တွေကို import လုပ်ပါတယ်။

နာမည်တွေ အတိအကျ ကိုက်ရပါမယ်။ Controller က သုံးတဲ့ fx:id တစ်ခုစီအတွက် Controller မှာ နာမည်တူ field (object ထဲက အချက်အလက် အကွက်) တစ်ခု ရှိရပါမယ်။ onAction="#name" တစ်ခုစီအတွက် Controller မှာ အဲဒီ နာမည်နဲ့ method တစ်ခု ရှိရပါမယ်။ ဒီစာမျက်နှာမှာ အဲဒီ field တွေနဲ့ method တွေက private (class ပြင်ပကနေ တိုက်ရိုက် သုံးလို့ မရ) ဖြစ်ပြီး @FXML လို့ မှတ်ထားပါတယ်။ ဒါက JavaFX ကို သူတို့ဆီ ရောက်ခွင့် ပေးပါတယ် (နောက် အပိုင်းမှာ ရှင်းပြထားပါတယ်)။ public field တွေနဲ့ method တွေကတော့ @FXML မပါလည်း အလုပ်လုပ်ပါတယ်။ Method တစ်ခု မရှိရင် file ကို load လုပ်လို့ မရဘဲ error ပြပါတယ်။ နာမည် မကိုက်တဲ့ field ကတော့ null (ဘာ object ကိုမှ မညွှန်းတဲ့ တန်ဖိုး) အတိုင်း ကျန်ခဲ့ပါတယ်။


@FXML Annotation

Annotation ဆိုတာ @ နဲ့ စပြီး သူ့နောက်က code အကြောင်း Java tool တွေကို အချက်အလက် ထပ်ပေးတဲ့ အမှတ်အသား တစ်ခုပါ။ @FXML annotation က FXML file ထဲက UI element ကို Controller ရဲ့ field ထဲ JavaFX က inject လုပ်ခွင့် ပေးပါတယ်: inject ဆိုတာ JavaFX က control ကို ဖန်တီးပြီး ကိုယ့်အစား field ထဲ ထည့်ပေးတာပါ။ Annotation မပါရင် private field က null အတိုင်း ရှိနေပါတယ်။

student-view.fxml အတွက် Controller

import javafx.fxml.FXML;
import javafx.scene.control.*;

public class StudentController {
    private StudentList model;

    @FXML private TextField nameField;
    @FXML private TextField gradeField;
    @FXML private TextArea display;
    @FXML private Label status;

    public void setModel(StudentList model) {
        this.model = model;
        refreshDisplay();
    }

    @FXML
    private void handleAdd() {
        String name = nameField.getText().trim();
        String gradeText = gradeField.getText().trim();

        if (name.isEmpty()) {
            status.setText("Error: Name cannot be empty.");
            return;
        }

        int grade;
        try {
            grade = Integer.parseInt(gradeText);
        } catch (NumberFormatException e) {
            status.setText("Error: Grade must be a number.");
            return;
        }

        try {
            Student s = new Student(name, grade);
            model.addStudent(s);
            status.setText("Added: " + s);
            nameField.clear();
            gradeField.clear();
            refreshDisplay();
        } catch (IllegalArgumentException e) {
            status.setText("Error: " + e.getMessage());
        }
    }

    @FXML
    private void handleRemove() {
        String name = nameField.getText().trim();
        if (name.isEmpty()) {
            status.setText("Error: Enter a name to remove.");
            return;
        }
        if (model.removeByName(name)) {
            status.setText("Removed: " + name);
            nameField.clear();
            refreshDisplay();
        } else {
            status.setText("Not found: " + name);
        }
    }

    @FXML
    private void handleSearch() {
        String name = nameField.getText().trim();
        if (name.isEmpty()) {
            status.setText("Error: Enter a name to search.");
            return;
        }
        Student found = model.findByName(name);
        if (found != null) {
            status.setText("Found: " + found);
        } else {
            status.setText("Not found: " + name);
        }
    }

    private void refreshDisplay() {
        display.clear();
        for (int i = 0; i < model.getAll().size(); i++) {
            display.appendText((i + 1) + ". " + model.getAll().get(i) + "\n");
        }
    }
}

Student နဲ့ StudentList က MVC Architecture ထဲက Model (app ရဲ့ ဒေတာနဲ့ စည်းမျဉ်းတွေကို ထိန်းထားတဲ့ class တွေ) class တွေပါ။ Grade (အမှတ်) က 0 ကနေ 100 အထိ ဖြစ်ရမယ်ဆိုတဲ့ စည်းမျဉ်းကို Student ကိုယ်တိုင်က စစ်ပါတယ်။ Controller က ရိုက်ထည့်ထားတဲ့ စာသားကို သုံးလို့ ရမရ စစ်ရုံပဲ စစ်ပြီး Model က လက်မခံရင် Model ရဲ့ message ကို ပြပါတယ်။

@FXML အလုပ်လုပ်ပုံ

FXML Controller ဘာဖြစ်လဲ
fx:id="nameField" @FXML private TextField nameField; JavaFX က field ထဲကို FXML ထဲက TextField ကို ထည့်ပေးတယ်
onAction="#handleAdd" @FXML private void handleAdd() Button နှိပ်ရင် ဒီ method ကို ခေါ်တယ်

Handler method တစ်ခုမှာ ဒီမှာ handleAdd() လို parameter (method ကို ပေးလိုက်တဲ့ တန်ဖိုးကို လက်ခံတဲ့ variable) မပါတာ ဖြစ်ဖြစ်၊ void handleAdd(ActionEvent event) မှာလို ActionEvent parameter (click ကို ဖော်ပြတဲ့ object) တစ်ခု ပါတာ ဖြစ်ဖြစ် ရပါတယ်။ JavaFX က နှစ်မျိုးလုံးကို လက်ခံပါတယ်။ ဒုတိယ ပုံစံအတွက် import javafx.event.ActionEvent; လိုပါတယ်။

Java ထဲက field နာမည်က FXML ထဲက fx:id နဲ့ အတိအကျ ကိုက်ရပါမယ်။ FXML မှာ fx:id="nameField" လို့ ရေးထားပြီး Java field ကို nameInput လို့ နာမည်ပေးထားရင် JavaFX က ဖြည့်ပေးဖို့ nameField ဆိုတဲ့ field ကို ရှာမတွေ့တာကြောင့် nameInput က null အတိုင်း ရှိနေပါတယ်။


FXMLLoader နဲ့ FXML ကို Load လုပ်ခြင်း

FXMLLoader class က FXML file ကို ဖတ်ပြီး UI element တွေကို ဖန်တီးကာ Controller နဲ့ ချိတ်ပေးပါတယ်။

Application Class (app ကို စတင်ပေးတဲ့ class)

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class StudentApp extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        FXMLLoader loader = new FXMLLoader(
            getClass().getResource("student-view.fxml")
        );
        Parent root = loader.load();

        // Get the controller and pass the model
        StudentController controller = loader.getController();
        controller.setModel(new StudentList());

        stage.setTitle("Student Manager");
        stage.setScene(new Scene(root, 450, 450));
        stage.show();
    }

    public static void main(String[] args) {
        launch(args);
    }
}

getClass().getResource("student-view.fxml") က FXML file ကို ရှာပါတယ်။ ဒီလို file နာမည် သက်သက်နဲ့ဆိုရင် StudentApp class ရှိတဲ့ folder ထဲမှာပဲ ရှာတာကြောင့် FXML file ကို အဲဒီ folder ထဲမှာ ထားရပါမယ် (အောက်က File တွေကို စီစဉ်ခြင်း အပိုင်းကို ကြည့်ပါ)။ ဒီလို program က ရှာပြီး load လုပ်တဲ့ .fxml file ဒါမှမဟုတ် ပုံ (image) လို file တစ်ခုကို resource လို့ ခေါ်ပါတယ်။

Application class က controller ဆီ ရောက်ဖို့ မလိုရင် ပိုတိုတဲ့ ပုံစံကိုလည်း သုံးလို့ ရပါတယ်: Parent root = FXMLLoader.load(getClass().getResource("student-view.fxml"));။ ဒီပုံစံမှာ getController() ကို ခေါ်လို့ မရတာကြောင့် Model ကို ပေးပို့ဖို့ ဒီစာမျက်နှာမှာ အပေါ်က အဆင့်နှစ်ဆင့် ပုံစံကို သုံးထားပါတယ်။

FXMLLoader run တဲ့အခါ ဖြစ်တာတွေ

  1. ဖန်တီးပါတယ်: fx:controller မှာ နာမည်ပေးထားတဲ့ Controller class ရဲ့ instance (class တစ်ခုကနေ ဖန်တီးထားတဲ့ object) တစ်ခုကို parameter မပါတဲ့ constructor (object ဖန်တီးချိန် attribute တွေကို စတင် သတ်မှတ်ပေးတဲ့ method) နဲ့ ဖန်တီးပါတယ်။ ဒီအချိန်မှာ သူ့ @FXML field တွေက null ဖြစ်နေသေးပါတယ်
  2. ဖတ်ပါတယ်: file ရဲ့ ကျန်တဲ့ အပိုင်းကို ဖတ်ပြီး UI node တွေ (screen ကို ဖွဲ့စည်းထားတဲ့ object တွေ: layout တွေနဲ့ control တွေ) ကို ဖန်တီးပါတယ်
  3. Inject လုပ်ပါတယ်: fx:id element တစ်ခုစီကို ကိုက်ညီတဲ့ @FXML field ထဲ ထည့်ပါတယ်
  4. ချိတ်ပါတယ်: onAction တစ်ခုစီကို ကိုက်ညီတဲ့ @FXML method နဲ့ ချိတ်ပါတယ်
  5. ခေါ်ပါတယ်: Controller ပေါ်မှာ initialize() ကို ခေါ်ပါတယ် (ရှိရင်)
  6. Return ပြန်ပေးပါတယ်: root node (တခြား အားလုံးကို ကိုင်ထားတဲ့ အပြင်ဆုံး layout) ကို ပြန်ပေးပါတယ်။ အဲဒါက scene ထဲမှာ မရှိသေးပါဘူး: Application class က သူ့ကို Scene တစ်ခုထဲ ထည့်ပါတယ်

အဆင့် 2 ကနေ 4 အထိက loader က file ကို ဖတ်သွားရင်း element တစ်ခုချင်းစီအလိုက် တပြိုင်နက် ဖြစ်ပါတယ်။

initialize() Method

ကိုယ့် Controller မှာ parameter မပါတဲ့ initialize() ဆိုတဲ့ method ရှိရင် @FXML field အားလုံးကို inject လုပ်ပြီးတဲ့နောက် load() return မပြန်ခင်မှာ JavaFX က အဲဒီ method ကို အလိုအလျောက် ခေါ်ပါတယ်။ အဲဒီ method ကို @FXML လို့ မှတ်ပါ (ဒါမှမဟုတ် public လုပ်ပါ)။ @FXML မပါတဲ့ private initialize() ကို မခေါ်ပါဘူး။ စတင် စာသား သတ်မှတ်တာ ဒါမှမဟုတ် ရွေးချယ်စရာ စာရင်း ဖြည့်တာလို control တွေ ရှိပြီးမှ လုပ်လို့ရတဲ့ ပြင်ဆင်မှု (setup) တွေအတွက် သုံးပါ။

@FXML
private void initialize() {
    // Called automatically after the FXML is loaded
    // All @FXML fields are now available
    status.setText("Ready: type a name and a grade.");
}

initialize() run တဲ့ အချိန်မှာ root ကို scene ထဲ မထည့်ရသေးတာကြောင့် အဲဒီမှာ ဘယ် control ပေါ်မှာမဆို getScene() ကို ခေါ်ရင် null ကို return ပြန်ပါတယ်။

ဒီစာမျက်နှာရဲ့ ဒီဇိုင်းမှာ Controller က Model ကို မရခင် initialize() က run ပါတယ်။ Application class က Model ကို setModel() နဲ့ ပေးပို့ပြီး setModel() က load() return ပြန်ပြီးမှ run တာကြောင့် initialize() ထဲက model ကို သုံးတဲ့ code က NullPointerException (object အစား null ကို ကိုင်ထားတဲ့ variable ကို သုံးမိလို့ ဖြစ်တဲ့ Java ရဲ့ error) နဲ့ ရပ်သွားပါတယ်။ Model ကို လိုတဲ့ code ကို setModel() ထဲမှာ ထားပါ၊ အပေါ်က Controller က refreshDisplay() နဲ့ လုပ်ထားသလိုပါ။


FXML မှာ အဖြစ်များတဲ့ Error များ

FXML ပြဿနာ အများစုက file တစ်ခုထဲက နာမည် တစ်ခုက တခြား file ထဲက နာမည်နဲ့ မကိုက်တာကနေ လာပါတယ်။ Error message က များသောအားဖြင့် အဲဒီ နာမည်ကို ဖော်ပြပါတယ်။

မြင်ရတာ ဖြစ်နိုင်တဲ့ အကြောင်းရင်း
App စတင်တဲ့အခါ Location is not set. ဒါမှမဟုတ် Location is required. (file ရှိရာ နေရာကို မသတ်မှတ်ရသေးပါ / သတ်မှတ်ဖို့ လိုပါတယ်) getResource က file ကို ရှာမတွေ့ပါဘူး: file နာမည်ကို စစ်ပါ၊ .fxml file က path (file ရှိရာ လမ်းကြောင်း) ညွှန်တဲ့ folder ထဲမှာ ရှိမရှိ စစ်ပါ
... is not a valid type. (မှန်ကန်တဲ့ type မဟုတ်ပါ) FXML က <?import ...?> line မပါတဲ့ class တစ်ခုကို သုံးထားပါတယ်
Error resolving onAction='#handleSave', either the event handler is not in the Namespace or there is an error in the script. (onAction ကို ချိတ်လို့ မရပါ: event handler ကို ရှာမတွေ့ပါ ဒါမှမဟုတ် script မှာ အမှား ရှိပါတယ်) Controller မှာ အဲဒီ နာမည်နဲ့ method မရှိပါဘူး၊ ဒါမှမဟုတ် method က @FXML မပါတဲ့ private method ဖြစ်နေပါတယ်
ကိုယ့် controller နာမည် ရှေ့မှာ ClassNotFoundException (class ကို ရှာမတွေ့ပါ) fx:controller မှာ ရေးထားတဲ့ နာမည်က class နာမည်နဲ့ မကိုက်ပါဘူး
File load လုပ်နေတုန်း NullPointerException initialize() က null ဖြစ်နေသေးတဲ့ တစ်ခုခုကို သုံးနေပါတယ်: loader က မဖြည့်ခဲ့တဲ့ field တစ်ခု (ဘာကြောင့်လဲ ဆိုတာ နောက် row မှာ ကြည့်ပါ)၊ ဒါမှမဟုတ် နောက်မှ setModel() ကနေ ရောက်လာမယ့် Model တစ်ခု
Button နှိပ်တဲ့အခါ NullPointerException ... because "this.nameField" is null (this.nameField က null ဖြစ်နေလို့) Field ကို မဖြည့်ခဲ့ပါဘူး: သူ့နာမည်က fx:id နဲ့ မတူတာ၊ ဒါမှမဟုတ် @FXML မပါတဲ့ private field ဖြစ်နေတာပါ

fx:id တစ်ခုမှာ controller ထဲက ကိုက်ညီတဲ့ field ရှိဖို့ မလိုပါဘူး၊ field မပါတဲ့ fx:id က error မဖြစ်စေပါဘူး။


File တွေကို စီစဉ်ခြင်း

FXML ကို အခြေခံတဲ့ IA project အသေးလေး တစ်ခုကို ဒီလို စီစဉ်လို့ ရပါတယ်:

src/
├── StudentApp.java           (Application: loads the FXML, passes in the Model)
├── Student.java              (Model: one student's data and rules)
├── StudentList.java          (Model: the collection of students)
├── StudentController.java    (Controller: event handlers)
└── student-view.fxml         (View: UI layout)

ကွင်းထဲက စာတွေက file တစ်ခုစီရဲ့ အလုပ်ကို ပြပါတယ်: Application (FXML ကို load လုပ်ပြီး Model ကို ပေးပို့တယ်)၊ Model (ကျောင်းသား တစ်ယောက်ရဲ့ ဒေတာနဲ့ စည်းမျဉ်းတွေ)၊ Model (ကျောင်းသားတွေရဲ့ collection)၊ Controller (event handler တွေ)၊ View (UI layout)။

Convention (အများ လိုက်နာတဲ့ ထုံးစံ): FXML file ကို သူ့ကို load လုပ်တဲ့ class နဲ့ folder (package: ဆက်စပ်တဲ့ class တွေကို စုထားတဲ့ အုပ်စု) တစ်ခုတည်း ထဲမှာ ထားပါ။ ဒါဆိုရင် getClass().getResource("student-view.fxml") က folder path မပါဘဲ file နာမည် သက်သက်နဲ့ file ကို ရှာတွေ့ပါတယ်။

Screen အများ ပါတဲ့ ပိုကြီးတဲ့ IA project တစ်ခုက သူ့ file တွေကို folder တွေထဲမှာ ခွဲထားနိုင်ပါတယ်:

src/
├── App.java
├── model/
│   ├── Student.java
│   └── StudentList.java
├── controller/
│   ├── MainController.java
│   └── AddStudentController.java
└── view/
    ├── main-view.fxml
    └── add-student-view.fxml

File တွေက folder (package) တွေထဲမှာ ရှိတဲ့အခါ Java file တစ်ခုစီက package model; လို package line တစ်ကြောင်းနဲ့ စပြီး တခြား folder တွေက သုံးတဲ့ class တွေအတွက် import line တွေ လိုပါတယ်။ FXML file ကို Java code နဲ့ ချိတ်ပေးတဲ့ နာမည် နှစ်ခုလည်း ပြောင်းပါတယ်:

  • fx:controller မှာ fx:controller="controller.MainController" လို class နာမည် အပြည့်အစုံ လိုပါတယ်။
  • getResource မှာ getResource("/view/main-view.fxml") လို file ဆီ သွားတဲ့ path လိုပါတယ်။ / နဲ့ စတဲ့ path ကို တောင်းတဲ့ class ရဲ့ folder ကနေ မဟုတ်ဘဲ run နေတဲ့ program ရဲ့ resource တွေရဲ့ root (အပေါ်ဆုံး folder) ကနေ ရေတွက်ပါတယ်။ Program ကို build လုပ်တဲ့အခါ (run လို့ရတဲ့ ပုံစံ တည်ဆောက်တဲ့အခါ) .fxml file ကို အဲဒီ resource တွေထဲမှာ ထည့်ထားရပါမယ်: BlueJ လို editor တချို့က compile လုပ်ပြီးသား class တွေရဲ့ ဘေးမှာ ထားပေးပြီး Maven နဲ့ Gradle လို build tool (ကိုယ့် project လိုအပ်တဲ့ library တွေကို download လုပ်ပေးတဲ့ program) တွေက resources folder ကနေ copy ကူးပေးပါတယ်။

Scene Builder (မဖြစ်မနေ မလိုပါ)

Scene Builder က FXML file တွေအတွက် အခမဲ့ visual editor (ပုံနဲ့ မြင်ရင်း ပြင်တဲ့ editor) တစ်ခုပါ။ XML ကို လက်နဲ့ ရိုက်ရေးမယ့်အစား screen ရဲ့ preview (ကြိုမြင်ကွင်း) ပေါ်ကို control တွေကို ဆွဲထည့်ပါတယ်။

အားသာချက်များ:

  • တည်ဆောက်နေရင်းနဲ့ layout ကို မြင်ရတယ်
  • Property တွေ (စာသား၊ အရွယ်အစား၊ alignment: နေရာ ညှိပုံ) ကို panel တစ်ခုမှာ သတ်မှတ်လို့ ရတယ်
  • Scene Builder က ကိုယ့်အစား FXML ကို ရေးပေးတယ်

သုံးပုံ:

  1. Scene Builder ကို Gluon ကနေ download လုပ်ပါ
  2. ကိုယ့် .fxml file ကို Scene Builder မှာ ဖွင့်ပါ၊ ဒါမှမဟုတ် file အသစ် တစ်ခု စပါ
  3. Library panel ထဲက control တွေကို layout ပေါ်ကို ဆွဲထည့်ပါ
  4. Control တစ်ခုကို ရွေးပြီး ညာဘက်က Inspector panel ရဲ့ Code အပိုင်းမှာ သူ့ fx:id နဲ့ On Action ကို သတ်မှတ်ပါ
  5. ဘယ်ဘက်က Hierarchy panel ရဲ့ အောက်မှာ ရှိတဲ့ Controller အပိုင်းမှာ controller ရဲ့ class နာမည်ကို ရိုက်ထည့်ပါ
  6. Save လုပ်ပါ၊ Scene Builder က layout ကို .fxml file ထဲ ရေးပေးပါတယ်။ စတင်ဖို့ controller တစ်ခု လိုချင်ရင် View > Show Sample Controller Skeleton ကို ရွေးပါ: ကိုက်ညီတဲ့ @FXML field တွေနဲ့ ဗလာ handler method တွေ ပါတဲ့ controller class တစ်ခုကို ပြပေးပြီး ကိုယ့် project ထဲ copy ကူးယူလို့ ရပါတယ်

Scene Builder က အထောက်အကူ ဖြစ်ပေမဲ့ မဖြစ်မနေ မလိုပါဘူး။ FXML ကို လက်နဲ့လည်း ရေးလို့ ရပါတယ်၊ layout အသေးလေးတွေအတွက် ကောင်းကောင်း အဆင်ပြေပါတယ်။ Scene Builder က screen ကို စီစဉ်ပေးတာပါ၊ program ဘာလုပ်လဲ ဆိုတာကတော့ ကိုယ်ရေးတဲ့ Java code ပါပဲ။


FXML နဲ့ Java-Only: ဘယ်အချိန်မှာ ဘာကို သုံးမလဲ

နည်းလမ်း ဘယ်မှာ အသင့်တော်ဆုံးလဲ
FXML Control အများကြီး ပါတဲ့ screen တွေ၊ screen အများ ပါတဲ့ app တွေ၊ Scene Builder မှာ layout ကို ပုံနဲ့ မြင်ရင်း ဒီဇိုင်းဆွဲတာ
Java-only Screen တစ်ခုတည်း ပါတဲ့ app အသေးလေးတွေ၊ ဒေတာပေါ် မူတည်ပြီး control တွေ ပြောင်းတဲ့ screen တွေ (app run နေစဉ် code က control တွေကို ဖန်တီးရလို့)၊ JavaFX အခြေခံတွေကို လေ့လာတာ

IA product တစ်ခုအတွက် ဘယ်နည်းလမ်းကိုမဆို သုံးလို့ ရပါတယ်။ FXML ကို ရွေးရတဲ့ အကြောင်းရင်းများ:

  • Screen တစ်ခုစီရဲ့ layout က ဖတ်လို့ ဒါမှမဟုတ် Scene Builder မှာ ဖွင့်လို့ ရတဲ့ file တစ်ခုတည်းထဲမှာ ရှိတယ်
  • Control တစ်ခုကို ရွှေ့တာ၊ ပုံစံ ပြန်ပြင်တာ ဒါမှမဟုတ် Java code က မသုံးတဲ့ control တစ်ခုကို ထည့်တာတွေအတွက် Java code ကို ပြင်ဖို့ မလိုဘူး
  • FXML file တစ်ခုစီက View ဖြစ်ပြီး သူ့ controller class က Controller ရဲ့ အခန်းကဏ္ဍကို ယူတာကြောင့် MVC design ရဲ့ အပိုင်းတွေကို သီးခြားစီ ထားဖို့ ကူညီပေးတယ်။ ဒါပေမဲ့ FXML က သူ့ဘာသာ သီးခြားစီ ဖြစ်အောင် မလုပ်ပေးပါဘူး: ဒေတာနဲ့ သူ့ စည်းမျဉ်းတွေက controller ထဲမှာ မဟုတ်ဘဲ Model class တွေထဲမှာပဲ ရှိရပါမယ်

Quick Check (အမြန် စစ်ဆေးမေးခွန်း)

Q1. FXML file ထဲမှာ fx:id attribute (tag ထဲမှာ နာမည်=တန်ဖိုး ပုံစံနဲ့ ရေးတဲ့ setting) က ဘာလုပ်သလဲ။

Q2. Controller က fx:id="status" နဲ့ ကိုက်အောင် private Label status; ကို ကြေညာထားပြီး FXML load လုပ်နေစဉ် ဘာကမှ အဲဒီ field ကို မသုံးပါဘူး။ အဲဒီ field မှာ @FXML annotation ထည့်ဖို့ မေ့သွားရင် ဘာဖြစ်မလဲ။

Q3. FXMLLoader.load() က ဘာလုပ်သလဲ။

Q4. FXML file တစ်ခုအတွက် ဘယ် Controller class ကို သုံးရမလဲဆိုတာ JavaFX က ဘယ်လို သိသလဲ။

Q5. Controller ရဲ့ initialize() method ကို ဘယ်အချိန်မှာ ခေါ်သလဲ။


Code Completion (code ကွက်လပ် ဖြည့်ခြင်း)

ဒီ FXML ကို အခြေခံတဲ့ application ထဲက ပျောက်နေတဲ့ အပိုင်းတွေကို ဖြည့်ပါ။

ကွက်လပ် ဖြည့်ပါ: fx:id="inputField" နဲ့ onAction="#handleClick" ပါတဲ့ button တစ်ခု ပါဝင်တဲ့ FXML file နဲ့ ကိုက်တဲ့ Controller ကို ပြည့်စုံအောင် ဖြည့်ပါ။ Button ကို နှိပ်တဲ့အခါ label က ရိုက်ထည့်ထားတာကို ပြပြီး ပြီးရင် text field ကို ရှင်းပေးရပါမယ်။

import javafx.fxml.FXML;
import javafx.scene.control.*;

public class MyController {

     private TextField inputField;
    @FXML private Label output;

    @FXML
    private void () {
        String text = inputField.;
        output.setText("You typed: " + text);
        inputField.;
    }
}

Spot the Error (အမှား ရှာခြင်း)

ဒီ FXML နဲ့ Controller အတွဲက အတူ အလုပ်လုပ်ရမှာပါ၊ ဒါပေမဲ့ မကိုက်တာ တစ်ခု ရှိနေပါတယ်။

Bug Hunt (အမှား ရှာပါ): FXML မှာ <TextField fx:id="nameInput"/> ပါပေမဲ့ button ကို နှိပ်တဲ့အခါ Controller က NullPointerException နဲ့ crash (program ရုတ်တရက် ပျက်ပြီး ရပ်သွားခြင်း) ဖြစ်ပါတယ်။

1public class FormController { 2 @FXML private Label status; 3 @FXML private TextField nameField; 4 5 @FXML 6 private void handleSubmit() { 7 String name = nameField.getText(); 8 status.setText("Hello, " + name); 9 } 10}

Line 3 အတွက် မှန်ကန်တဲ့ ပြင်နည်းကို ရွေးပါ:


Predict the Output (output ကို ကြိုခန့်မှန်းခြင်း)

ကြိုခန့်မှန်းပါ: Window စပွင့်လာတဲ့အခါ status label က ဘယ်စာသားကို ပြမလဲ။

// In FXML: <Label fx:id="status" text="Loading..."/>

// In Controller:
@FXML private Label status;

@FXML
private void initialize() {
    status.setText("Welcome!");
}

Practice Exercises (လေ့ကျင့်ခန်းများ)

(4 marks) စတာတွေက လေ့ကျင့်ခန်း တစ်ခုချင်းစီအတွက် ရနိုင်တဲ့ အမှတ်ပါ။ IB စာမေးပွဲ မေးခွန်းတွေမှာ အမှတ်ကို ဒီလို ပြလေ့ ရှိပါတယ်။

Core (အခြေခံ အဆင့်)

  1. FXML ကို အစကနေ ရေးခြင်း (4 marks): ဂဏန်း ရိုက်ထည့်ဖို့ TextField နှစ်ခု၊ Button လေးခု (+, -, *, /) နဲ့ ရလဒ် ပြဖို့ Label တစ်ခု ပါတဲ့ ရိုးရှင်းတဲ့ calculator (ဂဏန်း တွက်စက်) အတွက် FXML file တစ်ခုကို Construct (ကိုယ်တိုင် တည်ဆောက်ပါ) လုပ်ပါ။ Root layout အဖြစ် VBox ကို သုံးပါ။ Controller အတွက် လိုမယ့် fx:controller၊ fx:id နဲ့ onAction attribute တွေကို ထည့်ပါ။

  2. ကိုက်ညီတဲ့ Controller (4 marks): ကိုယ့် calculator FXML နဲ့ ကိုက်တဲ့ Controller class ကို Construct လုပ်ပါ။ Button တစ်ခုစီက ဂဏန်း နှစ်ခုကို ဖတ်ပြီး တွက်ချက်ကာ ရလဒ်ကို label မှာ ပြရပါမယ်။ Input (program ထဲ ထည့်ပေးတဲ့ ဒေတာ) က မှန်ကန်တဲ့ ဂဏန်း မဟုတ်တဲ့အခါ ဒါမှမဟုတ် 0 နဲ့ စားတဲ့အခါ ရလဒ်အစား message တစ်ခု ပြပါ။

Extension (တိုးချဲ့ အဆင့်)

  1. FXML အဖြစ် ပြောင်းခြင်း (6 marks): JavaFX Basics ထဲက Name List Manager ကို ယူပြီး FXML version တစ်ခု Construct လုပ်ပါ: UI layout ကို FXML file ထဲ ရွှေ့ပါ၊ event handler တွေကို Controller class ထဲ ရွှေ့ပါ၊ FXML file ကို load လုပ်တဲ့ Application class တစ်ခု ရေးပါ။ နာမည်တွေ သိမ်းတဲ့ ArrayList<String> ကို program ရဲ့ ရိုးရှင်းတဲ့ Model အဖြစ် Controller ထဲမှာ field တစ်ခုအနေနဲ့ ထားပါ။

  2. FXML screen အများ (6 marks): FXML file နှစ်ခုကို Construct လုပ်ပါ: login screen (username နဲ့ password field တွေ၊ login button တစ်ခု ပါတဲ့ login-view.fxml) နဲ့ welcome screen (နှုတ်ဆက်တဲ့ label တစ်ခု ပါတဲ့ welcome-view.fxml)။ Login screen ကို အရင် load လုပ်တဲ့ Application class နဲ့ login button ကို နှိပ်တဲ့အခါ window ကို welcome screen ဆီ ပြောင်းပေးတဲ့ code ကို ရေးပါ။ Screen ပြောင်းတာကို Application class က လုပ်လို့ ရပါတယ် (အဲဒီအခါ controller က Application class ဆီ ရောက်နိုင်မယ့် နည်းလမ်း တစ်ခု လိုပါတယ်၊ Multi-Window Apps မှာ နည်းလမ်း တစ်ခုကို ပြထားပါတယ်)၊ ဒါမှမဟုတ် login screen ရဲ့ controller ကိုယ်တိုင်က လုပ်လို့လည်း ရပါတယ်။

Challenge (စိန်ခေါ်မှု အဆင့်)

  1. House Point မှတ်တမ်း App (8 marks): House (ကျောင်းသားတွေကို ခွဲထားတဲ့ ကျောင်းတွင်း အသင်း) လေးခု ရှိတဲ့ ကျောင်း တစ်ကျောင်းအတွက် house point (house အလိုက် ရတဲ့ အမှတ်) တွေကို မှတ်တမ်းတင်တဲ့ FXML application တစ်ခုကို Construct လုပ်ပါ: house တွေက Cedar, Maple, Oak နဲ့ Willow ပါ။ ဆရာက ကျောင်းသား တစ်ယောက်ရဲ့ နာမည်ကို ရိုက်ထည့်၊ house တစ်ခုကို ရွေး၊ 1 ကနေ 5 အထိ point အရေအတွက်ကို ရိုက်ထည့်ပြီး Award ကို နှိပ်ပါတယ်။ Model၊ View နဲ့ Controller အဖြစ် ခွဲပြီး စီစဉ်ပါ:
    • Model: နာမည် အလွတ်၊ လေးခုထဲက မဟုတ်တဲ့ house နဲ့ 1 ကနေ 5 ပြင်ပက point တွေကို လက်မခံတဲ့ Award class တစ်ခုနဲ့ award (အမှတ် ပေးမှု) တွေကို ArrayList ထဲမှာ သိမ်းထားပြီး totalFor(String house) method ပါတဲ့ HousePoints class တစ်ခု။ စည်းမျဉ်းတွေနဲ့ တွက်ချက်မှုက ဒီမှာ ရှိရပါမယ်။
    • View: input သုံးခု၊ Award button၊ award တွေရဲ့ စာရင်း၊ house လေးခုရဲ့ စုစုပေါင်း အမှတ်တွေနဲ့ status label (အခြေအနေ ပြတဲ့ label) တစ်ခု ပါတဲ့ house-points-view.fxml။ Layout အားလုံးက ဒီမှာ ရှိရပါမယ်။
    • Controller: initialize() ထဲမှာ house ComboBox (drop-down list: နှိပ်လိုက်ရင် အောက်ကို ကျလာတဲ့ ရွေးချယ်စရာ စာရင်း) ကို ဖြည့်ပါတယ်၊ setModel() method ကနေတစ်ဆင့် Model ကို လက်ခံပါတယ်၊ Award button ကို ကိုင်တွယ်ပါတယ်၊ award တစ်ခုစီ ပြီးတိုင်း list နဲ့ စုစုပေါင်း အမှတ်တွေကို update လုပ်ပါတယ်။
    • Application class: FXML file ကို load လုပ်ပြီး HousePoints object အသစ် တစ်ခုကို controller ဆီ ပေးပါတယ်။
  2. App စဖွင့်ချိန် error ကို ရှင်းပြခြင်း (6 marks): ကျောင်းသား တစ်ယောက်ရဲ့ homework planner (အိမ်စာ အစီအစဉ် app) မှာ PlannerController တစ်ခု ရှိပြီး သူ့ initialize() method ထဲမှာ taskCount.setText("Tasks: " + model.size()); ဆိုတဲ့ line ပါပါတယ်။ Controller ထဲမှာ taskCount က FXML file ထဲက သူ့ fx:id နဲ့ နာမည် ကိုက်တဲ့ @FXML label တစ်ခုပါ။ Application class က loader.load() ကို run ပြီးမှ controller.setModel(new TaskList()); ကို run ပါတယ်။ Window မပွင့်ခင်မှာ program က NullPointerException နဲ့ ရပ်သွားပါတယ်။ loader.load() run တဲ့ အချိန်ကနေ ဘာတွေ ဖြစ်လဲဆိုတာ အစဉ်လိုက် ဖော်ပြရင်း ဘာကြောင့်လဲ ဆိုတာ Explain (ရှင်းပြပါ) လုပ်ပါ။ အတန်းဖော် တစ်ယောက်က line နှစ်ကြောင်းကို နေရာလဲဖို့ အကြံပေးပါတယ်၊ ဆိုလိုတာက controller.setModel(new TaskList()); ကို loader.load() မတိုင်ခင် run ဖို့ပါ။ အဲဒီနည်း ဘာကြောင့် အလုပ်မဖြစ်လဲ Explain လုပ်ပြီး အလုပ်ဖြစ်တဲ့ ပြင်နည်း တစ်ခုကို Describe (ဖော်ပြရှင်းပြပါ) လုပ်ပါ။ Code ရေးဖို့ မလိုပါဘူး။

ဆက်စပ် ခေါင်းစဉ်များ

  • အရင် လေ့လာထားရမယ့် ခေါင်းစဉ်: JavaFX Basics (JavaFX အခြေခံများ): control တွေနဲ့ event handling
  • အရင် လေ့လာထားရမယ့် ခေါင်းစဉ်: MVC Architecture (MVC ဖွဲ့စည်းပုံ): Model-View-Controller ကို နားလည်ခြင်း
  • နောက်ထပ် လေ့လာမယ့် ခေါင်းစဉ်: Multi-Window Apps (Window အများ ပါတဲ့ app များ): window (stage) အများကို စီမံပြီး သူတို့ကြား ဒေတာ မျှသုံးခြင်း
  • နောက်ထပ် လေ့လာမယ့် ခေါင်းစဉ်: File I/O in Apps: ဒေတာကို file ထဲ သိမ်းပြီး ပြန် load လုပ်ခြင်း
  • ဆက်စပ် ခေါင်းစဉ်: JavaFX Cookbook: Start here (JavaFX code နမူနာ စုစည်းမှု: ဒီကနေ စပါ): FXML နဲ့ controller တွေနဲ့ တည်ဆောက်ထားတဲ့ IA feature တွေအတွက် recipe (ချက်ချင်း ယူသုံးလို့ရတဲ့ code နမူနာ) များ

© EduCS.me. ကွန်ပျူတာသိပ္ပံ ပညာရေးအတွက် အရင်းအမြစ်တွေ စုထားတဲ့ နေရာပါ။ EduCS is an independent educational resource. It is not affiliated with, or endorsed by, the International Baccalaureate Organization. (EduCS ဟာ သီးခြား ပညာရေး အရင်းအမြစ်တစ်ခုပါ။ International Baccalaureate Organization နဲ့ ဆက်စပ်မှု မရှိသလို သူတို့ရဲ့ ထောက်ခံချက်လည်း မရှိပါဘူး။) ဆက်သွယ်ရန်