繁体   English   中英

JavaFX:在TreeTable中添加CheckBoxTreeItem吗?

[英]JavaFX: Add CheckBoxTreeItem in TreeTable?

我正在尝试JavaFX,并尝试在树表中添加一个复选框Item,但看起来它仅支持简单的树项。

我的代码是Oracle TreeTableView示例的修改版本:

 public class TreeTableViewSample extends Application implements Runnable {

List<Employee> employees = Arrays.<Employee>asList(
        new Employee("Ethan Williams", 30.0),
        new Employee("Emma Jones", 10.0),
        new Employee("Michael Brown", 70.0),
        new Employee("Anna Black", 50.0),
        new Employee("Rodger York", 20.0),
        new Employee("Susan Collins", 70.0));

/*  private final ImageView depIcon = new ImageView (
 new Image(getClass().getResourceAsStream("department.png"))
 );
 */
final CheckBoxTreeItem<Employee> root
        = new CheckBoxTreeItem<>(new Employee("Sales Department", 0.0));
final CheckBoxTreeItem<Employee> root2
        = new CheckBoxTreeItem<>(new Employee("Departments", 0.0));

public static void main(String[] args) {
    Application.launch(TreeTableViewSample.class, args);
}

@Override
public void start(Stage stage) {
    root.setExpanded(true);
    employees.stream().forEach((employee) -> {
        root.getChildren().add(new CheckBoxTreeItem<>(employee));
    });
    stage.setTitle("Tree Table View Sample");
    final Scene scene = new Scene(new Group(), 400, 400);
    scene.setFill(Color.LIGHTGRAY);
    Group sceneRoot = (Group) scene.getRoot();

    TreeTableColumn<Employee, String> empColumn
            = new TreeTableColumn<>("Employee");
    empColumn.setPrefWidth(150);
    empColumn.setCellValueFactory(
            (TreeTableColumn.CellDataFeatures<Employee, String> param)
            -> new ReadOnlyStringWrapper(param.getValue().getValue().getName())
    );

    TreeTableColumn<Employee, Double> salaryColumn
            = new TreeTableColumn<>("Salary");
    salaryColumn.setPrefWidth(190);
    /*   salaryColumn.setCellValueFactory(
     (TreeTableColumn.CellDataFeatures<Employee, String> param) -> 
     new ReadOnlyDoubleWrapper(param.getValue().getValue().getEmail())
     );
     */
    salaryColumn.setCellFactory(ProgressBarTreeTableCell.<Employee>forTreeTableColumn());
    root2.getChildren().add(root);

    TreeTableView<Employee> treeTableView = new TreeTableView<>(root2);
    treeTableView.getColumns().setAll(empColumn, salaryColumn);
    sceneRoot.getChildren().add(treeTableView);
    stage.setScene(scene);
    stage.show();
    ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
    executorService.scheduleAtFixedRate(this, 3, 10, TimeUnit.SECONDS);

}

@Override
public void run() {
    root2.getValue().setSalary(calcSalary(root));
}

public double calcSalary(TreeItem<Employee> t) {
    Double salary = 0.0;
    if (!t.isLeaf()) {

        ObservableList<TreeItem<Employee>> al = t.getChildren();
        for (int i = 0; i < al.size(); i++) {
            TreeItem<Employee> get = al.get(i);
            salary += calcSalary(get);
        }
        t.getValue().setSalary(salary);
    }
    return salary += t.getValue().getSalary();
}

public class Employee {

    private SimpleStringProperty name;
    private SimpleDoubleProperty salary;

    public SimpleStringProperty nameProperty() {
        if (name == null) {
            name = new SimpleStringProperty(this, "name");
        }
        return name;
    }

    public SimpleDoubleProperty salaryProperty() {
        if (salary == null) {
            salary = new SimpleDoubleProperty(this, "salary");
        }
        return salary;
    }

    private Employee(String name, Double salary) {
        this.name = new SimpleStringProperty(name);
        this.salary = new SimpleDoubleProperty(salary);
    }

    public String getName() {
        return name.get();
    }

    public void setName(String fName) {
        name.set(fName);
    }

    public Double getSalary() {
        return salary.get();
    }

    public void setSalary(Double fName) {
        salary.set(fName);
    }
}
}

在上述示例中,有什么方法可以将复选框用于树项目? 我正在使用JavaFx 8。

我还尝试创建薪金条,也可以将其用作任务及其子任务的进度条。 (只需玩UI)。 但是不知道如何将它们与员工的真实价值联系起来,因为我猜普通的表视图不同于树形表视图。 谢谢 ! :)

没有对应于CheckBoxTreeCell的单元格实现:这是一个具有CheckBox的单元格,该单元格绑定到CheckBoxTreeItem的selected / indeterminate属性。 明显的对应CheckBoxTreeTableCell只是具有复选框的单元格,绑定到单元格数据。

需要的是CheckBoxTreeTableRow:这是有权访问TreeItem并可以管理checkBox和treeItem之间的绑定的单元层。 以下是CheckBoxTreeCell的快速​​实现,简化和调整后的副本。 取消绑定/绑定在updateItem中处理。

更新 :清洁溶液(冗长!)

看起来TableRowSkinBase准备处理自定义行图形,它有一个graphicsProperty()方法,该方法用于行皮肤内的所有布局代码。

/**
 * Returns the graphic to draw on the inside of the disclosure node. Null
 * is acceptable when no graphic should be shown. Commonly this is the
 * graphic associated with a TreeItem (i.e. treeItem.getGraphic()), rather
 * than a graphic associated with a cell.
 */
protected abstract ObjectProperty<Node> graphicProperty();

TreeTableRowSkin实现它来返回TreeItem的图形,因此重写以返回tableRow的图形应该可以正常工作。 除非...不是-布局歪斜,如下面的原始问题解答所述。 挖掘发现了罪魁祸首:TreeTableCellSkin硬编码自己的布局代码,以考虑其padding中的任何图形,从而... treeItem的图形。

因此需要一个完整的解决方案

  • 一个不对treeItem图形进行硬编码的Tree / TableCellSkin(下面的示例仍然不是很干净,它依赖于super添加了图形宽度并再次减去它)
  • 安装增强外观的Tree / TableCell
  • 一个Tree / TableRowSkin,可根据需要覆盖graphicsProperty,在下面返回行图形
  • 一个Tree / TableRow,可根据需要更新其图形,在下面将其图形设置为一个复选框,该复选框又可能包含treeItem的图形

第一对名为DefaultTreeTableCell / Skin,第二对名为CheckBoxTreeTableRow / Skin。

用法(要插入OP的代码段示例)

// just for fun, have root items with some graphic
final CheckBoxTreeItem<Employee> root = new CheckBoxTreeItem<>(
        new Employee("Sales Department", 0.0), new Circle(10, Color.RED));
final CheckBoxTreeItem<Employee> root2 = new CheckBoxTreeItem<>(
        new Employee("Departments", 0.0), new Circle(10, Color.BLUE));

// configure treeTableView to use the extended tableRow 
treeTableView.setRowFactory(item -> new CheckBoxTreeTableRow<>());

// configure table columns to use the extended table cell
empColumn.setCellFactory(p -> new DefaultTreeTableCell<>());
// all cell types must have a skin that copes with row graphics
salaryColumn.setCellFactory(e -> {
    TreeTableCell cell = new ProgressBarTreeTableCell() {

        @Override
        protected Skin<?> createDefaultSkin() {
            return new DefaultTreeTableCell.DefaultTreeTableCellSkin<>(this);
        }

    };
    return cell;
});

单元/行实现:

/**
 * TreeTableCell actually showing something. This is copied from TreeTableColumn plus
 * installs DefaultTreeTableCellSkin which handles row graphic width.
 */
public class DefaultTreeTableCell<S, T> extends TreeTableCell<S, T> {

    @Override
    protected void updateItem(T item, boolean empty) {
        if (item == getItem()) return;

        super.updateItem(item, empty);

        if (item == null) {
            super.setText(null);
            super.setGraphic(null);
        } else if (item instanceof Node) {
            super.setText(null);
            super.setGraphic((Node)item);
        } else {
            super.setText(item.toString());
            super.setGraphic(null);
        }
    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new DefaultTreeTableCellSkin<>(this);
    }

    /**
     * TreeTableCellSkin that handles row graphic in its leftPadding, if
     * it is in the treeColumn of the associated TreeTableView.
     * <p>
     * It assumes that per-row graphics - including the graphic of the TreeItem, if any -
     * is folded into the TreeTableRow graphic and patches its leftLabelPadding
     * to account for the graphic width.
     * <p>
     * 
     * Note: TableRowSkinBase seems to be designed to cope with variations of row 
     * graphic - it has a method <code>graphicProperty()</code> that's always used
     * internally when calculating offsets in the treeColumn.
     * Subclasses override as needed, the layout code remains constant. The real 
     * problem is the TreeTableCell hard-codes the TreeItem as the only graphic
     * owner. 
     *  
     */
    public static class DefaultTreeTableCellSkin<S, T> extends TreeTableCellSkin<S, T> {

