Issue
In my application, I want to apply the behavior of a ToggleGroup
to a group of TitledPane
s. To do so, I implemented this:
ToggleAdapter.java
package sample;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
public class ToggleAdapter implements Toggle {
final private TitledPane titledPane;
final private ObjectProperty<ToggleGroup> toggleGroupProperty = new SimpleObjectProperty<>();
private ToggleAdapter(TitledPane titledPane) {
this.titledPane = titledPane;
}
@Override
public ToggleGroup getToggleGroup() {
return toggleGroupProperty.get();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
toggleGroupProperty.set(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroupProperty;
}
@Override
public boolean isSelected() {
return titledPane.isExpanded();
}
@Override
public void setSelected(boolean selected) {
titledPane.setExpanded(selected);
}
@Override
public BooleanProperty selectedProperty() {
return titledPane.expandedProperty();
}
@Override
public Object getUserData() {
return titledPane.getUserData();
}
@Override
public void setUserData(Object value) {
titledPane.setUserData(value);
}
@Override
public ObservableMap<Object, Object> getProperties() {
return FXCollections.emptyObservableMap();
}
public static Toggle asToggle(final TitledPane titledPane) {
return new ToggleAdapter(titledPane);
}
}
sample.fxml
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.VBox?>
<VBox spacing="7.0" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
<children>
<TitledPane fx:id="titledPane1" text="Title 1">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TextField />
<TitledPane fx:id="titledPane2" expanded="false" text="Title 2">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
<TitledPane fx:id="titledPane3" expanded="false" text="Title 3">
<content>
<TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
</content>
</TitledPane>
</children>
<padding>
<Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
</padding>
</VBox>
Controller.java
package sample;
import javafx.fxml.FXML;
import javafx.scene.control.TitledPane;
import javafx.scene.control.ToggleGroup;
public class Controller {
@FXML private TitledPane titledPane1;
@FXML private TitledPane titledPane2;
@FXML private TitledPane titledPane3;
@FXML
private void initialize() {
final var toggleGroup = new ToggleGroup();
final var toggle1 = ToggleAdapter.asToggle(titledPane1);
toggle1.setToggleGroup(toggleGroup);
final var toggle2 = ToggleAdapter.asToggle(titledPane2);
toggle2.setToggleGroup(toggleGroup);
final var toggle3 = ToggleAdapter.asToggle(titledPane3);
toggle3.setToggleGroup(toggleGroup);
toggleGroup.selectToggle(toggle1);
}
}
Main.java
package sample;
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{
Parent root = FXMLLoader.load(getClass().getResource("sample.fxml"));
primaryStage.setTitle("Hello World");
final Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
My naive approach doesn't work, but I have no idea why not. Any ideas?
EDIT: I am aware of Acordion
, but this would not be suitable because I cannot place all three TitledPane
s in the same parent container.
Solution
Changing the implementation of ToggleAdapter
to this actually solves the problem:
package sample;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.ObjectPropertyBase;
import javafx.collections.FXCollections;
import javafx.collections.ObservableMap;
import javafx.scene.control.TitledPane;
import javafx.scene.control.Toggle;
import javafx.scene.control.ToggleGroup;
import java.util.Optional;
public class ToggleAdapter implements Toggle {
final private TitledPane titledPane;
final private ObjectProperty<ToggleGroup> toggleGroupProperty = new ObjectPropertyBase<>() {
private ToggleGroup old;
@Override
protected void invalidated() {
if (old != null) {
old.getToggles().remove(ToggleAdapter.this);
}
old = get();
if (get() != null && get().getToggles().contains(ToggleAdapter.this) == false) {
get().getToggles().add(ToggleAdapter.this);
}
}
@Override
public Object getBean() {
return ToggleAdapter.this;
}
@Override
public String getName() {
return "toggleGroup";
}
};
@Override
public ToggleGroup getToggleGroup() {
return toggleGroupProperty.get();
}
@Override
public void setToggleGroup(ToggleGroup toggleGroup) {
toggleGroupProperty.set(toggleGroup);
}
@Override
public ObjectProperty<ToggleGroup> toggleGroupProperty() {
return toggleGroupProperty;
}
@Override
public boolean isSelected() {
return titledPane.isExpanded();
}
@Override
public void setSelected(boolean selected) {
titledPane.setExpanded(selected);
}
@Override
public BooleanProperty selectedProperty() {
return titledPane.expandedProperty();
}
@Override
public Object getUserData() {
return titledPane.getUserData();
}
@Override
public void setUserData(Object value) {
titledPane.setUserData(value);
}
@Override
public ObservableMap<Object, Object> getProperties() {
return FXCollections.emptyObservableMap();
}
public static Toggle asToggle(final TitledPane titledPane) {
return new ToggleAdapter(titledPane);
}
public ToggleAdapter(TitledPane titledPane) {
this.titledPane = titledPane;
selectedProperty().addListener(obs -> {
Optional.ofNullable(getToggleGroup()).ifPresent(toggleGroup -> {
if (isSelected()) {
toggleGroup.selectToggle(this);
} else if (toggleGroup.getSelectedToggle() == this) {
toggleGroup.selectToggle(null);
}
});
});
}
}
Thank you, +James_D for the idea on how to change the implementation.
Answered By - Hannes
Answer Checked By - Dawn Plyler (JavaFixing Volunteer)