Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions HMCL/src/main/java/com/jfoenix/controls/JFXTreeCell.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package com.jfoenix.controls;

import com.jfoenix.utils.JFXNodeUtils;
import javafx.beans.InvalidationListener;
import javafx.beans.WeakInvalidationListener;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;

import java.lang.ref.WeakReference;

/// JFXTreeCell is simple material design implementation of a tree cell.
///
/// @author Shadi Shaheen
/// @version 1.0
/// @since 2017-02-15
public class JFXTreeCell<T> extends TreeCell<T> {

protected JFXRippler cellRippler = new JFXRippler(this) {
@Override
protected Node getMask() {
Region clip = new Region();
JFXNodeUtils.updateBackground(JFXTreeCell.this.getBackground(), clip);
double width = control.getLayoutBounds().getWidth();
double height = control.getLayoutBounds().getHeight();
clip.resize(width, height);
return clip;
}

@Override
protected void positionControl(Node control) {
// do nothing
}
};
private HBox hbox;
private final StackPane selectedPane = new StackPane();

private final InvalidationListener treeItemGraphicInvalidationListener = observable -> updateDisplay(getItem(),
isEmpty());
private final WeakInvalidationListener weakTreeItemGraphicListener = new WeakInvalidationListener(
treeItemGraphicInvalidationListener);

private WeakReference<TreeItem<T>> treeItemRef;

public JFXTreeCell() {
selectedPane.getStyleClass().add("selection-bar");
selectedPane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectedPane背景色被硬编码为 Color.RED,这会绕过主题/CSS,且与现有 JFoenix 控件普遍通过样式表定制的方式不一致。建议移除代码里的 Background 设置,只保留 style class(例如 selection-bar),并在 CSS 中定义颜色/宽度等样式。

Suggested change
selectedPane.setBackground(new Background(new BackgroundFill(Color.RED, CornerRadii.EMPTY, Insets.EMPTY)));

Copilot uses AI. Check for mistakes.
selectedPane.setPrefWidth(3);
selectedPane.setMouseTransparent(true);
selectedProperty().addListener((o, oldVal, newVal) -> selectedPane.setVisible(newVal ? true : false));
Comment on lines +39 to +72
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JFXTreeCell 没有像其它 JFoenix 单元格/控件那样注册默认的 style class(例如 JFXListCell 会添加 jfx-list-cell)。缺少 style class 会让样式表难以针对 TreeCell 定制。建议添加 DEFAULT_STYLE_CLASS(例如 jfx-tree-cell)并在构造/初始化时 getStyleClass().add(...)

Copilot uses AI. Check for mistakes.

final InvalidationListener treeItemInvalidationListener = observable -> {
TreeItem<T> oldTreeItem = treeItemRef == null ? null : treeItemRef.get();
if (oldTreeItem != null) {
oldTreeItem.graphicProperty().removeListener(weakTreeItemGraphicListener);
}

TreeItem<T> newTreeItem = getTreeItem();
if (newTreeItem != null) {
newTreeItem.graphicProperty().addListener(weakTreeItemGraphicListener);
treeItemRef = new WeakReference<>(newTreeItem);
}
};
final WeakInvalidationListener weakTreeItemListener = new WeakInvalidationListener(treeItemInvalidationListener);
treeItemProperty().addListener(weakTreeItemListener);
if (getTreeItem() != null) {
getTreeItem().graphicProperty().addListener(weakTreeItemGraphicListener);
}
}

@Override
protected void layoutChildren() {
super.layoutChildren();
if (!getChildren().contains(selectedPane)) {
getChildren().add(0, cellRippler);
cellRippler.rippler.clear();
getChildren().add(0, selectedPane);
}
cellRippler.resizeRelocate(0, 0, getWidth(), getHeight());
cellRippler.releaseRipple();
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

layoutChildren() 每次布局都会调用 cellRippler.releaseRipple(),会导致涟漪效果在重布局/滚动时被强制结束,并带来额外动画开销。JFXRippler 已在鼠标释放事件中自动调用 releaseRipple(),建议删除这里的调用(或仅在确有需要时在特定状态变化下调用)。

Suggested change
cellRippler.releaseRipple();

Copilot uses AI. Check for mistakes.
selectedPane.resizeRelocate(0, 0, selectedPane.prefWidth(-1), getHeight());
selectedPane.setVisible(isSelected());
}

private void updateDisplay(T item, boolean empty) {
if (item == null || empty) {
hbox = null;
setText(null);
setGraphic(null);
} else {
TreeItem<T> treeItem = getTreeItem();
if (treeItem != null && treeItem.getGraphic() != null) {
if (item instanceof Node) {
setText(null);
if (hbox == null) {
hbox = new HBox(3);
}
hbox.getChildren().setAll(treeItem.getGraphic(), (Node) item);
setGraphic(hbox);
} else {
hbox = null;
setText(item.toString());
setGraphic(treeItem.getGraphic());
}
} else {
hbox = null;
if (item instanceof Node) {
setText(null);
setGraphic((Node) item);
} else {
setText(item.toString());
setGraphic(null);
}
}
}
}

@Override
protected void updateItem(T item, boolean empty) {
super.updateItem(item, empty);
updateDisplay(item, empty);
setMouseTransparent(item == null || empty);
}
Comment on lines +107 to +145
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里把 item == null 当作空单元格处理(并在 updateItem 里把 cell 设为 mouseTransparent)会破坏“值为 null 但仍是有效条目”的场景:TreeView/ListView 允许集合/TreeItem 的 value 为 null,此时 empty 通常为 false,但该实现会让行无法交互且不显示 TreeItem 的 graphic。建议仅以 empty 判断是否空行,并对 item == null 的有效条目做正常渲染/交互(例如 setMouseTransparent(empty))。

Copilot uses AI. Check for mistakes.
}
48 changes: 48 additions & 0 deletions HMCL/src/main/java/com/jfoenix/controls/JFXTreeView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package com.jfoenix.controls;