        /**
         * @param treeTableCell
         */
        public DefaultTreeTableCellSkin(TreeTableCell<S, T> treeTableCell) {
            super(treeTableCell);
        }

        /**
         * Overridden to adjust the padding returned by super for row graphic.
         */
        @Override
        protected double leftLabelPadding() {
            double padding = super.leftLabelPadding();
            padding += getRowGraphicPatch();
            return padding;
        }

        /**
         * Returns the patch for leftPadding if the tableRow has a graphic of
         * its own.<p>
         * 
         * Note: this implemenation is a bit whacky as it relies on super's 
         * handling of treeItems graphics offset. A cleaner 
         * implementation would override leftLabelPadding from scratch.
         * <p>
         * PENDING JW: doooooo it!
         * 
         * @return
         */
        protected double getRowGraphicPatch() {
            if (!isTreeColumn()) return 0;
            Node graphic = getSkinnable().getTreeTableRow().getGraphic();
            if (graphic != null) {
                double height = getCellSize();
                // start with row's graphic
                double patch = graphic.prefWidth(height);
                // correct for super's having added treeItem's graphic
                TreeItem<S> item = getSkinnable().getTreeTableRow().getTreeItem();
                if (item.getGraphic() != null) {
                    double correct = item.getGraphic().prefWidth(height);
                    patch -= correct;
                }
                return patch;
            }
            return 0;
        }

        /**
         * Checks and returns whether our cell is attached to a treeTableView/column
         * and actually has a TreeItem.
         * @return
         */
        protected boolean isTreeColumn() {
            if (getSkinnable().isEmpty()) return false;
            TreeTableColumn<S, T> column = getSkinnable().getTableColumn();
            TreeTableView<S> view = getSkinnable().getTreeTableView();
            if (column.equals(view.getTreeColumn())) return true;
            return view.getVisibleLeafColumns().indexOf(column) == 0;
        }

    }

}

/**
 * Support custom graphic for Tree/TableRow. Here in particular a checkBox.
 * http://stackoverflow.com/q/29300551/203657
 * <p>
 * Basic idea: implement custom TreeTableRow that set's its graphic to the 
 * graphic/checkBox. Doesn't work: layout is broken, graphic appears
 * over the text. All fine if we set the graphic to the TreeItem that's
 * shown. Possible as long as the treeItem doesn't have a graphic of
 * its own.
 * <p>
 * Basic problem:
 * <li> TableRowSkinBase seems to be able to cope: has protected method
 *   graphicsProperty that should be implemented to return the graphic 
 *   if any. That graphic is added to the children list and sized/located
 *   in layoutChildren. 
 * <li> are added the graphic/disclosureNode as needed before
 *   calling super.layoutChildren,  
 * <li> graphic/disclosure are placed inside the leftPadding of the tableCell
 *   that is the treeColumn
 * <li> TreeTableCellSkin must cooperate in taking into account the graphic/disclosure 
 *   when calculating its leftPadding
 * <li> cellSkin is hard-coded to use the TreeItem's graphic (vs. the rowCell's)   
 *
 * PENDING JW: 
 * <li>- would expect to not alter the scenegraph during layout (might lead to
 *   endless loops or not) but done frequently in core code   
 * <p> 
 *  
 * Outline of the solution as implemented:
 * <li> need a TreeTableCell with a custom skin
 * <li> override leftPadding in skin to add row graphic if available
 * <li> need CheckBoxTreeTableRow that sets its graphic to checkBox (or a combination
 *   of checkBox and treeItem's)
 * <li> need custom rowSkin that implements graphicProperty to return the row graphic  
 *    
 * @author Jeanette Winzenburg, Berlin
 * 
 * @see DefaultTreeTableCell
 * @see DefaultTreeTableCellSkin
 * 
 */
public class CheckBoxTreeTableRow<T> extends TreeTableRow<T> {

    private CheckBox checkBox;

    private ObservableValue<Boolean> booleanProperty;

    private BooleanProperty indeterminateProperty;

    public CheckBoxTreeTableRow() {
        this(item -> {
            if (item instanceof CheckBoxTreeItem<?>) {
                return ((CheckBoxTreeItem<?>)item).selectedProperty();
            }
            return null;
        });
    }

