I'm working on a JavaFX project and would like to apply the decorator design pattern to my controllers to enhance their functionality. Specifically, I want to add additional behavior to existing controllers without modifying their code directly.
What would be the best approach to implement the decorator pattern in JavaFX controllers? Are there any specific considerations or best practices when applying this design pattern in the context of JavaFX? Can you provide a simple example or code snippet demonstrating how to create a decorator for a JavaFX controller?
I have different FXML files with Controller with common parts. The Common Parts:
@FXML
public Text Text1;
@FXML
public Text Text2;
@FXML
public TextField ID_IN;//Common ID id input field
@FXML
public Button Save; //Edit and Add Save button
@FXML
public Button Close;
@Override
public void showOutsideElements() {
ID_IN.setVisible(true);
Close.setVisible(true);
Save.setVisible(true);
Text1.setVisible(true);
Text2.setVisible(true);
}
@Override
public void hideOutsideElements() {
ID_IN.setVisible(false);
Close.setVisible(false);
Save.setVisible(false);
Text1.setVisible(false);
Text2.setVisible(false);
}
I would like to hide and show these elements with the decorated version.
@FXML
public void showOutsideElements() {
super.showOutsideElements();
SearchFile.setVisible(true);
AppRe_IN.setVisible(true);
}
@FXML
public void hideOutsideElements() {
super.hideOutsideElements();
SearchFile.setVisible(false);
AppRe_IN.setVisible(false);
}
My code would be more structured with this design pattern, but it seems like the FXML elements can only be accede by the controller.
Here is the actual code:
This is my Base controller:
package main.tooldatabase.controller;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.text.Text;
import java.sql.SQLException;
import java.util.Optional;
public abstract class BaseController<T> {
@FXML
protected TextField filterField; //Search field
//Input field titles
@FXML
protected Text Text1;
@FXML
protected Text Text2;
@FXML
protected Text Text3;
@FXML
protected Text Text4;
@FXML
protected Text Text5;
@FXML
protected Text Text6;
@FXML
protected Text Text7;
//Save button
@FXML
protected Button Save; //Edit and Add Save button
@FXML
protected Button Close;
//Tabels
@FXML
protected TableView<T> output_table;
@FXML
protected TableColumn<T, String> Actions;
protected boolean statusCode;
protected ObservableList<T> listData;
protected FilteredList<T> filteredData;
public BaseController() {
listData = FXCollections.observableArrayList();
Actions = new TableColumn<>("Actions");
Actions.setSortable(false);
//crudData = data;
}
public boolean isStatusCode() {
return statusCode;
}
public void setStatusCode(boolean statusCode) {
this.statusCode = statusCode;
}
protected void showData1(){
output_table.setItems(filteredData);
}
protected void showSaveConfirmation() {
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setTitle("Save Confirmation");
alert.setHeaderText(null);
alert.setContentText("Save operation completed successfully!");
alert.showAndWait();
}
protected boolean confirmDelete(T selectedItem) throws SQLException {
Alert confirmation = new Alert(Alert.AlertType.CONFIRMATION);
confirmation.setTitle("Delete Confirmation");
confirmation.setHeaderText("Confirm Deletion");
confirmation.setContentText("Are you sure you want to delete?");
Optional<ButtonType> result = confirmation.showAndWait();
if (result.isPresent() && result.get() == ButtonType.OK) {
return true;
}else{
return false;
}
}
// Abstract method for deletion
protected abstract void deleteSelectedItem(T selectedItem, String successMessage) throws SQLException;
// Method to show a success message after an action (deletion, saving, etc.)
protected void showSuccessMessage(String message) {
Alert successAlert = new Alert(Alert.AlertType.INFORMATION);
successAlert.setTitle("Success");
successAlert.setHeaderText(null);
successAlert.setContentText(message);
successAlert.showAndWait();
}
}
Here is Member Controller:
package main.tooldatabase.controller;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import main.tooldatabase.database.implementations.MainTables.implMemberDatab;
import main.tooldatabase.database.implementations.MainTables.implProjectDatab;
import main.tooldatabase.database.implementations.SwitchTables.impM_AssignDatab;
import main.tooldatabase.database.models.MainTables.MemberModel;
import main.tooldatabase.database.models.SwitchTables.M_AssignModel;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class MemberController extends BaseController<MemberModel> implements Initializable {
@FXML
private TextField Name_IN;
@FXML
private TextField ID_IN;
@FXML
private TableColumn<MemberModel, String> ID_OUT;
@FXML
private TableColumn<MemberModel, String> Name_OUT;
@FXML
private ComboBox<String> Position_IN;
@FXML
private TableColumn<MemberModel, String> Position_OUT;
@FXML
private ComboBox<String> Project_IN;
private final implMemberDatab crudData;
private final implProjectDatab ProjectData;
private final impM_AssignDatab attach;
ObservableList<String> PositionList;
public MemberController() {
super();
crudData = new implMemberDatab();
ProjectData = new implProjectDatab();
attach = new impM_AssignDatab();
ID_OUT=new TableColumn<>();
Name_OUT=new TableColumn<>();
Position_OUT=new TableColumn<>();
PositionList= FXCollections.observableArrayList("Position1","Position2","Position3");
}
@Override
protected void deleteSelectedItem(MemberModel selectedItem, String successMessage) throws SQLException {
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ID_OUT.setCellValueFactory((TableColumn.CellDataFeatures<MemberModel, String> cellData)
->cellData.getValue().member_idProperty());
Name_OUT.setCellValueFactory((TableColumn.CellDataFeatures<MemberModel, String> cellData)
-> cellData.getValue().nameProperty());
Position_OUT.setCellValueFactory((TableColumn.CellDataFeatures<MemberModel, String> cellData)
-> cellData.getValue().positionProperty());
try {
filteredData = new FilteredList<>(crudData.getAll(), b -> true);
} catch (SQLException e) {
throw new RuntimeException(e);
}
TableColumn<MemberModel, Object> newColumn = new TableColumn<>("Project");
newColumn.setCellValueFactory(new PropertyValueFactory<>("propertyName"));
output_table.getColumns().add(newColumn);
try {
ObservableList<String> teszt =ProjectData.getNames();
} catch (SQLException e) {
throw new RuntimeException(e);
}
Actions.setCellValueFactory(new PropertyValueFactory<>("member_id"));
Actions.setCellFactory(param -> new TableCell<MemberModel, String>() {
final Button deleteButton = new Button("Delete");
final Button editButton = new Button("Edit"); // Hozzáadott Edit gomb
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
// Ha nem üres a sor, akkor adj hozzá a gombot
deleteButton.setOnAction(event -> {
MemberModel selectedSample = getTableView().getItems().get(getIndex());
try {
onDeleteButtonClick(selectedSample);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
editButton.setOnAction(event -> {
MemberModel selectedSample = getTableView().getItems().get(getIndex());
onEditButtonClick(selectedSample); // Kérdéses az onEditButtonClick neve
});
HBox buttonsContainer = new HBox(deleteButton, editButton);
buttonsContainer.setSpacing(10); // Gombok közötti tér
setGraphic(buttonsContainer);
setText(null);
}
}
});
// Set the filter Predicate whenever the filter changes.
filterField.textProperty().addListener((observable, oldValue, newValue) -> {
filteredData.setPredicate(sample -> {
if (newValue == null || newValue.isEmpty()) {
return true;
}
String lowerCaseFilter = newValue.toLowerCase();
String first = "";
String second = "";
String third = "";
if(sample.getMember_id()!=null) {
first = sample.getMember_id().toLowerCase();
}
if(sample.getName()!=null) {
second = sample.getName().toLowerCase();
}
if(sample.getPosition()!=null) {
third = sample.getPosition().toLowerCase();
}
return first.contains(lowerCaseFilter) || second.contains(lowerCaseFilter)|| third.toLowerCase().contains(lowerCaseFilter);
});
});
output_table.getColumns().add(Actions);
statusCode = false;
try {
Project_IN.setItems(ProjectData.getNames());
} catch (SQLException e) {
throw new RuntimeException(e);
}
showData1();
newColumn.setCellValueFactory(cellData -> {
try {
return new ReadOnlyObjectWrapper<>(
ProjectData.getProject(cellData.getValue().getMember_id())
);
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
output_table.getSelectionModel().clearSelection();
Position_IN.setItems(PositionList);
hideOutsideElements();
}
private void autoId() throws SQLException {
MemberModel m = new MemberModel();
crudData.autoId(m);
ID_IN.setText(m.getMember_id());
}
@FXML
void AddItem(ActionEvent event) throws SQLException {
showOutsideElements();
statusCode = true;
autoId();
filterField.setText("");
}
@FXML
void OnClose(ActionEvent event) {
hideOutsideElements();
}
@FXML
void OnSave(ActionEvent event) throws SQLException, InvocationTargetException, IllegalAccessException {
MemberModel ToSave = new MemberModel();
M_AssignModel ToAssign = new M_AssignModel();
ToSave.setMember_id(ID_IN.getText());
ToSave.setName(Name_IN.getText());
ToSave.setPosition(Position_IN.getValue());
ToAssign.setMember_id(ID_IN.getText());
ToAssign.setProject_id(ProjectData.getProjectID(Project_IN.getValue()));
if(statusCode){
crudData.insert(ToSave);
attach.insert(ToAssign);
}else {
crudData.update(ToSave);
if(attach.IsThereRecord(ToAssign)){
attach.update(ToAssign);
}else {
attach.insert(ToAssign);
}
}
hideOutsideElements();
output_table.setItems(crudData.getAll());
filterField.setText("");
}
private void onEditButtonClick(MemberModel selectedSample) {
showOutsideElements();
//Project_IN.setItems(ProjData.getProjectNames());
ID_IN.setText(selectedSample.getMember_id());
Name_IN.setText(selectedSample.getName());
Position_IN.setItems(PositionList);
Position_IN.setValue(selectedSample.getPosition());
statusCode = false;
}
private void onDeleteButtonClick(MemberModel selectedSample) throws SQLException {
M_AssignModel connect = new M_AssignModel();
connect.setMember_id(selectedSample.getMember_id());
attach.delete(connect);
crudData.delete(selectedSample);
filterField.setText("");
hideOutsideElements();
output_table.setItems(crudData.getAll());
}
@FXML
void hideOutsideElements() {
Name_IN.setVisible(false);
Position_IN.setVisible(false);
ID_IN.setVisible(false);
Project_IN.setVisible(false);
Close.setVisible(false);
Save.setVisible(false);
Text1.setVisible(false);
Text2.setVisible(false);
Text3.setVisible(false);
Text6.setVisible(false);
}
@FXML
void showOutsideElements() {
Name_IN.setVisible(true);
Position_IN.setVisible(true);
ID_IN.setVisible(true);
ID_IN.setDisable(true);
Project_IN.setVisible(true);
Close.setVisible(true);
Save.setVisible(true);
Text1.setVisible(true);
Text2.setVisible(true);
Text3.setVisible(true);
Text6.setVisible(true);
}
}
Assembly Controller:
package main.tooldatabase.controller;
import javafx.collections.ObservableList;
import javafx.collections.transformation.FilteredList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import main.tooldatabase.database.implementations.MainTables.implAssemblyDatab;
import main.tooldatabase.database.models.MainTables.AssemblyModel;
import main.tooldatabase.interfaces.interCRUD;
import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.sql.SQLException;
import java.util.ResourceBundle;
public class AssemblyController extends BaseController<AssemblyModel> implements Initializable {
@FXML
private TextField AppRe_IN;
@FXML
private TableColumn<AssemblyModel, String> AppRe_OUT;
@FXML
private Button Close;
@FXML
private Button SearchFile;
@FXML
private TextField ID_IN;
@FXML
private TableColumn<AssemblyModel, String> ID_OUT;
//@FXML
// private ProgressBar progressBar; // Make sure to link ProgressBar in your FXML file
ObservableList<String> PhaseList;
private final interCRUD crudData = new implAssemblyDatab();
public AssemblyController() {
super();
ID_OUT=new TableColumn<>();
AppRe_OUT=new TableColumn<>();
FileChooser fileChooser = new FileChooser();
}
@Override
public void initialize(URL url, ResourceBundle resourceBundle) {
ID_OUT.setCellValueFactory((TableColumn.CellDataFeatures<AssemblyModel, String> cellData)
->cellData.getValue().assembly_idProperty());
AppRe_OUT.setCellValueFactory((TableColumn.CellDataFeatures<AssemblyModel, String> cellData)
-> cellData.getValue().application_releaseProperty());
try {
filteredData = new FilteredList<>(crudData.getAll(), b -> true);
} catch (SQLException e) {
throw new RuntimeException(e);
}
Actions.setCellValueFactory(new PropertyValueFactory<>("assembly_id"));
Actions.setCellFactory(param -> new TableCell<AssemblyModel, String>() {
final Button deleteButton = new Button("Delete");
final Button editButton = new Button("Edit"); // Hozzáadott Edit gomb
final Button openButton = new Button("Open");
@Override
protected void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setGraphic(null);
setText(null);
} else {
// Ha nem üres a sor, akkor adj hozzá a gombot
deleteButton.setOnAction(event -> {
AssemblyModel selectedSample = getTableView().getItems().get(getIndex());
try {
deleteSelectedItem(selectedSample,"Delete succes!");
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
editButton.setOnAction(event -> {
AssemblyModel selectedSample = getTableView().getItems().get(getIndex());
onEditButtonClick(selectedSample); // Kérdéses az onEditButtonClick neve
});
openButton.setOnAction(event -> {
AssemblyModel selectedSample = getTableView().getItems().get(getIndex());
File pdfFile = new File(selectedSample.getApplication_release());
if (pdfFile != null) {
try {
Desktop.getDesktop().open(pdfFile);
} catch (IOException e) {
e.printStackTrace();
// Handle the exception (e.g., show an alert to the user)
}
} else {
System.out.println("No PDF file selected.");
// Optionally, show an alert or message to the user
}
});
HBox buttonsContainer = new HBox(openButton,deleteButton, editButton);
buttonsContainer.setSpacing(10); // Gombok közötti tér
setGraphic(buttonsContainer);
setText(null);
}
}
});
// Set the filter Predicate whenever the filter changes.
filterField.textProperty().addListener((observable, oldValue, newValue) -> {
filteredData.setPredicate(sample -> {
if (newValue == null || newValue.isEmpty()) {
return true;
}
String lowerCaseFilter = newValue.toLowerCase();
String first = sample.getAssembly_id().toLowerCase();
String second = sample.getApplication_release().toLowerCase();
return first.contains(lowerCaseFilter) || second.contains(lowerCaseFilter);
});
});
output_table.getColumns().add(Actions);
statusCode = false;
showData1();
output_table.getSelectionModel().clearSelection();
hideOutsideElements();
}
private void autoId() throws SQLException {
AssemblyModel m = new AssemblyModel();
crudData.autoId(m);
ID_IN.setText(m.getAssembly_id());
}
@FXML
void AddItem(ActionEvent event) throws SQLException {
showOutsideElements();
//Phase_IN.setItems(PhaseList);
//Project_IN.setItems(ProjData.getProjectNames());
statusCode = true;
autoId();
filterField.setText("");
}
@FXML
void OnClose(ActionEvent event) {
hideOutsideElements();
}
@FXML
void OnSave(ActionEvent event) throws SQLException, InvocationTargetException, IllegalAccessException {
AssemblyModel ToSave = new AssemblyModel();
ToSave.setAssembly_id(ID_IN.getText());
ToSave.setApplication_release(AppRe_IN.getText());
if(statusCode){
crudData.insert(ToSave);
}else {
crudData.update(ToSave);
}
hideOutsideElements();
output_table.setItems(crudData.getAll());
filterField.setText("");
showSaveConfirmation();
}
@FXML
void ChooseFile(ActionEvent event) {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Choose a PDF File");
// Állítsd be a megengedett fájltípusokat, ha szükséges
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PDF Files", "*.pdf"));
File selectedFile = fileChooser.showOpenDialog(null);
if (selectedFile != null) {
// Itt megteheted, amit szeretnél a kiválasztott fájllal
System.out.println("Selected File: " + selectedFile.getAbsolutePath());
AppRe_IN.setText(selectedFile.getAbsolutePath());
// További logika a kiválasztott fájllal való munkához
}
}
private void onEditButtonClick(AssemblyModel selectedSample) {
showOutsideElements();
//Project_IN.setItems(ProjData.getProjectNames());
ID_IN.setText(selectedSample.getAssembly_id());
AppRe_IN.setText(selectedSample.getApplication_release());
statusCode = false;
}
private void onDeleteButtonClick(AssemblyModel selectedSample) throws SQLException {
crudData.delete(selectedSample);
filterField.setText("");
hideOutsideElements();
output_table.setItems(crudData.getAll());
}
@Override
protected void deleteSelectedItem(AssemblyModel selectedItem, String successMessage) throws SQLException {
// Perform the deletion
if(confirmDelete(selectedItem)) {
crudData.delete(selectedItem);
// Show success message after deletion
showSuccessMessage(successMessage);
}
// Refresh the table or update data after deletion if needed
output_table.setItems(crudData.getAll());
}
@FXML
void hideOutsideElements() {
ID_IN.setVisible(false);
AppRe_IN.setVisible(false);
Close.setVisible(false);
Save.setVisible(false);
SearchFile.setVisible(false);
Text1.setVisible(false);
Text6.setVisible(false);
}
@FXML
void showOutsideElements() {
ID_IN.setVisible(true);
AppRe_IN.setVisible(true);
AppRe_IN.setDisable(true);
Close.setVisible(true);
Save.setVisible(true);
SearchFile.setVisible(true);
Text1.setVisible(true);
Text6.setVisible(true);
}
}
I would like to create different decorator for different controller functionalites like Choose File and extend the show and hide method with controller specific elements.
I've researched the decorator pattern in general, but I'm looking for guidance on how to apply it effectively in the context of JavaFX. Any insights or code examples would be greatly appreciated!
FWIW, here is an example of applying a Decorator pattern to decorate text in JavaFX.
The purpose of the example is to demonstrate how to apply the Decorator pattern, not to demonstrate the best way to style text in JavaFX (that would be with style classes and CSS rules defined in a stylesheet).
In the example code provided below, the name mapping is:
Component -> Message
ConcreteComponent -> SimpleMessage
Decorator -> MessageDecorator
ConcreteDecorator -> BoldMessageDecorator
ConcreteDecorator -> ItalicMessageDecorator
operation() -> String getCSSRules();
operation() -> void applyStyle(String cssRules);
operation() -> StringProperty textProperty();
operation() -> Node getNode();
Perhaps if you look at the example it might provide you with some insight on if, when, and how you might wish to apply the Decorator pattern to future projects.
I'll provide the code without a lot of commentary. If you wish to learn more about the decorator pattern then there are many resources on the web you can learn from. The code itself is a straight implementation of the structure and hierarchy outlined in the linked Wikipedia article on the topic. The code also applies some JavaFX capabilities (e.g. CSS, scene graph rendering, and properties).
The app applies various decorators to a text label node to achieve different style combinations based on user interaction with the style toolbar in the app.
Message.java
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
public interface Message {
String getCSSRules();
void applyStyle(String cssRules);
StringProperty textProperty();
Node getNode();
}
SimpleMessage.java
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
import javafx.scene.control.Label;
public class SimpleMessage implements Message {
private static final String DEFAULT_STYLE = """
-fx-font-family: "sans-serif";
-fx-font-size: 25px;
-fx-text-alignment: center;
-fx-wrap-text: true;
-fx-text-fill: crimson;
-fx-background-color: lavenderblush;
""";
private static final String CSS_DATA_URL = "data:text/css,";
private static final String CSS_TEMPLATE = CSS_DATA_URL + // language=CSS
"""
.message {
%s
}
""";
private final Label label = new Label();
public SimpleMessage(String text) {
label.setText(text);
label.getStyleClass().add("message");
}
@Override
public String getCSSRules() {
return DEFAULT_STYLE;
}
@Override
public void applyStyle(String cssRules) {
System.out.println("Applying\n" + cssRules);
label.getStylesheets().setAll(
CSS_TEMPLATE.formatted(
cssRules
)
);
}
@Override
public StringProperty textProperty() {
return label.textProperty();
}
@Override
public Node getNode() {
return label;
}
}
MessageDecorator.java
import javafx.beans.property.StringProperty;
import javafx.scene.Node;
abstract public class MessageDecorator implements Message {
private final Message messageToBeDecorated;
public MessageDecorator(Message messageToBeDecorated) {
this.messageToBeDecorated = messageToBeDecorated;
}
@Override
public void applyStyle(String cssRules) {
messageToBeDecorated.applyStyle(cssRules);
}
@Override
public StringProperty textProperty() {
return messageToBeDecorated.textProperty();
}
@Override
public String getCSSRules() {
return messageToBeDecorated.getCSSRules();
}
@Override
public Node getNode() {
return messageToBeDecorated.getNode();
}
}
BoldMessageDecorator.java
public class BoldMessageDecorator extends MessageDecorator {
private static final String BOLD_STYLE = "-fx-font-weight: bold;\n";
public BoldMessageDecorator(Message messageToBeDecorated) {
super(messageToBeDecorated);
}
@Override
public String getCSSRules() {
return super.getCSSRules() + BOLD_STYLE;
}
}
ItalicMessageDecorator.java
public class ItalicMessageDecorator extends MessageDecorator {
private static final String ITALIC_STYLE = "-fx-font-style: italic;\n";
public ItalicMessageDecorator(Message messageToBeDecorated) {
super(messageToBeDecorated);
}
@Override
public String getCSSRules() {
return super.getCSSRules() + ITALIC_STYLE;
}
}
DecoratedApp
import javafx.application.Application;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleButton;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import java.util.List;
public class DecoratorApp extends Application {
private static final String SAMPLE_TEXT =
"Lasciate ogne speranza, voi ch'intrate";
private static final String CSS_DATA_URL = "data:text/css,";
private static final String CSS = CSS_DATA_URL + // language=CSS
"""
.root {
-fx-font-size: 20px;
}
.bold {
-fx-font-weight: bold;
-fx-font-family: "sans-serif";
}
.italic {
-fx-font-style: italic;
-fx-font-family: "sans-serif";
}
""";
private final Message sampleMessage = new SimpleMessage(
SAMPLE_TEXT
);
private final ObjectProperty<Message> message = new SimpleObjectProperty<>(
sampleMessage
);
private final ToggleButton bold = new ToggleButton("B");
private final ToggleButton italic = new ToggleButton("i");
private final List<Toggle> toggles = List.of(bold, italic);
@Override
public void start(Stage stage) {
bold.getStyleClass().add("bold");
italic.getStyleClass().add("italic");
bold.selectedProperty().addListener(o ->
applyStyles()
);
italic.selectedProperty().addListener(o ->
applyStyles()
);
ToolBar toolBar = new ToolBar(
bold,
italic
);
StackPane content = new StackPane(
message.get().getNode()
);
message.addListener(o ->
content.getChildren().setAll(
message.get().getNode()
)
);
content.setPadding(
new Insets(10)
);
content.setPrefSize(
300, 100
);
BorderPane layout = new BorderPane();
layout.setTop(toolBar);
layout.setCenter(content);
applyStyles();
Scene scene = new Scene(layout);
scene.getStylesheets().add(CSS);
stage.setScene(scene);
stage.show();
}
private void applyStyles() {
List<Toggle> selectedToggles = toggles.stream()
.filter(Toggle::isSelected)
.toList();
System.out.println(
selectedToggles.stream()
.map(toggle ->
((ToggleButton) toggle).getText()
).toList()
);
Message styledMessage = sampleMessage;
if (selectedToggles.contains(bold)) {
styledMessage = new BoldMessageDecorator(styledMessage);
}
if (selectedToggles.contains(italic)) {
styledMessage = new ItalicMessageDecorator(styledMessage);
}
styledMessage.applyStyle(styledMessage.getCSSRules());
message.set(styledMessage);
}
public static void main(String[] args) {
launch(args);
}
}
On Application to FXML and FXML controllers
I won't even try to extend this to work with FXML and controllers (I am not sure that would be such a good idea in any case) or to adapt the sample code from your question. You can review the comments on the question for some thoughts on applying the Decorator pattern with FXML and controllers.