App structure and navigation (app ရဲ့ ဖွဲ့စည်းပုံနဲ့ screen တွေကြား သွားလာခြင်း)

IA (Internal Assessment) အတွက် tool တွေပါ၊ စာမေးပွဲ ခေါင်းစဉ် မဟုတ်ပါဘူး။ ဒါတွေက ကိုယ့် IA အတွက် ပြင်သုံးရမယ့် JavaFX (Java နဲ့ desktop app တည်ဆောက်တဲ့ library) ပုံစံတွေပါ။ IA ဆိုတာ IB သင်တန်းမှာ ကျောင်းသားတစ်ယောက်ချင်း ကိုယ်တိုင် တည်ဆောက်ရပြီး ကျောင်းက ဆရာက အမှတ်ပေးတဲ့ အပိုင်းပါ။ ဒီထဲက ဘာကိုမှ စာမေးပွဲမှာ မမေးပါဘူး၊ IA ကလည်း ကိုယ်တိုင် လုပ်တဲ့ အလုပ် ဖြစ်ရပါမယ်။

ဒီစာမျက်နှာက app တစ်ခုလုံးရဲ့ ပုံသဏ္ဌာန် အကြောင်းပါ: setup code (screen ဖွင့်ချိန်မှာ ကြိုလုပ်ထားရမယ့် ပြင်ဆင်မှု code) ဘယ်အချိန် run လဲ၊ menu တွေ၊ app ကို သေသေသပ်သပ် ပိတ်ခြင်း၊ ဒုတိယ window တစ်ခု ဖွင့်ခြင်း၊ screen တွေကြား ဒေတာ ပေးပို့ခြင်း၊ app တစ်ခုလုံးမှာ ဒေတာ မျှသုံးခြင်း၊ tab (window တစ်ခုထဲမှာ ခေါင်းစဉ်ကို နှိပ်ပြီး ပြောင်းကြည့်ရတဲ့ စာမျက်နှာ) တွေ ခွဲခြင်းနဲ့ app ကို ပြီးပြည့်စုံတဲ့ ပုံပေါက်အောင် CSS (screen ရဲ့ အရောင်၊ စာလုံး ပုံစံတွေကို သတ်မှတ်တဲ့ ရေးထုံး) အနည်းငယ် ထည့်ခြင်း တို့ပါ။

ဒီကို ပထမဆုံး ရောက်တာလား။ Start here: how every recipe fits together (ဒီမှာ စပါ: recipe တိုင်း ဘယ်လို ချိတ်ဆက်ထားလဲ) ကို အရင် ဖတ်ပါ။ အဲဒီ စာမျက်နှာက ကိုယ်ပြင်ရမယ့် နေရာ နှစ်ခုကို ရှင်းပြပါတယ်: SceneBuilder (FXML layout ကို ပုံနဲ့ မြင်ရင်း ပြင်တဲ့ editor) ထဲက layout (control တွေကို screen ပေါ်မှာ စီစဉ်ထားပုံ) နဲ့ controller (user လုပ်တာတွေကို ကိုင်တွယ်ပြီး Model နဲ့ screen တွေကို ချိတ်ပေးတဲ့ class) ပါ။ Recipe (ချက်ချင်း ယူသုံးလို့ရတဲ့ code နမူနာ) တိုင်း မှီခိုနေတဲ့ fx:id နဲ့ onAction ချိတ်ဆက်မှုတွေကိုလည်း ရှင်းပြပါတယ်။


Screen load လုပ်တဲ့အခါ setup code ကို run ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: screen တစ်ခုရဲ့ FXML ကို load လုပ်ပြီးတာနဲ့ JavaFX က အလိုအလျောက် ခေါ်တဲ့ initialize() method (သီးခြား အလုပ်တစ်ခု လုပ်ပေးတဲ့ code အပိုင်း) ကို သုံးပြီး screen ဖွင့်ချိန် ပြင်ဆင်မှုတွေ လုပ်ပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: table ဒါမှမဟုတ် combo box (ရွေးချယ်စရာ စာရင်း) ကို စတင် ဒေတာနဲ့ ဖြည့်တာ၊ default (မူလ သတ်မှတ်ထားတဲ့) ရွေးချယ်မှု သတ်မှတ်တာ၊ save လုပ်ထားတဲ့ record (အချက်အလက် အများကြီး စုထားတဲ့ ဒေတာ တစ်ခု) တွေကို load လုပ်တာ ဒါမှမဟုတ် ပထမ field ကို focus (keyboard နဲ့ ရိုက်ထည့်တာကို လက်ခံမယ့် control ဖြစ်နေခြင်း) ပေးတာ။ User က screen ကို မမြင်ခင် အဆင်သင့် ဖြစ်နေရမယ့် အရာ မှန်သမျှပါ။

