自定义控件_新的自定义控件:TaskProgressView

自定义控件

自定义控件

我已经编写了一个新的自定义控件,并将其提交到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

自定义控件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值