Примерна задача

Да се разшири приложението за управление на данни за софтуерни разработчици, така че да поддържа работа с ComboBox, ListView, TableView, MenuBar, ContextMenu, FXML диалогови прозорци и Alert.

Приложението трябва да реализира следните функционалности:

  • Главно меню (MenuBar) със следните команди:
    • Нов – изчиства формата;
    • Изтрий избран – изтрива избрания разработчик от таблицата;
    • Изход – затваря приложението след потвърждение;
  • За програмата – отваря FXML диалогов прозорец с надпис: “Система за управление на ИТ екип” и “Автор Име Фалимия”.
  • Контекстно меню (ContextMenu) към TableView, което съдържа:
    • Изтриване – премахва избрания разработчик след потвърждение.
  • Използване на стандартни диалогови прозорци (Alert) за:
    • грешки;
    • потвърждение на действия.

Стъпка 1: Съобщенията за успех и грешка да се извеждат в Alert.

В контролера DeveloperController трябва да се добави метод showAlert, който ще бъде помощен за създаване на Alert.

    private Optional<ButtonType> showAlert(Alert.AlertType type, String title, String header, String content) {
        Alert alert = new Alert(type);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        return alert.showAndWait();
    }

На всички места в DeveloperController, където се ползва метода displayMessage , трябва да се смени с използването на метода showAlert

В метода handleAddDeveloper при създаване или при грешка:

       @FXML
    protected void handleAddDeveloper() {
        // Извличане на данни от формата
        String name = nameField.getText();
        Level level = levelCombo.getValue();
        // Извличане на всички избрани елементи от ListView и конкатенация в общ String
        List<Technology> selectedTechs = techListView.getSelectionModel().getSelectedItems();

        try {
            createDeveloper(name, level, selectedTechs);
            clearForm();
            showAlert(Alert.AlertType.INFORMATION, "Успешно действие", null, "Успешно добавен!");
        } catch (IllegalArgumentException exception) {
            showAlert(Alert.AlertType.ERROR, "Грешка","Грешка по време на създаване", exception.getMessage());
        }
    }

В метода createDeveloper при възникнало изключение:

    private boolean createDeveloper(String name, Level level, List<Technology> selectedTechs) {
        try {
            Developer newDev = new Developer(name, level, selectedTechs);
            return developersData.add(newDev);
        } catch (IllegalArgumentException exception) {
            throw exception;
        }
    }

Стъпка 2: Добавяне на новите графични елементи

В developer-view.fxml трябва да се промени основния изглвд от HBox на BorderPane, за да може да се позиционира основното меню в прозореца.

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="bg.tu_varna.sit.ps.lab8.task1.controllers.DeveloperController"
            stylesheets="@css/styles.css"
            prefWidth="950.0"
            prefHeight="600.0">

</BorderPane>

В center секцията преместваме изгледа от 7 упражнение:

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="bg.tu_varna.sit.ps.lab8.task1.controllers.DeveloperController"
            stylesheets="@css/styles.css"
            prefWidth="950.0"
            prefHeight="600.0">

    <center>
        <HBox spacing="20" stylesheets="@css/styles.css" styleClass="main-container">

            <!-- Ляв панел: Форма за добавяне (асоциирана с CSS клас form-pane) -->
            <VBox spacing="10" prefWidth="250" styleClass="form-pane">
                <Label text="Нов служител" id="form-title"/>

                <Label text="Име и фамилия:"/>
                <TextField fx:id="nameField" promptText="Напр. Иван Иванов"/>

                <Label text="Ниво (Позиция):"/>
                <ComboBox fx:id="levelCombo" maxWidth="150"/>

                <Label text="Умения (задръжте Ctrl):"/>
                <ListView fx:id="techListView" prefHeight="150"/>

                <Button fx:id="addButton" text="Добави в екипа" onAction="#handleAddDeveloper"
                        maxWidth="150" id="add-btn"/>
            </VBox>

            <!-- Десен панел: Таблица -->
            <VBox HBox.hgrow="ALWAYS" spacing="10">
                <Label text="Списък на екипа"/>

                <TableView fx:id="developerTable" VBox.vgrow="ALWAYS">
                    <columns>
                        <TableColumn fx:id="colName" text="Служител" prefWidth="150.0"/>
                        <TableColumn fx:id="colLevel" text="Ниво" prefWidth="100.0"/>
                        <TableColumn fx:id="colSkills" text="Технологии" prefWidth="240.0"/>
                    </columns>
                </TableView>
            </VBox>
        </HBox>
    </center>

