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 关于如何更改实现的想法。
在我的应用程序中,我想将 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 关于如何更改实现的想法。