import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;

/// JFXTreeView is the material design implementation of a TreeView
/// with expand/collapse animation and selection indicator.
Comment on lines +25 to +26
Copy link

Copilot AI Feb 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

类注释提到“expand/collapse animation”,但当前实现只设置了 cellFactory 和样式类,没有任何展开/收起动画相关逻辑或自定义 Skin。建议要么补齐对应实现(通常需要自定义 TreeViewSkin/TreeCell 行为),要么更新注释以避免误导 API 使用者。

Suggested change
/// JFXTreeView is the material design implementation of a TreeView
/// with expand/collapse animation and selection indicator.
/// JFXTreeView is a material design styled TreeView control
/// that uses JFXTreeCell and a custom style class.

Copilot uses AI. Check for mistakes.
///
/// @author Shadi Shaheen
/// @version 1.0
/// @since 2017-02-15
public class JFXTreeView<T> extends TreeView<T> {

private static final String DEFAULT_STYLE_CLASS = "jfx-tree-view";

public JFXTreeView() {
init();
}

public JFXTreeView(TreeItem<T> root) {
super(root);
init();
}

private void init() {
this.setCellFactory((view) -> new JFXTreeCell<>());
this.getStyleClass().add(DEFAULT_STYLE_CLASS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package com.jfoenix.controls.datamodels.treetable;

import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.control.TreeTableColumn;

/// data model that is used in JFXTreeTableView, it's used to implement
/// the grouping feature.
///
/// **Note:** the data object used in JFXTreeTableView **must** extends this class
///
/// @param <T> is the concrete object of the Tree table
/// @author Shadi Shaheen
/// @version 1.0
/// @since 2016-03-09
public class RecursiveTreeObject<T> {

/// grouped children objects
private ObservableList<T> children = FXCollections.observableArrayList();

public ObservableList<T> getChildren() {
return children;
}

public void setChildren(ObservableList<T> children) {
this.children = children;
}

/// Whether or not the object is grouped by a specified tree table column
ObjectProperty<TreeTableColumn<T, ?>> groupedColumn = new SimpleObjectProperty<>();

public final ObjectProperty<TreeTableColumn<T, ?>> groupedColumnProperty() {
return this.groupedColumn;
}

public final TreeTableColumn<T, ?> getGroupedColumn() {
return this.groupedColumnProperty().get();
}

public final void setGroupedColumn(final TreeTableColumn<T, ?> groupedColumn) {
this.groupedColumnProperty().set(groupedColumn);
}

/// the value that must be shown when grouped
ObjectProperty<Object> groupedValue = new SimpleObjectProperty<>();

public final ObjectProperty<Object> groupedValueProperty() {
return this.groupedValue;
}

public final Object getGroupedValue() {
return this.groupedValueProperty().get();
}

public final void setGroupedValue(final Object groupedValue) {
this.groupedValueProperty().set(groupedValue);
}

}