</BorderPane>

Към top секцията домавяме основното меню:

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="bg.tu_varna.sit.ps.lab8.task1.controllers.DeveloperController"
            stylesheets="@css/styles.css"
            prefWidth="950.0"
            prefHeight="600.0">

    <top>
        <MenuBar>
            <Menu text="Файл">
                <MenuItem text="Нов" onAction="#handleNew"/>
                <MenuItem text="Изтрий избран" onAction="#handleDeleteSelected"/>
                <MenuItem text="Изход" onAction="#handleExit"/>
            </Menu>
            <Menu text="Помощ">
                <MenuItem text="За програмата" onAction="#handleAboutDialog"/>
            </Menu>
        </MenuBar>
    </top>

        <center>
        <HBox spacing="20" stylesheets="@css/styles.css" styleClass="main-container">

            <!-- Ляв панел: Форма за добавяне (асоциирана с CSS клас form-pane) -->
            <VBox spacing="10" prefWidth="250" styleClass="form-pane">
                <Label text="Нов служител" id="form-title"/>

                <Label text="Име и фамилия:"/>
                <TextField fx:id="nameField" promptText="Напр. Иван Иванов"/>

                <Label text="Ниво (Позиция):"/>
                <ComboBox fx:id="levelCombo" maxWidth="150"/>

                <Label text="Умения (задръжте Ctrl):"/>
                <ListView fx:id="techListView" prefHeight="150"/>

                <Button fx:id="addButton" text="Добави в екипа" onAction="#handleAddDeveloper"
                        maxWidth="150" id="add-btn"/>
            </VBox>

            <!-- Десен панел: Таблица -->
            <VBox HBox.hgrow="ALWAYS" spacing="10">
                <Label text="Списък на екипа"/>

                <TableView fx:id="developerTable" VBox.vgrow="ALWAYS">
                    <columns>
                        <TableColumn fx:id="colName" text="Служител" prefWidth="150.0"/>
                        <TableColumn fx:id="colLevel" text="Ниво" prefWidth="100.0"/>
                        <TableColumn fx:id="colSkills" text="Технологии" prefWidth="240.0"/>
                    </columns>
                </TableView>
            </VBox>
        </HBox>
    </center>

</BorderPane>

Контекстното меню трябва да е вградено в TableView това става по следняи начин:

<BorderPane xmlns:fx="http://javafx.com/fxml"
            fx:controller="bg.tu_varna.sit.ps.lab8.task1.controllers.DeveloperController"
            stylesheets="@css/styles.css"
            prefWidth="950.0"
            prefHeight="600.0">

    <top>
        <MenuBar>
            <Menu text="Файл">
                <MenuItem text="Нов" onAction="#handleNew"/>
                <MenuItem text="Изтрий избран" onAction="#handleDeleteSelected"/>
                <SeparatorMenuItem/>
                <MenuItem text="Изход" onAction="#handleExit"/>
            </Menu>
            <Menu text="Помощ">
                <MenuItem text="За програмата" onAction="#handleAboutDialog"/>
            </Menu>
        </MenuBar>
    </top>

    <center>
        <HBox spacing="20" stylesheets="@css/styles.css" styleClass="main-container">

            <!-- Ляв панел: Форма за добавяне (асоциирана с CSS клас form-pane) -->
            <VBox spacing="10" prefWidth="250" styleClass="form-pane">
                <Label text="Нов служител" id="form-title"/>

                <Label text="Име и фамилия:"/>
                <TextField fx:id="nameField" promptText="Напр. Иван Иванов"/>

                <Label text="Ниво (Позиция):"/>
                <ComboBox fx:id="levelCombo" maxWidth="150"/>

                <Label text="Умения (задръжте Ctrl):"/>
                <ListView fx:id="techListView" prefHeight="150"/>

                <Button fx:id="addButton" text="Добави в екипа" onAction="#handleAddDeveloper"
                        maxWidth="150" id="add-btn"/>
            </VBox>

            <!-- Десен панел: Таблица -->
            <VBox HBox.hgrow="ALWAYS" spacing="10">
                <Label text="Списък на екипа"/>

                <TableView fx:id="developerTable" VBox.vgrow="ALWAYS">
                     <contextMenu>
                        <ContextMenu>
                            <items>
                                <MenuItem text="Изтрий" onAction="#handleDeleteSelected"/>
                            </items>
                        </ContextMenu>
                    </contextMenu>
                    <columns>
                        <TableColumn fx:id="colName" text="Служител" prefWidth="150.0"/>
                        <TableColumn fx:id="colLevel" text="Ниво" prefWidth="100.0"/>
                        <TableColumn fx:id="colSkills" text="Технологии" prefWidth="240.0"/>
                    </columns>
                </TableView>
            </VBox>
        </HBox>
    </center>

