Last active
April 22, 2019 01:55
-
-
Save seraphy/b00f761b39bac7815bd96548fd4e459e to your computer and use it in GitHub Desktop.
JavaFXでTitledPaneを縦分割するSplitePaneをTitledPaneの開閉に伴って自動的に均等リサイズする実装例。
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package jp.seraphyware.example.lava8learn.jfx; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Objects; | |
import javafx.application.Application; | |
import javafx.application.Platform; | |
import javafx.beans.InvalidationListener; | |
import javafx.beans.value.ChangeListener; | |
import javafx.beans.value.ObservableValue; | |
import javafx.geometry.Orientation; | |
import javafx.scene.Node; | |
import javafx.scene.Scene; | |
import javafx.scene.control.Label; | |
import javafx.scene.control.SplitPane; | |
import javafx.scene.control.TextArea; | |
import javafx.scene.control.TitledPane; | |
import javafx.scene.layout.Region; | |
import javafx.scene.layout.VBox; | |
import javafx.stage.Stage; | |
/** | |
* TitledPaneの開閉イベントにより、SplitePaneの分割を自動的に調整する。 | |
* SplitePaneは縦分割されているもののみ受け入れ可能である。 | |
* また、TitledPaneはAnimationされていないものが推奨される。 | |
*/ | |
class TitledPaneStackingCoordinator { | |
private final SplitPane container; | |
private InvalidationListener expandChangeListener = obs -> { | |
Platform.runLater(() -> { | |
doLayout(); | |
}); | |
}; | |
public TitledPaneStackingCoordinator(SplitPane container) { | |
this.container = Objects.requireNonNull(container); | |
if (container.getOrientation() != Orientation.VERTICAL) { | |
throw new IllegalArgumentException("縦方向の分割のみサポートしています"); | |
} | |
} | |
public void doLayout() { | |
double totalHeight = 0; | |
List<Node> nodes = container.getItems(); | |
double[] heights = new double[nodes.size()]; | |
for (int idx = 0; idx < nodes.size(); idx++) { | |
Node node = nodes.get(idx); | |
double height; | |
if (node instanceof TitledPane) { | |
TitledPane tp = (TitledPane) node; | |
height = node.prefHeight(-1); | |
if (!tp.isExpanded()) { | |
// minimizeしている場合は推奨サイズ | |
((TitledPane) node).setMaxHeight(height); | |
} else { | |
// expandedしている場合、推奨サイズか現在サイズの大きい方を採用 | |
height = Math.max(tp.getHeight(), height); | |
((TitledPane) node).setMaxHeight(Double.MAX_VALUE); | |
} | |
} else if (node instanceof Region) { | |
height = ((Region) node).getHeight(); | |
} else { | |
height = node.prefHeight(-1); | |
} | |
totalHeight += height; | |
heights[idx] = totalHeight; | |
System.out.println(node + "=" + height); | |
} | |
for (int idx = 0; idx < nodes.size() - 1; idx++) { | |
double height = heights[idx]; | |
double pert = height / totalHeight; | |
System.out.println(idx + "=" + pert); | |
container.getDividers().get(idx).setPosition(pert); | |
} | |
} | |
private HashMap<TitledPane, ContentWatcher> watchingContents = new HashMap<>(); | |
private class ContentWatcher implements ChangeListener<Node> { | |
private TitledPane parent; | |
private Node content; | |
public ContentWatcher(TitledPane parent) { | |
this.parent = Objects.requireNonNull(parent); | |
parent.contentProperty().addListener(this); | |
watchContentVisible(parent.getContent()); | |
} | |
public void unwatch() { | |
parent.contentProperty().removeListener(this); | |
} | |
public void unwatchContentVisible() { | |
if (content != null) { | |
content.visibleProperty().removeListener(expandChangeListener); | |
content = null; | |
} | |
} | |
private void watchContentVisible(Node content) { | |
this.content = content; | |
if (content != null) { | |
content.visibleProperty().addListener(expandChangeListener); | |
} | |
} | |
@Override | |
public void changed(ObservableValue<? extends Node> observable, Node oldValue, Node newValue) { | |
unwatchContentVisible(); | |
watchContentVisible(content); | |
} | |
} | |
public void add(TitledPane titledPane) { | |
titledPane.expandedProperty().addListener(expandChangeListener); | |
ContentWatcher oldWatcher = watchingContents.put(titledPane, new ContentWatcher(titledPane)); | |
if (oldWatcher != null) { | |
oldWatcher.unwatch(); | |
} | |
} | |
public void remove(TitledPane titledPane) { | |
titledPane.expandedProperty().removeListener(expandChangeListener); | |
ContentWatcher oldWatcher = watchingContents.get(titledPane); | |
if (oldWatcher != null) { | |
oldWatcher.unwatch(); | |
} | |
} | |
} | |
public class TitledPaneStackingExample extends Application { | |
@Override | |
public void start(Stage stg) throws Exception { | |
stg.setTitle(getClass().getSimpleName()); | |
SplitPane splitter = new SplitPane(); | |
{ | |
TextArea text1 = new TextArea("aaa"); | |
TextArea text2 = new TextArea("bbb"); | |
TextArea text3 = new TextArea("ccc"); | |
text1.setPrefRowCount(3); | |
text2.setPrefRowCount(3); | |
text3.setPrefRowCount(3); | |
SplitPane pane = new SplitPane(); | |
pane.setOrientation(Orientation.VERTICAL); | |
TitledPane pane1 = new TitledPane("aaa", text1); | |
pane1.setAnimated(false); | |
pane1.setMaxHeight(Double.MAX_VALUE); | |
TitledPane pane2 = new TitledPane("bbb", text2); | |
pane2.setAnimated(false); | |
pane2.setMaxHeight(Double.MAX_VALUE); | |
pane2.heightProperty().addListener((self, old, v) -> { | |
System.out.println("height=" + v); | |
}); | |
TitledPane pane3 = new TitledPane("ccc", text3); | |
//pane3.setAnimated(false); | |
pane3.setMaxHeight(Double.MAX_VALUE); | |
pane.getItems().addAll(pane1, new VBox(new Label("@@@@")), pane2, pane3); | |
splitter.getItems().add(pane); | |
// TitledPaneのon/offによりSplitePaneを自動的にリサイズする | |
// (閉じていないTitledPaneを均等割りする) | |
TitledPaneStackingCoordinator cood = new TitledPaneStackingCoordinator(pane); | |
cood.add(pane1); | |
cood.add(pane2); | |
cood.add(pane3); | |
Platform.runLater(cood::doLayout); | |
} | |
stg.setScene(new Scene(splitter)); | |
stg.show(); | |
} | |
public static void main(String[] args) { | |
launch(args); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment