Ver3.0 ๐จ JavaFX๋ก ๋ง๋๋ ๋๋ง์ ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ์ค์ ๊ฐ์ด๋

๐จ JavaFX๋ก ๋ง๋๋ ๋๋ง์ ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ ์ค์ ๊ฐ์ด๋
์น๊ตฌ์ฒ๋ผ ์ฝ๊ฒ ๋ฐฐ์ฐ๋ JavaFX ์์ ์ ๋ณต ๐
๐ JavaFX, ์ ์ง๊ธ ๋ฐฐ์์ผ ํ ๊น?
์๋
! ์ค๋์ JavaFX๋ฅผ ํ์ฉํด์ ๋ฉ์ง ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋๋ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊ฒ. ํน์ "์์ฆ ์๋์ ๋ฐ์คํฌํฑ ์ฑ์ด ํ์ํด?"๋ผ๊ณ ์๊ฐํ ์๋ ์๋๋ฐ, ์ฌ์ค ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์
์ ์ฌ์ ํ ์์ฒญ๋๊ฒ ๋ง์ด ์ฌ์ฉ๋๊ณ ์์ด. ํนํ ์
๋ฌด์ฉ ํ๋ก๊ทธ๋จ, ๊ฐ๋ฐ ๋๊ตฌ, ๋ฏธ๋์ด ํธ์ง ์ํํธ์จ์ด ๊ฐ์ ๋ถ์ผ์์๋ ๋ฐ์คํฌํฑ ์ฑ์ด ํ์์ง! ๐ป
JavaFX๋ Java ๊ธฐ๋ฐ์ GUI(Graphical User Interface) ํ๋ ์์ํฌ๋ก, ์์ ์ ๋ง์ด ์ฐ์ด๋ Swing์ ๋์ฒดํ๊ธฐ ์ํด ๋ง๋ค์ด์ก์ด. Swing๋ณด๋ค ํจ์ฌ ํ๋์ ์ด๊ณ ์ธ๋ จ๋ UI๋ฅผ ๋ง๋ค ์ ์๊ณ , CSS ์คํ์ผ๋ง๋ ์ง์ํด์ ์น ๊ฐ๋ฐ์๋ค์๊ฒ๋ ์น์ํ๊ฒ ๋๊ปด์ง ๊ฑฐ์ผ. ๊ฒ๋ค๊ฐ ์ ๋๋ฉ์ด์
, ๋ฉํฐ๋ฏธ๋์ด, 3D ๊ทธ๋ํฝ๊น์ง ์ง์ํ๋๊น ์ ๋ง ๊ฐ๋ ฅํ์ง? ๐
- โ ํฌ๋ก์ค ํ๋ซํผ ์ง์ (Windows, macOS, Linux ๋ชจ๋ OK!)
- โ CSS๋ฅผ ํ์ฉํ ์คํ์ผ๋ง์ผ๋ก ๋์์ธ ์์ ๋ UP
- โ FXML์ ํตํ UI์ ๋ก์ง์ ๋ช ํํ ๋ถ๋ฆฌ
- โ Scene Builder๋ก ๋๋๊ทธ ์ค ๋๋กญ UI ๋์์ธ ๊ฐ๋ฅ
- โ ํ๋ถํ ์ปจํธ๋กค๊ณผ ์ฐจํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๊ณต
- โ Java ์ํ๊ณ์ ๋ชจ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ํ์ฉ ๊ฐ๋ฅ
๐ ๏ธ ๊ฐ๋ฐ ํ๊ฒฝ ์ค์ ํ๊ธฐ
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก JavaFX ๊ฐ๋ฐ์ ์์ํด๋ณผ๊น? ๋จผ์ ๊ฐ๋ฐ ํ๊ฒฝ์ ์ธํ ํด์ผ ํ๋๋ฐ, ์๊ฐ๋ณด๋ค ๊ฐ๋จํด! ์ฐจ๊ทผ์ฐจ๊ทผ ๋ฐ๋ผ์ค๋ฉด ๊ธ๋ฐฉ ์ค๋นํ ์ ์์ด. โ
JDK ์ค์น
JavaFX๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๋จผ์ JDK(Java Development Kit)๊ฐ ํ์ํด. JDK 11 ์ด์์ ๊ถ์ฅํ๋๋ฐ, ์ต์ ๋ฒ์ ์ธ JDK 17์ด๋ JDK 21์ ์ฌ์ฉํ๋ฉด ๋ ์ข์. Oracle JDK๋ OpenJDK ์ค์์ ์ ํํ ์ ์๋๋ฐ, ๊ฐ์ธ์ ์ผ๋ก๋ OpenJDK๋ฅผ ์ถ์ฒํด! ๋ฌด๋ฃ๊ณ ์ฑ๋ฅ๋ ์ข๊ฑฐ๋ . ๐
Oracle ๊ณต์ ์ฌ์ดํธ๋ AdoptOpenJDK์์ ๋ค์ด๋ก๋ํ ์ ์์ด. ์ค์น ํ์๋ ํ๊ฒฝ๋ณ์ ์ค์ ๋ ์์ง ๋ง๊ณ !
JavaFX SDK ๋ค์ด๋ก๋
JDK 11๋ถํฐ๋ JavaFX๊ฐ JDK์์ ๋ถ๋ฆฌ๋์๊ธฐ ๋๋ฌธ์ ๋ณ๋๋ก ๋ค์ด๋ก๋ํด์ผ ํด. Gluon ๊ณต์ ์ฌ์ดํธ(gluonhq.com/products/javafx)์์ ์ด์์ฒด์ ์ ๋ง๋ JavaFX SDK๋ฅผ ๋ค์ด๋ฐ์ผ๋ฉด ๋ผ.
๋ค์ด๋ก๋ํ ํ์ผ์ ์์ถ์ ํ๋ฉด lib ํด๋ ์์ ์ฌ๋ฌ ๊ฐ์ JAR ํ์ผ๋ค์ด ์์ ๊ฑฐ์ผ. ์ด ํ์ผ๋ค์ ํ๋ก์ ํธ์ ์ถ๊ฐํด์ฃผ๋ฉด JavaFX๋ฅผ ์ฌ์ฉํ ์ค๋น๊ฐ ๋๋๋ ๊ฑฐ์ง! ๐ฏ
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์ ๊ฒฝ์ฐ 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() ๋ฉ์๋๋ฅผ ์คํ์์ผ์ค.
๐จ ๋ ์ด์์ ๋ง์คํฐํ๊ธฐ
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()์ ์ฌ์ฉํด์ ์ฌ๋ฌ ์นธ์ ์ฐจ์งํ๊ฒ ๋ง๋ค ์๋ ์์ด! ๐ฏ
๋ ์ด์์์ ์ค๊ณํ ๋๋ ๋จผ์ ์ข ์ด์ ์ค์ผ์น๋ฅผ ๊ทธ๋ ค๋ณด๋ ๊ฑธ ์ถ์ฒํด. ์ด๋ค ์์ญ์ ์ด๋ค ๋ ์ด์์์ผ๋ก ๊ตฌ์ฑํ ์ง ๋ฏธ๋ฆฌ ๊ณํํ๋ฉด ์ฝ๋ฉ์ด ํจ์ฌ ์์ํด์ ธ!
๐ฎ ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ์ํธ์์ฉ
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์ผ๋ก ๋ฒํผ์ ๋ฐฐ์นํ๊ณ , ๊ฐ ๋ฒํผ์ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ์ด. ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด ๋ฉค๋ฒ ๋ณ์๋ฅผ ์ฌ์ฉํ๊ณ , ์กฐ๊ฑด๋ฌธ์ผ๋ก ๋ค์ํ ์
๋ ฅ์ ์ฒ๋ฆฌํ์ง.
์ด๋ฐ ์์ผ๋ก ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ์ํ ๊ด๋ฆฌ๋ฅผ ๊ฒฐํฉํ๋ฉด ๋ณต์กํ ์ํธ์์ฉ๋ ๊ตฌํํ ์ ์์ด. ๊ณ์ฐ๊ธฐ๋ ๊ฐ๋จํด ๋ณด์ด์ง๋ง ์ค์ ๋ก๋ ์ฌ๋ฌ ๊ฐ๋
์ด ๋ค์ด๊ฐ ์๋ ์ข์ ํ์ต ์์ ์ผ! ๐ช
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 ๋์์ธ๊ณผ ๋ก์ง์ด ์์ ํ ๋ถ๋ฆฌ๋์ด์ ์ฝ๋ ๊ด๋ฆฌ๊ฐ ํจ์ฌ ์ฌ์์ ธ! ๐
โข ๋ณต์กํ 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");
2. ๋ณ์ ํ์ฉ: CSS์์ ์์์ ๋ณ์๋ก ์ ์ํ๋ฉด ํ ๋ง ๋ณ๊ฒฝ์ด ์ฌ์์ ธ
3. ์์ฌ ํด๋์ค: :hover, :pressed, :focused ๋ฑ์ ํ์ฉํด์ ์ํธ์์ฉ ํผ๋๋ฐฑ์ ์ ๊ณตํด
4. ๊ทธ๋ฆผ์ ํจ๊ณผ: dropshadow๋ฅผ ์ ์ ํ ์ฌ์ฉํ๋ฉด ์ ์ฒด๊ฐ์ด ์๊ฒจ
5. ์ ๋๋ฉ์ด์ : ๋ถ๋๋ฌ์ด ์ ํ ํจ๊ณผ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์์ผ
๐ ์ฐจํธ์ ๋ฐ์ดํฐ ์๊ฐํ
๋ฐ์ดํฐ๋ฅผ ์๊ฐ์ ์ผ๋ก ํํํ๋ ๊ฑด ์ ๋ง ์ค์ํด! 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๋ ์ ๋ง ๊ฐ๋จํ์ง? ๋ฐ์ดํฐ๋ง ๋ฃ์ด์ฃผ๋ฉด ์๋์ผ๋ก ๋น์จ์ ๊ณ์ฐํ๊ณ ์์๊ฒ ๊ทธ๋ ค์ค. ์์๋ ์๋์ผ๋ก ๋ฐฐ์ ๋๊ณ ! ๋ฌผ๋ก ์ํ๋ฉด ์ปค์คํ ์์์ ์ง์ ํ ์๋ ์์ด. ๐จ
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: ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ๋ ๋ค์ด์ผ๋ก๊ทธ๋ฅผ ๋์์ ์
๋ ฅ๋ฐ์. ์ฌ์ฉ์ ๊ฒฝํ์ด ํจ์ฌ ์ข์์ ธ!
ํํฐ๋ง: 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์ ์ ๋ง ๊ฐ๋ ฅํด! ์ฌ๋ฌ ์์ฑ์ ๋์์ ์ ๋๋ฉ์ด์ ํ ์ ์๊ณ , ๋ณต์กํ ํ์ด๋ฐ ์ ์ด๋ ๊ฐ๋ฅํด. ๊ฒ์์ด๋ ์ธํฐ๋ํฐ๋ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค ๋ ํ์์ ์ด์ผ! ๐ฎ
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 ๋ช ๋ น์ด๋ฅผ ์คํํ๋ฉด ์๋์ผ๋ก ๋น๋๋ผ! ์ ๋ง ๊ฐํธํ์ง? ๐
2. ์ฝ๋ ๋๋ ํ: ์์ ์ฉ ์ฑ์ด๋ผ๋ฉด ProGuard ๊ฐ์ ๋๊ตฌ๋ก ์ฝ๋๋ฅผ ๋ณดํธํ๋ ๊ฒ ์ข์
3. ๋์งํธ ์๋ช : Windows์์๋ ์ฝ๋ ์๋ช ์ด ์์ผ๋ฉด ๊ฒฝ๊ณ ๊ฐ ๋ฐ ์ ์์ด
4. ์๋ ์ ๋ฐ์ดํธ: ์ ๋ฐ์ดํธ ๋ฉ์ปค๋์ฆ์ ๊ตฌํํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ์ข์์ ธ
5. ํ ์คํธ: ๋ค์ํ ํ๊ฒฝ์์ ์ถฉ๋ถํ ํ ์คํธํด์ผ ํด
๐ ํ์ต ๋ฆฌ์์ค์ ์ปค๋ฎค๋ํฐ
JavaFX๋ฅผ ๊ณ์ ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด! ๐
โข OpenJFX Wiki: ์คํ์์ค ํ๋ก์ ํธ์ ์ํค์ ์ ์ฉํ ์ ๋ณด๊ฐ ๋ง์
โข JavaFX Tutorial (jenkov.com): ์ด๋ณด์์๊ฒ ์น์ ํ ํํ ๋ฆฌ์ผ
โข ControlsFX: ์ถ๊ฐ ์ปจํธ๋กค ๋ผ์ด๋ธ๋ฌ๋ฆฌ, ์ ๋ง ์ ์ฉํด!
โข Reddit r/JavaFX: JavaFX ๊ฐ๋ฐ์๋ค์ ์ปค๋ฎค๋ํฐ
โข GitHub: ์คํ์์ค JavaFX ํ๋ก์ ํธ๋ค์ ์ฐธ๊ณ ํ๋ฉด ๋ง์ด ๋ฐฐ์ธ ์ ์์ด
โข ์ฌ๋ฅ๋ท: ํ๋ก๊ทธ๋๋ฐ ์ฌ๋ฅ์ ๊ณต์ ํ๊ณ ๋ฐฐ์ธ ์ ์๋ ํ๋ซํผ! ์ฌ๊ธฐ์ JavaFX ์ ๋ฌธ๊ฐ๋ฅผ ๋ง๋ ์๋ ์์ด ๐
โข Bro Code: ์ฌ๋ฏธ์๊ณ ์ฌ์ด JavaFX ํํ ๋ฆฌ์ผ
โข Genuine Coder: ์ค์ ํ๋ก์ ํธ ์ค์ฌ ๊ฐ์
๐ ์ค๋ ฅ ํฅ์์ ์ํ ํ
JavaFX๋ฅผ ๋ง์คํฐํ๊ธฐ ์ํ ๋๋ง์ ํ์ ๊ณต์ ํ ๊ฒ:
1. ์์ ํ๋ก์ ํธ๋ถํฐ ์์ ๐ฏ
์ฒ์๋ถํฐ ํฐ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค๋ ค๊ณ ํ์ง ๋ง. ๊ณ์ฐ๊ธฐ, ๋ฉ๋ชจ์ฅ, To-Do ๋ฆฌ์คํธ ๊ฐ์ ๊ฐ๋จํ ์ฑ๋ถํฐ ๋ง๋ค์ด๋ด. ๊ธฐ์ด๋ฅผ ํํํ ๋ค์ง๋ ๊ฒ ์ค์ํด!
2. ๋ค๋ฅธ ์ฌ๋์ ์ฝ๋ ์ฝ๊ธฐ ๐
GitHub์์ JavaFX ํ๋ก์ ํธ๋ฅผ ์ฐพ์์ ์ฝ๋๋ฅผ ์ฝ์ด๋ด. ๋ค๋ฅธ ์ฌ๋๋ค์ด ์ด๋ป๊ฒ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋์ง ๋ณด๋ฉด ์ ๋ง ๋ง์ด ๋ฐฐ์ธ ์ ์์ด.
3. ๋งค์ผ ์กฐ๊ธ์ฉ ์ฝ๋ฉ ๐ช
ํ๋ฃจ์ 30๋ถ์ด๋ผ๋ ๊พธ์คํ ์ฝ๋ฉํ๋ ๊ฒ ํ ๋ฒ์ ๋ชฐ์์ ํ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ํจ๊ณผ์ ์ด์ผ. ์ต๊ด์ด ์ค๋ ฅ์ ๋ง๋ค์ด!
4. ๋ฌธ์ ์ฝ๋ ์ต๊ด ๐
๊ณต์ ๋ฌธ์๋ฅผ ์ฝ๋ ์ต๊ด์ ๋ค์ฌ. ์ฒ์์ ์ด๋ ต๊ฒ ๋๊ปด์ง์ง๋ง, ๊ฐ์ฅ ์ ํํ๊ณ ์์ ํ ์ ๋ณด๋ฅผ ์ป์ ์ ์์ด.
5. ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ ๐ค
์ง๋ฌธ๋ ํ๊ณ ๋ต๋ณ๋ ํด๋ด. ๋ค๋ฅธ ์ฌ๋์ ๋์์ฃผ๋ค ๋ณด๋ฉด ์์ ์ ์ดํด๋๊ฐ ๋ ๊น์ด์ ธ. ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ง์์ ๋๋๋ ๊ฒ๋ ์ข์ ๋ฐฉ๋ฒ์ด์ผ!
6. ์ค์ ๋ฌธ์ ํด๊ฒฐ ๐ง
์์ ์ด๋ ์ฃผ๋ณ ์ฌ๋๋ค์ด ์ค์ ๋ก ํ์๋ก ํ๋ ํ๋ก๊ทธ๋จ์ ๋ง๋ค์ด๋ด. ๋๊ธฐ๋ถ์ฌ๊ฐ ํ์คํ ๋ฌ๋ผ์ ธ!
๐ฏ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ ๋ง ๊ธด ์ฌ์ ์ด์์ด! ๐ JavaFX์ ๊ธฐ์ด๋ถํฐ ์ค์ ํ๋ก์ ํธ๊น์ง ํจ๊ป ์ดํด๋ดค๋๋ฐ, ์ด๋ ์ด?
JavaFX๋ ์ ๋ง ๊ฐ๋ ฅํ๊ณ ํ๋์ ์ธ GUI ํ๋ ์์ํฌ์ผ. ์ฒ์์ ๋ฐฐ์ธ ๊ฒ ๋ง์ ๋ณด์ด์ง๋ง, ํ๋์ฉ ์ฐจ๊ทผ์ฐจ๊ทผ ์ตํ๋ค ๋ณด๋ฉด ์ด๋์ ๋ฉ์ง ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง๋ค ์ ์๊ฒ ๋ ๊ฑฐ์ผ! ๐ช
๊ธฐ์ตํด์ผ ํ ํต์ฌ ํฌ์ธํธ๋ค์ ์ ๋ฆฌํด๋ณผ๊ฒ:
2. ๋ ์ด์์์ ์ ์ฌ์ ์์ ํ์ฉํ๊ธฐ
3. FXML๊ณผ CSS๋ก UI์ ๋ก์ง ๋ถ๋ฆฌํ๊ธฐ
4. ์ด๋ฒคํธ ์ฒ๋ฆฌ์ ๋ฐ์ดํฐ ๋ฐ์ธ๋ฉ ๋ง์คํฐํ๊ธฐ
5. ์ ๋๋ฉ์ด์ ์ผ๋ก ์ฌ์ฉ์ ๊ฒฝํ ํฅ์์ํค๊ธฐ
6. TableView์ ์ฐจํธ๋ก ๋ฐ์ดํฐ ์๊ฐํํ๊ธฐ
7. ์ค์ ํ๋ก์ ํธ๋ก ๋ฐฐ์ด ๋ด์ฉ ํตํฉํ๊ธฐ
ํ๋ก๊ทธ๋๋ฐ์ ๊ฒฐ๊ตญ ๋ง์ด ๋ง๋ค์ด๋ณด๋ ๊ฒ ๋ต์ด์ผ! ์ด ๊ธ์์ ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์์ ๋ง์ ํ๋ก์ ํธ๋ฅผ ์์ํด๋ด. ์ฒ์์ ์๊ณ ๊ฐ๋จํ ๊ฒ๋ถํฐ ์์ํด์ ์ ์ ๋ณต์กํ ๊ธฐ๋ฅ์ ์ถ๊ฐํด๋๊ฐ๋ ๊ฑฐ์ง. ๐
๋งํ๋ ๋ถ๋ถ์ด ์์ผ๋ฉด ๊ณต์ ๋ฌธ์๋ฅผ ์ฐพ์๋ณด๊ณ , ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํ๊ณ , ๋ค๋ฅธ ์ฌ๋์ ์ฝ๋๋ฅผ ์ฐธ๊ณ ํด๋ด. ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท์์ JavaFX ์ ๋ฌธ๊ฐ๋ค๊ณผ ์ง์์ ๋๋๋ ๊ฒ๋ ์์ง ๋ง! ํจ๊ป ์ฑ์ฅํ๋ ๊ฒ ๊ฐ์ฅ ๋น ๋ฅธ ๊ธธ์ด๊ฑฐ๋ . ๐
๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์
๊ฐ๋ฐ์ ์ฌ์ ํ ์ค์ํ๊ณ ์์๋ ๋ง์. ํนํ ์
๋ฌด์ฉ ๋๊ตฌ, ๊ฐ๋ฐ ๋๊ตฌ, ๋ฏธ๋์ด ํธ์ง ์ํํธ์จ์ด ๊ฐ์ ๋ถ์ผ์์๋ ํ์์ ์ด์ง. JavaFX๋ฅผ ๋ง์คํฐํ๋ฉด ์ ๋ง ๋ค์ํ ๋ถ์ผ์์ ํ์ฝํ ์ ์์ด!
์, ์ด์ ๋น์ ์ฐจ๋ก์ผ! ์ค๋ ๋ฐฐ์ด ๋ด์ฉ์ ๋ฐํ์ผ๋ก ์ฒซ ๋ฒ์งธ JavaFX ํ๋ก์ ํธ๋ฅผ ์์ํด๋ด. ์์ ์์์ด ํฐ ๋ณํ๋ฅผ ๋ง๋ค์ด๋ผ ๊ฑฐ์ผ. ํ์ดํ
! ๐ชโจ
Happy Coding! ๐จ๐ป๐
๐ ์ถํํฉ๋๋ค! JavaFX ๊ฐ๋ฐ ์ค์ต์ ์๋ฃํ์ต๋๋ค! ๐
์ด์ ๋น์ ๋ง์ ๋ฉ์ง ๋ฐ์คํฌํฑ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ง๋ค์ด๋ณด์ธ์! ๐ชโจ
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.