</BorderPane>

Стъпка 3: Към контролера трябва да се добавят новите методи за управление на потребителския интерфейс и полета за новите елементи:


public class DeveloperController {

    // --- Елементи от формата за въвеждане ---
    @FXML
    private TextField nameField;
    @FXML
    private Button addButton;

    @FXML
    private ComboBox<Level> levelCombo;
    @FXML
    private ListView<Technology> techListView;
    @FXML
    private TableView<Developer> developerTable;
    @FXML
    private TableColumn<Developer, String> colName;
    @FXML
    private TableColumn<Developer, Level> colLevel;
    @FXML
    private TableColumn<Developer, List<Technology>> colSkills;

    // Списък за визуализация в таблицата
    private final ObservableList<Developer> developersData;

    public DeveloperController() {
        developersData = FXCollections.observableArrayList();
    }

    @FXML
    public void initialize() {

        initComboBox();

        initListView();

        initTableView();
    }

    //Конфигуриране на ComboBox
    private void initComboBox() {

        levelCombo.setItems(FXCollections.observableArrayList(Level.values()));
    }

    //Конфигуриране на ListView
    private void initListView() {

        techListView.setItems(FXCollections.observableArrayList(Technology.values()));
        // Разрешаване на множествен избор на технологии (чрез клавиш Ctrl/Cmd)
        techListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
    }

    // Конфигуриране на TableView (Свързване на колоните с класа Developer)
    private void initTableView() {

        colName.setCellValueFactory(new PropertyValueFactory<>("name"));
        colLevel.setCellValueFactory(new PropertyValueFactory<>("level"));
        colSkills.setCellValueFactory(new PropertyValueFactory<>("skills"));

        developerTable.setItems(developersData);
    }
    @FXML
    protected void handleAddDeveloper() {
        // Извличане на данни от формата
        String name = nameField.getText();
        Level level = levelCombo.getValue();
        // Извличане на всички избрани елементи от ListView и конкатенация в общ String
        List<Technology> selectedTechs = techListView.getSelectionModel().getSelectedItems();

        try {
            createDeveloper(name, level, selectedTechs);
            clearForm();
            showAlert(Alert.AlertType.INFORMATION, "Успешно действие", null, "Успешно добавен!");
        } catch (IllegalArgumentException exception) {
            showAlert(Alert.AlertType.ERROR, "Грешка","Грешка по време на създаване", exception.getMessage());
        }
    }

    private boolean createDeveloper(String name, Level level, List<Technology> selectedTechs) {
        try {
            Developer newDev = new Developer(name, level, selectedTechs);
            return developersData.add(newDev);
        } catch (IllegalArgumentException exception) {
            throw exception;
        }
    }

    private void clearForm() {
        nameField.clear();
        techListView.getSelectionModel().clearSelection();
        levelCombo.getSelectionModel().clearSelection();
    }

    public void handleNew() {

    }

    public void handleExit() {

    }

