Locked History Actions

JavaFx/setManaged

setManagedの意味

setManagedというメソッドがなぜ存在するのか不思議だったのだが、その意味はこうだ。

  • trueの場合、そこでの何らかの変更があった場合に、それをトップレベルにまで伝えてすべてのレイアウトをやり直す。
  • デフォルトではtrueになっている。
  • レイアウト用のペイン、つまりBorderPaneやら何やらのみではなく、すべてのParentとNodeの関係においてこの仕組みが働く。

ということ。例えば、以下の例のようにAnimationTimerを使用してボールを転がすような場合、ボールが転がるたび、つまりAnimationTimerのtickごとにトップレベルからの再レイアウトがかかってしまう。

このような場合には、Group自体をunmanagedにする。すると、再レイアウトはかからない。もちろん、再レイアウトがされないので、自力でレイアウトする必要がある。

この例では問題は無いが、例えばAnimationTimerのtickごとにLabelに現在時刻を表示するような場合、Labelをunmanagedにしなければならないが、そうするとおそらくLabelはレイアウトの枠組みからはずれてしまい、自力で所定の位置に置かなければならなくなると思われる。

AnimationTimerでもTimelineでも同じだが、こういった定期的に、しかも1秒に数十回以上行うような場合には、注意しないと不必要なrootからのレイアウトがその数だけ発生してしまう。

import static java.lang.Math.*;

import java.util.*;

import javafx.animation.*;
import javafx.application.*;
import javafx.scene.*;
import javafx.scene.control.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.scene.shape.*;
import javafx.stage.*;

public class GraphicsWithRegions extends Application {

  private Circle ball;
  private double direction;
  private Random random = new Random();
  private double speed = 10;
  @Override
  
  public void start(final Stage primaryStage) {

    ball = new Circle(100, 100, 20);
    ball.setFill(Color.BLUE);
    direction = 2 * PI * random.nextDouble();

    Group group = new Group(ball);
    Pane pane = new Pane(group);
    group.setManaged(false); // <------------- 無いとレイアウトがかかりすぎる。

    BorderPane border = new BorderPane() {
      @Override
      public void layoutChildren() {
        System.out.println("layoutChildren");
        super.layoutChildren();
      }
    };
    border.setCenter(pane);
    border.setTop(new TextArea("top area"));
    border.setBottom(new TextField("bottom field"));
    final Scene scene = new Scene(border, 800, 600, Color.BLACK);
    primaryStage.setScene(scene);
    primaryStage.show();

    ball.setCenterX(pane.getWidth() / 2);
    ball.setCenterY(pane.getHeight() / 2);
    
    new AnimationTimer() {
      @Override
      public void handle(long now) {
        double x = ball.getCenterX();
        double y = ball.getCenterY();
        double newDirection = random.nextDouble() * PI;
        
        if (y < 0) {
          direction = newDirection;
        } else if (pane.getWidth() < x) {
          direction = newDirection + PI * 0.5;
        } else if (pane.getHeight() < y) {
          direction = newDirection + PI;
        } else if (x < 0) {
          direction = newDirection + PI * 1.5;
        }

        ball.setCenterX(x + speed * cos(direction));
        ball.setCenterY(y + speed * sin(direction));
      }
    }.start();
  }

  public static void main(String[] args) {
    launch(args);
  }
}

子からのレイアウト要求を遮断してしまうには、例えば以下を使う。

このクラスは、他から起こったレイアウト要求には対象を正しくレイアウトするが、 対象からのレイアウト要求は無視する。

※Sceneにある一つのコントロールでもその要求をだせば、Scene全体のレイアウトがやり直される。

import javafx.scene.*;
import javafx.scene.layout.*;

/**
 * ただ一つの子を持ち、自身に挿入し、自身のサイズを与えるがマネージしない。
 * これにより子からのレイアウト要求を遮断する。
 */
public class UnmanageChild extends Region {

  private Node node;  
  public UnmanageChild(Node node) {
    this.node = node;
    getChildren().add(node);
    node.setManaged(false);
  }

  @Override
  protected void layoutChildren() {        
    node.relocate(0, 0);
    node.resize(getWidth(), getHeight());
  }
  
  @Override protected double computeMinWidth(double height)  { return node.minWidth(height); }
  @Override protected double computeMaxWidth(double height)  { return node.maxWidth(height); }
  @Override protected double computeMinHeight(double width)  { return node.minHeight(width); }
  @Override protected double computeMaxHeight(double width)  { return node.maxHeight(width); }
  @Override protected double computePrefWidth(double height) { return node.prefWidth(height); }
  @Override protected double computePrefHeight(double width) { return node.prefHeight(width); }
}

当然ながらこれではうまく行かない場合が出てくる。例えば、Sliderを以下のような要件で使う場合、

  • 1)AnimationTimerによって、現在の動画の再生位置を取得し、それをSliderサムに反映させたい。

  • 2)動画の長さによってMajorTickUnit等の値を変更したい。

1)ではAnimationTimerのtickごとにレイアウトがかかってしまうため、これによる全画面レイアウトは避けたいが、2)の場合には再レイアウトしてもらわないと困る。

以下のようにすればよい。

  Slider slider = ...
  UnmanagedChild unmanager = new UnmanageChild(slider);
  
  // MajorTickUnit等を設定する場合
  public void settingUnits() {
    slider.setMajorTickUnit(.....
    unmanager.requestLayout();
  }