티스토리 뷰

TabPane을 이용해 TextViewer 만들기
 

 이번 시간에는 JavaFX의 TabPane을 활용해서 TextViewer를 한번 만들어보겠습니다.


 바로 아래와 같이 생긴 TextViewer를 만들어 보겠습니다.


 
 






 이건 제가 만든 것을 보여 드리는 형식이기 때문에 한번 보시고 필요한 부분이 있으면 사용하셔도 됩니다.

 


 물론 별로 도움이 안 될수도 있으니 그냥 가볍게 봐주시길 바랍니다 ^^




TabPane

 일단 위 디자인을 구현하기 위해서는 TabPane이 중심이라고도 할 수 있습니다. 

 
 그래서 먼저 할 작업은 저대로 TabPane의 디자인을 입히는 일이죠. 

  

  CSS 파일은 미리 다 만들어 놨습니다. 여깄습니다.   textviewer.css


 

 TabPane은 Tab 객체를 TabPane에 add 하는 방식으로 이루어져 있습니다. 그리고 만약 tab에 contents를 넣고 싶다고 하면 

 


 Tab 객체에 setContent 라는 메소드로 집어 넣을수가 있습니다.


 

 즉 TabPane을 사용하려면 저 디자인 전체가 TabPane으로 이루어져 있다는 것이죠.



FXML 작성

 그럼 이제 FXML 을 작성해보도록 하겠습니다.


 위에서 말했듯이 저 디자인의 전체가 TabPane입니다. 그럼 상단의 button 들이 있는 bar는 어떻게 만들까요?

 
 저도 고민을 많이 했는데... JavaFX에는 StackPane이라는 컨테이너가 있습니다. 


 이 StackPane은 안에 Contents들을 겹치는 방식으로 배치를 할 수 있습니다. 

 


 그렇다면 TabPane을 제일 아래에 깔고 그 위에 bar를 붙이면 되지 않을까요? 



 저는 그 생각으로 구현을 했습니다. 그렇게 작성한 FXML 코드는 아래와 같습니다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<VBox xmlns="http://javafx.com/javafx/8" fx:controller="sample.Controller"
      xmlns:fx="http://javafx.com/fxml/1" stylesheets="@textviewer.css">
    <padding>
        <Insets top="10" left="10" right="10" bottom="10"/>
    </padding>
    <StackPane style="-fx-border-color:#868E95" fx:id="viewerPane">
        <TabPane styleClass="maintab" fx:id="mainTab">
 
        </TabPane>
        <HBox style="-fx-background-color:linear-gradient(#F1F5F9, #DFE5EC);
                -fx-border-width:1; -fx-border-color:#868E95;"
              maxHeight="35" StackPane.alignment="TOP_LEFT" alignment="CENTER_LEFT">
            <Button fx:id="newFileButton" styleClass="viewer_button">
                <graphic>
                    <ImageView>
                        <Image url="@ui/image/newFile.png"/>
                    </ImageView>
                </graphic>
                <HBox.margin>
                    <Insets>
                        <left>10</left>
                    </Insets>
                </HBox.margin>
            </Button>
            <Button fx:id="saveButton" styleClass="viewer_button">
                <graphic>
                    <ImageView>
                        <Image url="@ui/image/save.png"/>
                    </ImageView>
                </graphic>
                <HBox.margin>
                    <Insets>
                        <left>10</left>
                    </Insets>
                </HBox.margin>
            </Button>
            <Button fx:id="printButton" styleClass="viewer_button">
                <graphic>
                    <ImageView>
                        <Image url="@ui/image/print.png"/>
                    </ImageView>
                </graphic>
                <HBox.margin>
                    <Insets>
                        <left>10</left>
                    </Insets>
                </HBox.margin>
            </Button>
            <HBox alignment="CENTER_RIGHT">
                <Button fx:id="newWindowButton" styleClass="viewer_button">
                    <graphic>
                        <ImageView>
                            <Image url="@ui/image/newWindow.png"/>
                        </ImageView>
                    </graphic>
                    <HBox.margin>
                        <Insets>
                            <right>10</right>
                        </Insets>
                    </HBox.margin>
                </Button>
                <HBox.hgrow>
                    <Priority fx:constant="ALWAYS"/>
                </HBox.hgrow>
            </HBox>
            <StackPane.margin>
                <Insets>
                    <top>30</top>
                </Insets>
            </StackPane.margin>
        </HBox>
        <VBox.vgrow>
            <Priority fx:constant="SOMETIMES"/>
        </VBox.vgrow>
    </StackPane>
</VBox>
cs



저의 경우는 VBox를 전체 Container로 깔고 그 위에다 StackPane을 깔았습니다. 



그 위에 TabPane을 깔고 그 다음에 Button이 있는 Bar인 HBox를 추가 해줍니다. 그리곤 HBox에 Button 들을 넣기만 하면 됩니다.



 아 참 Button에 들어갈 이미지는 여기 있습니다.




images.zip




 적절한 경로에 넣어주시면 아래와 같은 결과를 볼 수 있습니다.






 



 그럼 이제는 기능을 넣어볼 차례입니다.