SceneBuilder ထဲမှာ (layout): setup က ကိုင်တွယ်မယ့် control (button၊ text box စတဲ့ user သုံးတဲ့ အစိတ်အပိုင်း) တွေကို ထည့်ပြီး fx:id တွေ ပေးပါ။ ဒီမှာတော့ Label (စာသား ပြတဲ့ control) တစ်ခုပါ၊ သူ့ fx:id က lblWelcome ပါ။ Handler (button နှိပ်တာလို event ဖြစ်တဲ့အခါ run တဲ့ code) မလိုပါဘူး၊ initialize() က သူ့ဘာသာ run ပါတယ်။

<Label fx:id="lblWelcome" />

Controller ထဲမှာ (.java file):

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

@FXML private Label lblWelcome;

@FXML
public void initialize() {
    // Runs once, automatically, after the controls are built and injected.
    // your IA logic here: your opening setup
    lblWelcome.setText("Welcome back");
    loadSavedData();
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: // your IA logic here လို့ မှတ်ထားတဲ့ initialize() ရဲ့ body ပါ။ ဒါက ကိုယ့် table column တွေ သတ်မှတ်တာ၊ ဒေတာ load လုပ်တာနဲ့ default တွေပါ။ Method ကို ဘယ်အချိန် run မလဲ ဆိုတာကတော့ JavaFX က ပေးထားတဲ့ wiring (control တွေနဲ့ code ကို ချိတ်ဆက်ပေးတဲ့ အသင့် code) ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • Column သတ်မှတ်တာ၊ ဒေတာ load လုပ်တာနဲ့ default ရွေးချယ်မှုတွေကို ဒီမှာ ထည့်ပါ။
  • JavaFX က ခေါ်ပေးဖို့ method နာမည်က initialize ဖြစ်ရပါမယ်။ @FXML annotation (@ နဲ့ စပြီး code အကြောင်း Java tool တွေကို အချက်ပြတဲ့ အမှတ်အသား) ပါရင် method က private (class အပြင်ကနေ တိုက်ရိုက် သုံးလို့ မရတဲ့) ဖြစ်နေလည်း ရပါတယ်။

သတိထားရမယ့် အချက်များ:

  • initialize() ထဲမှာ သုံးတဲ့ fx:id မှန်သမျှက layout ထဲက control တစ်ခုနဲ့ အတိအကျ ကိုက်ရပါမယ်၊ မဟုတ်ရင် အဲဒီ field (object ထဲက အချက်အလက် အကွက်) က null (ဘာ object ကိုမှ မညွှန်းတဲ့ တန်ဖိုး) ဖြစ်နေပါမယ်။
  • @FXML field တွေက initialize() run ခါနီးအထိ null ဖြစ်နေတာကြောင့် controller ရဲ့ constructor (object ဖန်တီးချိန် attribute တွေကို စတင် သတ်မှတ်ပေးတဲ့ method) ထဲမှာ control တွေကို မသုံးပါနဲ့။ initialize() က control တွေကို စိတ်ချစွာ သုံးလို့ရတဲ့ အစောဆုံး နေရာပါ။
  • သူက screen ကို load လုပ်တိုင်း တစ်ကြိမ် run ပါတယ်၊ window ကို ပြန်ပြတိုင်း run တာ မဟုတ်ပါဘူး။

တွဲသုံးလို့ရတဲ့ recipe များ: Put the cursor where the user should start (user စတင် စာရိုက်ရမယ့် နေရာမှာ cursor ကို ထားခြင်း)၊ Display rows in a scrollable table (scroll လုပ်လို့ရတဲ့ table ထဲမှာ row တွေ ပြခြင်း)။


Menu bar မှာ လုပ်ဆောင်ချက်တွေ ထည့်ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: window ရဲ့ အပေါ်ဆုံးမှာ menu bar (window ထိပ်က menu တန်း) တစ်ခု ထည့်ပြီး နှိပ်လိုက်ရင် ကိုယ့် method တွေကို run ပေးတဲ့ item တွေ ထည့်ပေးပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: ပုံမှန် File၊ Edit၊ Help menu တွေ၊ ဒါမှမဟုတ် screen ပေါ်မှာ button တွေအဖြစ် မထားချင်တဲ့ command တွေကို အုပ်စုလိုက် စုထားတာမျိုးပါ။

SceneBuilder ထဲမှာ (layout): MenuBar တစ်ခု ထည့်ပြီး သူ့ထဲမှာ Menu တွေနဲ့ MenuItem တွေ ထည့်ကာ MenuItem တစ်ခုစီရဲ့ onAction ကို handler နာမည် တစ်ခုစီ သတ်မှတ်ပါ။

<MenuBar>
    <menus>
        <Menu text="File">
            <items>
                <MenuItem text="Save" onAction="#handleSave" />
                <MenuItem text="Exit" onAction="#handleExit" />
            </items>
        </Menu>
        <Menu text="Help">
            <items>
                <MenuItem text="About" onAction="#handleAbout" />
            </items>
        </Menu>
    </menus>
</MenuBar>

Controller ထဲမှာ (.java file):

@FXML
private void handleSave() {
    // your IA logic here: save the data
}

@FXML
private void handleExit() {
    // your IA logic here: quit the app
}

@FXML
private void handleAbout() {
    // your IA logic here: show an information dialog
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: // your IA logic here လို့ မှတ်ထားတဲ့ handler တစ်ခုစီရဲ့ body ပါ။ Menu ဖွဲ့စည်းပုံနဲ့ handler တွေဆီ ချိတ်ထားတဲ့ wiring က ပြန်သုံးလို့ ရပါတယ်။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • Menu တွေနဲ့ item တွေကို နာမည်ပြောင်းပြီး onAction တစ်ခုစီကို ကိုယ့် controller ထဲမှာ ရှိတဲ့ method တစ်ခုဆီ ညွှန်းပါ။
  • MenuItem ရဲ့ handler က button ရဲ့ handler နဲ့ အလုပ်လုပ်ပုံ အတူတူပဲ ဖြစ်လို့ method တစ်ခုတည်းကို menu item တစ်ခုနဲ့ toolbar (button တွေ တန်းစီထားတဲ့ ဘား) button တစ်ခုအတွက် ပြန်သုံးလို့ ရပါတယ်။

သတိထားရမယ့် အချက်များ:

  • onAction နာမည် တစ်ခုစီက @FXML method တစ်ခုနဲ့ အတိအကျ ကိုက်ရပါမယ်၊ # ကိုလည်း ထည့်ရေးရပါမယ်။ စာလုံး မှားရင် load လုပ်ချိန်မှာ error ဖြစ်ပါတယ်။

တွဲသုံးလို့ရတဲ့ recipe များ: App ကို သေသေသပ်သပ် ပိတ်ခြင်း၊ Tell the user something (user ကို တစ်ခုခု အသိပေးခြင်း)။


App ကို သေသေသပ်သပ် ပိတ်ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: application ကို ပိတ်ပါတယ်။ နည်းလမ်း နှစ်မျိုး ရှိပြီး မပိတ်ခင် ရှင်းလင်းစရာ (cleanup) ရှိတဲ့အခါ အဲဒီ ကွာခြားချက်က အရေးကြီးပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: Exit menu item ဒါမှမဟုတ် Quit button။

SceneBuilder ထဲမှာ (layout): Exit menu item ဒါမှမဟုတ် Quit button ကို ထည့်ပြီး သူ့ onAction ကို handleExit လို့ သတ်မှတ်ပါ။

<Button text="Quit" onAction="#handleExit" />

Controller ထဲမှာ (.java file):

import javafx.application.Platform;

@FXML
private void handleExit() {
    // your IA logic here: save anything unsaved before leaving
    quit();
}

// Cleaner: shuts JavaFX down, running your Application.stop() first.
private void quit() {
    Platform.exit();
}

// Harder stop: ends the program immediately.
private void forceQuit() {
    System.exit(0);
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: မပိတ်ခင် save လုပ်ရမယ့် အဆင့် ရှိရင် // your IA logic here လို့ မှတ်ထားတဲ့ နေရာမှာ ထည့်ပါ။ ပိတ်တဲ့ နည်းလမ်း နှစ်မျိုးက ပြန်သုံးလို့ရတဲ့ wiring ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • ပုံမှန် ပိတ်တဲ့အခါ Platform.exit() ကို ဦးစားပေး သုံးပါ။ ကိုယ့် Application class က stop() ကို override (parent ရဲ့ method ကို child က အစားထိုး ရေးခြင်း) လုပ်ထားရင် ဒေတာ save လုပ်တာ ဒါမှမဟုတ် resource (file၊ connection လို သုံးပြီးရင် ပြန်လွှတ်ပေးရမယ့် အရာ) တွေကို လွှတ်ပေးတာကို အဲဒီ method ထဲမှာ လုပ်ရပါတယ်၊ Platform.exit() က အဲဒီ method run ဖြစ်အောင် သေချာ လုပ်ပေးပါတယ်။
  • အဲဒီ cleanup မပါဘဲ ချက်ချင်း ရပ်ဖို့ တကယ် လိုမှပဲ System.exit(0) ကို သုံးပါ။

သတိထားရမယ့် အချက်များ:

  • System.exit(0) က Application.stop() ကို run မပေးတာကြောင့် အဲဒီထဲက ပိတ်ချိန် save လုပ်တဲ့ code ကို ကျော်သွားပါတယ်။ 0 က “ပုံမှန် ပြီးဆုံးတယ်” လို့ ဆိုလိုပါတယ်။
  • နောက်ဆုံး window ကို ပိတ်လိုက်ရင်လည်း ကိုယ့် setup ပေါ်မူတည်ပြီး app ပိတ်သွားနိုင်တာကြောင့် ကိုယ့် Exit control က တကယ် ဘယ်လမ်းကြောင်းကို သွားလဲ စမ်းကြည့်ပါ။

တွဲသုံးလို့ရတဲ့ recipe များ: Menu bar မှာ လုပ်ဆောင်ချက်တွေ ထည့်ခြင်း၊ Ask before doing something destructive (ဒေတာ ပျက်စေနိုင်တဲ့ အလုပ် မလုပ်ခင် အရင် မေးခြင်း)။


ဒုတိယ window တစ်ခု ဖွင့်ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: နောက်ထပ် FXML screen တစ်ခုကို Stage (window) အသစ် တစ်ခုထဲ load လုပ်ပြီး သီးခြား window တစ်ခုအဖြစ် ပြပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: main screen ရဲ့ အပေါ်မှာ ပွင့်လာမယ့် Add ဒါမှမဟုတ် Edit dialog (user ဆီက တစ်ခုခုကို မေးတဲ့ window အသေး)၊ details window ဒါမှမဟုတ် settings window မျိုးပါ။

SceneBuilder ထဲမှာ (layout): FXML file (screen ပုံစံကို ဖော်ပြတဲ့ file) နှစ်ခု လိုပါတယ်: ဒုတိယ screen ကို ဖွင့်ပေးတဲ့ button ပါတဲ့ main screen (onAction က handleOpenDetails) နဲ့ ကိုယ်ပိုင် controller ပါတဲ့ ဒုတိယ screen (Details.fxml) ပါ။

<Button text="Details..." onAction="#handleOpenDetails" />

Controller ထဲမှာ (.java file):

Import line များ:

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Modality;
import javafx.stage.Stage;
import java.io.IOException;

Method:

@FXML
private void handleOpenDetails() {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Details.fxml"));
        Parent root = loader.load();

        Stage stage = new Stage();
        stage.setTitle("Details");
        stage.setScene(new Scene(root));
        stage.show();
    } catch (IOException e) {
        // handle the error
    }
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: ဒီ recipe က အများစု wiring ပါ။ ကိုယ့်အပိုင်းက ဘယ် ဒုတိယ screen ကို ဖွင့်မလဲ (Details.fxml) ဆိုတာပါ၊ အဲဒီ screen မှာ ဒေတာ လိုရင်တော့ နောက် recipe ကို ကြည့်ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • "Details.fxml" ကို ကိုယ့် ဒုတိယ screen ရဲ့ FXML file နဲ့ ပြောင်းပြီး title ကိုလည်း ကိုက်အောင် ပြောင်းပါ။
  • ဥပမာကို window နှစ်ခုအထိပဲ ထားပါ။ ဒီထက် များရင် tab တွေနဲ့ ဒါမှမဟုတ် scene (window ထဲမှာ ပြတဲ့ screen တစ်ခုစာ content) လဲပြီး ပြတာနဲ့ ကိုင်တွယ်တာက များသောအားဖြင့် ပိုကောင်းပါတယ်။

သတိထားရမယ့် အချက်များ:

  • onAction မှာ ပေးထားတဲ့ handleOpenDetails method က controller ထဲမှာ ရှိရပါမယ်၊ မဟုတ်ရင် screen ကို load လုပ်လို့ မရပါဘူး။
  • getResource("Details.fxml") က ကိုယ့် class ရဲ့ ဘေးမှာ (folder တူထဲမှာ) file ကို ရှာပါတယ်။ null ကို return (ရလဒ်အဖြစ် ပြန်ပေး) လုပ်ရင် path (file ရှိရာ လမ်းကြောင်း) ဒါမှမဟုတ် file နာမည် မှားနေပါတယ်။
  • Stage အသစ်က ပထမ window နဲ့ ယှဉ်ပြီး ပွင့်လာပါတယ်။ ဒုတိယ window ကို မပိတ်မချင်း ပထမ window ကို သုံးလို့ မရအောင် တားချင်ရင် မပြခင် stage.initModality(Modality.APPLICATION_MODAL) ကို ခေါ်ပါ။

တွဲသုံးလို့ရတဲ့ recipe များ: Screen တွေကြား အချက်အလက် ပေးပို့ခြင်း၊ App တစ်ခုလုံးမှာ ဒေတာ မျှသုံးခြင်း။


Screen တွေကြား အချက်အလက် ပေးပို့ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: FXMLLoader ကနေ ဒုတိယ screen ရဲ့ controller ကို ယူပြီး သူ့ပေါ်က public (ဘယ်ကနေမဆို သုံးလို့ရတဲ့) setter (private attribute ရဲ့ တန်ဖိုးကို ပြောင်းတဲ့ method) တစ်ခုကို ခေါ်ကာ ဒေတာကို ဒုတိယ screen ဆီ ပေးပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: user ရွေးထားတဲ့ row (table ထဲက အလျားလိုက် အတန်း) အတွက် edit window ဖွင့်တာ၊ ဒါမှမဟုတ် ရွေးထားတဲ့ တန်ဖိုး တစ်ခုကို details screen ဆီ ပို့တာမျိုးပါ။

SceneBuilder ထဲမှာ (layout): ယခင် recipe က FXML file နှစ်ခုပါပဲ။ ဒုတိယ screen ရဲ့ controller (EditController) မှာ public setter တစ်ခု ထပ်ထည့်ပါတယ်။

Controller ထဲမှာ (.java file): ဒါက ယခင် recipe ပေါ်မှာ ဆက်တည်ဆောက်တာပါ။ Line အသစ်တွေက controller ကို ယူတာနဲ့ သူ့ setter ကို ခေါ်တာပါ။

private void editItem(Item selected) {
    try {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("Edit.fxml"));
        Parent root = loader.load();

        // Get the controller of the screen we just loaded, then pass data in.
        EditController controller = loader.getController();
        controller.setItem(selected);

        Stage stage = new Stage();
        stage.setScene(new Scene(root));
        stage.show();
    } catch (IOException e) {
        // handle the error
    }
}

ဒုတိယ screen ရဲ့ controller မှာ public setter တစ်ခု ပါပါတယ်:

public class EditController {

    private Item item;

    public void setItem(Item item) {
        this.item = item;
        // your IA logic here: fill this screen's fields from the item
    }
}

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: // your IA logic here လို့ မှတ်ထားတဲ့ setItem(...) ရဲ့ body ပါ၊ အဲဒီမှာ ပေးလိုက်တဲ့ ဒေတာနဲ့ ဒုတိယ screen ကို ဖြည့်ပါ။ Controller ကို ယူတာနဲ့ setter ကို ခေါ်တာက ပြန်သုံးလို့ရတဲ့ wiring ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • EditController နဲ့ setItem(...) ကို ကိုယ့် controller နဲ့ အဲဒီ screen လိုအပ်တဲ့ ဒေတာကို ယူတဲ့ setter တစ်ခုနဲ့ အစားထိုးပါ။
  • ပေးလိုက်တဲ့ ဒေတာနဲ့ ဒုတိယ screen ရဲ့ field တွေကို ဖြည့်ဖို့ setter က သဘာဝကျတဲ့ နေရာလည်း ဖြစ်ပါတယ်။

သတိထားရမယ့် အချက်များ:

  • loader.getController() ကို loader.load() ပြီးမှ ခေါ်ပါ၊ မတိုင်ခင် မခေါ်ပါနဲ့။ Load မလုပ်ခင်မှာ controller မရှိသေးလို့ null ကို return လုပ်ပါတယ်။
  • တခြား controller ရဲ့ field တွေထဲ တိုက်ရိုက် လှမ်းထည့်တာ မဟုတ်ဘဲ public setter ကနေတစ်ဆင့် ဒေတာ ပေးပါ။ Setter က screen တွေကို loose coupling (class တွေ အချင်းချင်း သိပ် မမှီခိုခြင်း) ဖြစ်အောင် ထားပေးပါတယ်။

တွဲသုံးလို့ရတဲ့ recipe များ: ဒုတိယ window တစ်ခု ဖွင့်ခြင်း၊ Find out which row the user clicked (user နှိပ်လိုက်တဲ့ row ကို သိခြင်း)။


App တစ်ခုလုံးမှာ ဒေတာ မျှသုံးခြင်း

ဒါက ဘာလုပ်ပေးလဲ: screen တိုင်းက ဖတ်လို့ ရေးလို့ရတဲ့ မျှသုံး object တစ်ခုကို ထားပေးပါတယ်။ အဲဒီ object က တစ်ခုတည်းပဲ အမြဲ ရှိအောင် Singleton (instance တစ်ခုတည်းပဲ ရှိတဲ့ class ပုံစံ) class အသေးလေး တစ်ခုကို သုံးပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: login ဝင်ထားတဲ့ user၊ app တစ်ခုလုံးအတွက် setting တွေ ဒါမှမဟုတ် screen အများက အတူ သုံးတဲ့ ဈေးဝယ်တောင်း (shopping cart) တစ်ခုတည်း မျိုးပါ။

SceneBuilder ထဲမှာ (layout): ထည့်စရာ မရှိပါဘူး၊ ဒါက ကိုယ်ပိုင် screen မရှိတဲ့ ရိုးရိုး class တစ်ခုပါ။

Controller ထဲမှာ (.java file):

public class AppSettings {

    private static AppSettings instance;     // the one and only instance

    // your IA logic here: the fields you want to share app-wide
    private String currentUser;

    private AppSettings() {
        // private constructor: nothing outside can make a second one
    }

    public static AppSettings getInstance() {
        if (instance == null) {
            instance = new AppSettings();
        }
        return instance;
    }

    public String getCurrentUser() {
        return currentUser;
    }

    public void setCurrentUser(String currentUser) {
        this.currentUser = currentUser;
    }
}

ဘယ် screen ကမဆို မျှသုံး object တစ်ခုတည်းကိုပဲ သုံးပါတယ်:

AppSettings.getInstance().setCurrentUser("Sam");        // on the login screen
String user = AppSettings.getInstance().getCurrentUser(); // on any other screen

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: မျှသုံးမယ့် ဒေတာအတွက် ကိုယ်ထည့်တဲ့ field တွေနဲ့ getter (private attribute ရဲ့ တန်ဖိုးကို ပြန်ပေးတဲ့ method)၊ setter တွေပါ၊ // your IA logic here လို့ မှတ်ထားပါတယ်။ Singleton ဖွဲ့စည်းပုံ (private constructor နဲ့ static getInstance()) က ပြန်သုံးလို့ရတဲ့ wiring ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • Screen တွေကြား မျှသုံးဖို့ လိုတာ မှန်သမျှအတွက် field တွေ၊ getter တွေ၊ setter တွေ ထည့်ပါ။
  • Singleton ဖြစ်အောင် လုပ်ပေးတဲ့ အပိုင်း နှစ်ခုက private constructor နဲ့ static getInstance() method ပါ။ နှစ်ခုလုံးကို ထားပါ။

သတိထားရမယ့် အချက်များ:

  • Singleton က မျှသုံးတဲ့ global state (program တစ်ခုလုံးက ရောက်နိုင်တဲ့ ဒေတာ) ပါ။ App တစ်ခုလုံးနဲ့ တကယ် ဆိုင်တဲ့ ဒေတာအတွက် အသုံးဝင်ပေမဲ့ အရာအားလုံးကို အဲဒီထဲ မထည့်ပါနဲ့၊ ထည့်ရင် screen တွေကို လိုက်ကြည့်ရ ခက်သွားပါတယ်။ ဒေတာ အများစုကို အပေါ်က setter နည်းနဲ့ပဲ ဆက်ပေးပို့သင့်ပါတယ်။
  • ဒါက HL (Higher Level: IB သင်တန်းမှာ ပိုနက်နက်ရှိုင်းရှိုင်း လေ့လာရတဲ့ အဆင့်) ရဲ့ design pattern (ထပ်ကြုံရတဲ့ ဒီဇိုင်း ပြဿနာအတွက် ဖြေရှင်းပုံစံ) သင်ခန်းစာထဲက Singleton အယူအဆနဲ့ အတူတူပါ။ SL (Standard Level: IB သင်တန်းရဲ့ အခြေခံ အဆင့်) ကျောင်းသားတွေက သီအိုရီ အပြည့်အစုံ မလိုဘဲ recipe အနေနဲ့ သုံးလို့ ရပါတယ်။

တွဲသုံးလို့ရတဲ့ recipe များ: Screen တွေကြား အချက်အလက် ပေးပို့ခြင်း၊ Save and load text (စာသားကို save လုပ်ခြင်းနဲ့ load လုပ်ခြင်း)။


App ကြီးကို tab တွေ ခွဲခြင်း

ဒါက ဘာလုပ်ပေးလဲ: window တစ်ခုထဲမှာ အပိုင်း အများကို tab စာမျက်နှာတွေအဖြစ် ထားပေးပါတယ်၊ ဒါကြောင့် user က window အသစ်တွေ မဖွင့်ဘဲ အပိုင်းတွေကြား ပြောင်းကြည့်နိုင်ပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: ဥပမာ ဒေတာ ထည့်တဲ့ အပိုင်း၊ list ပြတဲ့ အပိုင်းနဲ့ settings အပိုင်းကို window တစ်ခုတည်းထဲမှာ ခွဲထားတာမျိုးပါ။

SceneBuilder ထဲမှာ (layout): TabPane (tab တွေကို ကိုင်ထားတဲ့ container) တစ်ခု ထည့်ပြီး အပိုင်း တစ်ခုစီအတွက် Tab တစ်ခုစီ ထည့်ကာ Tab တစ်ခုစီရဲ့ content ထဲမှာ အဲဒီ အပိုင်းရဲ့ control တွေကို ထည့်ပါ။

<TabPane>
    <tabs>
        <Tab text="Items" closable="false">
            <content>
                <!-- the controls for this section go here -->
            </content>
        </Tab>
        <Tab text="Settings" closable="false">
            <content>
                <!-- the controls for this section go here -->
            </content>
        </Tab>
    </tabs>
</TabPane>

Controller ထဲမှာ (.java file): ဘာမှ ထူးထူးခြားခြား မလိုပါဘူး၊ tab တစ်ခုစီထဲက control တွေကို screen တစ်ခုတည်းမှာလိုပဲ fx:id နဲ့ onAction နဲ့ ချိတ်ပါတယ်။

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: tab တစ်ခုစီထဲက control တွေနဲ့ သူတို့ရဲ့ handler တွေပါ၊ ဒါတွေက ဒီ Cookbook ရဲ့ တခြား အပိုင်းတွေက ပုံမှန် recipe တွေပါပဲ။ TabPane က layout ပိုင်းဆိုင်ရာ wiring ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • အပိုင်း တစ်ခုစီအတွက် Tab တစ်ခုစီ ထည့်ပြီး အဲဒီ အပိုင်းရဲ့ control တွေကို သူ့ <content> ထဲမှာ ထည့်ပါ။
  • User မပိတ်နိုင်စေချင်တဲ့ tab တွေမှာ closable="false" ကို သတ်မှတ်ပါ။

သတိထားရမယ့် အချက်များ:

  • Tab အားလုံးက window တစ်ခုတည်းကို မျှသုံးပြီး default အနေနဲ့ controller တစ်ခုတည်းကိုလည်း မျှသုံးပါတယ်။ App ကြီးကြီးအတွက် အပိုင်း တစ်ခုစီမှာ ကိုယ်ပိုင် controller ရှိအောင် tab တစ်ခုစီရဲ့ content ထဲကို သီးခြား FXML တစ်ခုစီ load လုပ်လို့ ရပါတယ်။
  • Tab တွေက အလုပ် တစ်ခုတည်းရဲ့ အပိုင်းတွေနဲ့ ကိုက်ပါတယ်။ တကယ် သီးခြား အလုပ်တွေအတွက်တော့ ဒုတိယ window က ပိုရှင်းလေ့ ရှိပါတယ်။

တွဲသုံးလို့ရတဲ့ recipe များ: ဒုတိယ window တစ်ခု ဖွင့်ခြင်း၊ App ကို ပြီးပြည့်စုံတဲ့ ပုံပေါက်အောင် လုပ်ခြင်း။


App ကို ပြီးပြည့်စုံတဲ့ ပုံပေါက်အောင် လုပ်ခြင်း

ဒါက ဘာလုပ်ပေးလဲ: scene ကို CSS stylesheet (screen ရဲ့ အရောင်၊ စာလုံး ပုံစံတွေကို သတ်မှတ်တဲ့ CSS file) တစ်ခု ချိတ်ပေးပါတယ်၊ ဒါကြောင့် Java code ကို မပြောင်းဘဲ နေရာခြားပုံ (spacing) နဲ့ အရောင်တွေကို ပိုကောင်းအောင် လုပ်နိုင်ပါတယ်။

IA မှာ ဘယ်အချိန် သုံးမလဲ: app တည်ဆောက်တာ ပြီးခါနီးမှာ လုပ်ရ လွယ်ပြီး အကျိုးသက်ရောက်မှု များတဲ့ အဆင့်ပါ၊ app ကို “default မီးခိုးရောင်” ကနေ စဉ်းစားပြီး ဒီဇိုင်းဆွဲထားတဲ့ ပုံပေါက်အောင် ပြောင်းပေးပါတယ်။

SceneBuilder ထဲမှာ (layout): ပုံစံ ပြောင်းချင်တဲ့ node (scene ထဲမှာ ပါတဲ့ အစိတ်အပိုင်း) တွေကို style class (CSS rule နဲ့ ချိတ်ဖို့ node ကို ပေးတဲ့ နာမည်) တစ်ခု ပေးပါ (Properties panel ထဲက “Style Class”)၊ ဥပမာ ခေါင်းစဉ် label ပေါ်မှာ primary-heading။ Stylesheet ကို ဒီမှာ root node (အပေါ်ဆုံး node၊ များသောအားဖြင့် layout container တစ်ခု) ပေါ်မှာလည်း ချိတ်လို့ ရပါတယ်၊ ဒါမှမဟုတ် အောက်မှာ ပြထားသလို code ထဲမှာ ချိတ်ပါ။

Controller ထဲမှာ (.java file): scene ကို တည်ဆောက်တဲ့အခါ stylesheet ကို ချိတ်ပါ။

Scene scene = new Scene(root, 600, 400);
scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm());

ကိုယ့် class တွေရဲ့ ဘေးမှာ (folder တူထဲမှာ) ထားတဲ့ style.css အသေးလေး တစ်ခု:

.root {
    -fx-font-family: "Segoe UI", sans-serif;
    -fx-background-color: #f4f6fb;
}

.button {
    -fx-background-color: #2d6cdf;
    -fx-text-fill: white;
    -fx-padding: 8 16 8 16;
    -fx-background-radius: 6;
}

.primary-heading {
    -fx-font-size: 18px;
    -fx-font-weight: bold;
}

ကိုက်ညီတဲ့ CSS rule သက်ရောက်အောင် code ထဲမှာ node ကို style class တစ်ခု ပေးပါ:

lblTitle.getStyleClass().add("primary-heading");

ကိုယ်ပိုင် logic ထည့်ရမယ့် နေရာ: stylesheet ထဲက အရောင်တွေ၊ font တွေနဲ့ နေရာခြားပုံတွေက ကိုယ့် ဒီဇိုင်း ရွေးချယ်မှုတွေပါ။ Stylesheet ကို ချိတ်တာက ပြန်သုံးလို့ရတဲ့ wiring ပါ။

ကိုယ့် project နဲ့ ကိုက်အောင် ပြင်ပါ:

  • အရောင်တွေ၊ font တွေနဲ့ နေရာခြားပုံကို ကိုယ့် app နဲ့ ကိုက်အောင် ပြောင်းပါ။ သုံးမယ့် အရောင်တွေကို နည်းနည်းပဲ ထားပါ၊ အရောင် နှစ်မျိုး သုံးမျိုးဆို လုံလောက်ပါတယ်။
  • ပုံစံ တသမတ်တည်း ဖြစ်အောင် control အမျိုးအစား တစ်ခုလုံးကို (.button) ပုံစံ ပေးပြီး သီးသန့် node တွေအတွက် နာမည်ပေးထားတဲ့ style class (.primary-heading) တွေကို သုံးပါ။

သတိထားရမယ့် အချက်များ:

  • JavaFX CSS က web CSS နဲ့ ဆင်တူပေမဲ့ အတူတူ မဟုတ်ပါဘူး: property (အရောင်၊ နေရာခြားပုံ စတဲ့ ပုံစံ setting တစ်ခုစီ) နာမည်တွေက -fx- နဲ့ စပါတယ် (ဥပမာ background-color မဟုတ်ဘဲ -fx-background-color)။ Web CSS property နာမည်တွေကို JavaFX က လျစ်လျူရှုပါလိမ့်မယ်။
  • Stylesheet က ဘာမှ အကျိုးသက်ရောက်မှု မရှိသလို ဖြစ်နေရင် getResource("style.css") က file ကို ရှာတွေ့လား စစ်ပါ။ အဲဒီမှာ null ဖြစ်နေရင် path မှားနေတာပါ။

တွဲသုံးလို့ရတဲ့ recipe များ: App ကြီးကို tab တွေ ခွဲခြင်း၊ Put an image on a button (button ပေါ်မှာ ပုံ ထည့်ခြင်း)။