    public void handleAboutDialog() {

    }

    private Optional<ButtonType> showAlert(Alert.AlertType type, String title, String header, String content) {
        Alert alert = new Alert(type);
        alert.setTitle(title);
        alert.setHeaderText(header);
        alert.setContentText(content);
        return alert.showAndWait();
    }
}

Стъпка 4: Метода handleNew трябва да имплементира почистване на полетата. С метода clearForm се пчиства формата завъвеждане. за да се премахне селекцията на TableView се ползва метода clearSelection

    public void handleNew(ActionEvent actionEvent) {
        clearForm();
        developerTable.getSelectionModel().clearSelection();
    }

Стъпка 5: Метода handleExit трябва да имплементира затваряне на приложението. Това се прави като се вземе декора от графичен елемент и се затвори прозореца свързан с този декор.

            Stage stage = (Stage) developerTable.getScene().getWindow();
            stage.close();

Пълната реализация на метода: Преди да се затори програмата ще се покаже Alert, с който да се потвърди затварянето.

    public void handleExit(ActionEvent actionEvent) {
        Optional<ButtonType> result = showAlert(Alert.AlertType.CONFIRMATION, "Изход", "Затваряне на приложението", "Сигурни ли сте, че искате да затворите приложението?");
        if (result.isPresent() && result.get() == ButtonType.OK) {
            Stage stage = (Stage) developerTable.getScene().getWindow();
            stage.close();
        }
    }

Стъпка 6: Метода handleDeleteSelected трябва да имлементира изтриване на селектирания елемент от TableView - Първо трябва да се извлече селектирания елемнт с метода getSelectedItem - Следващата стъпка е са се провери дали е извлечен елемент - Ако няма селектиран елемент се извежда съобщение Alert - При наличен елемент се извежда съобщение за потвърждение с Alert - След потвърждаване се изтрива обекта от колекцията на TableView

    public void handleDeleteSelected(ActionEvent actionEvent) {
        Developer selectedDeveloper = developerTable.getSelectionModel().getSelectedItem();
        if (selectedDeveloper == null) {
            showAlert(Alert.AlertType.WARNING, "Предупреждение",null, "Моля, изберете разработчик от таблицата.");
        } else {
            Optional<ButtonType> result =  showAlert(Alert.AlertType.WARNING, "Предупреждение", "Изтриване на разработчик", "Сигурни ли сте, че искате да изтриете избрания разработчик?");
            if (result.isPresent() && result.get() == ButtonType.OK) {
                developersData.remove(selectedDeveloper);
            }
        }
    }

Стъпка 7: Метода handleAboutDialog трябва да имплементира отварянето на диалог.

За да се отвори нов прозорец трябва да се създаде fxml файл с оформление

bg/tu_varna/sit/ps/lab8/task1/about-view.fxml

<VBox alignment="CENTER"
      spacing="12"
      styleClass="main-container"
      stylesheets="@css/styles.css">

    <Label text="Система за управление на ИТ екип" styleClass="dialog-title"/>
    <Label text="Автор Име Фалимия" wrapText="true"/>

</VBox>

В styles.css се добавя оформление за заглавието на диалога:

.dialog-title {
    -fx-font-size: 20px;
    -fx-font-weight: bold;
    -fx-text-fill: #2c3e50;
}

Като допълнение към стандартното отваряне на прозорец се използва initModality за поведението на приложението при стартиран дъщерен прозорец.

    public void handleAboutDialog(ActionEvent actionEvent) {
        try {
            FXMLLoader loader = new FXMLLoader(
                    DeveloperApplication.class.getResource("about-view.fxml"));
            Parent root = loader.load();

            Stage stage = new Stage();
            stage.setTitle("За програмата");
            stage.initModality(Modality.APPLICATION_MODAL);
            stage.setScene(new Scene(root));
            stage.showAndWait();
        } catch (Exception e) {
            showAlert(Alert.AlertType.ERROR, "Грешка", "Неуспешно отваряне на прозореца.", e.getMessage());
        }
    }

This site uses Just the Docs, a documentation theme for Jekyll.