기능 구현


 기능은 간단하게 Controller.java 에서 구현했습니다. 


 형식은 각 버튼에 이벤트 리스너를 달고 각 상황에 맞는 처리를 해주면 됩니다. 코드는 아래와 같습니다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Controller implements Initializable {
    @FXML
    Button saveButton;
    @FXML
    Button printButton;
    @FXML
    Button newFileButton;
    @FXML
    Button newWindowButton;
    @FXML
    TabPane mainainTab;
    @FXML
    StackPane viewerPane;
 
    private TextViewerEventHandler handler;
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        handler = new TextViewerEventHandler(viewerPane, mainainTab);
        newFileButton.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
        saveButton.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
        printButton.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
        newWindowButton.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
    }
}
cs



 Controller에서 모든 동작을 처리 해줘도 되지만


 나중에 재사용 할 것을 대비해서 EventHandler 클래스를 따로 구현했습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public class TextViewerEventHandler implements EventHandler<MouseEvent> {
  private TabPane tabPane;
  private Pane context;
 
  public TextViewerEventHandler(Pane context, TabPane tabPane) {
    this.tabPane = tabPane;
    this.context = context;
  }
 
  @Override
  public void handle(MouseEvent event) {
    switch (event.getPickResult().getIntersectedNode().getId()) {
      case "newFileButton":
        FileChooser newFileChooser = new FileChooser();
        File newFile = newFileChooser .showOpenDialog(context.getScene().getWindow());
        openNewTab(newFile.getPath());
        break;
      case "saveButton":
        if (isTabExist()) {
          if (isTabExist()) {
            FileChooser saveFileChooser = new FileChooser();
            File saveFile = saveFileChooser.showSaveDialog(context.getScene().getWindow());
            if (saveFile != null) {
              saveFile(saveFile);
            }
          }
        }
        break;
      case "printButton":
        if (isTabExist()) {
          PrinterJob printerJob = PrinterJob.createPrinterJob();
          if (printerJob.showPrintDialog(context.getScene().getWindow())) {
            boolean success = printerJob.printPage(tabPane.getSelectionModel().getSelectedItem().getContent());
            if (success) {
              printerJob.endJob();
            }
          }
        }
        break;
      case "newWindowButton":
        if (isTabExist()) {
          Stage stage = new Stage();
          stage.setTitle(tabPane.getSelectionModel().getSelectedItem().getText());
          HBox hBox = (HBox) tabPane.getSelectionModel().getSelectedItem().getContent();
          TextArea textArea = (TextArea) hBox.getChildren().get(0);
          TextArea newArea = new TextArea(textArea.getText());
          newArea.setEditable(false);
          stage.setScene(new Scene(newArea, 1000800));
          stage.show();
        }
        break;
    }
  }
 
  /**
   * TabPane에 Tab이 존재하는지 검사
   * @return
   */
  private boolean isTabExist() {
    return tabPane.getSelectionModel().getSelectedItem() != null;
  }
 
  /**
   * 지정된 경로에 현재 Focus 된 Tab의 Text file 저장
   * @param file
   */
  private void saveFile(File file){
    HBox hBox = (HBox) tabPane.getSelectionModel().getSelectedItem().getContent();
    TextArea textArea = (TextArea) hBox.getChildren().get(0);
    try{
      FileWriter writer = null;
      writer = new FileWriter(file);
      writer.write(textArea.getText().replaceAll("\n""\r\n"));
      writer.close();
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
 
  /**
   * Txt File을 읽어 Tab으로 생성
   * @param txtFile
   * @return
   */
  private Tab addTabFromFile(File txtFile){
    Tab tab = new Tab();
    tab.setText(txtFile.getName());
    TextArea textArea = new TextArea();
    textArea.setEditable(false);
 
    HBox hBox = new HBox();
    hBox.setStyle("-fx-background-color:white");
    HBox.setHgrow(textArea, Priority.ALWAYS);
 
    //상단의 메뉴 바 하단에 배치
    hBox.setMargin(textArea, new Insets(35333));
 
    BufferedReader br = null;
    try{
      br = new BufferedReader(new InputStreamReader(new FileInputStream(txtFile)));
      String line;
      while((line = br.readLine()) != null){
        textArea.appendText(line + "\n");
      }
    } catch (FileNotFoundException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    hBox.getChildren().add(textArea);
    tab.setContent(hBox);
    return tab;
  }
 
  public void openNewTab(String path){
    tabPane.getTabs().add(addTabFromFile(new File(path)));
  }
}
cs


그 후 Main에서는 fxml을 로드 시켜주기만 하면 됩니다. 아래 처럼 말이죠,


1
2
3
4
5
6
7
8
9
10
11
public class Main extends Application {
    @Override
    public void start(Stage primaryStage) throws Exception {
 
        Parent root = FXMLLoader.load(getClass().getResource("textviewer.fxml"));
        root.setStyle("-fx-background-color:white");
        primaryStage.setTitle("TextViewer Test");
        primaryStage.setScene(new Scene(root, 800,800));
        primaryStage.show();
    }
}
cs





결과 화면 

 그럼 결과적으로 다음과 같은 화면을 얻을수 있습니다.






 물론 디자인적으로 수정이 필요할 것 같긴 합니다 ...

 



  읽어주셔서 감사합니다 :)

반응형
댓글