์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐ŸŽจ JavaFX๋กœ ๋งŒ๋“œ๋Š” ๋‚˜๋งŒ์˜ ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์‹ค์ „ ๊ฐ€์ด๋“œ

๐ŸŽจ JavaFX๋กœ ๋งŒ๋“œ๋Š” ๋‚˜๋งŒ์˜ ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ์‹ค์ „ ๊ฐ€์ด๋“œ

์นœ๊ตฌ์ฒ˜๋Ÿผ ์‰ฝ๊ฒŒ ๋ฐฐ์šฐ๋Š” JavaFX ์™„์ „์ •๋ณต ๐Ÿš€

๐ŸŒŸ JavaFX, ์™œ ์ง€๊ธˆ ๋ฐฐ์›Œ์•ผ ํ• ๊นŒ?

์•ˆ๋…•! ์˜ค๋Š˜์€ JavaFX๋ฅผ ํ™œ์šฉํ•ด์„œ ๋ฉ‹์ง„ ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“œ๋Š” ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด ์ด์•ผ๊ธฐํ•ด๋ณผ๊ฒŒ. ํ˜น์‹œ "์š”์ฆ˜ ์‹œ๋Œ€์— ๋ฐ์Šคํฌํ†ฑ ์•ฑ์ด ํ•„์š”ํ•ด?"๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜๋„ ์žˆ๋Š”๋ฐ, ์‚ฌ์‹ค ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์—ฌ์ „ํžˆ ์—„์ฒญ๋‚˜๊ฒŒ ๋งŽ์ด ์‚ฌ์šฉ๋˜๊ณ  ์žˆ์–ด. ํŠนํžˆ ์—…๋ฌด์šฉ ํ”„๋กœ๊ทธ๋žจ, ๊ฐœ๋ฐœ ๋„๊ตฌ, ๋ฏธ๋””์–ด ํŽธ์ง‘ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐ™์€ ๋ถ„์•ผ์—์„œ๋Š” ๋ฐ์Šคํฌํ†ฑ ์•ฑ์ด ํ•„์ˆ˜์ง€! ๐Ÿ’ป

JavaFX๋Š” Java ๊ธฐ๋ฐ˜์˜ GUI(Graphical User Interface) ํ”„๋ ˆ์ž„์›Œํฌ๋กœ, ์˜ˆ์ „์— ๋งŽ์ด ์“ฐ์ด๋˜ Swing์„ ๋Œ€์ฒดํ•˜๊ธฐ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ์–ด. Swing๋ณด๋‹ค ํ›จ์”ฌ ํ˜„๋Œ€์ ์ด๊ณ  ์„ธ๋ จ๋œ UI๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ , CSS ์Šคํƒ€์ผ๋ง๋„ ์ง€์›ํ•ด์„œ ์›น ๊ฐœ๋ฐœ์ž๋“ค์—๊ฒŒ๋„ ์นœ์ˆ™ํ•˜๊ฒŒ ๋А๊ปด์งˆ ๊ฑฐ์•ผ. ๊ฒŒ๋‹ค๊ฐ€ ์• ๋‹ˆ๋ฉ”์ด์…˜, ๋ฉ€ํ‹ฐ๋ฏธ๋””์–ด, 3D ๊ทธ๋ž˜ํ”ฝ๊นŒ์ง€ ์ง€์›ํ•˜๋‹ˆ๊นŒ ์ •๋ง ๊ฐ•๋ ฅํ•˜์ง€? ๐Ÿ˜Ž

๐Ÿ’ก JavaFX์˜ ํ•ต์‹ฌ ์žฅ์ 
  • โœ… ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์ง€์› (Windows, macOS, Linux ๋ชจ๋‘ OK!)
  • โœ… CSS๋ฅผ ํ™œ์šฉํ•œ ์Šคํƒ€์ผ๋ง์œผ๋กœ ๋””์ž์ธ ์ž์œ ๋„ UP
  • โœ… FXML์„ ํ†ตํ•œ UI์™€ ๋กœ์ง์˜ ๋ช…ํ™•ํ•œ ๋ถ„๋ฆฌ
  • โœ… Scene Builder๋กœ ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ UI ๋””์ž์ธ ๊ฐ€๋Šฅ
  • โœ… ํ’๋ถ€ํ•œ ์ปจํŠธ๋กค๊ณผ ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ œ๊ณต
  • โœ… Java ์ƒํƒœ๊ณ„์˜ ๋ชจ๋“  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ํ™œ์šฉ ๊ฐ€๋Šฅ
JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฒ„ํŠผ ์ž…๋ ฅ์ฐฝ ์ฐจํŠธ ํ…Œ์ด๋ธ” JavaFX ๊ตฌ์„ฑ์š”์†Œ Scene Stage Node Layout ๊ฐœ๋ฐœ ๋„๊ตฌ Scene Builder IntelliJ IDEA Eclipse ๐ŸŽจ ํฌ๋กœ์Šค ํ”Œ๋žซํผ ์ง€์› Windows ยท macOS ยท Linux ๐Ÿš€ ๐Ÿ’ป

๐Ÿ› ๏ธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •ํ•˜๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ JavaFX ๊ฐœ๋ฐœ์„ ์‹œ์ž‘ํ•ด๋ณผ๊นŒ? ๋จผ์ € ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์„ ์„ธํŒ…ํ•ด์•ผ ํ•˜๋Š”๋ฐ, ์ƒ๊ฐ๋ณด๋‹ค ๊ฐ„๋‹จํ•ด! ์ฐจ๊ทผ์ฐจ๊ทผ ๋”ฐ๋ผ์˜ค๋ฉด ๊ธˆ๋ฐฉ ์ค€๋น„ํ•  ์ˆ˜ ์žˆ์–ด. โ˜•

1

JDK ์„ค์น˜

JavaFX๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋จผ์ € JDK(Java Development Kit)๊ฐ€ ํ•„์š”ํ•ด. JDK 11 ์ด์ƒ์„ ๊ถŒ์žฅํ•˜๋Š”๋ฐ, ์ตœ์‹  ๋ฒ„์ „์ธ JDK 17์ด๋‚˜ JDK 21์„ ์‚ฌ์šฉํ•˜๋ฉด ๋” ์ข‹์•„. Oracle JDK๋‚˜ OpenJDK ์ค‘์—์„œ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, ๊ฐœ์ธ์ ์œผ๋กœ๋Š” OpenJDK๋ฅผ ์ถ”์ฒœํ•ด! ๋ฌด๋ฃŒ๊ณ  ์„ฑ๋Šฅ๋„ ์ข‹๊ฑฐ๋“ . ๐Ÿ˜Š

Oracle ๊ณต์‹ ์‚ฌ์ดํŠธ๋‚˜ AdoptOpenJDK์—์„œ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด. ์„ค์น˜ ํ›„์—๋Š” ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์„ค์ •๋„ ์žŠ์ง€ ๋ง๊ณ !

2

JavaFX SDK ๋‹ค์šด๋กœ๋“œ

JDK 11๋ถ€ํ„ฐ๋Š” JavaFX๊ฐ€ JDK์—์„œ ๋ถ„๋ฆฌ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ ๋‹ค์šด๋กœ๋“œํ•ด์•ผ ํ•ด. Gluon ๊ณต์‹ ์‚ฌ์ดํŠธ(gluonhq.com/products/javafx)์—์„œ ์šด์˜์ฒด์ œ์— ๋งž๋Š” JavaFX SDK๋ฅผ ๋‹ค์šด๋ฐ›์œผ๋ฉด ๋ผ.

๋‹ค์šด๋กœ๋“œํ•œ ํŒŒ์ผ์˜ ์••์ถ•์„ ํ’€๋ฉด lib ํด๋” ์•ˆ์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ JAR ํŒŒ์ผ๋“ค์ด ์žˆ์„ ๊ฑฐ์•ผ. ์ด ํŒŒ์ผ๋“ค์„ ํ”„๋กœ์ ํŠธ์— ์ถ”๊ฐ€ํ•ด์ฃผ๋ฉด JavaFX๋ฅผ ์‚ฌ์šฉํ•  ์ค€๋น„๊ฐ€ ๋๋‚˜๋Š” ๊ฑฐ์ง€! ๐ŸŽฏ

3

IDE ์„ค์ • (IntelliJ IDEA ๊ธฐ์ค€)

IDE๋Š” IntelliJ IDEA, Eclipse, NetBeans ๋“ฑ ๋ญ๋“  ์ƒ๊ด€์—†์ง€๋งŒ, ์—ฌ๊ธฐ์„œ๋Š” IntelliJ IDEA๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์„ค๋ช…ํ• ๊ฒŒ. IntelliJ๋Š” JavaFX ๊ฐœ๋ฐœ์— ์ •๋ง ํŽธ๋ฆฌํ•œ ๊ธฐ๋Šฅ๋“ค์„ ๋งŽ์ด ์ œ๊ณตํ•˜๊ฑฐ๋“ ! ๐Ÿ’ช

ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ๋‹จ๊ณ„:
1. File โ†’ New โ†’ Project ์„ ํƒ
2. JavaFX ํ…œํ”Œ๋ฆฟ ์„ ํƒ (์—†๋‹ค๋ฉด Java ํ”„๋กœ์ ํŠธ๋กœ ์ƒ์„ฑ)
3. Project SDK๋ฅผ ์„ค์น˜ํ•œ JDK๋กœ ์„ค์ •
4. File โ†’ Project Structure โ†’ Libraries์—์„œ JavaFX SDK์˜ lib ํด๋” ์ถ”๊ฐ€
5. Run โ†’ Edit Configurations์—์„œ VM options์— ๋‹ค์Œ ์ถ”๊ฐ€:

--module-path "JavaFX SDK ๊ฒฝ๋กœ/lib" --add-modules javafx.controls,javafx.fxml

์ด๋ ‡๊ฒŒ ์„ค์ •ํ•˜๋ฉด JavaFX ๋ชจ๋“ˆ์„ ์ œ๋Œ€๋กœ ์ธ์‹ํ•  ์ˆ˜ ์žˆ์–ด!

๐Ÿ’ก Maven์ด๋‚˜ Gradle ์‚ฌ์šฉํ•˜๊ธฐ ์ˆ˜๋™์œผ๋กœ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒŒ ๋ฒˆ๊ฑฐ๋กญ๋‹ค๋ฉด Maven์ด๋‚˜ Gradle ๊ฐ™์€ ๋นŒ๋“œ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑธ ๊ฐ•๋ ฅ ์ถ”์ฒœํ•ด! ์˜์กด์„ฑ ๊ด€๋ฆฌ๊ฐ€ ํ›จ์”ฌ ํŽธํ•ด์ง€๊ฑฐ๋“ . ํŠนํžˆ ํŒ€ ํ”„๋กœ์ ํŠธ๋ฅผ ํ•  ๋•Œ๋Š” ํ•„์ˆ˜์•ผ.

Maven์˜ ๊ฒฝ์šฐ pom.xml์— JavaFX ์˜์กด์„ฑ๋งŒ ์ถ”๊ฐ€ํ•˜๋ฉด ์ž๋™์œผ๋กœ ํ•„์š”ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•ด์ค˜. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€?

๐ŸŽฏ ์ฒซ ๋ฒˆ์งธ JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋งŒ๋“ค๊ธฐ

์ž, ์ด์ œ ํ™˜๊ฒฝ ์„ค์ •๋„ ๋๋‚ฌ์œผ๋‹ˆ ๋ณธ๊ฒฉ์ ์œผ๋กœ ์ฒซ ๋ฒˆ์งธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์ „ํ†ต์ ์ธ "Hello World"๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด๋ณด์ž! ๐Ÿ‘‹

๐Ÿ“ ๊ธฐ๋ณธ ๊ตฌ์กฐ ์ดํ•ดํ•˜๊ธฐ

JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํฌ๊ฒŒ ์„ธ ๊ฐ€์ง€ ํ•ต์‹ฌ ๊ฐœ๋…์œผ๋กœ ๊ตฌ์„ฑ๋ผ:

1. Stage (๋ฌด๋Œ€) ๐ŸŽญ
Stage๋Š” ๋ง ๊ทธ๋Œ€๋กœ ๋ฌด๋Œ€์•ผ. ์œˆ๋„์šฐ ์ฐฝ ์ž์ฒด๋ฅผ ์˜๋ฏธํ•˜์ง€. ํ•˜๋‚˜์˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ์—ฌ๋Ÿฌ ๊ฐœ์˜ Stage๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์–ด. ๋ฉ”์ธ ์œˆ๋„์šฐ, ํŒ์—… ์ฐฝ, ๋‹ค์ด์–ผ๋กœ๊ทธ ๋“ฑ์ด ๋ชจ๋‘ ๊ฐ๊ฐ์˜ Stage์•ผ.

2. Scene (์žฅ๋ฉด) ๐ŸŽฌ
Scene์€ Stage ์œ„์— ์˜ฌ๋ผ๊ฐ€๋Š” ์ปจํ…์ธ  ์ปจํ…Œ์ด๋„ˆ์•ผ. ํ•˜๋‚˜์˜ Stage๋Š” ํ•œ ๋ฒˆ์— ํ•˜๋‚˜์˜ Scene๋งŒ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ์–ด. ํ™”๋ฉด ์ „ํ™˜์ด ํ•„์š”ํ•˜๋ฉด Scene์„ ๊ต์ฒดํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜์ง€.

3. Node (๋…ธ๋“œ) ๐ŸŒณ
Node๋Š” Scene ์•ˆ์— ๋ฐฐ์น˜๋˜๋Š” ๋ชจ๋“  UI ์š”์†Œ๋“ค์„ ๋งํ•ด. ๋ฒ„ํŠผ, ํ…์ŠคํŠธ, ์ด๋ฏธ์ง€, ๋ ˆ์ด์•„์›ƒ ๋“ฑ ๋ชจ๋“  ๊ฒƒ์ด Node์•ผ. Node๋“ค์€ ํŠธ๋ฆฌ ๊ตฌ์กฐ๋กœ ๊ตฌ์„ฑ๋˜๋Š”๋ฐ, ์ด๋ฅผ Scene Graph๋ผ๊ณ  ๋ถˆ๋Ÿฌ.

๐Ÿ’ป Hello World ์ฝ”๋“œ ์ž‘์„ฑ