    public CheckBoxTreeTableRow(
            final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedProperty) {
        this.getStyleClass().add("check-box-tree-cell");
        setSelectedStateCallback(getSelectedProperty);
        checkBox = new CheckBox();
        checkBox.setAlignment(Pos.TOP_LEFT);
    }

    // --- selected state callback property
    private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> 
            selectedStateCallback = 
            new SimpleObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>(
            this, "selectedStateCallback");

    /**
     * Property representing the {@link Callback} that is bound to by the 
     * CheckBox shown on screen.
     */
    public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 
        return selectedStateCallback; 
    }

    /** 
     * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
     */
    public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) { 
        selectedStateCallbackProperty().set(value); 
    }

    /**
     * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
     */
    public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() { 
        return selectedStateCallbackProperty().get(); 
    }

    /** {@inheritDoc} */
    @Override 
    protected void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
            TreeItem<T> treeItem = getTreeItem();
            checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic());
            setGraphic(checkBox);
            // uninstall bindings
            if (booleanProperty != null) {
                checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
            }
            if (indeterminateProperty != null) {
                checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty);
            }

            // install new bindings.
            // this can only handle TreeItems of type CheckBoxTreeItem
            if (treeItem instanceof CheckBoxTreeItem) {
                CheckBoxTreeItem<T> cbti = (CheckBoxTreeItem<T>) treeItem;
                booleanProperty = cbti.selectedProperty();
                checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);

                indeterminateProperty = cbti.indeterminateProperty();
                checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty);
            } else {
                throw new IllegalStateException("item must be CheckBoxTreeItem");
            }
        }

    }

    @Override
    protected Skin<?> createDefaultSkin() {
        return new CheckBoxTreeTableRowSkin<>(this);
    }

    public static class CheckBoxTreeTableRowSkin<S> extends TreeTableRowSkin<S> {
        protected ObjectProperty<Node> checkGraphic;

        /**
         * @param control
         */
        public CheckBoxTreeTableRowSkin(TreeTableRow<S> control) {
            super(control);
        }

        /**
         * Note: this is implicitly called from the constructor of LabeledSkinBase.
         * At that time, checkGraphic is not yet instantiated. So we do it here,
         * still having to create it at least twice. That'll be a problem if 
         * anybody would listen to changes ...
         */
        @Override
        protected ObjectProperty<Node> graphicProperty() {
            if (checkGraphic == null) {
                checkGraphic = new SimpleObjectProperty<Node>(this, "checkGraphic");
            }
            CheckBoxTreeTableRow<S> treeTableRow = getTableRow();
            if (treeTableRow.getTreeItem() == null) {
                checkGraphic.set(null);   
            } else {
                checkGraphic.set(treeTableRow.getGraphic());
            }
            return checkGraphic;
        }

        protected CheckBoxTreeTableRow<S> getTableRow() {
            return (CheckBoxTreeTableRow<S>) super.getSkinnable();
        }
    }

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(CheckBoxTreeTableRow.class.getName());
}


原始答案:hack!

里面有一段坚果代码:

/**
 * @author Jeanette Winzenburg, Berlin
 */
public class CheckBoxTreeTableRowHack<T> extends TreeTableRow<T> {

    private CheckBox checkBox;

    private ObservableValue<Boolean> booleanProperty;

    private BooleanProperty indeterminateProperty;

    public CheckBoxTreeTableRowHack() {
        setSelectedStateCallback(item -> {
            if (item instanceof CheckBoxTreeItem<?>) {
                return ((CheckBoxTreeItem<?>)item).selectedProperty();
            }
            return null;
        });
        this.checkBox = new CheckBox();
        // something weird going on with layout
        checkBox.setAlignment(Pos.TOP_LEFT);
    }

    // --- selected state callback property
    private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> 
            selectedStateCallback = 
            new SimpleObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>(
            this, "selectedStateCallback");

    /**
     * Property representing the {@link Callback} that is bound to by the 
     * CheckBox shown on screen.
     */
    public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() { 
        return selectedStateCallback; 
    }

