我想编写一个方法,将整个场景图(包括节点的名称和与节点关联的css属性)打印到控制台。这将使得能够查看场景图的层次结构以及与每个元素相关联的所有css属性。我尝试这样做(请参阅下面的scala代码),但我的尝试失败了。
我正在测试它的场景图包含我在this tutorial之后制作的自定义控制器。它不打印自定义控制器中包含的任何嵌套控件。自定义控制器在舞台上显示正常(并且它正常运行)所以我知道所有必需的控件都是场景图的一部分,但由于某种原因,示例代码不会递归到自定义控制器中。它会打印自定义控制器的名称,但不会打印控制器内的嵌套元素。
object JavaFXUtils {
def printNodeHierarchy(node: Node): Unit = {
val builder = new StringBuilder()
traverse(0, node, builder)
println(builder)
}
private def traverse(depth: Int, node: Node, builder: StringBuilder) {
val tab = getTab(depth)
builder.append(tab)
startTag(node, builder)
middleTag(depth, node, builder)
node match {
case parent: Parent => builder.append(tab)
case _ =>
}
endTag(node, builder)
}
private def getTab(depth: Int): String = {
val builder = new StringBuilder("\n")
for(i <- 0 until depth) {
builder.append(" ")
}
builder.toString()
}
private def startTag(node: Node, builder: StringBuilder): Unit = {
def styles: String = {
val styleClasses = node.getStyleClass
if(styleClasses.isEmpty) {
""
} else {
val b = new StringBuilder(" styleClass=\"")
for(i <- 0 until styleClasses.size()) {
if(i > 0) {
b.append(" ")
}
b.append(".").append(styleClasses.get(i))
}
b.append("\"")
b.toString()
}
}
def id: String = {
val nodeId = node.getId
if(nodeId == null || nodeId.isEmpty) {
""
} else {
val b = new StringBuilder(" id=\"").append(nodeId).append("\"")
b.toString()
}
}
builder.append(s"<${node.getClass.getSimpleName}$id$styles>")
}
private def middleTag(depth: Int, node: Node, builder: StringBuilder): Unit = {
node match {
case parent: Parent =>
val children: ObservableList[Node] = parent.getChildrenUnmodifiable
for (i <- 0 until children.size()) {
traverse(depth + 1, children.get(i), builder)
}
case _ =>
}
}
private def endTag(node: Node, builder: StringBuilder) {
builder.append(s"</${node.getClass.getSimpleName}>")
}
}
打印整个场景图的内容的正确方法是什么?接受的答案可以用Java或Scala编写。
更新
经过进一步审核,我注意到它对某些自定义控件有效,但不是全部。我有3个自定义控件。我将展示其中的两个。自定义控件是ClientLogo和MenuViewController。先前列出的遍历代码正确显示ClientLogo的子代,但不显示MenuViewController的子代。 (也许这是因为MenuViewController是TitledPane的子类?)
client_logo.fxml:
<?import javafx.scene.image.ImageView?>
<fx:root type="javafx.scene.layout.StackPane" xmlns:fx="http://javafx.com/fxml" id="clientLogo">
<ImageView fx:id="logo"/>
</fx:root>
ClientLogo.scala
class ClientLogo extends StackPane {
@FXML @BeanProperty var logo: ImageView = _
val logoFxml: URL = classOf[ClientLogo].getResource("/fxml/client_logo.fxml")
val loader: FXMLLoader = new FXMLLoader(logoFxml)
loader.setRoot(this)
loader.setController(this)
loader.load()
logo.setImage(Config.clientLogo)
logo.setPreserveRatio(false)
var logoWidth: Double = .0
def getLogoWidth = logoWidth
def setLogoWidth(logoWidth: Double) {
this.logoWidth = logoWidth
logo.setFitWidth(logoWidth)
}
var logoHeight: Double = .0
def getLogoHeight = logoHeight
def setLogoHeight(logoHeight: Double) {
this.logoHeight = logoHeight
logo.setFitHeight(logoHeight)
}
}
ClientLogo的用法:
<ClientLogo MigPane.cc="id clientLogo, pos (50px) (-25px)"/>
menu.fxml:
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.shape.Line?>
<?import javafx.scene.control.Label?>
<fx:root type="javafx.scene.control.TitledPane" xmlns:fx="http://javafx.com/fxml" id="menu" collapsible="true" expanded="false">
<GridPane vgap="0" hgap="0">
<children>
<Line fx:id="line" styleClass="menu-line" startX="0" startY="1" endX="150" endY="1" GridPane.rowIndex="0" GridPane.columnIndex="0"/>
<Label fx:id="btnSettings" id="btn-settings" styleClass="btn-menu" text="%text.change.password" GridPane.rowIndex="1" GridPane.columnIndex="0"/>
<Label fx:id="btnAdmin" id="btn-admin" styleClass="btn-menu" text="%text.admin" GridPane.rowIndex="2" GridPane.columnIndex="0"/>
<Label fx:id="btnQuickTips" id="btn-quick-tips" styleClass="btn-menu" text="%text.quick.tips" GridPane.rowIndex="3" GridPane.columnIndex="0"/>
<Label fx:id="btnLogout" id="btn-logout" styleClass="btn-menu" text="%text.logout" GridPane.rowIndex="4" GridPane.columnIndex="0"/>
</children>
</GridPane>
</fx:root>
MenuViewController.scala:
class MenuViewController extends TitledPane with ViewController[UserInfo] with LogHelper with Resources {
private var menuWidth: Double = .0
@FXML @BeanProperty var line: Line = _
@FXML @BeanProperty var btnSettings: Label = _
@FXML @BeanProperty var btnAdmin: Label = _
@FXML @BeanProperty var btnQuickTips: Label = _
@FXML @BeanProperty var btnLogout: Label = _
val menuFxml: URL = classOf[MenuViewController].getResource("/fxml/menu.fxml")
val loader: FXMLLoader = new FXMLLoader(menuFxml)
loader.setRoot(this)
loader.setController(this)
loader.setResources(resources)
loader.load()
var userInfo: UserInfo = _
@FXML
private def initialize() {
def handle(message: Any): Unit = {
setExpanded(false)
uiController(message)
}
btnSettings.setOnMouseClicked(EventHandlerFactory.mouseEvent(e => handle(SettingsClicked)))
btnAdmin.setOnMouseClicked(EventHandlerFactory.mouseEvent(e => handle(AdminClicked)))
btnQuickTips.setOnMouseClicked(EventHandlerFactory.mouseEvent(e => handle(QuickTipsClicked)))
btnLogout.setOnMouseClicked(EventHandlerFactory.mouseEvent(e => handle(LogoutClicked)))
}
override def update(model: UserInfo) {
userInfo = model
setText(if (userInfo == null) "Menu" else userInfo.displayName)
}
def getMenuWidth: Double = {
return menuWidth
}
def setMenuWidth(menuWidth: Double) {
this.menuWidth = menuWidth
val spaceToRemove: Double = (menuWidth / 3)
line.setEndX(menuWidth - spaceToRemove)
line.setTranslateX(spaceToRemove / 2)
Array(btnSettings, btnAdmin, btnQuickTips, btnLogout).foreach(btn => {
btn.setMinWidth(menuWidth)
btn.setPrefWidth(menuWidth)
btn.setMaxWidth(menuWidth)
})
}
}
MenuViewController的用法:
<MenuViewController MigPane.cc="id menu, pos (100% - 250px) (29)" menuWidth="200" fx:id="menu"/>
这是将场景图打印到控制台的输出示例:
<BorderPane styleClass=".root">
<StackPane>
<MigPane id="home" styleClass=".top-blue-bar-bottom-blue-curve">
<ClientLogo id="clientLogo">
<ImageView id="logo"></ImageView>
</ClientLogo>
...
<MenuViewController id="menu" styleClass=".titled-pane">
</MenuViewController>
</MigPane>
</StackPane>
</BorderPane>
如您所见,ClientLogo自定义控件中的子元素已正确遍历,但缺少menu.fxml中的元素。我希望看到the substructure that is added to a TitledPane和GridPane及其嵌套的子结构在menu.fxml中列出。
答案 0 :(得分:5)
推荐工具
ScenicView是第三方工具,用于在SceneGraph上进行内省。强烈建议您使用ScenicView而不是开发自己的调试工具来转储场景图。
转储实用程序
这是一个非常简单的例程,可以将场景图转储到System.out
,通过DebugUtil.dump(stage.getScene().getRoot())
调用它:
import javafx.scene.Node;
import javafx.scene.Parent;
public class DebugUtil {
/** Debugging routine to dump the scene graph. */
public static void dump(Node n) {
dump(n, 0);
}
private static void dump(Node n, int depth) {
for (int i = 0; i < depth; i++) System.out.print(" ");
System.out.println(n);
if (n instanceof Parent) {
for (Node c : ((Parent) n).getChildrenUnmodifiable()) {
dump(c, depth + 1);
}
}
}
}
上面的简单例程通常会转储所有样式类信息,因为节点上的默认toString()
输出样式数据。
另一个增强功能是检查正在打印的节点是否也是Labeled或Text的实例,然后在这些情况下还打印与节点关联的文本字符串。
<强>买者强>
对于某些控件类型,为控件创建的一些节点由控件的皮肤实例化,该皮肤可以在CSS中定义,并且皮肤有时会懒惰地创建其子节点。
要查看这些节点,您需要做的是确保在 以下事件之后运行转储,在场景图上运行了css布局:
stage.show()
已被要求进行初始场景。在运行转储之前,请检查您是否已完成其中一项操作。
的示例// Modify scene.
// . . .
// Start a timer which delays the scene dump call.
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long now) {
// Take action on receiving a pulse.
dump(stage.getScene().getRoot());
// Stop monitoring pulses.
// You can stop immediately like this sample.
stop();
// OR you could count a specified number pulses before stopping.
}
};
timer.start();
答案 1 :(得分:0)
基于@ jewelsea的优秀答案,这是Jython的一个版本。它通过使用一个特殊的案例处理程序来进行更深入的遍历,这些控件对findchildren方法没有很好的响应。它应该直截了当地转换为泛型java,只需使用python代码作为伪代码!
from javafx.scene import Parent
from javafx.scene import control
class SceneGraph(object):
def parse(self,n,depth=0):
print " "*depth,n
if isinstance(n,control.Tab):
for i in n.getContent().getChildrenUnmodifiable():
self.parse(i,depth+1)
elif isinstance(n,control.TabPane):
for i in n.getTabs():
self.parse(i,depth+1)
elif isinstance(n,Parent):
for i in n.getChildrenUnmodifiable():
self.parse(i,depth+1)
elif isinstance(n,control.Accordion):
for i in n.getPanes():
self.parse(i,depth+1)
其他控件可能需要特殊处理程序,并且可以通过仔细检查类heirachy来改进代码。
在这个阶段,我不完全确定如何最好地处理第三方控件包。