TitledPane 的切换适配器

Toggle adapter for TitledPane

在我的应用程序中,我想将 ToggleGroup 的行为应用于一组 TitledPane。为此,我实现了这个:

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);
    }
}

我天真的方法行不通,但我不知道为什么不行。有什么想法吗?

编辑:我知道 Acordion,但这不合适,因为我不能将所有三个 TitledPane 放在同一个父容器中。

首先,您正在重新发明轮子。不要那样做;已经有一个控件,Accordion,它实现了你在这里想要做的事情。

您只需要:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Accordion?>
<?import javafx.scene.control.TextArea?>
<?import javafx.scene.control.TitledPane?>

<Accordion expandedPane="${titledPane1}" xmlns="http://javafx.com/javafx/15.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="sample.Controller">
   <panes>
      <TitledPane fx:id="titledPane1" text="Title 1" expanded="true">
         <content>
            <TextArea minHeight="-Infinity" prefHeight="125.0" prefRowCount="1" wrapText="true" />
         </content>
      </TitledPane>
      <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>
   </panes>
   <padding>
      <Insets bottom="14.0" left="14.0" right="14.0" top="14.0" />
   </padding>
</Accordion>

public class Controller {
    
    @FXML private Accordion accordion ;
    @FXML private TitledPane titledPane1;
    @FXML private TitledPane titledPane2;
    @FXML private TitledPane titledPane3;

    @FXML
    private void initialize() {
        accordion.setExpandedPane(titledPane1);
    }
}

不需要ToggleAdapter

您的代码不起作用的原因是,我认为您假设 ToggleGroup 观察其每个切换的 selected 状态,并在以下情况下更新另一个切换的状态一个被选中。事实并非如此;如果需要,切换实现实际上有责任在其切换组中保持单一选择。您可以通过向 ToggleAdapter 中的选定状态添加一个侦听器来做到这一点(但再次强调,重新发明标准 API 中定义的功能总是错误的)。

private ToggleAdapter(TitledPane titledPane) {
    this.titledPane = titledPane;
    selectedProperty().addListener(obs -> {
        ToggleGroup tg = getToggleGroup();
        if (tg != null) {
            if (isSelected()) {
                tg.selectToggle(this);
            } else if (tg.getSelectedToggle() == this) {
                tg.selectToggle(null);
            }
        }
    });
}

ToggleAdapter的实现改成这样实际上解决了问题:

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);
                }
            });
        });
    }
}

谢谢 +James_D 关于如何更改实现的想法。