์ด์ œ ์‹ค์ œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด๋ณผ๊ฒŒ. ์•„๋ž˜ ์ฝ”๋“œ๋Š” ๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด์•ผ:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class HelloJavaFX extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        // Label ์ƒ์„ฑ (ํ…์ŠคํŠธ ํ‘œ์‹œ์šฉ)
        Label label = new Label("์•ˆ๋…•ํ•˜์„ธ์š”! JavaFX ์„ธ๊ณ„์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰");
        label.setStyle("-fx-font-size: 18px; -fx-text-fill: #667eea;");
        
        // StackPane ๋ ˆ์ด์•„์›ƒ ์ƒ์„ฑ
        StackPane root = new StackPane();
        root.getChildren().add(label);
        
        // Scene ์ƒ์„ฑ (๋„ˆ๋น„ 500, ๋†’์ด 300)
        Scene scene = new Scene(root, 500, 300);
        
        // Stage ์„ค์ •
        primaryStage.setTitle("๋‚˜์˜ ์ฒซ JavaFX ์•ฑ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์ฝ”๋“œ๋ฅผ ํ•˜๋‚˜์”ฉ ๋œฏ์–ด๋ณผ๊นŒ? ๐Ÿ˜Š

Application ํด๋ž˜์Šค ์ƒ์†: ๋ชจ๋“  JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ Application ํด๋ž˜์Šค๋ฅผ ์ƒ์†๋ฐ›์•„์•ผ ํ•ด. ์ด๊ฒŒ JavaFX์˜ ์‹œ์ž‘์ ์ด์•ผ.

start() ๋ฉ”์„œ๋“œ: ์ด ๋ฉ”์„œ๋“œ๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ง„์ž…์ ์ด์•ผ. ์—ฌ๊ธฐ์„œ UI๋ฅผ ๊ตฌ์„ฑํ•˜๊ณ  Stage๋ฅผ ์„ค์ •ํ•˜์ง€. primaryStage๋Š” JavaFX๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ด์ฃผ๋Š” ๋ฉ”์ธ ์œˆ๋„์šฐ์•ผ.

Label๊ณผ StackPane: Label์€ ํ…์ŠคํŠธ๋ฅผ ํ‘œ์‹œํ•˜๋Š” ์ปจํŠธ๋กค์ด๊ณ , StackPane์€ ์ž์‹ ์š”์†Œ๋“ค์„ ์ค‘์•™์— ์Œ“์•„์„œ ๋ฐฐ์น˜ํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ์ด์•ผ. ๊ฐ„๋‹จํ•œ UI์— ๋”ฑ์ด์ง€!

CSS ์Šคํƒ€์ผ๋ง: setStyle() ๋ฉ”์„œ๋“œ๋กœ CSS ์Šคํƒ€์ผ์„ ์ง์ ‘ ์ ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ์›น ๊ฐœ๋ฐœ ๊ฒฝํ—˜์ด ์žˆ๋‹ค๋ฉด ์ •๋ง ์ต์ˆ™ํ•˜๊ฒŒ ๋А๊ปด์งˆ ๊ฑฐ์•ผ.

launch() ๋ฉ”์„œ๋“œ: main ๋ฉ”์„œ๋“œ์—์„œ ํ˜ธ์ถœํ•˜๋Š” ์ด ๋ฉ”์„œ๋“œ๊ฐ€ JavaFX ๋Ÿฐํƒ€์ž„์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  start() ๋ฉ”์„œ๋“œ๋ฅผ ์‹คํ–‰์‹œ์ผœ์ค˜.

โš ๏ธ ์ฃผ์˜์‚ฌํ•ญ JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋ฐ˜๋“œ์‹œ JavaFX Application Thread์—์„œ UI ์ž‘์—…์„ ํ•ด์•ผ ํ•ด. ๋‹ค๋ฅธ ์Šค๋ ˆ๋“œ์—์„œ UI๋ฅผ ์กฐ์ž‘ํ•˜๋ ค๊ณ  ํ•˜๋ฉด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์–ด. ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋”ฉ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋Š” Platform.runLater()๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค๋Š” ์ , ๊ผญ ๊ธฐ์–ตํ•ด!

๐ŸŽจ ๋ ˆ์ด์•„์›ƒ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ

UI๋ฅผ ๋งŒ๋“ค ๋•Œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ๊ฒŒ ๋ญ˜๊นŒ? ๋ฐ”๋กœ ๋ ˆ์ด์•„์›ƒ์ด์•ผ! JavaFX๋Š” ๋‹ค์–‘ํ•œ ๋ ˆ์ด์•„์›ƒ ์ปจํ…Œ์ด๋„ˆ๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ, ๊ฐ๊ฐ์˜ ํŠน์ง•์„ ์ž˜ ์ดํ•ดํ•˜๊ณ  ์ƒํ™ฉ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ์ค‘์š”ํ•ด. ๋งˆ์น˜ ๋ ˆ๊ณ  ๋ธ”๋ก์„ ์กฐ๋ฆฝํ•˜๋“ฏ์ด ๋ ˆ์ด์•„์›ƒ์„ ์กฐํ•ฉํ•˜๋ฉด ๋ณต์žกํ•œ UI๋„ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿงฉ

๐Ÿ“ฆ ์ฃผ์š” ๋ ˆ์ด์•„์›ƒ ์ปจํ…Œ์ด๋„ˆ

๋ ˆ์ด์•„์›ƒ ํŠน์ง• ์‚ฌ์šฉ ์‹œ๋‚˜๋ฆฌ์˜ค
StackPane ์ž์‹ ์š”์†Œ๋ฅผ ์ค‘์•™์— ๊ฒน์ณ์„œ ๋ฐฐ์น˜ ๋‹จ์ˆœํ•œ ์ค‘์•™ ์ •๋ ฌ, ์˜ค๋ฒ„๋ ˆ์ด ํšจ๊ณผ
BorderPane ์ƒํ•˜์ขŒ์šฐ ์ค‘์•™ 5๊ฐœ ์˜์—ญ์œผ๋กœ ๋ถ„ํ•  ์ „ํ˜•์ ์ธ ์•ฑ ๋ ˆ์ด์•„์›ƒ (ํ—ค๋”, ์‚ฌ์ด๋“œ๋ฐ”, ๋ฉ”์ธ ๋“ฑ)
HBox ์ž์‹ ์š”์†Œ๋ฅผ ๊ฐ€๋กœ๋กœ ๋‚˜์—ด ๋ฒ„ํŠผ ๊ทธ๋ฃน, ํˆด๋ฐ”
VBox ์ž์‹ ์š”์†Œ๋ฅผ ์„ธ๋กœ๋กœ ๋‚˜์—ด ํผ ๋ ˆ์ด์•„์›ƒ, ๋ฉ”๋‰ด
GridPane ๊ฒฉ์ž ํ˜•ํƒœ๋กœ ๋ฐฐ์น˜ ๋ณต์žกํ•œ ํผ, ๊ณ„์‚ฐ๊ธฐ UI
FlowPane ์ž๋™์œผ๋กœ ์ค„๋ฐ”๊ฟˆํ•˜๋ฉฐ ๋ฐฐ์น˜ ํƒœ๊ทธ ๋ชฉ๋ก, ์ด๋ฏธ์ง€ ๊ฐค๋Ÿฌ๋ฆฌ
AnchorPane ์•ต์ปค ํฌ์ธํŠธ ๊ธฐ์ค€์œผ๋กœ ๋ฐฐ์น˜ ์ ˆ๋Œ€ ์œ„์น˜ ์ง€์ •์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ

๐Ÿ—๏ธ BorderPane ์‹ค์ „ ํ™œ์šฉ

์‹ค๋ฌด์—์„œ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ ˆ์ด์•„์›ƒ ์ค‘ ํ•˜๋‚˜๊ฐ€ BorderPane์ด์•ผ. ์ „ํ˜•์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค๊ธฐ์— ์™„๋ฒฝํ•˜๊ฑฐ๋“ ! ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์•Œ์•„๋ณด์ž:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.stage.Stage;

public class BorderPaneExample extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        BorderPane root = new BorderPane();
        
        // ์ƒ๋‹จ ๋ฉ”๋‰ด๋ฐ”
        MenuBar menuBar = new MenuBar();
        Menu fileMenu = new Menu("ํŒŒ์ผ");
        Menu editMenu = new Menu("ํŽธ์ง‘");
        Menu helpMenu = new Menu("๋„์›€๋ง");
        
        MenuItem newItem = new MenuItem("์ƒˆ๋กœ ๋งŒ๋“ค๊ธฐ");
        MenuItem openItem = new MenuItem("์—ด๊ธฐ");
        MenuItem saveItem = new MenuItem("์ €์žฅ");
        fileMenu.getItems().addAll(newItem, openItem, saveItem);
        
        menuBar.getMenus().addAll(fileMenu, editMenu, helpMenu);
        root.setTop(menuBar);
        
        // ์ขŒ์ธก ์‚ฌ์ด๋“œ๋ฐ”
        VBox leftPanel = new VBox(10);
        leftPanel.setPadding(new Insets(15));
        leftPanel.setStyle("-fx-background-color: #f0f0f0;");
        
        Label sidebarTitle = new Label("๐Ÿ“ ํ”„๋กœ์ ํŠธ");
        sidebarTitle.setStyle("-fx-font-size: 16px; -fx-font-weight: bold;");
        
        ListView<String> projectList = new ListView<>();
        projectList.getItems().addAll(
            "ํ”„๋กœ์ ํŠธ 1",
            "ํ”„๋กœ์ ํŠธ 2",
            "ํ”„๋กœ์ ํŠธ 3"
        );
        
        leftPanel.getChildren().addAll(sidebarTitle, projectList);
        root.setLeft(leftPanel);
        
        // ์ค‘์•™ ์ปจํ…์ธ  ์˜์—ญ
        VBox centerContent = new VBox(15);
        centerContent.setPadding(new Insets(20));
        
        Label titleLabel = new Label("ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐Ÿ‘‹");
        titleLabel.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
        
        TextArea textArea = new TextArea();
        textArea.setPromptText("์—ฌ๊ธฐ์— ๋‚ด์šฉ์„ ์ž…๋ ฅํ•˜์„ธ์š”...");
        textArea.setPrefRowCount(10);
        
        centerContent.getChildren().addAll(titleLabel, textArea);
        root.setCenter(centerContent);
        
        // ํ•˜๋‹จ ์ƒํƒœ๋ฐ”
        HBox statusBar = new HBox();
        statusBar.setPadding(new Insets(5, 10, 5, 10));
        statusBar.setStyle("-fx-background-color: #e0e0e0;");
        
        Label statusLabel = new Label("์ค€๋น„ ์™„๋ฃŒ โœ“");
        statusBar.getChildren().add(statusLabel);
        root.setBottom(statusBar);
        
        Scene scene = new Scene(root, 800, 600);
        primaryStage.setTitle("BorderPane ๋ ˆ์ด์•„์›ƒ ์˜ˆ์ œ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ „๋ฌธ์ ์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ ˆ์ด์•„์›ƒ์ด ๋‚˜ํƒ€๋‚  ๊ฑฐ์•ผ! ์ƒ๋‹จ์— ๋ฉ”๋‰ด๋ฐ”, ์ขŒ์ธก์— ์‚ฌ์ด๋“œ๋ฐ”, ์ค‘์•™์— ๋ฉ”์ธ ์ปจํ…์ธ , ํ•˜๋‹จ์— ์ƒํƒœ๋ฐ”๊ฐ€ ์žˆ๋Š” ๊ตฌ์กฐ์ง€. ์ด๋Ÿฐ ๋ ˆ์ด์•„์›ƒ์€ IDE, ํ…์ŠคํŠธ ์—๋””ํ„ฐ, ํŒŒ์ผ ๊ด€๋ฆฌ์ž ๋“ฑ ์ˆ˜๋งŽ์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํ‘œ์ค€ ๊ตฌ์กฐ์•ผ. ๐Ÿ“

๐Ÿ“Š GridPane์œผ๋กœ ํผ ๋งŒ๋“ค๊ธฐ

์‚ฌ์šฉ์ž ์ž…๋ ฅ์„ ๋ฐ›๋Š” ํผ์„ ๋งŒ๋“ค ๋•Œ๋Š” GridPane์ด ์ตœ๊ณ ์•ผ! ํ–‰๊ณผ ์—ด์„ ์ง€์ •ํ•ด์„œ ๊น”๋”ํ•˜๊ฒŒ ์ •๋ ฌํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ :

GridPane grid = new GridPane();
grid.setHgap(10);  // ๊ฐ€๋กœ ๊ฐ„๊ฒฉ
grid.setVgap(10);  // ์„ธ๋กœ ๊ฐ„๊ฒฉ
grid.setPadding(new Insets(20));

// ๋ผ๋ฒจ๊ณผ ์ž…๋ ฅ ํ•„๋“œ ์ถ”๊ฐ€
Label nameLabel = new Label("์ด๋ฆ„:");
TextField nameField = new TextField();
grid.add(nameLabel, 0, 0);  // 0์—ด, 0ํ–‰
grid.add(nameField, 1, 0);  // 1์—ด, 0ํ–‰

Label emailLabel = new Label("์ด๋ฉ”์ผ:");
TextField emailField = new TextField();
grid.add(emailLabel, 0, 1);
grid.add(emailField, 1, 1);

Label passwordLabel = new Label("๋น„๋ฐ€๋ฒˆํ˜ธ:");
PasswordField passwordField = new PasswordField();
grid.add(passwordLabel, 0, 2);
grid.add(passwordField, 1, 2);

Button submitButton = new Button("์ œ์ถœ");
grid.add(submitButton, 1, 3);

GridPane์˜ add() ๋ฉ”์„œ๋“œ๋Š” (๋…ธ๋“œ, ์—ด, ํ–‰) ์ˆœ์„œ๋กœ ์ธ์ž๋ฅผ ๋ฐ›์•„. ํ•„์š”ํ•˜๋ฉด setColumnSpan()์ด๋‚˜ setRowSpan()์„ ์‚ฌ์šฉํ•ด์„œ ์—ฌ๋Ÿฌ ์นธ์„ ์ฐจ์ง€ํ•˜๊ฒŒ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์–ด! ๐ŸŽฏ

๐Ÿ’ก ๋ ˆ์ด์•„์›ƒ ์กฐํ•ฉ์˜ ๋งˆ๋ฒ• ์‹ค์ œ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ์—ฌ๋Ÿฌ ๋ ˆ์ด์•„์›ƒ์„ ์ค‘์ฒฉํ•ด์„œ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„. ์˜ˆ๋ฅผ ๋“ค์–ด BorderPane ์•ˆ์— VBox๋ฅผ ๋„ฃ๊ณ , ๊ทธ ์•ˆ์— ๋‹ค์‹œ HBox๋ฅผ ๋„ฃ๋Š” ์‹์ด์ง€. ๋ ˆ๊ณ  ๋ธ”๋ก์ฒ˜๋Ÿผ ์ž์œ ๋กญ๊ฒŒ ์กฐํ•ฉํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒŒ JavaFX์˜ ํฐ ์žฅ์ ์ด์•ผ!

๋ ˆ์ด์•„์›ƒ์„ ์„ค๊ณ„ํ•  ๋•Œ๋Š” ๋จผ์ € ์ข…์ด์— ์Šค์ผ€์น˜๋ฅผ ๊ทธ๋ ค๋ณด๋Š” ๊ฑธ ์ถ”์ฒœํ•ด. ์–ด๋–ค ์˜์—ญ์„ ์–ด๋–ค ๋ ˆ์ด์•„์›ƒ์œผ๋กœ ๊ตฌ์„ฑํ• ์ง€ ๋ฏธ๋ฆฌ ๊ณ„ํšํ•˜๋ฉด ์ฝ”๋”ฉ์ด ํ›จ์”ฌ ์ˆ˜์›”ํ•ด์ ธ!
JavaFX ๋ ˆ์ด์•„์›ƒ ๊ตฌ์กฐ MenuBar (Top) StatusBar (Bottom) Left Panel ํ•ญ๋ชฉ 1 ํ•ญ๋ชฉ 2 ํ•ญ๋ชฉ 3 Center Content Area ์ปจํ…์ธ  ์˜์—ญ 1 ์ปจํ…์ธ  ์˜์—ญ 2 ์ปจํ…์ธ  ์˜์—ญ 3 GridPane ์˜์—ญ VBox ์˜์—ญ Right Panel ๐ŸŽจ ๐Ÿ“Š โš™๏ธ

๐ŸŽฎ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ ์ƒํ˜ธ์ž‘์šฉ

UI๋ฅผ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ ์‚ฌ์šฉ์ž์™€ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด์•ผ๊ฒ ์ง€? JavaFX์˜ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ์‹œ์Šคํ…œ์€ ์ •๋ง ์ง๊ด€์ ์ด๊ณ  ๊ฐ•๋ ฅํ•ด! ๋ฒ„ํŠผ ํด๋ฆญ, ๋งˆ์šฐ์Šค ์ด๋™, ํ‚ค๋ณด๋“œ ์ž…๋ ฅ ๋“ฑ ๋ชจ๋“  ์‚ฌ์šฉ์ž ์•ก์…˜์— ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ์–ด. ๐ŸŽฏ

๐Ÿ–ฑ๏ธ ๊ธฐ๋ณธ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ

๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ•์€ ๋žŒ๋‹ค ํ‘œํ˜„์‹์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์•ผ. Java 8๋ถ€ํ„ฐ ์ง€์›๋˜๋Š” ๋žŒ๋‹ค ๋•๋ถ„์— ์ฝ”๋“œ๊ฐ€ ์ •๋ง ๊น”๋”ํ•ด์กŒ์–ด:

Button button = new Button("ํด๋ฆญํ•˜์„ธ์š”!");

// ๋žŒ๋‹ค ํ‘œํ˜„์‹์œผ๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
button.setOnAction(event -> {
    System.out.println("๋ฒ„ํŠผ์ด ํด๋ฆญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽ‰");
    button.setText("ํด๋ฆญ๋จ!");
});

// ๋งˆ์šฐ์Šค ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ
button.setOnMouseEntered(event -> {
    button.setStyle("-fx-background-color: #667eea; -fx-text-fill: white;");
});

button.setOnMouseExited(event -> {
    button.setStyle("");  // ๊ธฐ๋ณธ ์Šคํƒ€์ผ๋กœ ๋ณต์›
});

์ด๋ ‡๊ฒŒ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด! setOnAction()์€ ๋ฒ„ํŠผ ํด๋ฆญ์ด๋‚˜ ์—”ํ„ฐ ํ‚ค ์ž…๋ ฅ ๊ฐ™์€ ์•ก์…˜ ์ด๋ฒคํŠธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ , setOnMouseEntered()์™€ setOnMouseExited()๋Š” ๋งˆ์šฐ์Šค ํ˜ธ๋ฒ„ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค ๋•Œ ์œ ์šฉํ•ด. ๐Ÿ˜Š

โŒจ๏ธ ํ‚ค๋ณด๋“œ ์ด๋ฒคํŠธ

ํ‚ค๋ณด๋“œ ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ๋„ ์ค‘์š”ํ•ด. ํŠนํžˆ ํ…์ŠคํŠธ ์—๋””ํ„ฐ๋‚˜ ๊ฒŒ์ž„ ๊ฐ™์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ํ•„์ˆ˜์ง€:

TextField textField = new TextField();

textField.setOnKeyPressed(event -> {
    switch (event.getCode()) {
        case ENTER:
            System.out.println("์—”ํ„ฐ ํ‚ค ์ž…๋ ฅ: " + textField.getText());
            break;
        case ESCAPE:
            textField.clear();
            break;
        case CONTROL:
            System.out.println("Ctrl ํ‚ค ๋ˆŒ๋ฆผ");
            break;
    }
});

// ํ…์ŠคํŠธ ๋ณ€๊ฒฝ ๊ฐ์ง€
textField.textProperty().addListener((observable, oldValue, newValue) -> {
    System.out.println("ํ…์ŠคํŠธ ๋ณ€๊ฒฝ: " + oldValue + " โ†’ " + newValue);
});

textProperty().addListener()๋Š” ์ •๋ง ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ด์•ผ. ํ…์ŠคํŠธ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ฐ˜์‘ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ . ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์ด๋‚˜ ์ž…๋ ฅ ๊ฒ€์ฆ์— ์™„๋ฒฝํ•˜์ง€! ๐Ÿ”

๐ŸŽจ ์‹ค์ „ ์˜ˆ์ œ: ๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ๊ธฐ

์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ๋ฅผ ํ™œ์šฉํ•ด์„œ ๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ๊ธฐ๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์ด ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์—ฌ๋Ÿฌ ๊ฐœ๋…์„ ํ•œ ๋ฒˆ์— ์ตํž ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ:

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class SimpleCalculator extends Application {
    
    private TextField display;
    private double firstNumber = 0;
    private String operator = "";
    private boolean startNewNumber = true;
    
    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(10);
        root.setPadding(new Insets(15));
        root.setAlignment(Pos.CENTER);
        
        // ๋””์Šคํ”Œ๋ ˆ์ด
        display = new TextField("0");
        display.setEditable(false);
        display.setAlignment(Pos.CENTER_RIGHT);
        display.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
        display.setPrefHeight(60);
        
        // ๋ฒ„ํŠผ ๊ทธ๋ฆฌ๋“œ
        GridPane buttonGrid = new GridPane();
        buttonGrid.setHgap(5);
        buttonGrid.setVgap(5);
        buttonGrid.setAlignment(Pos.CENTER);
        
        String[][] buttons = {
            {"7", "8", "9", "/"},
            {"4", "5", "6", "*"},
            {"1", "2", "3", "-"},
            {"0", "C", "=", "+"}
        };
        
        for (int row = 0; row < 4; row++) {
            for (int col = 0; col < 4; col++) {
                String buttonText = buttons[row][col];
                Button button = createButton(buttonText);
                buttonGrid.add(button, col, row);
            }
        }
        
        root.getChildren().addAll(display, buttonGrid);
        
        Scene scene = new Scene(root, 300, 400);
        primaryStage.setTitle("๊ฐ„๋‹จํ•œ ๊ณ„์‚ฐ๊ธฐ ๐Ÿงฎ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private Button createButton(String text) {
        Button button = new Button(text);
        button.setPrefSize(60, 60);
        button.setStyle("-fx-font-size: 18px; -fx-font-weight: bold;");
        
        button.setOnAction(event -> handleButtonClick(text));
        
        // ์—ฐ์‚ฐ์ž ๋ฒ„ํŠผ ์Šคํƒ€์ผ
        if (text.matches("[+\\-*/=]")) {
            button.setStyle(button.getStyle() + 
                "-fx-background-color: #667eea; -fx-text-fill: white;");
        }
        
        return button;
    }
    
    private void handleButtonClick(String value) {
        if (value.matches("[0-9]")) {
            // ์ˆซ์ž ๋ฒ„ํŠผ
            if (startNewNumber) {
                display.setText(value);
                startNewNumber = false;
            } else {
                display.setText(display.getText() + value);
            }
        } else if (value.equals("C")) {
            // ์ดˆ๊ธฐํ™”
            display.setText("0");
            firstNumber = 0;
            operator = "";
            startNewNumber = true;
        } else if (value.equals("=")) {
            // ๊ณ„์‚ฐ ์‹คํ–‰
            calculate();
            operator = "";
            startNewNumber = true;
        } else {
            // ์—ฐ์‚ฐ์ž
            if (!operator.isEmpty()) {
                calculate();
            }
            firstNumber = Double.parseDouble(display.getText());
            operator = value;
            startNewNumber = true;
        }
    }
    
    private void calculate() {
        double secondNumber = Double.parseDouble(display.getText());
        double result = 0;
        
        switch (operator) {
            case "+":
                result = firstNumber + secondNumber;
                break;
            case "-":
                result = firstNumber - secondNumber;
                break;
            case "*":
                result = firstNumber * secondNumber;
                break;
            case "/":
                if (secondNumber != 0) {
                    result = firstNumber / secondNumber;
                } else {
                    display.setText("์˜ค๋ฅ˜");
                    return;
                }
                break;
        }
        
        display.setText(String.valueOf(result));
        firstNumber = result;
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์™€! ์ด์ œ ์™„์ „ํžˆ ์ž‘๋™ํ•˜๋Š” ๊ณ„์‚ฐ๊ธฐ๊ฐ€ ์™„์„ฑ๋์–ด! ๐ŸŽ‰ ์ด ์˜ˆ์ œ์—์„œ๋Š” GridPane์œผ๋กœ ๋ฒ„ํŠผ์„ ๋ฐฐ์น˜ํ•˜๊ณ , ๊ฐ ๋ฒ„ํŠผ์— ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋ฅผ ์—ฐ๊ฒฐํ–ˆ์–ด. ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•ด ๋ฉค๋ฒ„ ๋ณ€์ˆ˜๋ฅผ ์‚ฌ์šฉํ–ˆ๊ณ , ์กฐ๊ฑด๋ฌธ์œผ๋กœ ๋‹ค์–‘ํ•œ ์ž…๋ ฅ์„ ์ฒ˜๋ฆฌํ–ˆ์ง€.

์ด๋Ÿฐ ์‹์œผ๋กœ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ๊ฒฐํ•ฉํ•˜๋ฉด ๋ณต์žกํ•œ ์ƒํ˜ธ์ž‘์šฉ๋„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ๊ณ„์‚ฐ๊ธฐ๋Š” ๊ฐ„๋‹จํ•ด ๋ณด์ด์ง€๋งŒ ์‹ค์ œ๋กœ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ๋…์ด ๋“ค์–ด๊ฐ€ ์žˆ๋Š” ์ข‹์€ ํ•™์Šต ์˜ˆ์ œ์•ผ! ๐Ÿ’ช

๐ŸŽฏ ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค 1. ๋žŒ๋‹ค ํ‘œํ˜„์‹ ํ™œ์šฉ: ๊ฐ„๋‹จํ•œ ์ด๋ฒคํŠธ๋Š” ๋žŒ๋‹ค๋กœ ์ฒ˜๋ฆฌํ•˜๋ฉด ์ฝ”๋“œ๊ฐ€ ๊น”๋”ํ•ด์ ธ
2. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ถ„๋ฆฌ: ๋ณต์žกํ•œ ๋กœ์ง์€ ๋ณ„๋„ ๋ฉ”์„œ๋“œ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ
3. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ: ์‚ฌ์šฉ์ž ์ž…๋ ฅ์€ ํ•ญ์ƒ ๊ฒ€์ฆํ•˜๊ณ  ์˜ˆ์™ธ ์ƒํ™ฉ์„ ๋Œ€๋น„ํ•ด์•ผ ํ•ด
4. ํ”ผ๋“œ๋ฐฑ ์ œ๊ณต: ๋ฒ„ํŠผ ํด๋ฆญ, ํ˜ธ๋ฒ„ ํšจ๊ณผ ๋“ฑ์œผ๋กœ ์‚ฌ์šฉ์ž์—๊ฒŒ ์‹œ๊ฐ์  ํ”ผ๋“œ๋ฐฑ์„ ์ค˜
5. ํ‚ค๋ณด๋“œ ๋‹จ์ถ•ํ‚ค: ๋งˆ์šฐ์Šค๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ํ‚ค๋ณด๋“œ๋กœ๋„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค๋ฉด ์ ‘๊ทผ์„ฑ์ด ์ข‹์•„์ ธ

๐ŸŽญ FXML๊ณผ Scene Builder๋กœ UI ๋””์ž์ธํ•˜๊ธฐ

์ง€๊ธˆ๊นŒ์ง€๋Š” Java ์ฝ”๋“œ๋กœ ์ง์ ‘ UI๋ฅผ ๋งŒ๋“ค์—ˆ๋Š”๋ฐ, ์‚ฌ์‹ค ๋” ํŽธํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์–ด! ๋ฐ”๋กœ FXML๊ณผ Scene Builder๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฑฐ์•ผ. FXML์€ XML ๊ธฐ๋ฐ˜์˜ ๋งˆํฌ์—… ์–ธ์–ด๋กœ, UI ๊ตฌ์กฐ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด. ๋งˆ์น˜ HTML์ฒ˜๋Ÿผ ๋ง์ด์•ผ! ๐ŸŒ

๐Ÿ“„ FXML์ด๋ž€?

FXML์„ ์‚ฌ์šฉํ•˜๋ฉด UI ๋””์ž์ธ๊ณผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์™„์ „ํžˆ ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด. ์ด๊ฒŒ ์™œ ์ค‘์š”ํ•˜๋ƒ๋ฉด, ๋””์ž์ด๋„ˆ์™€ ๊ฐœ๋ฐœ์ž๊ฐ€ ํ˜‘์—…ํ•˜๊ธฐ ์‰ฌ์›Œ์ง€๊ณ , ์ฝ”๋“œ์˜ ์œ ์ง€๋ณด์ˆ˜์„ฑ๋„ ํฌ๊ฒŒ ํ–ฅ์ƒ๋˜๊ฑฐ๋“ !

๊ฐ„๋‹จํ•œ FXML ์˜ˆ์ œ๋ฅผ ๋ณผ๊นŒ?

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

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

<VBox xmlns:fx="http://javafx.com/fxml"
      fx:controller="com.example.MyController"
      spacing="10" alignment="CENTER">
    
    <padding>
        <Insets top="20" right="20" bottom="20" left="20"/>
    </padding>
    
    <Label text="์‚ฌ์šฉ์ž ๋กœ๊ทธ์ธ" style="-fx-font-size: 24px; -fx-font-weight: bold;"/>
    
    <TextField fx:id="usernameField" promptText="์‚ฌ์šฉ์ž ์ด๋ฆ„"/>
    
    <PasswordField fx:id="passwordField" promptText="๋น„๋ฐ€๋ฒˆํ˜ธ"/>
    
    <Button text="๋กœ๊ทธ์ธ" onAction="#handleLogin"
            style="-fx-background-color: #667eea; -fx-text-fill: white;"/>
    
</VBox>

๋ณด๋‹ค์‹œํ”ผ FXML์€ ์ •๋ง ์ฝ๊ธฐ ์‰ฝ์ง€? XML ํƒœ๊ทธ๋กœ UI ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•˜๊ณ , fx:id๋กœ ์ปจํŠธ๋กค์— ID๋ฅผ ๋ถ€์—ฌํ•ด์„œ Java ์ฝ”๋“œ์—์„œ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ์–ด. onAction ์†์„ฑ์œผ๋กœ ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ๋„ ์—ฐ๊ฒฐํ•  ์ˆ˜ ์žˆ๊ณ ! ๐Ÿ˜Š

๐ŸŽจ Scene Builder ํ™œ์šฉํ•˜๊ธฐ

Scene Builder๋Š” FXML์„ ์‹œ๊ฐ์ ์œผ๋กœ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ๋Š” ๋„๊ตฌ์•ผ. ๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ์œผ๋กœ UI๋ฅผ ๋””์ž์ธํ•˜๋ฉด ์ž๋™์œผ๋กœ FXML ์ฝ”๋“œ๊ฐ€ ์ƒ์„ฑ๋ผ. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? Gluon ์›น์‚ฌ์ดํŠธ์—์„œ ๋ฌด๋ฃŒ๋กœ ๋‹ค์šด๋กœ๋“œํ•  ์ˆ˜ ์žˆ์–ด.

Scene Builder ์‚ฌ์šฉ ๋ฐฉ๋ฒ•:

1. Scene Builder๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ์ƒˆ FXML ํŒŒ์ผ ์ƒ์„ฑ
2. ์™ผ์ชฝ Library ํŒจ๋„์—์„œ ์›ํ•˜๋Š” ์ปจํŠธ๋กค์„ ๋“œ๋ž˜๊ทธํ•ด์„œ ์ค‘์•™ ์บ”๋ฒ„์Šค์— ๋ฐฐ์น˜
3. ์˜ค๋ฅธ์ชฝ Properties ํŒจ๋„์—์„œ ์†์„ฑ ์„ค์ • (ํฌ๊ธฐ, ์ƒ‰์ƒ, ํ…์ŠคํŠธ ๋“ฑ)
4. Controller ํด๋ž˜์Šค ์ง€์ • ๋ฐ fx:id ์„ค์ •
5. ์ด๋ฒคํŠธ ํ•ธ๋“ค๋Ÿฌ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ง€์ •
6. ์ €์žฅํ•˜๋ฉด FXML ํŒŒ์ผ ์™„์„ฑ! ๐ŸŽ‰

๐Ÿ”— Controller ํด๋ž˜์Šค ์ž‘์„ฑ

FXML ํŒŒ์ผ์„ ๋งŒ๋“ค์—ˆ์œผ๋ฉด ์ด์ œ Controller ํด๋ž˜์Šค๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ด. Controller๋Š” FXML์— ์ •์˜๋œ UI ์š”์†Œ๋“ค๊ณผ ์ƒํ˜ธ์ž‘์šฉํ•˜๋Š” Java ํด๋ž˜์Šค์•ผ:

package com.example;

import javafx.fxml.FXML;
import javafx.scene.control.TextField;
import javafx.scene.control.PasswordField;
import javafx.scene.control.Alert;

public class MyController {
    
    @FXML
    private TextField usernameField;
    
    @FXML
    private PasswordField passwordField;
    
    @FXML
    private void handleLogin() {
        String username = usernameField.getText();
        String password = passwordField.getText();
        
        if (username.isEmpty() || password.isEmpty()) {
            showAlert("์˜ค๋ฅ˜", "๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!");
            return;
        }
        
        // ์‹ค์ œ๋กœ๋Š” ์—ฌ๊ธฐ์„œ ์ธ์ฆ ๋กœ์ง์„ ์ˆ˜ํ–‰
        if (username.equals("admin") && password.equals("1234")) {
            showAlert("์„ฑ๊ณต", "๋กœ๊ทธ์ธ ์„ฑ๊ณต! ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค! ๐ŸŽ‰");
        } else {
            showAlert("์‹คํŒจ", "์‚ฌ์šฉ์ž ์ด๋ฆ„ ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ž˜๋ชป๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
        }
    }
    
    private void showAlert(String title, String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
}

@FXML ์–ด๋…ธํ…Œ์ด์…˜์ด ํ•ต์‹ฌ์ด์•ผ! ์ด ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์ด๋ฉด FXML ํŒŒ์ผ์˜ fx:id์™€ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋ผ. ๋ฉ”์„œ๋“œ์—๋„ @FXML์„ ๋ถ™์ด๋ฉด FXML์˜ onAction๊ณผ ์—ฐ๊ฒฐ๋˜๊ณ . ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐ŸŽฏ

๐Ÿš€ FXML ๋กœ๋”ฉํ•˜๊ธฐ

๋งˆ์ง€๋ง‰์œผ๋กœ Main ํด๋ž˜์Šค์—์„œ FXML์„ ๋กœ๋”ฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์•ผ ํ•ด:

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 {
        // FXML ํŒŒ์ผ ๋กœ๋”ฉ
        FXMLLoader loader = new FXMLLoader(getClass().getResource("login.fxml"));
        Parent root = loader.load();
        
        Scene scene = new Scene(root, 400, 300);
        primaryStage.setTitle("๋กœ๊ทธ์ธ ํ™”๋ฉด");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

FXMLLoader๊ฐ€ FXML ํŒŒ์ผ์„ ํŒŒ์‹ฑํ•˜๊ณ  UI๋ฅผ ์ƒ์„ฑํ•ด์ค˜. ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด UI ๋””์ž์ธ๊ณผ ๋กœ์ง์ด ์™„์ „ํžˆ ๋ถ„๋ฆฌ๋˜์–ด์„œ ์ฝ”๋“œ ๊ด€๋ฆฌ๊ฐ€ ํ›จ์”ฌ ์‰ฌ์›Œ์ ธ! ๐Ÿ‘

๐Ÿ’ก FXML vs ์ˆœ์ˆ˜ Java ์ฝ”๋“œ, ๋ญ˜ ์„ ํƒํ• ๊นŒ? FXML์„ ์‚ฌ์šฉํ•˜๋ฉด ์ข‹์€ ๊ฒฝ์šฐ:
โ€ข ๋ณต์žกํ•œ UI๋ฅผ ๋งŒ๋“ค ๋•Œ
โ€ข ๋””์ž์ด๋„ˆ์™€ ํ˜‘์—…ํ•  ๋•Œ
โ€ข UI์™€ ๋กœ์ง์„ ๋ช…ํ™•ํžˆ ๋ถ„๋ฆฌํ•˜๊ณ  ์‹ถ์„ ๋•Œ
โ€ข Scene Builder๋กœ ์‹œ๊ฐ์  ๋””์ž์ธ์„ ํ•˜๊ณ  ์‹ถ์„ ๋•Œ

์ˆœ์ˆ˜ Java ์ฝ”๋“œ๊ฐ€ ๋‚˜์€ ๊ฒฝ์šฐ:
โ€ข ๊ฐ„๋‹จํ•œ UI์ผ ๋•Œ
โ€ข ๋™์ ์œผ๋กœ UI๋ฅผ ์ƒ์„ฑํ•ด์•ผ ํ•  ๋•Œ
โ€ข ํ”„๋กœ๊ทธ๋ž˜๋ฐ ๋ฐฉ์‹์˜ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ
โ€ข ๋น ๋ฅธ ํ”„๋กœํ† ํƒ€์ดํ•‘์„ ํ•  ๋•Œ

์ •๋‹ต์€ ์—†์–ด! ํ”„๋กœ์ ํŠธ ์ƒํ™ฉ์— ๋งž๊ฒŒ ์„ ํƒํ•˜๋ฉด ๋ผ. ๋‘ ๋ฐฉ์‹์„ ํ˜ผ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜๋‹ˆ๊นŒ ์œ ์—ฐํ•˜๊ฒŒ ์ ‘๊ทผํ•ด๋ด! ๐Ÿ˜Š

๐ŸŽจ CSS ์Šคํƒ€์ผ๋ง์œผ๋กœ UI ๊พธ๋ฏธ๊ธฐ

JavaFX์˜ ๊ฐ€์žฅ ํฐ ์žฅ์  ์ค‘ ํ•˜๋‚˜๊ฐ€ ๋ฐ”๋กœ CSS ์ง€์›์ด์•ผ! ์›น ๊ฐœ๋ฐœ ๊ฒฝํ—˜์ด ์žˆ๋‹ค๋ฉด ์ •๋ง ์ต์ˆ™ํ•˜๊ฒŒ ๋А๊ปด์งˆ ๊ฑฐ์•ผ. CSS๋ฅผ ํ™œ์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์™ธ๊ด€์„ ์™„์ „ํžˆ ๋ฐ”๊ฟ€ ์ˆ˜ ์žˆ์–ด. ๋งˆ์น˜ ๋งˆ๋ฒ•์ฒ˜๋Ÿผ! โœจ

๐Ÿ–Œ๏ธ ์ธ๋ผ์ธ ์Šคํƒ€์ผ vs ์™ธ๋ถ€ CSS

์Šคํƒ€์ผ์„ ์ ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€์•ผ:

1. ์ธ๋ผ์ธ ์Šคํƒ€์ผ - Java ์ฝ”๋“œ์—์„œ ์ง์ ‘ ์ ์šฉ

button.setStyle("-fx-background-color: #667eea; " +
                "-fx-text-fill: white; " +
                "-fx-font-size: 16px; " +
                "-fx-padding: 10px 20px; " +
                "-fx-border-radius: 5px;");

2. ์™ธ๋ถ€ CSS ํŒŒ์ผ - ๋ณ„๋„์˜ CSS ํŒŒ์ผ๋กœ ๊ด€๋ฆฌ

/* styles.css */
.custom-button {
    -fx-background-color: #667eea;
    -fx-text-fill: white;
    -fx-font-size: 16px;
    -fx-padding: 10px 20px;
    -fx-border-radius: 5px;
    -fx-cursor: hand;
}

.custom-button:hover {
    -fx-background-color: #5568d3;
}

.custom-button:pressed {
    -fx-background-color: #4451b8;
}

Java ์ฝ”๋“œ์—์„œ CSS ํŒŒ์ผ์„ ๋กœ๋”ฉํ•˜๋Š” ๋ฐฉ๋ฒ•:

scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());
button.getStyleClass().add("custom-button");

์™ธ๋ถ€ CSS ํŒŒ์ผ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ›จ์”ฌ ๊ด€๋ฆฌํ•˜๊ธฐ ํŽธํ•ด! ์Šคํƒ€์ผ์„ ํ•œ ๊ณณ์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ณ , ์žฌ์‚ฌ์šฉ๋„ ์‰ฝ๊ฑฐ๋“ . ๐ŸŽจ

๐ŸŒˆ JavaFX CSS ์†์„ฑ ์ •๋ฆฌ

JavaFX CSS๋Š” ์›น CSS์™€ ๋น„์Šทํ•˜์ง€๋งŒ ์†์„ฑ ์ด๋ฆ„ ์•ž์— -fx- ์ ‘๋‘์‚ฌ๊ฐ€ ๋ถ™์–ด. ์ฃผ์š” ์†์„ฑ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ:

์นดํ…Œ๊ณ ๋ฆฌ ์†์„ฑ ์„ค๋ช…
์ƒ‰์ƒ -fx-background-color ๋ฐฐ๊ฒฝ ์ƒ‰์ƒ
-fx-text-fill ํ…์ŠคํŠธ ์ƒ‰์ƒ
-fx-border-color ํ…Œ๋‘๋ฆฌ ์ƒ‰์ƒ
ํฌ๊ธฐ -fx-font-size ๊ธ€์ž ํฌ๊ธฐ
-fx-padding ์•ˆ์ชฝ ์—ฌ๋ฐฑ
-fx-spacing ์š”์†Œ ๊ฐ„ ๊ฐ„๊ฒฉ
๋ชจ์–‘ -fx-border-radius ํ…Œ๋‘๋ฆฌ ๋‘ฅ๊ธ€๊ธฐ
-fx-background-radius ๋ฐฐ๊ฒฝ ๋‘ฅ๊ธ€๊ธฐ
-fx-border-width ํ…Œ๋‘๋ฆฌ ๋‘๊ป˜
ํšจ๊ณผ -fx-effect ๊ทธ๋ฆผ์ž, ๋ธ”๋Ÿฌ ๋“ฑ
-fx-opacity ํˆฌ๋ช…๋„ (0.0~1.0)

๐ŸŽฏ ์‹ค์ „ ์Šคํƒ€์ผ๋ง ์˜ˆ์ œ

์ด์ œ ์‹ค์ œ๋กœ ๋ฉ‹์ง„ ์Šคํƒ€์ผ์„ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ํ˜„๋Œ€์ ์ธ ๋‹คํฌ ํ…Œ๋งˆ ๋ฒ„ํŠผ์„ ๋งŒ๋“ค์–ด๋ณด์ž:

/* modern-dark.css */

/* ์ „์ฒด ๋ฐฐ๊ฒฝ */
.root {
    -fx-background-color: #1e1e1e;
}

/* ๊ธฐ๋ณธ ๋ฒ„ํŠผ ์Šคํƒ€์ผ */
.modern-button {
    -fx-background-color: linear-gradient(to bottom, #667eea, #764ba2);
    -fx-text-fill: white;
    -fx-font-size: 14px;
    -fx-font-weight: bold;
    -fx-padding: 12px 24px;
    -fx-background-radius: 8px;
    -fx-cursor: hand;
    -fx-effect: dropshadow(gaussian, rgba(102, 126, 234, 0.4), 10, 0, 0, 4);
}

.modern-button:hover {
    -fx-background-color: linear-gradient(to bottom, #5568d3, #6a3f8f);
    -fx-scale-x: 1.05;
    -fx-scale-y: 1.05;
}

.modern-button:pressed {
    -fx-background-color: linear-gradient(to bottom, #4451b8, #5a3576);
    -fx-translate-y: 2px;
}

/* ํ…์ŠคํŠธ ํ•„๋“œ */
.modern-textfield {
    -fx-background-color: #2d2d2d;
    -fx-text-fill: white;
    -fx-prompt-text-fill: #888888;
    -fx-border-color: #667eea;
    -fx-border-width: 2px;
    -fx-border-radius: 5px;
    -fx-background-radius: 5px;
    -fx-padding: 10px;
    -fx-font-size: 14px;
}

.modern-textfield:focused {
    -fx-border-color: #764ba2;
    -fx-effect: dropshadow(gaussian, rgba(118, 75, 162, 0.5), 8, 0, 0, 0);
}

/* ๋ ˆ์ด๋ธ” */
.modern-label {
    -fx-text-fill: #e0e0e0;
    -fx-font-size: 16px;
}

.title-label {
    -fx-text-fill: white;
    -fx-font-size: 28px;
    -fx-font-weight: bold;
}

/* ํŒจ๋„ */
.modern-panel {
    -fx-background-color: #2d2d2d;
    -fx-background-radius: 10px;
    -fx-padding: 20px;
    -fx-effect: dropshadow(gaussian, rgba(0, 0, 0, 0.5), 15, 0, 0, 5);
}

์ด CSS๋ฅผ ์ ์šฉํ•˜๋ฉด ์ •๋ง ์„ธ๋ จ๋œ ๋‹คํฌ ํ…Œ๋งˆ UI๊ฐ€ ์™„์„ฑ๋ผ! ๊ทธ๋ผ๋””์–ธํŠธ, ๊ทธ๋ฆผ์ž ํšจ๊ณผ, ํ˜ธ๋ฒ„ ์• ๋‹ˆ๋ฉ”์ด์…˜๊นŒ์ง€ ๋ชจ๋‘ ํฌํ•จ๋˜์–ด ์žˆ์–ด. ๐ŸŒŸ

Java ์ฝ”๋“œ์—์„œ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ:

scene.getStylesheets().add(getClass().getResource("modern-dark.css").toExternalForm());

Button button = new Button("ํด๋ฆญํ•˜์„ธ์š”");
button.getStyleClass().add("modern-button");

TextField textField = new TextField();
textField.getStyleClass().add("modern-textfield");
textField.setPromptText("์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š”...");

Label titleLabel = new Label("๋ฉ‹์ง„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜");
titleLabel.getStyleClass().addAll("modern-label", "title-label");
๐ŸŽจ ์Šคํƒ€์ผ๋ง ํŒ 1. ์ผ๊ด€์„ฑ ์œ ์ง€: ์ƒ‰์ƒ ํŒ”๋ ˆํŠธ๋ฅผ ์ •ํ•˜๊ณ  ์ „์ฒด์ ์œผ๋กœ ์ผ๊ด€๋˜๊ฒŒ ์‚ฌ์šฉํ•ด
2. ๋ณ€์ˆ˜ ํ™œ์šฉ: CSS์—์„œ ์ƒ‰์ƒ์„ ๋ณ€์ˆ˜๋กœ ์ •์˜ํ•˜๋ฉด ํ…Œ๋งˆ ๋ณ€๊ฒฝ์ด ์‰ฌ์›Œ์ ธ
3. ์˜์‚ฌ ํด๋ž˜์Šค: :hover, :pressed, :focused ๋“ฑ์„ ํ™œ์šฉํ•ด์„œ ์ƒํ˜ธ์ž‘์šฉ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•ด
4. ๊ทธ๋ฆผ์ž ํšจ๊ณผ: dropshadow๋ฅผ ์ ์ ˆํžˆ ์‚ฌ์šฉํ•˜๋ฉด ์ž…์ฒด๊ฐ์ด ์ƒ๊ฒจ
5. ์• ๋‹ˆ๋ฉ”์ด์…˜: ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ํšจ๊ณผ๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํ–ฅ์ƒ์‹œ์ผœ
JavaFX CSS ์Šคํƒ€์ผ๋ง ๋ผ์ดํŠธ ํ…Œ๋งˆ ํ…์ŠคํŠธ ์ž…๋ ฅ ํ•„๋“œ ํ™•์ธ ๋ฒ„ํŠผ ์ทจ์†Œ ๋ฒ„ํŠผ โœ“ โš  โœ— ๋ฐ๊ณ  ๊น”๋”ํ•œ ์ธํ„ฐํŽ˜์ด์Šค ๋‹คํฌ ํ…Œ๋งˆ ํ…์ŠคํŠธ ์ž…๋ ฅ ํ•„๋“œ ํ™•์ธ ๋ฒ„ํŠผ ์ทจ์†Œ ๋ฒ„ํŠผ โœ“ โš  โœ— ๋ˆˆ์ด ํŽธ์•ˆํ•œ ๋‹คํฌ ๋ชจ๋“œ

๐Ÿ“Š ์ฐจํŠธ์™€ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”

๋ฐ์ดํ„ฐ๋ฅผ ์‹œ๊ฐ์ ์œผ๋กœ ํ‘œํ˜„ํ•˜๋Š” ๊ฑด ์ •๋ง ์ค‘์š”ํ•ด! JavaFX๋Š” ๋‹ค์–‘ํ•œ ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณตํ•ด์„œ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”๊ฐ€ ์ •๋ง ์‰ฌ์›Œ. ๋Œ€์‹œ๋ณด๋“œ, ๋ถ„์„ ๋„๊ตฌ, ๋ชจ๋‹ˆํ„ฐ๋ง ์•ฑ์„ ๋งŒ๋“ค ๋•Œ ํ•„์ˆ˜์ ์ด์ง€! ๐Ÿ“ˆ

๐Ÿ“‰ JavaFX ์ฐจํŠธ ์ข…๋ฅ˜

JavaFX๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ฃผ์š” ์ฐจํŠธ๋“ค์„ ์‚ดํŽด๋ณผ๊นŒ:

1. LineChart (์„  ๊ทธ๋ž˜ํ”„) ๐Ÿ“ˆ
์‹œ๊ฐ„์— ๋”ฐ๋ฅธ ๋ณ€ํ™”๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ์— ์™„๋ฒฝํ•ด. ์ฃผ์‹ ์ฐจํŠธ, ์˜จ๋„ ๋ณ€ํ™”, ํŠธ๋ž˜ํ”ฝ ์ถ”์ด ๋“ฑ์— ์‚ฌ์šฉ๋ผ.

2. BarChart (๋ง‰๋Œ€ ๊ทธ๋ž˜ํ”„) ๐Ÿ“Š
์นดํ…Œ๊ณ ๋ฆฌ๋ณ„ ๋น„๊ต์— ์ตœ์ ์ด์•ผ. ๋งค์ถœ ๋น„๊ต, ์„ค๋ฌธ ๊ฒฐ๊ณผ, ์ˆœ์œ„ ๋“ฑ์„ ํ‘œํ˜„ํ•  ๋•Œ ์ข‹์•„.

3. PieChart (์› ๊ทธ๋ž˜ํ”„) ๐Ÿฅง
์ „์ฒด์—์„œ ๊ฐ ๋ถ€๋ถ„์ด ์ฐจ์ง€ํ•˜๋Š” ๋น„์œจ์„ ๋ณด์—ฌ์ค˜. ์‹œ์žฅ ์ ์œ ์œจ, ์˜ˆ์‚ฐ ๋ฐฐ๋ถ„ ๋“ฑ์— ์‚ฌ์šฉ๋ผ.

4. AreaChart (์˜์—ญ ์ฐจํŠธ) ๐ŸŒŠ
LineChart์™€ ๋น„์Šทํ•˜์ง€๋งŒ ์„  ์•„๋ž˜ ์˜์—ญ์„ ์ฑ„์›Œ์„œ ๋ˆ„์  ํšจ๊ณผ๋ฅผ ๋ณด์—ฌ์ค˜.

5. ScatterChart (์‚ฐ์ ๋„) โญ
๋‘ ๋ณ€์ˆ˜ ๊ฐ„์˜ ์ƒ๊ด€๊ด€๊ณ„๋ฅผ ๋ณด์—ฌ์ฃผ๋Š” ๋ฐ ์œ ์šฉํ•ด. ํ†ต๊ณ„ ๋ถ„์„์— ์ž์ฃผ ์“ฐ์ด์ง€.

๐Ÿ’ป LineChart ์‹ค์ „ ์˜ˆ์ œ

์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋ฅผ ํ‘œ์‹œํ•˜๋Š” LineChart๋ฅผ ๋งŒ๋“ค์–ด๋ณผ๊นŒ? ์˜ˆ๋ฅผ ๋“ค์–ด CPU ์‚ฌ์šฉ๋ฅ ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ์ฐจํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class RealtimeChart extends Application {
    
    private XYChart.Series<Number, Number> series;
    private int timeCounter = 0;
    private Random random = new Random();
    
    @Override
    public void start(Stage primaryStage) {
        // X์ถ• (์‹œ๊ฐ„)
        NumberAxis xAxis = new NumberAxis();
        xAxis.setLabel("์‹œ๊ฐ„ (์ดˆ)");
        xAxis.setAutoRanging(true);
        
        // Y์ถ• (CPU ์‚ฌ์šฉ๋ฅ )
        NumberAxis yAxis = new NumberAxis(0, 100, 10);
        yAxis.setLabel("CPU ์‚ฌ์šฉ๋ฅ  (%)");
        
        // LineChart ์ƒ์„ฑ
        LineChart<Number, Number> lineChart = new LineChart<>(xAxis, yAxis);
        lineChart.setTitle("์‹ค์‹œ๊ฐ„ CPU ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ’ป");
        lineChart.setAnimated(false);  // ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ๋ฅผ ์œ„ํ•ด ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋„๊ธฐ
        lineChart.setCreateSymbols(false);  // ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ ์‹ฌ๋ณผ ์ˆจ๊ธฐ๊ธฐ
        
        // ๋ฐ์ดํ„ฐ ์‹œ๋ฆฌ์ฆˆ ์ƒ์„ฑ
        series = new XYChart.Series<>();
        series.setName("CPU ์‚ฌ์šฉ๋ฅ ");
        lineChart.getData().add(series);
        
        // ์Šคํƒ€์ผ ์ ์šฉ
        lineChart.setStyle("-fx-background-color: #2d2d2d;");
        
        StackPane root = new StackPane(lineChart);
        Scene scene = new Scene(root, 800, 500);
        
        primaryStage.setTitle("์‹ค์‹œ๊ฐ„ ์ฐจํŠธ ์˜ˆ์ œ");
        primaryStage.setScene(scene);
        primaryStage.show();
        
        // ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ ์‹œ์ž‘
        startDataUpdate();
    }
    
    private void startDataUpdate() {
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
        
        scheduler.scheduleAtFixedRate(() -> {
            Platform.runLater(() -> {
                // ๋žœ๋ค CPU ์‚ฌ์šฉ๋ฅ  ์ƒ์„ฑ (์‹ค์ œ๋กœ๋Š” ์‹œ์Šคํ…œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์™€์•ผ ํ•จ)
                double cpuUsage = 30 + random.nextDouble() * 40;
                
                // ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
                series.getData().add(new XYChart.Data<>(timeCounter, cpuUsage));
                
                // ์ตœ๋Œ€ 50๊ฐœ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ๋งŒ ์œ ์ง€
                if (series.getData().size() > 50) {
                    series.getData().remove(0);
                }
                
                timeCounter++;
            });
        }, 0, 500, TimeUnit.MILLISECONDS);  // 0.5์ดˆ๋งˆ๋‹ค ์—…๋ฐ์ดํŠธ
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์™€! ์ด์ œ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์—…๋ฐ์ดํŠธ๋˜๋Š” ์ฐจํŠธ๊ฐ€ ์™„์„ฑ๋์–ด! ๐ŸŽ‰

์ด ์˜ˆ์ œ์—์„œ ์ค‘์š”ํ•œ ํฌ์ธํŠธ๋“ค:

Platform.runLater(): UI ์—…๋ฐ์ดํŠธ๋Š” ๋ฐ˜๋“œ์‹œ JavaFX Application Thread์—์„œ ํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์–ด.

ScheduledExecutorService: ๋ฐฑ๊ทธ๋ผ์šด๋“œ์—์„œ ์ฃผ๊ธฐ์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ–ˆ์–ด. ์‹ค์‹œ๊ฐ„ ๋ชจ๋‹ˆํ„ฐ๋ง์— ํ•„์ˆ˜์ ์ด์ง€!

๋ฐ์ดํ„ฐ ์ œํ•œ: ๋ฌดํ•œ์ • ๋ฐ์ดํ„ฐ๊ฐ€ ์Œ“์ด๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ธฐ๋‹ˆ๊นŒ ์ตœ๋Œ€ 50๊ฐœ๋กœ ์ œํ•œํ–ˆ์–ด. ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋Š” ์ž๋™์œผ๋กœ ์ œ๊ฑฐ๋˜์ง€.

์• ๋‹ˆ๋ฉ”์ด์…˜ ๋น„ํ™œ์„ฑํ™”: ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ์—์„œ๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ์˜คํžˆ๋ ค ๋ฐฉํ•ด๊ฐ€ ๋  ์ˆ˜ ์žˆ์–ด์„œ ๊บผ๋’€์–ด.

๐Ÿฅง PieChart ์˜ˆ์ œ

์ด๋ฒˆ์—” PieChart๋กœ ๊ฐ„๋‹จํ•œ ์˜ˆ์‚ฐ ๋ถ„์„ ์ฐจํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.chart.PieChart;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class BudgetPieChart extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(15);
        root.setPadding(new Insets(20));
        root.setStyle("-fx-background-color: #f5f5f5;");
        
        Label titleLabel = new Label("2024๋…„ ์˜ˆ์‚ฐ ๋ถ„์„ ๐Ÿ’ฐ");
        titleLabel.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
        
        // ๋ฐ์ดํ„ฐ ์ƒ์„ฑ
        ObservableList<PieChart.Data> pieChartData = FXCollections.observableArrayList(
            new PieChart.Data("๊ฐœ๋ฐœ", 35),
            new PieChart.Data("๋งˆ์ผ€ํŒ…", 25),
            new PieChart.Data("์šด์˜", 20),
            new PieChart.Data("์ธ์‚ฌ", 15),
            new PieChart.Data("๊ธฐํƒ€", 5)
        );
        
        PieChart pieChart = new PieChart(pieChartData);
        pieChart.setTitle("๋ถ€์„œ๋ณ„ ์˜ˆ์‚ฐ ๋ฐฐ๋ถ„");
        pieChart.setLegendVisible(true);
        
        // ๋ฐ์ดํ„ฐ ๋ ˆ์ด๋ธ”์— ํผ์„ผํŠธ ํ‘œ์‹œ
        pieChartData.forEach(data -> {
            data.nameProperty().bind(
                javafx.beans.binding.Bindings.concat(
                    data.getName(), " (", 
                    String.format("%.1f", data.getPieValue()), "%)"
                )
            );
        });
        
        root.getChildren().addAll(titleLabel, pieChart);
        
        Scene scene = new Scene(root, 600, 500);
        primaryStage.setTitle("PieChart ์˜ˆ์ œ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

PieChart๋Š” ์ •๋ง ๊ฐ„๋‹จํ•˜์ง€? ๋ฐ์ดํ„ฐ๋งŒ ๋„ฃ์–ด์ฃผ๋ฉด ์ž๋™์œผ๋กœ ๋น„์œจ์„ ๊ณ„์‚ฐํ•˜๊ณ  ์˜ˆ์˜๊ฒŒ ๊ทธ๋ ค์ค˜. ์ƒ‰์ƒ๋„ ์ž๋™์œผ๋กœ ๋ฐฐ์ •๋˜๊ณ ! ๋ฌผ๋ก  ์›ํ•˜๋ฉด ์ปค์Šคํ…€ ์ƒ‰์ƒ์„ ์ง€์ •ํ•  ์ˆ˜๋„ ์žˆ์–ด. ๐ŸŽจ

โš ๏ธ ์ฐจํŠธ ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ 1. ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ œ์–ด: ์‹ค์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ ์‹œ์—๋Š” ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋„๋Š” ๊ฒŒ ์ข‹์•„
2. ๋ฐ์ดํ„ฐ ๊ฐœ์ˆ˜ ์ œํ•œ: ๋„ˆ๋ฌด ๋งŽ์€ ๋ฐ์ดํ„ฐ ํฌ์ธํŠธ๋Š” ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์ผ์œผ์ผœ
3. ์‹ฌ๋ณผ ์ตœ์†Œํ™”: LineChart์—์„œ ์‹ฌ๋ณผ์„ ์ˆจ๊ธฐ๋ฉด ๋ Œ๋”๋ง์ด ๋นจ๋ผ์ ธ
4. ์Šค๋ ˆ๋“œ ๊ด€๋ฆฌ: ๋ฐฑ๊ทธ๋ผ์šด๋“œ ์ž‘์—…์€ ๋ณ„๋„ ์Šค๋ ˆ๋“œ์—์„œ, UI ์—…๋ฐ์ดํŠธ๋Š” Platform.runLater()๋กœ
5. ๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ: ์˜ค๋ž˜๋œ ๋ฐ์ดํ„ฐ๋Š” ์ฃผ๊ธฐ์ ์œผ๋กœ ์ œ๊ฑฐํ•ด์ค˜์•ผ ํ•ด

๐Ÿ—‚๏ธ TableView๋กœ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌํ•˜๊ธฐ

TableView๋Š” JavaFX์—์„œ ๊ฐ€์žฅ ๊ฐ•๋ ฅํ•œ ์ปดํฌ๋„ŒํŠธ ์ค‘ ํ•˜๋‚˜์•ผ! ์—‘์…€์ฒ˜๋Ÿผ ํ‘œ ํ˜•ํƒœ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ณ  ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์–ด. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ ๋„๊ตฌ, ์žฌ๊ณ  ๊ด€๋ฆฌ ์‹œ์Šคํ…œ, ๊ณ ๊ฐ ๊ด€๋ฆฌ ํ”„๋กœ๊ทธ๋žจ ๋“ฑ์—์„œ ํ•„์ˆ˜์ ์ด์ง€. ๐Ÿ“‹

๐Ÿ‘ค Person ๋ชจ๋ธ ํด๋ž˜์Šค

๋จผ์ € TableView์— ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ ๋ชจ๋ธ์„ ๋งŒ๋“ค์–ด์•ผ ํ•ด. JavaFX๋Š” JavaFX Properties๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ์„ ์ง€์›ํ•ด:

import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.SimpleIntegerProperty;

public class Person {
    private final SimpleStringProperty name;
    private final SimpleIntegerProperty age;
    private final SimpleStringProperty email;
    
    public Person(String name, int age, String email) {
        this.name = new SimpleStringProperty(name);
        this.age = new SimpleIntegerProperty(age);
        this.email = new SimpleStringProperty(email);
    }
    
    // Getter ๋ฉ”์„œ๋“œ
    public String getName() {
        return name.get();
    }
    
    public SimpleStringProperty nameProperty() {
        return name;
    }
    
    public int getAge() {
        return age.get();
    }
    
    public SimpleIntegerProperty ageProperty() {
        return age;
    }
    
    public String getEmail() {
        return email.get();
    }
    
    public SimpleStringProperty emailProperty() {
        return email;
    }
    
    // Setter ๋ฉ”์„œ๋“œ
    public void setName(String name) {
        this.name.set(name);
    }
    
    public void setAge(int age) {
        this.age.set(age);
    }
    
    public void setEmail(String email) {
        this.email.set(email);
    }
}

SimpleStringProperty์™€ SimpleIntegerProperty๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒŒ ํ•ต์‹ฌ์ด์•ผ! ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ TableView๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ผ. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐Ÿ˜Š

๐Ÿ“Š TableView ๊ตฌํ˜„

์ด์ œ TableView๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž. CRUD(Create, Read, Update, Delete) ๊ธฐ๋Šฅ๊นŒ์ง€ ๋ชจ๋‘ ๊ตฌํ˜„ํ•ด๋ณผ๊ฒŒ:

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.*;
import javafx.stage.Stage;
import javafx.util.converter.IntegerStringConverter;

public class PersonTableView extends Application {
    
    private TableView<Person> table;
    private ObservableList<Person> data;
    
    @Override
    public void start(Stage primaryStage) {
        BorderPane root = new BorderPane();
        root.setPadding(new Insets(15));
        
        // ์ƒ๋‹จ ํƒ€์ดํ‹€
        Label titleLabel = new Label("๐Ÿ‘ฅ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ์‹œ์Šคํ…œ");
        titleLabel.setStyle("-fx-font-size: 24px; -fx-font-weight: bold;");
        HBox titleBox = new HBox(titleLabel);
        titleBox.setPadding(new Insets(0, 0, 15, 0));
        root.setTop(titleBox);
        
        // TableView ์ƒ์„ฑ
        table = new TableView<>();
        table.setEditable(true);  // ํŽธ์ง‘ ๊ฐ€๋Šฅํ•˜๊ฒŒ ์„ค์ •
        
        // ์ปฌ๋Ÿผ ์ƒ์„ฑ
        TableColumn<Person, String> nameCol = new TableColumn<>("์ด๋ฆ„");
        nameCol.setMinWidth(150);
        nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
        nameCol.setCellFactory(TextFieldTableCell.forTableColumn());
        nameCol.setOnEditCommit(event -> {
            event.getRowValue().setName(event.getNewValue());
        });
        
        TableColumn<Person, Integer> ageCol = new TableColumn<>("๋‚˜์ด");
        ageCol.setMinWidth(100);
        ageCol.setCellValueFactory(new PropertyValueFactory<>("age"));
        ageCol.setCellFactory(TextFieldTableCell.forTableColumn(new IntegerStringConverter()));
        ageCol.setOnEditCommit(event -> {
            event.getRowValue().setAge(event.getNewValue());
        });
        
        TableColumn<Person, String> emailCol = new TableColumn<>("์ด๋ฉ”์ผ");
        emailCol.setMinWidth(200);
        emailCol.setCellValueFactory(new PropertyValueFactory<>("email"));
        emailCol.setCellFactory(TextFieldTableCell.forTableColumn());
        emailCol.setOnEditCommit(event -> {
            event.getRowValue().setEmail(event.getNewValue());
        });
        
        table.getColumns().addAll(nameCol, ageCol, emailCol);
        
        // ์ƒ˜ํ”Œ ๋ฐ์ดํ„ฐ
        data = FXCollections.observableArrayList(
            new Person("๊น€์ฒ ์ˆ˜", 25, "kim@example.com"),
            new Person("์ด์˜ํฌ", 30, "lee@example.com"),
            new Person("๋ฐ•๋ฏผ์ˆ˜", 28, "park@example.com")
        );
        table.setItems(data);
        
        root.setCenter(table);
        
        // ํ•˜๋‹จ ๋ฒ„ํŠผ๋“ค
        HBox buttonBox = new HBox(10);
        buttonBox.setPadding(new Insets(15, 0, 0, 0));
        
        Button addButton = new Button("โž• ์ถ”๊ฐ€");
        addButton.setStyle("-fx-background-color: #68d391; -fx-text-fill: white; -fx-font-weight: bold;");
        addButton.setOnAction(event -> showAddDialog());
        
        Button deleteButton = new Button("๐Ÿ—‘๏ธ ์‚ญ์ œ");
        deleteButton.setStyle("-fx-background-color: #f5576c; -fx-text-fill: white; -fx-font-weight: bold;");
        deleteButton.setOnAction(event -> {
            Person selected = table.getSelectionModel().getSelectedItem();
            if (selected != null) {
                data.remove(selected);
            } else {
                showAlert("์•Œ๋ฆผ", "์‚ญ์ œํ•  ํ•ญ๋ชฉ์„ ์„ ํƒํ•ด์ฃผ์„ธ์š”.");
            }
        });
        
        Button refreshButton = new Button("๐Ÿ”„ ์ƒˆ๋กœ๊ณ ์นจ");
        refreshButton.setStyle("-fx-background-color: #667eea; -fx-text-fill: white; -fx-font-weight: bold;");
        refreshButton.setOnAction(event -> table.refresh());
        
        buttonBox.getChildren().addAll(addButton, deleteButton, refreshButton);
        root.setBottom(buttonBox);
        
        Scene scene = new Scene(root, 600, 500);
        primaryStage.setTitle("TableView ์˜ˆ์ œ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private void showAddDialog() {
        Dialog<Person> dialog = new Dialog<>();
        dialog.setTitle("์ƒˆ ์‚ฌ์šฉ์ž ์ถ”๊ฐ€");
        dialog.setHeaderText("์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”");
        
        ButtonType addButtonType = new ButtonType("์ถ”๊ฐ€", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(addButtonType, ButtonType.CANCEL);
        
        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        grid.setPadding(new Insets(20));
        
        TextField nameField = new TextField();
        nameField.setPromptText("์ด๋ฆ„");
        TextField ageField = new TextField();
        ageField.setPromptText("๋‚˜์ด");
        TextField emailField = new TextField();
        emailField.setPromptText("์ด๋ฉ”์ผ");
        
        grid.add(new Label("์ด๋ฆ„:"), 0, 0);
        grid.add(nameField, 1, 0);
        grid.add(new Label("๋‚˜์ด:"), 0, 1);
        grid.add(ageField, 1, 1);
        grid.add(new Label("์ด๋ฉ”์ผ:"), 0, 2);
        grid.add(emailField, 1, 2);
        
        dialog.getDialogPane().setContent(grid);
        
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == addButtonType) {
                try {
                    return new Person(
                        nameField.getText(),
                        Integer.parseInt(ageField.getText()),
                        emailField.getText()
                    );
                } catch (NumberFormatException e) {
                    showAlert("์˜ค๋ฅ˜", "๋‚˜์ด๋Š” ์ˆซ์ž๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.");
                    return null;
                }
            }
            return null;
        });
        
        dialog.showAndWait().ifPresent(person -> {
            if (person != null) {
                data.add(person);
            }
        });
    }
    
    private void showAlert(String title, String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle(title);
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์™„์ „ํ•œ CRUD ๊ธฐ๋Šฅ์„ ๊ฐ€์ง„ TableView๊ฐ€ ์™„์„ฑ๋์–ด! ๐ŸŽ‰

์ฃผ์š” ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณด๋ฉด:

ํŽธ์ง‘ ๊ฐ€๋Šฅํ•œ ์…€: TextFieldTableCell์„ ์‚ฌ์šฉํ•ด์„œ ๋”๋ธ”ํด๋ฆญ์œผ๋กœ ์ง์ ‘ ํŽธ์ง‘ํ•  ์ˆ˜ ์žˆ์–ด.

PropertyValueFactory: ๋ชจ๋ธ ํด๋ž˜์Šค์˜ property ๋ฉ”์„œ๋“œ์™€ ์ž๋™์œผ๋กœ ์—ฐ๊ฒฐ๋ผ. "name"์„ ์ง€์ •ํ•˜๋ฉด nameProperty()๋ฅผ ์ฐพ์•„์„œ ๋ฐ”์ธ๋”ฉํ•ด์ค˜.

ObservableList: ๋ฐ์ดํ„ฐ๊ฐ€ ์ถ”๊ฐ€๋˜๊ฑฐ๋‚˜ ์‚ญ์ œ๋˜๋ฉด TableView๊ฐ€ ์ž๋™์œผ๋กœ ์—…๋ฐ์ดํŠธ๋ผ. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€?

Dialog: ์ƒˆ ๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ๋•Œ ๋‹ค์ด์–ผ๋กœ๊ทธ๋ฅผ ๋„์›Œ์„œ ์ž…๋ ฅ๋ฐ›์•„. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ํ›จ์”ฌ ์ข‹์•„์ ธ!

๐Ÿ’ก TableView ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ์ •๋ ฌ: ์ปฌ๋Ÿผ ํ—ค๋”๋ฅผ ํด๋ฆญํ•˜๋ฉด ์ž๋™์œผ๋กœ ์ •๋ ฌ๋ผ. setSortable(false)๋กœ ๋น„ํ™œ์„ฑํ™”ํ•  ์ˆ˜๋„ ์žˆ์–ด.

ํ•„ํ„ฐ๋ง: FilteredList๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด.

์ปค์Šคํ…€ ์…€: setCellFactory()๋กœ ์…€์˜ ๋ชจ์–‘์„ ์™„์ „ํžˆ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•ํ•  ์ˆ˜ ์žˆ์–ด. ๋ฒ„ํŠผ, ์ด๋ฏธ์ง€, ์ฒดํฌ๋ฐ•์Šค ๋“ฑ์„ ๋„ฃ์„ ์ˆ˜ ์žˆ์ง€!

๋‹ค์ค‘ ์„ ํƒ: table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE)๋กœ ์—ฌ๋Ÿฌ ํ–‰์„ ์„ ํƒํ•  ์ˆ˜ ์žˆ์–ด.

์ปจํ…์ŠคํŠธ ๋ฉ”๋‰ด: ์šฐํด๋ฆญ ๋ฉ”๋‰ด๋ฅผ ์ถ”๊ฐ€ํ•ด์„œ ํŽธ์ง‘, ์‚ญ์ œ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ์–ด.

๐ŸŽฌ ์• ๋‹ˆ๋ฉ”์ด์…˜๊ณผ ์ „ํ™˜ ํšจ๊ณผ

JavaFX์˜ ์• ๋‹ˆ๋ฉ”์ด์…˜ API๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•ด! ๋ถ€๋“œ๋Ÿฌ์šด ์ „ํ™˜ ํšจ๊ณผ์™€ ๋™์ ์ธ UI๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด. ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์ง€. ์›€์ง์ž„์ด ์žˆ๋Š” UI๋Š” ํ›จ์”ฌ ์ƒ๋™๊ฐ ์žˆ๊ณ  ์ „๋ฌธ์ ์œผ๋กœ ๋ณด์ด๊ฑฐ๋“ ! โœจ

๐ŸŒŠ ๊ธฐ๋ณธ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ข…๋ฅ˜

JavaFX๋Š” ๋‹ค์–‘ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํด๋ž˜์Šค๋ฅผ ์ œ๊ณตํ•ด:

1. TranslateTransition - ์œ„์น˜ ์ด๋™
2. RotateTransition - ํšŒ์ „
3. ScaleTransition - ํฌ๊ธฐ ๋ณ€๊ฒฝ
4. FadeTransition - ํˆฌ๋ช…๋„ ๋ณ€๊ฒฝ
5. PathTransition - ๊ฒฝ๋กœ๋ฅผ ๋”ฐ๋ผ ์ด๋™
6. Timeline - ์ปค์Šคํ…€ ์• ๋‹ˆ๋ฉ”์ด์…˜

๐Ÿ’ซ ์‹ค์ „ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์˜ˆ์ œ

์—ฌ๋Ÿฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ์กฐํ•ฉํ•ด์„œ ๋ฉ‹์ง„ ํšจ๊ณผ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž:

import javafx.animation.*;
import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class AnimationDemo extends Application {
    
    @Override
    public void start(Stage primaryStage) {
        VBox root = new VBox(30);
        root.setAlignment(Pos.CENTER);
        root.setStyle("-fx-background-color: #2d2d2d;");
        
        // ์› ์ƒ์„ฑ
        Circle circle = new Circle(50);
        circle.setFill(Color.web("#667eea"));
        
        // ์‚ฌ๊ฐํ˜• ์ƒ์„ฑ
        Rectangle rectangle = new Rectangle(100, 100);
        rectangle.setFill(Color.web("#764ba2"));
        
        // ๋ฒ„ํŠผ๋“ค
        Button fadeButton = new Button("๐ŸŒŸ ํŽ˜์ด๋“œ ํšจ๊ณผ");
        styleButton(fadeButton);
        fadeButton.setOnAction(event -> playFadeAnimation(circle));
        
        Button rotateButton = new Button("๐Ÿ”„ ํšŒ์ „ ํšจ๊ณผ");
        styleButton(rotateButton);
        rotateButton.setOnAction(event -> playRotateAnimation(rectangle));
        
        Button scaleButton = new Button("๐Ÿ“ ํฌ๊ธฐ ๋ณ€๊ฒฝ");
        styleButton(scaleButton);
        scaleButton.setOnAction(event -> playScaleAnimation(circle));
        
        Button moveButton = new Button("โžก๏ธ ์ด๋™ ํšจ๊ณผ");
        styleButton(moveButton);
        moveButton.setOnAction(event -> playMoveAnimation(rectangle));
        
        Button comboButton = new Button("๐ŸŽญ ์กฐํ•ฉ ํšจ๊ณผ");
        styleButton(comboButton);
        comboButton.setOnAction(event -> playComboAnimation(circle));
        
        StackPane shapePane = new StackPane(circle, rectangle);
        shapePane.setPrefHeight(200);
        
        root.getChildren().addAll(
            shapePane,
            fadeButton,
            rotateButton,
            scaleButton,
            moveButton,
            comboButton
        );
        
        Scene scene = new Scene(root, 600, 600);
        primaryStage.setTitle("JavaFX ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฐ๋ชจ");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    private void styleButton(Button button) {
        button.setStyle(
            "-fx-background-color: #667eea; " +
            "-fx-text-fill: white; " +
            "-fx-font-size: 14px; " +
            "-fx-font-weight: bold; " +
            "-fx-padding: 10px 20px; " +
            "-fx-background-radius: 5px;"
        );
        button.setPrefWidth(200);
    }
    
    // ํŽ˜์ด๋“œ ์• ๋‹ˆ๋ฉ”์ด์…˜
    private void playFadeAnimation(Circle circle) {
        FadeTransition fade = new FadeTransition(Duration.seconds(1), circle);
        fade.setFromValue(1.0);
        fade.setToValue(0.1);
        fade.setCycleCount(2);
        fade.setAutoReverse(true);
        fade.play();
    }
    
    // ํšŒ์ „ ์• ๋‹ˆ๋ฉ”์ด์…˜
    private void playRotateAnimation(Rectangle rectangle) {
        RotateTransition rotate = new RotateTransition(Duration.seconds(2), rectangle);
        rotate.setByAngle(360);
        rotate.setCycleCount(1);
        rotate.play();
    }
    
    // ํฌ๊ธฐ ๋ณ€๊ฒฝ ์• ๋‹ˆ๋ฉ”์ด์…˜
    private void playScaleAnimation(Circle circle) {
        ScaleTransition scale = new ScaleTransition(Duration.seconds(1), circle);
        scale.setFromX(1.0);
        scale.setFromY(1.0);
        scale.setToX(2.0);
        scale.setToY(2.0);
        scale.setCycleCount(2);
        scale.setAutoReverse(true);
        scale.play();
    }
    
    // ์ด๋™ ์• ๋‹ˆ๋ฉ”์ด์…˜
    private void playMoveAnimation(Rectangle rectangle) {
        TranslateTransition move = new TranslateTransition(Duration.seconds(1), rectangle);
        move.setFromX(0);
        move.setToX(100);
        move.setCycleCount(2);
        move.setAutoReverse(true);
        move.play();
    }
    
    // ์กฐํ•ฉ ์• ๋‹ˆ๋ฉ”์ด์…˜
    private void playComboAnimation(Circle circle) {
        // ์—ฌ๋Ÿฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋™์‹œ์— ์‹คํ–‰
        FadeTransition fade = new FadeTransition(Duration.seconds(2), circle);
        fade.setFromValue(1.0);
        fade.setToValue(0.3);
        
        ScaleTransition scale = new ScaleTransition(Duration.seconds(2), circle);
        scale.setToX(2.0);
        scale.setToY(2.0);
        
        RotateTransition rotate = new RotateTransition(Duration.seconds(2), circle);
        rotate.setByAngle(720);
        
        ParallelTransition parallel = new ParallelTransition(fade, scale, rotate);
        parallel.setCycleCount(1);
        parallel.setAutoReverse(true);
        parallel.play();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

์™€! ๋‹ค์–‘ํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ํšจ๊ณผ๋ฅผ ํ•œ ๋ฒˆ์— ๋ณผ ์ˆ˜ ์žˆ์–ด! ๐ŸŽจ

ParallelTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ์—ฌ๋Ÿฌ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋™์‹œ์— ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด. ๋ฐ˜๋Œ€๋กœ SequentialTransition์„ ์‚ฌ์šฉํ•˜๋ฉด ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์ง€.

setCycleCount()๋กœ ๋ฐ˜๋ณต ํšŸ์ˆ˜๋ฅผ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๊ณ , setAutoReverse(true)๋ฅผ ์„ค์ •ํ•˜๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋๋‚œ ํ›„ ์—ญ๋ฐฉํ–ฅ์œผ๋กœ ์žฌ์ƒ๋ผ. ์ •๋ง ์œ ์šฉํ•˜์ง€? ๐Ÿ˜Š

โฑ๏ธ Timeline์œผ๋กœ ์ปค์Šคํ…€ ์• ๋‹ˆ๋ฉ”์ด์…˜

๋” ๋ณต์žกํ•œ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ํ•„์š”ํ•˜๋‹ค๋ฉด Timeline์„ ์‚ฌ์šฉํ•ด. KeyFrame์„ ์‚ฌ์šฉํ•ด์„œ ํŠน์ • ์‹œ์ ์˜ ์ƒํƒœ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ์–ด:

Timeline timeline = new Timeline(
    new KeyFrame(Duration.ZERO, 
        new KeyValue(circle.translateXProperty(), 0),
        new KeyValue(circle.fillProperty(), Color.BLUE)
    ),
    new KeyFrame(Duration.seconds(1),
        new KeyValue(circle.translateXProperty(), 200),
        new KeyValue(circle.fillProperty(), Color.RED)
    ),
    new KeyFrame(Duration.seconds(2),
        new KeyValue(circle.translateXProperty(), 0),
        new KeyValue(circle.fillProperty(), Color.GREEN)
    )
);

timeline.setCycleCount(Timeline.INDEFINITE);
timeline.play();

Timeline์€ ์ •๋ง ๊ฐ•๋ ฅํ•ด! ์—ฌ๋Ÿฌ ์†์„ฑ์„ ๋™์‹œ์— ์• ๋‹ˆ๋ฉ”์ด์…˜ํ•  ์ˆ˜ ์žˆ๊ณ , ๋ณต์žกํ•œ ํƒ€์ด๋ฐ ์ œ์–ด๋„ ๊ฐ€๋Šฅํ•ด. ๊ฒŒ์ž„์ด๋‚˜ ์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ๋•Œ ํ•„์ˆ˜์ ์ด์•ผ! ๐ŸŽฎ

๐ŸŽฏ ์• ๋‹ˆ๋ฉ”์ด์…˜ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค 1. ์ ์ ˆํ•œ ์†๋„: ๋„ˆ๋ฌด ๋น ๋ฅด๊ฑฐ๋‚˜ ๋А๋ฆฌ๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ๋‚˜๋น ์ ธ. 0.3~1์ดˆ๊ฐ€ ์ ๋‹นํ•ด
2. Easing ํ•จ์ˆ˜: setInterpolator()๋กœ ์ž์—ฐ์Šค๋Ÿฌ์šด ๊ฐ€์†/๊ฐ์† ํšจ๊ณผ๋ฅผ ์ค˜
3. ์„ฑ๋Šฅ ๊ณ ๋ ค: ๋„ˆ๋ฌด ๋งŽ์€ ์• ๋‹ˆ๋ฉ”์ด์…˜์„ ๋™์‹œ์— ์‹คํ–‰ํ•˜๋ฉด ์„ฑ๋Šฅ ์ €ํ•˜๊ฐ€ ์ƒ๊ฒจ
4. ๋ชฉ์ ์„ฑ: ์• ๋‹ˆ๋ฉ”์ด์…˜์€ ์žฅ์‹์ด ์•„๋‹ˆ๋ผ ์‚ฌ์šฉ์ž์—๊ฒŒ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•˜๋Š” ์ˆ˜๋‹จ์ด์•ผ
5. ์ผ๊ด€์„ฑ: ์•ฑ ์ „์ฒด์—์„œ ์ผ๊ด€๋œ ์• ๋‹ˆ๋ฉ”์ด์…˜ ์Šคํƒ€์ผ์„ ์œ ์ง€ํ•ด์•ผ ํ•ด

๐Ÿ”ง ์‹ค์ „ ํ”„๋กœ์ ํŠธ: ๊ฐ„๋‹จํ•œ ๋ฉ”๋ชจ์žฅ ์•ฑ ๋งŒ๋“ค๊ธฐ

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ชจ๋‘ ํ™œ์šฉํ•ด์„œ ์‹ค์ œ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”๋ชจ์žฅ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์ž! ํŒŒ์ผ ์ €์žฅ/๋ถˆ๋Ÿฌ์˜ค๊ธฐ, ํŽธ์ง‘ ๊ธฐ๋Šฅ, ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ๊นŒ์ง€ ๋ชจ๋‘ ๊ตฌํ˜„ํ•  ๊ฑฐ์•ผ. ์ด ํ”„๋กœ์ ํŠธ๋ฅผ ํ†ตํ•ด JavaFX์˜ ์ „์ฒด์ ์ธ ํ๋ฆ„์„ ์ดํ•ดํ•  ์ˆ˜ ์žˆ์„ ๊ฑฐ์•ผ! ๐Ÿ“

๐Ÿ“‹ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

๋จผ์ € ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ๋ฅผ ์„ค๊ณ„ํ•ด๋ณด์ž:

Main.java - ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ง„์ž…์ 
NotepadController.java - UI ๋กœ์ง ์ฒ˜๋ฆฌ
notepad.fxml - UI ๋ ˆ์ด์•„์›ƒ
styles.css - ์Šคํƒ€์ผ ์ •์˜

๐Ÿ’ป Main ํด๋ž˜์Šค

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 {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("notepad.fxml"));
        Parent root = loader.load();
        
        Scene scene = new Scene(root, 800, 600);
        scene.getStylesheets().add(getClass().getResource("styles.css").toExternalForm());
        
        primaryStage.setTitle("JavaFX ๋ฉ”๋ชจ์žฅ ๐Ÿ“");
        primaryStage.setScene(scene);
        primaryStage.show();
    }
    
    public static void main(String[] args) {
        launch(args);
    }
}

๐ŸŽจ FXML ๋ ˆ์ด์•„์›ƒ

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

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

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="NotepadController">
    
    <top>
        <VBox>
            <MenuBar>
                <Menu text="ํŒŒ์ผ">
                    <MenuItem text="์ƒˆ ํŒŒ์ผ" onAction="#handleNew"/>
                    <MenuItem text="์—ด๊ธฐ" onAction="#handleOpen"/>
                    <MenuItem text="์ €์žฅ" onAction="#handleSave"/>
                    <SeparatorMenuItem/>
                    <MenuItem text="์ข…๋ฃŒ" onAction="#handleExit"/>
                </Menu>
                <Menu text="ํŽธ์ง‘">
                    <MenuItem text="์ฐพ๊ธฐ" onAction="#handleFind"/>
                    <MenuItem text="๋ฐ”๊พธ๊ธฐ" onAction="#handleReplace"/>
                </Menu>
                <Menu text="๋„์›€๋ง">
                    <MenuItem text="์ •๋ณด" onAction="#handleAbout"/>
                </Menu>
            </MenuBar>
            
            <ToolBar>
                <Button text="์ƒˆ ํŒŒ์ผ" onAction="#handleNew"/>
                <Button text="์—ด๊ธฐ" onAction="#handleOpen"/>
                <Button text="์ €์žฅ" onAction="#handleSave"/>
                <Separator/>
                <Button text="์ฐพ๊ธฐ" onAction="#handleFind"/>
            </ToolBar>
        </VBox>
    </top>
    
    <center>
        <TextArea fx:id="textArea" wrapText="true"/>
    </center>
    
    <bottom>
        <HBox styleClass="status-bar">
            <Label fx:id="statusLabel" text="์ค€๋น„"/>
        </HBox>
    </bottom>
    
</BorderPane>

๐ŸŽฎ Controller ํด๋ž˜์Šค

import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.stage.FileChooser;
import javafx.stage.Stage;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Optional;

public class NotepadController {
    
    @FXML
    private TextArea textArea;
    
    @FXML
    private Label statusLabel;
    
    private File currentFile;
    private boolean isModified = false;
    
    @FXML
    private void initialize() {
        // ํ…์ŠคํŠธ ๋ณ€๊ฒฝ ๊ฐ์ง€
        textArea.textProperty().addListener((observable, oldValue, newValue) -> {
            isModified = true;
            updateStatus("์ˆ˜์ •๋จ");
        });
    }
    
    @FXML
    private void handleNew() {
        if (checkSaveBeforeAction()) {
            textArea.clear();
            currentFile = null;
            isModified = false;
            updateStatus("์ƒˆ ํŒŒ์ผ");
        }
    }
    
    @FXML
    private void handleOpen() {
        if (!checkSaveBeforeAction()) {
            return;
        }
        
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("ํŒŒ์ผ ์—ด๊ธฐ");
        fileChooser.getExtensionFilters().add(
            new FileChooser.ExtensionFilter("ํ…์ŠคํŠธ ํŒŒ์ผ", "*.txt")
        );
        
        File file = fileChooser.showOpenDialog(getStage());
        if (file != null) {
            try {
                String content = new String(Files.readAllBytes(Paths.get(file.getPath())));
                textArea.setText(content);
                currentFile = file;
                isModified = false;
                updateStatus("ํŒŒ์ผ ์—ด๋ฆผ: " + file.getName());
            } catch (IOException e) {
                showError("ํŒŒ์ผ์„ ์—ด ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + e.getMessage());
            }
        }
    }
    
    @FXML
    private void handleSave() {
        if (currentFile == null) {
            handleSaveAs();
        } else {
            saveFile(currentFile);
        }
    }
    
    private void handleSaveAs() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("๋‹ค๋ฅธ ์ด๋ฆ„์œผ๋กœ ์ €์žฅ");
        fileChooser.getExtensionFilters().add(
            new FileChooser.ExtensionFilter("ํ…์ŠคํŠธ ํŒŒ์ผ", "*.txt")
        );
        
        File file = fileChooser.showSaveDialog(getStage());
        if (file != null) {
            saveFile(file);
        }
    }
    
    private void saveFile(File file) {
        try {
            Files.write(Paths.get(file.getPath()), textArea.getText().getBytes());
            currentFile = file;
            isModified = false;
            updateStatus("์ €์žฅ๋จ: " + file.getName());
        } catch (IOException e) {
            showError("ํŒŒ์ผ์„ ์ €์žฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: " + e.getMessage());
        }
    }
    
    @FXML
    private void handleFind() {
        TextInputDialog dialog = new TextInputDialog();
        dialog.setTitle("์ฐพ๊ธฐ");
        dialog.setHeaderText("๊ฒ€์ƒ‰ํ•  ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”");
        dialog.setContentText("์ฐพ๊ธฐ:");
        
        Optional<String> result = dialog.showAndWait();
        result.ifPresent(searchText -> {
            String content = textArea.getText();
            int index = content.indexOf(searchText);
            
            if (index >= 0) {
                textArea.selectRange(index, index + searchText.length());
                updateStatus("์ฐพ์Œ: " + searchText);
            } else {
                showInfo("'" + searchText + "'๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
            }
        });
    }
    
    @FXML
    private void handleReplace() {
        Dialog<ButtonType> dialog = new Dialog<>();
        dialog.setTitle("๋ฐ”๊พธ๊ธฐ");
        dialog.setHeaderText("ํ…์ŠคํŠธ ๋ฐ”๊พธ๊ธฐ");
        
        ButtonType replaceButtonType = new ButtonType("๋ฐ”๊พธ๊ธฐ", ButtonBar.ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(replaceButtonType, ButtonType.CANCEL);
        
        GridPane grid = new GridPane();
        grid.setHgap(10);
        grid.setVgap(10);
        
        TextField findField = new TextField();
        findField.setPromptText("์ฐพ์„ ํ…์ŠคํŠธ");
        TextField replaceField = new TextField();
        replaceField.setPromptText("๋ฐ”๊ฟ€ ํ…์ŠคํŠธ");
        
        grid.add(new Label("์ฐพ๊ธฐ:"), 0, 0);
        grid.add(findField, 1, 0);
        grid.add(new Label("๋ฐ”๊พธ๊ธฐ:"), 0, 1);
        grid.add(replaceField, 1, 1);
        
        dialog.getDialogPane().setContent(grid);
        
        Optional<ButtonType> result = dialog.showAndWait();
        if (result.isPresent() && result.get() == replaceButtonType) {
            String findText = findField.getText();
            String replaceText = replaceField.getText();
            
            if (!findText.isEmpty()) {
                String content = textArea.getText();
                String newContent = content.replace(findText, replaceText);
                textArea.setText(newContent);
                updateStatus("๋ฐ”๊พธ๊ธฐ ์™„๋ฃŒ");
            }
        }
    }
    
    @FXML
    private void handleAbout() {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("์ •๋ณด");
        alert.setHeaderText("JavaFX ๋ฉ”๋ชจ์žฅ");
        alert.setContentText(
            "๋ฒ„์ „: 1.0\n" +
            "JavaFX๋ฅผ ํ™œ์šฉํ•œ ๊ฐ„๋‹จํ•œ ๋ฉ”๋ชจ์žฅ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค.\n\n" +
            "์žฌ๋Šฅ๋„ท(jaenung.net)์—์„œ ๋” ๋งŽ์€ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์ง€์‹์„ ๊ณต์œ ํ•˜์„ธ์š”! ๐Ÿš€"
        );
        alert.showAndWait();
    }
    
    @FXML
    private void handleExit() {
        if (checkSaveBeforeAction()) {
            System.exit(0);
        }
    }
    
    private boolean checkSaveBeforeAction() {
        if (isModified) {
            Alert alert = new Alert(Alert.AlertType.CONFIRMATION);
            alert.setTitle("ํ™•์ธ");
            alert.setHeaderText("๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ์Šต๋‹ˆ๋‹ค");
            alert.setContentText("์ €์žฅํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?");
            
            ButtonType saveButton = new ButtonType("์ €์žฅ");
            ButtonType dontSaveButton = new ButtonType("์ €์žฅ ์•ˆ ํ•จ");
            ButtonType cancelButton = new ButtonType("์ทจ์†Œ", ButtonBar.ButtonData.CANCEL_CLOSE);
            
            alert.getButtonTypes().setAll(saveButton, dontSaveButton, cancelButton);
            
            Optional<ButtonType> result = alert.showAndWait();
            if (result.isPresent()) {
                if (result.get() == saveButton) {
                    handleSave();
                    return true;
                } else if (result.get() == dontSaveButton) {
                    return true;
                } else {
                    return false;
                }
            }
        }
        return true;
    }
    
    private void updateStatus(String message) {
        statusLabel.setText(message);
    }
    
    private void showError(String message) {
        Alert alert = new Alert(Alert.AlertType.ERROR);
        alert.setTitle("์˜ค๋ฅ˜");
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
    
    private void showInfo(String message) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setTitle("์•Œ๋ฆผ");
        alert.setHeaderText(null);
        alert.setContentText(message);
        alert.showAndWait();
    }
    
    private Stage getStage() {
        return (Stage) textArea.getScene().getWindow();
    }
}

๐ŸŽจ CSS ์Šคํƒ€์ผ

/* styles.css */

.root {
    -fx-background-color: #ffffff;
}

.text-area {
    -fx-font-family: "Consolas", "Monaco", monospace;
    -fx-font-size: 14px;
}

.status-bar {
    -fx-background-color: #f0f0f0;
    -fx-padding: 5px 10px;
}

.status-bar .label {
    -fx-text-fill: #666666;
    -fx-font-size: 12px;
}

.button {
    -fx-background-color: #667eea;
    -fx-text-fill: white;
    -fx-padding: 5px 15px;
    -fx-background-radius: 3px;
    -fx-cursor: hand;
}

.button:hover {
    -fx-background-color: #5568d3;
}

.menu-bar {
    -fx-background-color: #f8f8f8;
}

์™„์„ฑ! ๐ŸŽ‰ ์ด์ œ ์™„์ „ํžˆ ์ž‘๋™ํ•˜๋Š” ๋ฉ”๋ชจ์žฅ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ๋งŒ๋“ค์–ด์กŒ์–ด!

์ด ํ”„๋กœ์ ํŠธ์—์„œ ๋‹ค๋ฃฌ ์ฃผ์š” ๊ฐœ๋…๋“ค:

โœ… FXML์„ ํ™œ์šฉํ•œ UI ๋””์ž์ธ
โœ… Controller ํŒจํ„ด์œผ๋กœ ๋กœ์ง ๋ถ„๋ฆฌ
โœ… ํŒŒ์ผ ์ž…์ถœ๋ ฅ (FileChooser, Files API)
โœ… ๋‹ค์ด์–ผ๋กœ๊ทธ ํ™œ์šฉ (Alert, TextInputDialog, Custom Dialog)
โœ… ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ ์ƒํƒœ ๊ด€๋ฆฌ
โœ… CSS ์Šคํƒ€์ผ๋ง

์ด๋Ÿฐ ๊ตฌ์กฐ๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ฉด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์ •๋ง ์‰ฌ์›Œ์ ธ. UI์™€ ๋กœ์ง์ด ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์–ด์„œ ๊ฐ๊ฐ ๋…๋ฆฝ์ ์œผ๋กœ ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๊ฑฐ๋“ ! ๐Ÿ˜Š

๐Ÿ’ก ํ”„๋กœ์ ํŠธ ํ™•์žฅ ์•„์ด๋””์–ด ์ด ๋ฉ”๋ชจ์žฅ์„ ๋” ๋ฐœ์ „์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ์•„์ด๋””์–ด๋“ค:

โ€ข ์—ฌ๋Ÿฌ ํƒญ์œผ๋กœ ๋™์‹œ์— ์—ฌ๋Ÿฌ ํŒŒ์ผ ํŽธ์ง‘
โ€ข ๋ฌธ๋ฒ• ๊ฐ•์กฐ (Syntax Highlighting)
โ€ข ์ž๋™ ์ €์žฅ ๊ธฐ๋Šฅ
โ€ข ์ตœ๊ทผ ํŒŒ์ผ ๋ชฉ๋ก
โ€ข ๊ธ€๊ผด ๋ฐ ํ…Œ๋งˆ ์„ค์ •
โ€ข ์ค„ ๋ฒˆํ˜ธ ํ‘œ์‹œ
โ€ข ๋‹จ์–ด ์ˆ˜ ์นด์šดํ„ฐ
โ€ข ์ธ์‡„ ๊ธฐ๋Šฅ

์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค์„ ํ•˜๋‚˜์”ฉ ์ถ”๊ฐ€ํ•˜๋ฉด์„œ JavaFX ์‹ค๋ ฅ์„ ๋” ํ‚ค์›Œ๋ณด์ž! ๐Ÿ’ช

๐Ÿš€ ๋ฐฐํฌ์™€ ํŒจํ‚ค์ง•

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์™„์„ฑํ–ˆ๋‹ค๋ฉด ์ด์ œ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฐฐํฌํ•ด์•ผ๊ฒ ์ง€? JavaFX ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ํŒจํ‚ค์ง•ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๐ŸŽ

๐Ÿ“ฆ JAR ํŒŒ์ผ ์ƒ์„ฑ

๊ฐ€์žฅ ๊ธฐ๋ณธ์ ์ธ ๋ฐฐํฌ ๋ฐฉ๋ฒ•์€ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ JAR ํŒŒ์ผ์„ ๋งŒ๋“œ๋Š” ๊ฑฐ์•ผ. IntelliJ IDEA์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ํ•ด:

1. File โ†’ Project Structure โ†’ Artifacts
2. + ๋ฒ„ํŠผ ํด๋ฆญ โ†’ JAR โ†’ From modules with dependencies
3. Main Class ์„ ํƒ
4. JAR files from libraries ์˜ต์…˜ ์„ ํƒ
5. Build โ†’ Build Artifacts โ†’ Build

๊ทธ๋Ÿฌ๋ฉด out/artifacts ํด๋”์— JAR ํŒŒ์ผ์ด ์ƒ์„ฑ๋ผ!

๐Ÿ–ฅ๏ธ ๋„ค์ดํ‹ฐ๋ธŒ ์‹คํ–‰ ํŒŒ์ผ ๋งŒ๋“ค๊ธฐ

JAR ํŒŒ์ผ์€ Java๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์–ด์•ผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด. ๋” ํŽธ๋ฆฌํ•˜๊ฒŒ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋„ค์ดํ‹ฐ๋ธŒ ์‹คํ–‰ ํŒŒ์ผ(.exe, .app, .deb)์„ ๋งŒ๋“œ๋Š” ๊ฒŒ ์ข‹์•„.

jpackage ๋„๊ตฌ ์‚ฌ์šฉํ•˜๊ธฐ (JDK 14 ์ด์ƒ)

jpackage๋Š” JDK์— ํฌํ•จ๋œ ๋„๊ตฌ๋กœ, ํ”Œ๋žซํผ๋ณ„ ์„ค์น˜ ํŒจํ‚ค์ง€๋ฅผ ๋งŒ๋“ค์–ด์ค˜:

# Windows์šฉ EXE ์ƒ์„ฑ
jpackage --input target/ \
         --name MyApp \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type exe \
         --icon icon.ico

# macOS์šฉ DMG ์ƒ์„ฑ
jpackage --input target/ \
         --name MyApp \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type dmg \
         --icon icon.icns

# Linux์šฉ DEB ์ƒ์„ฑ
jpackage --input target/ \
         --name MyApp \
         --main-jar myapp.jar \
         --main-class com.example.Main \
         --type deb

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด JRE๊ฐ€ ํฌํ•จ๋œ ๋…๋ฆฝ ์‹คํ–‰ ํŒŒ์ผ์ด ๋งŒ๋“ค์–ด์ ธ! ์‚ฌ์šฉ์ž๋Š” Java๋ฅผ ๋”ฐ๋กœ ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†์–ด. ์ •๋ง ํŽธ๋ฆฌํ•˜์ง€? ๐ŸŽฏ

๐Ÿ”ง Maven/Gradle๋กœ ๋นŒ๋“œ ์ž๋™ํ™”

Maven์ด๋‚˜ Gradle์„ ์‚ฌ์šฉํ•˜๋ฉด ๋นŒ๋“œ ๊ณผ์ •์„ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์–ด. Maven์˜ ๊ฒฝ์šฐ pom.xml์— ์ด๋ ‡๊ฒŒ ์„ค์ •ํ•ด:

<build>
    <plugins>
        <plugin>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-maven-plugin</artifactId>
            <version>0.0.8</version>
            <configuration>
                <mainClass>com.example.Main</mainClass>
            </configuration>
        </plugin>
        
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

๊ทธ๋ฆฌ๊ณ  mvn clean package ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋นŒ๋“œ๋ผ! ์ •๋ง ๊ฐ„ํŽธํ•˜์ง€? ๐Ÿ˜Š

โš ๏ธ ๋ฐฐํฌ ์‹œ ์ฃผ์˜์‚ฌํ•ญ 1. ๋ผ์ด์„ ์Šค ํ™•์ธ: ์‚ฌ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์˜ ๋ผ์ด์„ ์Šค๋ฅผ ํ™•์ธํ•˜๊ณ  ์ค€์ˆ˜ํ•ด์•ผ ํ•ด
2. ์ฝ”๋“œ ๋‚œ๋…ํ™”: ์ƒ์—…์šฉ ์•ฑ์ด๋ผ๋ฉด ProGuard ๊ฐ™์€ ๋„๊ตฌ๋กœ ์ฝ”๋“œ๋ฅผ ๋ณดํ˜ธํ•˜๋Š” ๊ฒŒ ์ข‹์•„
3. ๋””์ง€ํ„ธ ์„œ๋ช…: Windows์—์„œ๋Š” ์ฝ”๋“œ ์„œ๋ช…์ด ์—†์œผ๋ฉด ๊ฒฝ๊ณ ๊ฐ€ ๋œฐ ์ˆ˜ ์žˆ์–ด
4. ์ž๋™ ์—…๋ฐ์ดํŠธ: ์—…๋ฐ์ดํŠธ ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ๊ตฌํ˜„ํ•˜๋ฉด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์ด ์ข‹์•„์ ธ
5. ํ…Œ์ŠคํŠธ: ๋‹ค์–‘ํ•œ ํ™˜๊ฒฝ์—์„œ ์ถฉ๋ถ„ํžˆ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•ด

๐ŸŽ“ ํ•™์Šต ๋ฆฌ์†Œ์Šค์™€ ์ปค๋ฎค๋‹ˆํ‹ฐ

JavaFX๋ฅผ ๊ณ„์† ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์ด๋Ÿฐ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•ด๋ด! ๐Ÿ“š

๐Ÿ“– ๊ณต์‹ ๋ฌธ์„œ ๋ฐ ํŠœํ† ๋ฆฌ์–ผ โ€ข Oracle JavaFX Documentation: ๊ณต์‹ ๋ฌธ์„œ๋Š” ๊ฐ€์žฅ ์ •ํ™•ํ•˜๊ณ  ์ƒ์„ธํ•ด
โ€ข OpenJFX Wiki: ์˜คํ”ˆ์†Œ์Šค ํ”„๋กœ์ ํŠธ์˜ ์œ„ํ‚ค์— ์œ ์šฉํ•œ ์ •๋ณด๊ฐ€ ๋งŽ์•„
โ€ข JavaFX Tutorial (jenkov.com): ์ดˆ๋ณด์ž์—๊ฒŒ ์นœ์ ˆํ•œ ํŠœํ† ๋ฆฌ์–ผ
โ€ข ControlsFX: ์ถ”๊ฐ€ ์ปจํŠธ๋กค ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ, ์ •๋ง ์œ ์šฉํ•ด!
๐Ÿ’ฌ ์ปค๋ฎค๋‹ˆํ‹ฐ โ€ข Stack Overflow: JavaFX ํƒœ๊ทธ๋กœ ์งˆ๋ฌธํ•˜๋ฉด ๋‹ต๋ณ€์„ ๋น ๋ฅด๊ฒŒ ๋ฐ›์„ ์ˆ˜ ์žˆ์–ด
โ€ข Reddit r/JavaFX: JavaFX ๊ฐœ๋ฐœ์ž๋“ค์˜ ์ปค๋ฎค๋‹ˆํ‹ฐ
โ€ข GitHub: ์˜คํ”ˆ์†Œ์Šค JavaFX ํ”„๋กœ์ ํŠธ๋“ค์„ ์ฐธ๊ณ ํ•˜๋ฉด ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด
โ€ข ์žฌ๋Šฅ๋„ท: ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ณ  ๋ฐฐ์šธ ์ˆ˜ ์žˆ๋Š” ํ”Œ๋žซํผ! ์—ฌ๊ธฐ์„œ JavaFX ์ „๋ฌธ๊ฐ€๋ฅผ ๋งŒ๋‚  ์ˆ˜๋„ ์žˆ์–ด ๐Ÿ˜Š
๐Ÿ“บ ์œ ํŠœ๋ธŒ ์ฑ„๋„ โ€ข Coding with John: JavaFX ๊ธฐ์ดˆ๋ถ€ํ„ฐ ๊ณ ๊ธ‰๊นŒ์ง€
โ€ข Bro Code: ์žฌ๋ฏธ์žˆ๊ณ  ์‰ฌ์šด JavaFX ํŠœํ† ๋ฆฌ์–ผ
โ€ข Genuine Coder: ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์ค‘์‹ฌ ๊ฐ•์˜

๐ŸŒŸ ์‹ค๋ ฅ ํ–ฅ์ƒ์„ ์œ„ํ•œ ํŒ

JavaFX๋ฅผ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ ์œ„ํ•œ ๋‚˜๋งŒ์˜ ํŒ์„ ๊ณต์œ ํ• ๊ฒŒ:

1. ์ž‘์€ ํ”„๋กœ์ ํŠธ๋ถ€ํ„ฐ ์‹œ์ž‘ ๐ŸŽฏ
์ฒ˜์Œ๋ถ€ํ„ฐ ํฐ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค๋ ค๊ณ  ํ•˜์ง€ ๋งˆ. ๊ณ„์‚ฐ๊ธฐ, ๋ฉ”๋ชจ์žฅ, To-Do ๋ฆฌ์ŠคํŠธ ๊ฐ™์€ ๊ฐ„๋‹จํ•œ ์•ฑ๋ถ€ํ„ฐ ๋งŒ๋“ค์–ด๋ด. ๊ธฐ์ดˆ๋ฅผ ํƒ„ํƒ„ํžˆ ๋‹ค์ง€๋Š” ๊ฒŒ ์ค‘์š”ํ•ด!

2. ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ์ฝ”๋“œ ์ฝ๊ธฐ ๐Ÿ‘€
GitHub์—์„œ JavaFX ํ”„๋กœ์ ํŠธ๋ฅผ ์ฐพ์•„์„œ ์ฝ”๋“œ๋ฅผ ์ฝ์–ด๋ด. ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค์ด ์–ด๋–ป๊ฒŒ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š”์ง€ ๋ณด๋ฉด ์ •๋ง ๋งŽ์ด ๋ฐฐ์šธ ์ˆ˜ ์žˆ์–ด.

3. ๋งค์ผ ์กฐ๊ธˆ์”ฉ ์ฝ”๋”ฉ ๐Ÿ’ช
ํ•˜๋ฃจ์— 30๋ถ„์ด๋ผ๋„ ๊พธ์ค€ํžˆ ์ฝ”๋”ฉํ•˜๋Š” ๊ฒŒ ํ•œ ๋ฒˆ์— ๋ชฐ์•„์„œ ํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ํ›จ์”ฌ ํšจ๊ณผ์ ์ด์•ผ. ์Šต๊ด€์ด ์‹ค๋ ฅ์„ ๋งŒ๋“ค์–ด!

4. ๋ฌธ์„œ ์ฝ๋Š” ์Šต๊ด€ ๐Ÿ“–
๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฝ๋Š” ์Šต๊ด€์„ ๋“ค์—ฌ. ์ฒ˜์Œ์—” ์–ด๋ ต๊ฒŒ ๋А๊ปด์ง€์ง€๋งŒ, ๊ฐ€์žฅ ์ •ํ™•ํ•˜๊ณ  ์™„์ „ํ•œ ์ •๋ณด๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์–ด.

5. ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ ๐Ÿค
์งˆ๋ฌธ๋„ ํ•˜๊ณ  ๋‹ต๋ณ€๋„ ํ•ด๋ด. ๋‹ค๋ฅธ ์‚ฌ๋žŒ์„ ๋„์™€์ฃผ๋‹ค ๋ณด๋ฉด ์ž์‹ ์˜ ์ดํ•ด๋„๊ฐ€ ๋” ๊นŠ์–ด์ ธ. ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์ง€์‹์„ ๋‚˜๋ˆ„๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ!

6. ์‹ค์ œ ๋ฌธ์ œ ํ•ด๊ฒฐ ๐Ÿ”ง
์ž์‹ ์ด๋‚˜ ์ฃผ๋ณ€ ์‚ฌ๋žŒ๋“ค์ด ์‹ค์ œ๋กœ ํ•„์š”๋กœ ํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ์„ ๋งŒ๋“ค์–ด๋ด. ๋™๊ธฐ๋ถ€์—ฌ๊ฐ€ ํ™•์‹คํžˆ ๋‹ฌ๋ผ์ ธ!

๐ŸŽฏ ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด! ๐ŸŽ‰ JavaFX์˜ ๊ธฐ์ดˆ๋ถ€ํ„ฐ ์‹ค์ „ ํ”„๋กœ์ ํŠธ๊นŒ์ง€ ํ•จ๊ป˜ ์‚ดํŽด๋ดค๋Š”๋ฐ, ์–ด๋• ์–ด?

JavaFX๋Š” ์ •๋ง ๊ฐ•๋ ฅํ•˜๊ณ  ํ˜„๋Œ€์ ์ธ GUI ํ”„๋ ˆ์ž„์›Œํฌ์•ผ. ์ฒ˜์Œ์—” ๋ฐฐ์šธ ๊ฒŒ ๋งŽ์•„ ๋ณด์ด์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ์ตํžˆ๋‹ค ๋ณด๋ฉด ์–ด๋А์ƒˆ ๋ฉ‹์ง„ ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ฒŒ ๋  ๊ฑฐ์•ผ! ๐Ÿ’ช

๊ธฐ์–ตํ•ด์•ผ ํ•  ํ•ต์‹ฌ ํฌ์ธํŠธ๋“ค์„ ์ •๋ฆฌํ•ด๋ณผ๊ฒŒ:

โœจ JavaFX ํ•ต์‹ฌ ์ •๋ฆฌ 1. Stage, Scene, Node์˜ ๊ณ„์ธต ๊ตฌ์กฐ ์ดํ•ดํ•˜๊ธฐ
2. ๋ ˆ์ด์•„์›ƒ์„ ์ ์žฌ์ ์†Œ์— ํ™œ์šฉํ•˜๊ธฐ
3. FXML๊ณผ CSS๋กœ UI์™€ ๋กœ์ง ๋ถ„๋ฆฌํ•˜๊ธฐ
4. ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ์™€ ๋ฐ์ดํ„ฐ ๋ฐ”์ธ๋”ฉ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ
5. ์• ๋‹ˆ๋ฉ”์ด์…˜์œผ๋กœ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ
6. TableView์™€ ์ฐจํŠธ๋กœ ๋ฐ์ดํ„ฐ ์‹œ๊ฐํ™”ํ•˜๊ธฐ
7. ์‹ค์ „ ํ”„๋กœ์ ํŠธ๋กœ ๋ฐฐ์šด ๋‚ด์šฉ ํ†ตํ•ฉํ•˜๊ธฐ

ํ”„๋กœ๊ทธ๋ž˜๋ฐ์€ ๊ฒฐ๊ตญ ๋งŽ์ด ๋งŒ๋“ค์–ด๋ณด๋Š” ๊ฒŒ ๋‹ต์ด์•ผ! ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ž์‹ ๋งŒ์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋ด. ์ฒ˜์Œ์—” ์ž‘๊ณ  ๊ฐ„๋‹จํ•œ ๊ฒƒ๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•ด๋‚˜๊ฐ€๋Š” ๊ฑฐ์ง€. ๐Ÿš€

๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ์œผ๋ฉด ๊ณต์‹ ๋ฌธ์„œ๋ฅผ ์ฐพ์•„๋ณด๊ณ , ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์งˆ๋ฌธํ•˜๊ณ , ๋‹ค๋ฅธ ์‚ฌ๋žŒ์˜ ์ฝ”๋“œ๋ฅผ ์ฐธ๊ณ ํ•ด๋ด. ๊ทธ๋ฆฌ๊ณ  ์žฌ๋Šฅ๋„ท์—์„œ JavaFX ์ „๋ฌธ๊ฐ€๋“ค๊ณผ ์ง€์‹์„ ๋‚˜๋ˆ„๋Š” ๊ฒƒ๋„ ์žŠ์ง€ ๋งˆ! ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ๊ฐ€์žฅ ๋น ๋ฅธ ๊ธธ์ด๊ฑฐ๋“ . ๐Ÿ˜Š

๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์€ ์—ฌ์ „ํžˆ ์ค‘์š”ํ•˜๊ณ  ์ˆ˜์š”๋„ ๋งŽ์•„. ํŠนํžˆ ์—…๋ฌด์šฉ ๋„๊ตฌ, ๊ฐœ๋ฐœ ๋„๊ตฌ, ๋ฏธ๋””์–ด ํŽธ์ง‘ ์†Œํ”„ํŠธ์›จ์–ด ๊ฐ™์€ ๋ถ„์•ผ์—์„œ๋Š” ํ•„์ˆ˜์ ์ด์ง€. JavaFX๋ฅผ ๋งˆ์Šคํ„ฐํ•˜๋ฉด ์ •๋ง ๋‹ค์–‘ํ•œ ๋ถ„์•ผ์—์„œ ํ™œ์•ฝํ•  ์ˆ˜ ์žˆ์–ด!

์ž, ์ด์ œ ๋‹น์‹  ์ฐจ๋ก€์•ผ! ์˜ค๋Š˜ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ฒซ ๋ฒˆ์งธ JavaFX ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•ด๋ด. ์ž‘์€ ์‹œ์ž‘์ด ํฐ ๋ณ€ํ™”๋ฅผ ๋งŒ๋“ค์–ด๋‚ผ ๊ฑฐ์•ผ. ํ™”์ดํŒ…! ๐Ÿ’ชโœจ

Happy Coding! ๐ŸŽจ๐Ÿ’ป๐Ÿš€

โœ“ ๐Ÿš€ ๐Ÿ’ก ํ•™์Šต ์™„๋ฃŒ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ์ง€์†์  ์„ฑ์žฅ JavaFX ๋งˆ์Šคํ„ฐ ๋กœ๋“œ๋งต

๐ŸŽ‰ ์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! JavaFX ๊ฐœ๋ฐœ ์‹ค์Šต์„ ์™„๋ฃŒํ–ˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽ‰

์ด์ œ ๋‹น์‹ ๋งŒ์˜ ๋ฉ‹์ง„ ๋ฐ์Šคํฌํ†ฑ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”! ๐Ÿ’ชโœจ

๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.