我已经编写了一个新的自定义控件,并将其提交到ControlsFX项目。 这是一个高度专业的控件,用于显示后台任务,其当前状态和进度的列表。 这实际上是我为ControlsFX编写的第一个控件,只是出于乐趣的考虑,这意味着我自己没有用例(但是肯定会有一个用例)。 下面的屏幕快照显示了正在使用的控件。
如果您已经熟悉javafx.concurrent.Task类,您将很快掌握该控件显示其title,message和progress属性的值。 但它还会显示一个图标,该图标未包含在Task API中。 我添加了一个可选的图形工厂(回调),将为每个任务调用该图形工厂以查找将放置在表示该任务的列表视图单元格左侧的图形节点。
可以在此处找到显示控件正在运行的视频:
控制
由于此控件非常简单,因此我认为有必要为其发布完整的源代码,以便其他人可以学习使用。 下面的清单显示了控件本身的代码。 如预期的那样,它扩展了Control类,并为监视的任务提供了一个可观察的列表,并为图形工厂(回调)提供了一个对象属性。
package org.controlsfx.control;
import impl.org.controlsfx.skin.TaskProgressViewSkin;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.concurrent.WorkerStateEvent;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.control.Control;
import javafx.scene.control.Skin;
import javafx.util.Callback;
/**
* The task progress view is used to visualize the progress of long running
* tasks. These tasks are created via the {@link Task} class. This view
* manages a list of such tasks and displays each one of them with their
* name, progress, and update messages.<p>
* An optional graphic factory can be set to place a graphic in each row.
* This allows the user to more easily distinguish between different types
* of tasks.
*
* <h3>Screenshots</h3>
* The picture below shows the default appearance of the task progress view
* control:
* <center><img src="task-monitor.png" /></center>
*
* <h3>Code Sample</h3>
*
* <pre>
* TaskProgressView<MyTask> view = new TaskProgressView<>();
* view.setGraphicFactory(task -> return new ImageView("db-access.png"));
* view.getTasks().add(new MyTask());
* </pre>
*/
public class TaskProgressView<T extends Task<?>> extends Control {
/**
* Constructs a new task progress view.
*/
public TaskProgressView() {
getStyleClass().add("task-progress-view");
EventHandler<WorkerStateEvent> taskHandler = evt -> {
if (evt.getEventType().equals(
WorkerStateEvent.WORKER_STATE_SUCCEEDED)
|| evt.getEventType().equals(
WorkerStateEvent.WORKER_STATE_CANCELLED)
|| evt.getEventType().equals(
WorkerStateEvent.WORKER_STATE_FAILED)) {
getTasks().remove(evt.getSource());
}
};
getTasks().addListener(new ListChangeListener<Task<?>>() {
@Override
public void onChanged(Change<? extends Task<?>> c) {
while (c.next()) {
if (c.wasAdded()) {
for (Task<?> task : c.getAddedSubList()) {
task.addEventHandler(WorkerStateEvent.ANY,
taskHandler);
}
} else if (c.wasRemoved()) {
for (Task<?> task : c.getAddedSubList()) {
task.removeEventHandler(WorkerStateEvent.ANY,
taskHandler);
}
}
}
}
});
}
@Override
protected Skin<?> createDefaultSkin() {
return new TaskProgressViewSkin<>(this);
}
private final ObservableList<T> tasks = FXCollections
.observableArrayList();
/**
* Returns the list of tasks currently monitored by this view.
*
* @return the monitored tasks
*/
public final ObservableList<T> getTasks() {
return tasks;
}
private ObjectProperty<Callback<T, Node>> graphicFactory;
/**
* Returns the property used to store an optional callback for creating
* custom graphics for each task.
*
* @return the graphic factory property
*/
public final ObjectProperty<Callback<T, Node>> graphicFactoryProperty() {
if (graphicFactory == null) {
graphicFactory = new SimpleObjectProperty<Callback<T, Node>>(
this, "graphicFactory");
}
return graphicFactory;
}
/**
* Returns the value of {@link #graphicFactoryProperty()}.
*
* @return the optional graphic factory
*/
public final Callback<T, Node> getGraphicFactory() {
return graphicFactory == null ? null : graphicFactory.get();
}
/**
* Sets the value of {@link #graphicFactoryProperty()}.
*
* @param factory an optional graphic factory
*/
public final void setGraphicFactory(Callback<T, Node> factory) {
graphicFactoryProperty().set(factory);
}
皮
如您所料,皮肤使用带有自定义单元格工厂的ListView来显示任务。
package impl.org.controlsfx.skin;
import javafx.beans.binding.Bindings;
import javafx.concurrent.Task;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.SkinBase;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import org.controlsfx.control.TaskProgressView;
import com.sun.javafx.css.StyleManager;
public class TaskProgressViewSkin<T extends Task<?>> extends
SkinBase<TaskProgressView<T>> {
static {
StyleManager.getInstance().addUserAgentStylesheet(
TaskProgressView.class
.getResource("taskprogressview.css").toExternalForm()); //$NON-NLS-1$
}
public TaskProgressViewSkin(TaskProgressView<T> monitor) {
super(monitor);
BorderPane borderPane = new BorderPane();
borderPane.getStyleClass().add("box");
// list view
ListView<T> listView = new ListView<>();
listView.setPrefSize(500, 400);
listView.setPlaceholder(new Label("No tasks running"));
listView.setCellFactory(param -> new TaskCell());
listView.setFocusTraversable(false);
Bindings.bindContent(listView.getItems(), monitor.getTasks());
borderPane.setCenter(listView);
getChildren().add(listView);
}
class TaskCell extends ListCell<T> {
private ProgressBar progressBar;
private Label titleText;
private Label messageText;
private Button cancelButton;
private T task;
private BorderPane borderPane;
public TaskCell() {
titleText = new Label();
titleText.getStyleClass().add("task-title");
messageText = new Label();
messageText.getStyleClass().add("task-message");
progressBar = new ProgressBar();
progressBar.setMaxWidth(Double.MAX_VALUE);
progressBar.setMaxHeight(8);
progressBar.getStyleClass().add("task-progress-bar");
cancelButton = new Button("Cancel");
cancelButton.getStyleClass().add("task-cancel-button");
cancelButton.setTooltip(new Tooltip("Cancel Task"));
cancelButton.setOnAction(evt -> {
if (task != null) {
task.cancel();
}
});
VBox vbox = new VBox();
vbox.setSpacing(4);
vbox.getChildren().add(titleText);
vbox.getChildren().add(progressBar);
vbox.getChildren().add(messageText);
BorderPane.setAlignment(cancelButton, Pos.CENTER);
BorderPane.setMargin(cancelButton, new Insets(0, 0, 0, 4));
borderPane = new BorderPane();
borderPane.setCenter(vbox);
borderPane.setRight(cancelButton);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
}
@Override
public void updateIndex(int index) {
super.updateIndex(index);
/*
* I have no idea why this is necessary but it won't work without
* it. Shouldn't the updateItem method be enough?
*/
if (index == -1) {
setGraphic(null);
getStyleClass().setAll("task-list-cell-empty");
}
}
@Override
protected void updateItem(T task, boolean empty) {
super.updateItem(task, empty);
this.task = task;
if (empty || task == null) {
getStyleClass().setAll("task-list-cell-empty");
setGraphic(null);
} else if (task != null) {
getStyleClass().setAll("task-list-cell");
progressBar.progressProperty().bind(task.progressProperty());
titleText.textProperty().bind(task.titleProperty());
messageText.textProperty().bind(task.messageProperty());
cancelButton.disableProperty().bind(
Bindings.not(task.runningProperty()));
Callback<T, Node> factory = getSkinnable().getGraphicFactory();
if (factory != null) {
Node graphic = factory.call(task);
if (graphic != null) {
BorderPane.setAlignment(graphic, Pos.CENTER);
BorderPane.setMargin(graphic, new Insets(0, 4, 0, 0));
borderPane.setLeft(graphic);
}
} else {
/*
* Really needed. The application might have used a graphic
* factory before and then disabled it. In this case the border
* pane might still have an old graphic in the left position.
*/
borderPane.setLeft(null);
}
setGraphic(borderPane);
}
}
}
}
CSS
下面的样式表确保我们为任务标题使用粗体字体,更小/更细的进度条(无圆角),并在底部位置列出具有淡入/淡出分隔线的单元格。
.task-progress-view {
-fx-background-color: white;
}
.task-progress-view > * > .label {
-fx-text-fill: gray;
-fx-font-size: 18.0;
-fx-alignment: center;
-fx-padding: 10.0 0.0 5.0 0.0;
}
.task-progress-view > * > .list-view {
-fx-border-color: transparent;
-fx-background-color: transparent;
}
.task-title {
-fx-font-weight: bold;
}
.task-progress-bar .bar {
-fx-padding: 6px;
-fx-background-radius: 0;
-fx-border-radius: 0;
}
.task-progress-bar .track {
-fx-background-radius: 0;
}
.task-message {
}
.task-list-cell {
-fx-background-color: transparent;
-fx-padding: 4 10 8 10;
-fx-border-color: transparent transparent linear-gradient(from 0.0% 0.0% to 100.0% 100.0%, transparent, rgba(0.0,0.0,0.0,0.2), transparent) transparent;
}
.task-list-cell-empty {
-fx-background-color: transparent;
-fx-border-color: transparent;
}
.task-cancel-button {
-fx-base: red;
-fx-font-size: .75em;
-fx-font-weight: bold;
-fx-padding: 4px;
-fx-border-radius: 0;
-fx-background-radius: 0;
}
翻译自: https://www.javacodegeeks.com/2014/10/new-custom-control-taskprogressview.html