如何在 TreeView 中禁用或修改 TreeCell 的填充
Posted
技术标签:
【中文标题】如何在 TreeView 中禁用或修改 TreeCell 的填充【英文标题】:How to disable or modify padding of TreeCell in TreeView 【发布时间】:2015-04-18 11:55:27 【问题描述】:环境:JDK 7u75、Windows 8.1 x64、JavaFX2.2
示例代码:
public class TreeViewSample extends Application
public static void main(String[] args)
launch(args);
@Override
public void start(Stage primaryStage) throws MalformedURLException
primaryStage.setTitle("Tree View Sample");
TreeItem<String> rootItem = new TreeItem<String>("RootNode");
for (int i = 1; i < 5; i++)
TreeItem<String> item = new TreeItem<String>("SubNode" + i);
rootItem.getChildren().add(item);
item.getChildren().add(new TreeItem<String>("SubSubNode" + i + "" + i));
rootItem.getChildren().add(new TreeItem<String>("SecondRootNode"));
TreeView<String> tree = new TreeView<String>(rootItem);
StackPane root = new StackPane();
root.getChildren().add(tree);
Scene scene = new Scene(root, 300, 250);
scene.getStylesheets().add((new File("../css/styletest.css").toURI()).toURL().toString());
primaryStage.setScene(scene);
primaryStage.show();
CSS:
.tree-cell
-fx-skin: "com.sun.javafx.scene.control.skin.TreeCellSkin";
-fx-background-color: -fx-control-inner-background;
-fx-padding: 0.25em; /* 3 */
-fx-text-fill: -fx-text-inner-color;
-fx-indent: 10;
.tree-cell .label
-fx-padding: 0.0em 0.0em 0.0em 0.25em; /* 0 0 0 3 */
.tree-cell .tree-disclosure-node
-fx-padding: 4 2 4 8;
-fx-background-color: transparent;
.tree-cell .tree-disclosure-node .arrow
-fx-background-color: -fx-mark-color;
-fx-padding: 0.333333em; /* 4 */
-fx-shape: "M 0 -4 L 8 0 L 0 4 z";
我们需要删除所有节点的所有空格和箭头。 修改后的 CSS:
.tree-cell
-fx-skin: "com.sun.javafx.scene.control.skin.TreeCellSkin";
-fx-background-color: -fx-control-inner-background;
-fx-padding: 0px;
-fx-text-fill: -fx-text-inner-color;
-fx-indent: 0px;
.tree-cell .label
-fx-padding: 0.0em 0.0em 0.0em 0.0em;
.tree-cell .tree-disclosure-node
-fx-padding: 0px;
-fx-background-color: transparent;
.tree-cell .tree-disclosure-node .arrow
-fx-background-color: -fx-mark-color;
-fx-padding: 0.0em;
如您所见,除叶子外的所有节点都没有填充和缩进。
还有一个问题 - 如何删除\修改此填充?
【问题讨论】:
@jmtalarn 这是java,不是html @DarkMental(和 OP)想知道为什么你想要一棵看起来不像树的树? 我认为不可能完全删除叶节点的缩进。查看TreeCellSkin.layoutChildren
的源代码,有一个填充(disclosureWidth
)被考虑在内。此填充是最大公开宽度,如果存在,否则它是硬编码的默认值18
。我相信这可以防止删除左侧的剩余空间,尽管我不完全理解代码。由于在不是叶子时进行的计算,这似乎不会影响非叶子节点。 @kleopatra 你对我的回答提出质疑是对的,因为它确实不起作用。
【参考方案1】:
虽然我非常欣赏上述解决方案,但它太复杂了,尤其是因为它使用反射,并且必须明确允许使用 JPMS 反射。
由于defaultDisclosureWidth
后备值是硬编码的,我们可以通过填充来否定它。
public static class CustomTreeCell extends TreeCell<Foo>
static final PseudoClass LEAF = PseudoClass.getPseudoClass("leaf");
@Override
protected void updateItem(Foo item, boolean empty)
super.updateItem(item, empty);
// ...
pseudoClassStateChanged(LEAF, getTreeItem().isLeaf());
.tree-cell
-fx-background-insets: 0;
-fx-padding: 0;
-fx-indent: 0;
.tree-cell:leaf
-fx-padding: 0 0 0 -18px;
.tree-cell .tree-disclosure-node,
.tree-cell .arrow
-fx-min-width: 0;
-fx-pref-width: 0;
-fx-max-width: 0;
-fx-min-height: 0;
-fx-pref-height: 0;
-fx-max-height: 0;
使用-fx-indent
属性设置您想要的任何填充。
简单验证:
TreeItem<Foo> nodeRoot = new TreeItem<>(new Foo("root"));
TreeItem<Foo> nodeTest1 = new TreeItem<>(new Foo("test1"));
nodeRoot.getChildren().add(nodeTest1);
TreeItem<Foo> nodeTest2 = new TreeItem<>(new Foo("test2"));
nodeTest1.getChildren().add(nodeTest2);
TreeItem<Foo> nodeTest3 = new TreeItem<>(new Foo("test3"));
nodeTest2.getChildren().add(nodeTest3);
TreeItem<Foo> nodeTest4 = new TreeItem<>(new Foo("test4"));
nodeTest3.getChildren().add(nodeTest4);
【讨论】:
【参考方案2】:在这种情况下,JavaFX 开发人员将标准长度设为 18,并且没有提供更改它的机会。这是他们的评论:
/*
* This is rather hacky - but it is a quick workaround to resolve the
* issue that we don't know maximum width of a disclosure node for a given
* TreeView. If we don't know the maximum width, we have no way to ensure
* consistent indentation for a given TreeView.
*
* To work around this, we create a single WeakHashMap to store a max
* disclosureNode width per TreeView. We use WeakHashMap to help prevent
* any memory leaks.
*
* RT-19656 identifies a related issue, which is that we may not provide
* indentation to any TreeItems because we have not yet encountered a cell
* which has a disclosureNode. Once we scroll and encounter one, indentation
* happens in a displeasing way.
*/
但如果真的有必要,那么我们使用反射覆盖 TreeCellSkin 类和 layoutChildren 方法。但这并不安全:
final double defaultDisclosureWidth = maxDisclosureWidthMap.containsKey(tree) ?
maxDisclosureWidthMap.get(tree) : 18; // RT-19656: default width of default disclosure node
开
final double defaultDisclosureWidth = 0;
全班:
public class CustomTreeCellSkin<T> extends TreeCellSkin<T>
public CustomTreeCellSkin(TreeCell control)
super(control);
private boolean disclosureNodeDirty = true;
@Override
protected void layoutChildren(double x, double y, double w, double h)
try
TreeView<T> tree = getSkinnable().getTreeView();
if (tree == null) return;
if (disclosureNodeDirty)
Method method =TreeCellSkin.class.getDeclaredMethod("updateDisclosureNode");
method.setAccessible(true);
method.invoke(this);
disclosureNodeDirty=false;
Node disclosureNode = getSkinnable().getDisclosureNode();
TreeItem<?> treeItem = getSkinnable().getTreeItem();
int level = tree.getTreeItemLevel(treeItem);
if (!tree.isShowRoot()) level--;
double leftMargin = getIndent() * level;
x += leftMargin;
// position the disclosure node so that it is at the proper indent
boolean disclosureVisible = disclosureNode != null && treeItem != null && !treeItem.isLeaf();
Field field = TreeCellSkin.class.getDeclaredField("maxDisclosureWidthMap");
field.setAccessible(true);
Map<TreeView<?>, Double> maxDisclosureWidthMap = (Map<TreeView<?>, Double>) field.get(this);
final double defaultDisclosureWidth = 0; // RT-19656: default width of default disclosure node
double disclosureWidth = defaultDisclosureWidth;
if (disclosureVisible)
if (disclosureNode == null || disclosureNode.getScene() == null)
updateChildren();
if (disclosureNode != null)
disclosureWidth = disclosureNode.prefWidth(h);
if (disclosureWidth > defaultDisclosureWidth)
maxDisclosureWidthMap.put(tree, disclosureWidth);
double ph = disclosureNode.prefHeight(disclosureWidth);
disclosureNode.resize(disclosureWidth, ph);
positionInArea(disclosureNode, x, y,
disclosureWidth, ph, /*baseline ignored*/0,
HPos.CENTER, VPos.CENTER);
// determine starting point of the graphic or cell node, and the
// remaining width available to them
final int padding = treeItem != null && treeItem.getGraphic() == null ? 0 : 3;
x += disclosureWidth + padding;
w -= (leftMargin + disclosureWidth + padding);
// Rather ugly fix for RT-38519, where graphics are disappearing in
// certain circumstances
Node graphic = getSkinnable().getGraphic();
if (graphic != null && !getChildren().contains(graphic))
getChildren().add(graphic);
catch (NoSuchMethodException e)
e.printStackTrace();
catch (IllegalAccessException e)
e.printStackTrace();
catch (InvocationTargetException e)
e.printStackTrace();
catch (NoSuchFieldException e)
e.printStackTrace();
layoutLabelInArea(x, y, w, h);
在这种情况下的css中,你需要指定我们重写的类:
.tree-cell
-fx-skin: "CustomTreeCellSkin";
-fx-background-color: -fx-control-inner-background;
-fx-padding: 0px;
-fx-text-fill: -fx-text-inner-color;
-fx-indent: 0px;
.tree-cell .label
-fx-padding: 0.0em 0.0em 0.0em 0.0em;
.tree-cell .tree-disclosure-node
-fx-padding: 0px;
-fx-background-color: transparent;
.tree-cell .tree-disclosure-node .arrow
-fx-background-color: -fx-mark-color;
-fx-padding: 0.0em;
对不起我的英语。 =)
【讨论】:
不错的技巧 :) 只是挑剔一点:treeItem 可通过单元格公开访问,因此无需反射 @kleopatra 我没有注意到 treeItem。但是 updateDisclosureNode 方法,私有的,仍然必须被调用。 是的,当然 - 其他都是必要的,只是那个不是:) 您可以考虑编辑您的答案并将其替换为直接调用。以上是关于如何在 TreeView 中禁用或修改 TreeCell 的填充的主要内容,如果未能解决你的问题,请参考以下文章
如何在 WinForms 中禁用 TreeView 的节点重命名?
QML:如何在 TreeView 和 TableView 禁用边框?
在 XAML 中的 TreeView 中未选择任何内容时禁用 TreeView 上下文菜单项