    /** 
     * Sets the {@link Callback} that is bound to by the CheckBox shown on screen.
     */
    public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) { 
        selectedStateCallbackProperty().set(value); 
    }

    /**
     * Returns the {@link Callback} that is bound to by the CheckBox shown on screen.
     */
    public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() { 
        return selectedStateCallbackProperty().get(); 
    }

    /** {@inheritDoc} */
    @Override 
    public void updateItem(T item, boolean empty) {
        super.updateItem(item, empty);

        if (empty) {
            setText(null);
            setGraphic(null);
        } else {
//            
            TreeItem<T> treeItem = getTreeItem();
            // PENDING JW: this is nuts but working ..  certainly will pose problems
            // when re-using the cell
            treeItem.setGraphic(checkBox);
            // this is what CheckBoxTreeCell does, setting the graphic
            // of the tableRow confuses the layout
//            checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic());
//            setGraphic(checkBox);

            // uninstall bindings
            if (booleanProperty != null) {
                checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty);
            }
            if (indeterminateProperty != null) {
                checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty);
            }

            // install new bindings.
            // We special case things when the TreeItem is a CheckBoxTreeItem
            if (treeItem instanceof CheckBoxTreeItem) {
                CheckBoxTreeItem<T> cbti = (CheckBoxTreeItem<T>) treeItem;
                booleanProperty = cbti.selectedProperty();
                checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty);

                indeterminateProperty = cbti.indeterminateProperty();
                checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty);
            } else {
                throw new IllegalStateException("item must be CheckBoxTreeItem");
            }
        }

    }
}

// usage: in the example add
treeTableView.setRowFactory(f -> new CheckBoxTreeTableRowHack<>());

这确实很怪诞,最终可能会造成严重破坏-这是围绕TreeTableRowSkin中的布局故障造成的破坏,由于某种原因(我无法挖掘),无法将图形集定位到单元中。 无法使其在自定义CheckBoxTreeTableRowSkin中运行,该CheckBoxTreeTableRowSkin直接在其graphicProperty()中返回checkBox-因此,现在我们开始讨论一下。

 /** * @author Jeanette Winzenburg, Berlin */ public class CheckBoxTreeTableRowHack<T> extends TreeTableRow<T> { private CheckBox checkBox; private ObservableValue<Boolean> booleanProperty; private BooleanProperty indeterminateProperty; public CheckBoxTreeTableRowHack() { setSelectedStateCallback(item -> { if (item instanceof CheckBoxTreeItem<?>) { return ((CheckBoxTreeItem<?>)item).selectedProperty(); } return null; }); this.checkBox = new CheckBox(); // something weird going on with layout checkBox.setAlignment(Pos.TOP_LEFT); } // --- selected state callback property private ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallback = new SimpleObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>>( this, "selectedStateCallback"); /** * Property representing the {@link Callback} that is bound to by the * CheckBox shown on screen. */ public final ObjectProperty<Callback<TreeItem<T>, ObservableValue<Boolean>>> selectedStateCallbackProperty() { return selectedStateCallback; } /** * Sets the {@link Callback} that is bound to by the CheckBox shown on screen. */ public final void setSelectedStateCallback(Callback<TreeItem<T>, ObservableValue<Boolean>> value) { selectedStateCallbackProperty().set(value); } /** * Returns the {@link Callback} that is bound to by the CheckBox shown on screen. */ public final Callback<TreeItem<T>, ObservableValue<Boolean>> getSelectedStateCallback() { return selectedStateCallbackProperty().get(); } /** {@inheritDoc} */ @Override public void updateItem(T item, boolean empty) { super.updateItem(item, empty); if (empty) { setText(null); setGraphic(null); } else { // TreeItem<T> treeItem = getTreeItem(); // PENDING JW: this is nuts but working .. certainly will pose problems // when re-using the cell treeItem.setGraphic(checkBox); // this is what CheckBoxTreeCell does, setting the graphic // of the tableRow confuses the layout // checkBox.setGraphic(treeItem == null ? null : treeItem.getGraphic()); // setGraphic(checkBox); // uninstall bindings if (booleanProperty != null) { checkBox.selectedProperty().unbindBidirectional((BooleanProperty)booleanProperty); } if (indeterminateProperty != null) { checkBox.indeterminateProperty().unbindBidirectional(indeterminateProperty); } // install new bindings. // We special case things when the TreeItem is a CheckBoxTreeItem if (treeItem instanceof CheckBoxTreeItem) { CheckBoxTreeItem<T> cbti = (CheckBoxTreeItem<T>) treeItem; booleanProperty = cbti.selectedProperty(); checkBox.selectedProperty().bindBidirectional((BooleanProperty)booleanProperty); indeterminateProperty = cbti.indeterminateProperty(); checkBox.indeterminateProperty().bindBidirectional(indeterminateProperty); } else { throw new IllegalStateException("item must be CheckBoxTreeItem"); } } } } // usage: in the example add treeTableView.setRowFactory(f -> new CheckBoxTreeTableRowHack<>()); 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM