从外部进程更新CheckBoxTreeCell样式

时间:2014-09-09 14:06:51

标签: javafx treeview

我正在构建我的第一个javafx(2.2)应用程序。用户通过选择树视图中的复选框来选择要执行的任务数。

我试图找出在任务完成后如何更改相关TreeCell的样式。

public class WorkbenchSscce extends Application {

    public static void main(String...args) {

        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {

        final CheckBoxTreeItem<String> rootNode = new CheckBoxTreeItem<>("parent");
        final CheckBoxTreeItem<String> taskOne = new CheckBoxTreeItem<>("task one");
        final CheckBoxTreeItem<String> taskTwo = new CheckBoxTreeItem<>("task two");
        rootNode.getChildren().addAll(taskOne, taskTwo);

        TreeView<String> treeView = new TreeView<>(rootNode);
        treeView.setEditable(true);
        treeView.setCellFactory(CheckBoxTreeCell.<String>forTreeView());
        treeView.setShowRoot(false);

        Button executeButton = new Button("Execute");
        executeButton.setOnMouseClicked(new EventHandler<MouseEvent>() {

            @Override
            public void handle(MouseEvent mouseEvent) {

                if (taskOne.isSelected()) {
                    executeTask(1);
                    /**
                     * ?????
                     * give the TreeCell for taskOne a green background, to indicate it is complete
                     * ?????
                     */
                }
                if (taskTwo.isSelected()) {
                    executeTask(2);
                    /**
                     * ?????
                     * give the TreeCell for taskTwo a green background, to indicate it is complete
                     * ?????
                     */
                }
            }
        });
        VBox box = new VBox();
        box.getChildren().addAll(treeView, executeButton);
        Scene scene = new Scene(box);
        stage.setScene(scene);
        stage.show();
    }

    public void executeTask(int input) {

        // do something
    }
}

我可以看到如何在创建时设置CheckBoxTreeCells的样式。

我看到当用户事件发生在TreeView(使用EventListeners)时如何更改样式。

但是当事件源在应用程序内部时,我无法看到如何设置树形单元的样式。请参阅上面的MouseEvent处理程序中的注释。

1 个答案:

答案 0 :(得分:0)

关键是要观察Task的状态(我在这个例子中使用了Service而不是Task,因为它可以从细胞工厂运行多次) 。为此,您需要TreeItem的数据类型是具有表示任务/服务当前状态的可观察属性的东西。如果可以,最简单的方法是将TreeItem的数据类型设为Task本身(从概念上讲,您的TreeView正在显示Task s )。

这有点微妙,因为给定单元格所代表的项目(即任务)可能会发生变化。在这个例子中,我只观察单元格的item属性,删除一个侦听器,该侦听器从单元格不再表示的项目中观察任务的状态,并将侦听器添加到它现在表示的项目中。如果您使用EasyBind框架(以及它需要的Java 8),您可以稍微清理一下,执行类似

的操作
EasyBind.select(cell.itemProperty())
    .selectObject(Service::stateProperty)
    .addListener((ov, oldState, newState) -> updateCell(cell) );

完整示例(使用JavaFX 2.2,虽然我是在Java 8下编译的,但是有些Java 8的功能可能已经悄悄进入):

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;

import javafx.application.Application;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ReadOnlyStringProperty;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker.State;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.control.cell.CheckBoxTreeCell;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
import javafx.util.StringConverter;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {

        final BorderPane root = new BorderPane();
        final TreeView<SelectableService> tree = new TreeView<>();
        final TreeItem<SelectableService> treeRoot = new TreeItem<>(new SelectableService("Parent"));
        for (int i=1; i<=10; i++) {
            treeRoot.getChildren().add(new TreeItem<>(new SelectableService("Task "+i)));
        }
        tree.setRoot(treeRoot);

        final Button startButton = new Button("Start selected tasks");
        startButton.setOnAction(new EventHandler<ActionEvent>() {

            @Override
            public void handle(ActionEvent event) {
                for (SelectableService service : findSelectedTasks(treeRoot)) {
                    service.restart();
                }

            }
        });

        final HBox controls = new HBox(5);
        controls.getChildren().add(startButton);
        controls.setPadding(new Insets(10));
        controls.setAlignment(Pos.CENTER);

        root.setCenter(tree);
        root.setBottom(controls);

        tree.setCellFactory(new Callback<TreeView<SelectableService>, TreeCell<SelectableService>>() {

            @Override
            public TreeCell<SelectableService> call(TreeView<SelectableService> param) {
                return createCell();
            }

        });

        Scene scene = new Scene(root, 400, 600);

        scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private CheckBoxTreeCell<SelectableService> createCell() {

        // CheckBoxTreeCell whose check box state is mapped to the selected property of the task:

        final CheckBoxTreeCell<SelectableService> cell = new CheckBoxTreeCell<SelectableService>(new Callback<TreeItem<SelectableService>, ObservableValue<Boolean>>() {
            @Override
            public ObservableValue<Boolean> call(TreeItem<SelectableService> treeItem) {
                SelectableService task = treeItem.getValue();
                if (task != null) {
                    return task.selectedProperty();
                } else {
                    return null ;
                }
            }
        });

        final ChangeListener<State> taskStateListener = new ChangeListener<State>() {

            @Override
            public void changed(
                    ObservableValue<? extends State> observable,
                    State oldValue, State newValue) {
                updateCell(cell);
            }

        };

        cell.itemProperty().addListener(new ChangeListener<SelectableService>() {

            @Override
            public void changed(
                    ObservableValue<? extends SelectableService> observable,
                    SelectableService oldTask, SelectableService newTask) {

                if (oldTask != null) {
                    oldTask.stateProperty().removeListener(taskStateListener);
                }

                if (newTask != null) {
                    newTask.stateProperty().addListener(taskStateListener);
                }

                updateCell(cell);
            }

        });

        cell.setConverter(new StringConverter<TreeItem<SelectableService>>() {

            @Override
            public String toString(TreeItem<SelectableService> treeItem) {
                SelectableService task = treeItem.getValue();
                if (task == null) {
                    return null ;
                } else {
                    return task.getName();
                }
            }

            @Override
            public TreeItem<SelectableService> fromString(String string) {
                // Not supported
                throw new UnsupportedOperationException("Uneditable tree cell does not create SelectableTasks");
            }

        });

        return cell;
    }

    private void updateCell(CheckBoxTreeCell<SelectableService> cell) {

        cell.getStyleClass().removeAll(Arrays.asList("running", "finished", "failed"));

        SelectableService task = cell.getItem();
        if (task != null) {
            State state = task.getState();

            // Update style class:
            if (state == State.RUNNING) {
                cell.getStyleClass().add("running");
            } else if (state == State.SUCCEEDED) {
                cell.getStyleClass().add("finished");
            } else if (state == State.FAILED){
                cell.getStyleClass().add("failed");
            }
        }

    }

    private Set<SelectableService> findSelectedTasks(TreeItem<SelectableService> treeItem) {
        Set<SelectableService> selectedTasks = new HashSet<>();
        addTaskAndChildTasksIfSelected(selectedTasks, treeItem) ;
        return selectedTasks ;
    }

    private void addTaskAndChildTasksIfSelected(Set<SelectableService> selectedTasks, TreeItem<SelectableService> treeItem) {
        SelectableService task = treeItem.getValue();
        if (task != null && task.isSelected()) {
            selectedTasks.add(task);
        }
        for (TreeItem<SelectableService> child : treeItem.getChildren()) {
            addTaskAndChildTasksIfSelected(selectedTasks, child);
        }
    }

    public static class SelectableService extends Service<Void> {
        private final BooleanProperty selected = new SimpleBooleanProperty(this, "selected", false);

        public final BooleanProperty selectedProperty() {
            return this.selected;
        }

        public final boolean isSelected() {
            return this.selectedProperty().get();
        }

        public final void setSelected(final boolean selected) {
            this.selectedProperty().set(selected);
        }

        private final ReadOnlyStringWrapper name = new ReadOnlyStringWrapper(this, "name");

        private final void setName(String name) {
            this.name.set(name);
        }

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

        public final ReadOnlyStringProperty nameProperty() {
            return name.getReadOnlyProperty();
        }

        public SelectableService(String name) {
            setExecutor(Executors.newCachedThreadPool(new ThreadFactory() {
                @Override
                public Thread newThread(Runnable r) {
                    Thread t = new Thread(r);
                    t.setDaemon(true);
                    return t ;
                }
            }));
            setName(name);
        }

        @Override
        public Task<Void> createTask()  {

            return new Task<Void>() {
                @Override
                public Void call() throws Exception {
                    // just a mock task: pauses for a random time, then throws an exception with
                    // probability 0.25
                    Random rng = new Random();
                    Thread.sleep(2000 + rng.nextInt(2000));
                    if (rng.nextDouble() < 0.25) {
                        throw new Exception("Task failed");
                    }
                    return null ;                   
                }
            };

        }
    }

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

application.css就是

.finished {
    -fx-background: green ;
}
.failed {
    -fx-background: red ;
}
.running {
    -fx-background: yellow ;
}

顺便说一句,这在Java 8中相当清晰,但是由于你发布了JavaFX 2.2风格的代码,我认为你还在使用旧版本。 Java 8还允许你为css样式使用伪类,这样做更好一些(并且通常具有更好的性能,尽管这里有一点没有意义)。