-2

我尝试在https://github.com/GSI-CS-CO/chart-fx中结合 Chart-FX和 JavaFX 中的 TableView 合二为一,满足我的要求。目前我已经写了一个demo来实现。但是现在发现当图表中DataSet的点数越来越多的时候,会阻塞UI线程,导致TableView的刷新变得停滞。而且,我的刷新频率是毫秒级的(每5ms刷新一次表格,每秒刷新一次累计数据到图表中)。我尝试了很多方法。首先,平台。runLater 不是一个选项,因为刷新率太高会阻塞 UI 线程。然后我尝试用服务替换runLater,这明显减少了内存使用。但是Chart和TableView同时刷新时阻塞的问题一直没有解决。你能告诉我如何解决这个问题吗?

代码如下:

package de.gsi.chart.samples;

import java.time.ZoneOffset;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import de.gsi.chart.plugins.DataPointTooltip;
import de.gsi.chart.plugins.TableViewer;
import de.gsi.chart.plugins.Zoomer;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import de.gsi.chart.XYChart;
import de.gsi.chart.axes.spi.DefaultNumericAxis;
import de.gsi.chart.axes.spi.format.DefaultTimeFormatter;
import de.gsi.chart.plugins.EditAxis;
import de.gsi.chart.renderer.ErrorStyle;
import de.gsi.chart.renderer.datareduction.DefaultDataReducer;
import de.gsi.chart.renderer.spi.ErrorDataSetRenderer;
import de.gsi.chart.ui.ProfilerInfoBox;
import de.gsi.chart.ui.ProfilerInfoBox.DebugLevel;
import de.gsi.chart.ui.geometry.Side;
import de.gsi.dataset.event.AddedDataEvent;
import de.gsi.dataset.spi.CircularDoubleErrorDataSet;
import de.gsi.dataset.utils.ProcessingProfiler;

/**
 * @author rstein
 */
public class RollingBufferSample extends Application {
    private static final Logger LOGGER = LoggerFactory.getLogger(RollingBufferSample.class);
    public static final int DEBUG_UPDATE_RATE = 5000;
    // 0: just drop points that are drawn on the same pixel '3' points need to be at least 3 pixel apart to be drawn
    protected static final int MIN_PIXEL_DISTANCE = 0;
    public static int N_SAMPLES = 30; // default: 1000000
    public static int UPDATE_PERIOD = 1000; // [ms]
    public static int BUFFER_CAPACITY = 50000; // 750 samples @ 25 Hz <-> 30 s
    public final CircularDoubleErrorDataSet rollingBufferDipoleCurrent = new CircularDoubleErrorDataSet(
            "dipole current [A]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-1]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity2 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-2]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity3 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-3]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity4 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-4]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity5 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-5]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity6 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-6]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity7 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-7]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity8 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-8]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity9 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-9]", RollingBufferSample.BUFFER_CAPACITY);
    public final CircularDoubleErrorDataSet rollingBufferBeamIntensity10 = new CircularDoubleErrorDataSet(
            "beam intensity [ppp-10]", RollingBufferSample.BUFFER_CAPACITY);
    private final ErrorDataSetRenderer beamIntensityRenderer = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer2 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer3 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer4 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer5 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer6 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer7 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer8 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer9 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer beamIntensityRenderer10 = new ErrorDataSetRenderer();
    private final ErrorDataSetRenderer dipoleCurrentRenderer = new ErrorDataSetRenderer();
    private final DefaultNumericAxis yAxis1 = new DefaultNumericAxis("beam intensity", "ppp");
    private final DefaultNumericAxis yAxis2 = new DefaultNumericAxis("dipole current", "A");
    protected Timer[] timer;
    private  int i=0;

    private void generateBeamIntensityData() {
        final long startTime = ProcessingProfiler.getTimeStamp();
        final double now = System.currentTimeMillis() / 1000.0 + 1;
        // N.B. '+1' to check for resolution

        if (rollingBufferBeamIntensity.getDataCount() == 0) {
            // suppress auto notification since we plan to add multiple data points
            // N.B. this is for illustration of the 'setAutoNotification(..)' functionality
            // one may use also the add(double[], double[], ...) method instead
            boolean oldState = rollingBufferBeamIntensity.autoNotification().getAndSet(false);
            for (int n = RollingBufferSample.N_SAMPLES; n >= 0; --n) {
                final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0;
                final double y = 100 * RollingBufferSample.rampFunctionBeamIntensity(t);
                final double ey = 1;
                if(i<3500){
//                    rollingBufferBeamIntensity.add(t, (int)(Math.random()*1000), ey, ey);
                }
                i++;
                // N.B. update events suppressed by 'setAutoNotification(false)' above
            }
            rollingBufferBeamIntensity.autoNotification().set(oldState);
            // need to issue a separate update notification
            rollingBufferBeamIntensity.fireInvalidated(new AddedDataEvent(rollingBufferBeamIntensity));
        } else {
            final double t = now;
            final double y2 = 100 * RollingBufferSample.rampFunctionBeamIntensity(t);
            final double ey = 1;
            // single add automatically fires update event/update of chart
//            rollingBufferBeamIntensity.add(t, (int)(Math.random()*1000), ey, ey);
        }

        ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet");
    }

    private void generateDipoleCurrentData() {
        System.out.println(Thread.currentThread().getName());
        final long startTime = ProcessingProfiler.getTimeStamp();
        final double now = System.currentTimeMillis() / 1000.0 + 1; // N.B. '+1'
                // to check
                // for
                // resolution

        if (rollingBufferDipoleCurrent.getDataCount() == 0) {
            // suppress auto notification since we plan to add multiple data points
            // N.B. this is for illustration of the 'setAutoNotification(..)' functionality
            // one may use also the add(double[], double[], ...) method instead
            boolean oldState = rollingBufferDipoleCurrent.autoNotification().getAndSet(false);
            for (int n = RollingBufferSample.N_SAMPLES; n >= 0; --n) {
                final double t = now - n * RollingBufferSample.UPDATE_PERIOD / 1000.0;
                final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t);
                final double ey = 1;
                rollingBufferDipoleCurrent.add(t, (int)(Math.random()*1000), ey, ey);
                // N.B. update events suppressed by 'setAutoNotification(false)' above
            }
            rollingBufferDipoleCurrent.autoNotification().set(oldState);
            // need to issue a separate update notification
            rollingBufferDipoleCurrent.fireInvalidated(new AddedDataEvent(rollingBufferDipoleCurrent));
        } else {
            boolean oldState = rollingBufferDipoleCurrent.autoNotification().getAndSet(false);
            for (int j = 0; j < 200; j++) {
                final double t = now;
                final double y = 25 * RollingBufferSample.rampFunctionDipoleCurrent(t);
                final double ey = 1;
                // single add automatically fires update event/update of chart
                rollingBufferBeamIntensity.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity2.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity3.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity4.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity5.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity6.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity7.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity8.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity9.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                rollingBufferBeamIntensity10.add(System.currentTimeMillis(), (int)(Math.random()*1000), ey, ey);
                System.out.println("当前计数"+i++);
            }
            try {
                Thread.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            rollingBufferDipoleCurrent.autoNotification().set(oldState);
            // need to issue a separate update notification
            rollingBufferDipoleCurrent.fireInvalidated(new AddedDataEvent(rollingBufferDipoleCurrent));
        }

        ProcessingProfiler.getTimeDiff(startTime, "adding data into DataSet");
    }

    private HBox getHeaderBar(Scene scene) {
        final Button newDataSet = new Button("new DataSet");
        newDataSet.setOnAction(evt -> {
//            getTask(0).run();
//            getTask(1).run();

            Service<Integer> service=new Service() {
                @Override
                protected Task createTask() {
                    return new Task() {
                        @Override
                        protected Integer call() throws Exception {
                            int i=0;
                            while (true){
                                Thread.sleep(1000);
                                updateValue(i++);
                            }
                        }
                    };
                }
            };
            service.valueProperty().addListener(new ChangeListener<Integer>() {
                @Override
                public void changed(ObservableValue<? extends Integer> observable, Integer oldValue, Integer newValue) {
                        generateDipoleCurrentData();
                }
            });
            service.start();
        });

        final Button startTimer = new Button("timer");
        startTimer.setOnAction(evt -> {
            if (timer == null) {
                timer = new Timer[2];
                timer[0] = new Timer("sample-update-timer", true);
                rollingBufferBeamIntensity.reset();
                timer[0].scheduleAtFixedRate(getTask(0), 0, UPDATE_PERIOD);

                timer[1] = new Timer("sample-update-timer", true);
                rollingBufferDipoleCurrent.reset();
                timer[1].scheduleAtFixedRate(getTask(1), 0, UPDATE_PERIOD);
            } else {
                timer[0].cancel();
                timer[1].cancel();
                timer = null; // NOPMD
            }
        });

        // H-Spacer
        Region spacer = new Region();
        spacer.setMinWidth(Region.USE_PREF_SIZE);
        HBox.setHgrow(spacer, Priority.ALWAYS);

        final ProfilerInfoBox profilerInfoBox = new ProfilerInfoBox(DEBUG_UPDATE_RATE);
        profilerInfoBox.setDebugLevel(DebugLevel.VERSION);

        return new HBox(newDataSet, startTimer, spacer, profilerInfoBox);
    }

    protected TimerTask getTask(final int updateItem) {
        return new TimerTask() {
            private int updateCount;

            @Override
            public void run() {
                if (updateItem == 0) {
                    generateBeamIntensityData();
                } else {
                    generateDipoleCurrentData();
                }

                if (updateCount % 20 == 0 && LOGGER.isDebugEnabled()) {
                    LOGGER.atDebug().addArgument(updateCount).log("update iteration #{}");
                }
                updateCount++;
            }
        };
    }

    public BorderPane initComponents(Scene scene) {
        final BorderPane root = new BorderPane();
        generateBeamIntensityData();
        generateDipoleCurrentData();
        initErrorDataSetRenderer(beamIntensityRenderer);
        initErrorDataSetRenderer(beamIntensityRenderer2);
        initErrorDataSetRenderer(beamIntensityRenderer3);
        initErrorDataSetRenderer(beamIntensityRenderer4);
        initErrorDataSetRenderer(beamIntensityRenderer5);
        initErrorDataSetRenderer(beamIntensityRenderer6);
        initErrorDataSetRenderer(beamIntensityRenderer7);
        initErrorDataSetRenderer(beamIntensityRenderer8);
        initErrorDataSetRenderer(beamIntensityRenderer9);
        initErrorDataSetRenderer(beamIntensityRenderer10);
        initErrorDataSetRenderer(dipoleCurrentRenderer);

        final DefaultNumericAxis xAxis1 = new DefaultNumericAxis("time");
        xAxis1.setAutoRangeRounding(false);
        xAxis1.setTickLabelRotation(45);
        xAxis1.setMinorTickCount(30);
        xAxis1.invertAxis(false);
        xAxis1.setTimeAxis(true);
        yAxis2.setSide(Side.RIGHT);
        yAxis2.setAnimated(false);
        // N.B. it's important to set secondary axis on the 2nd renderer before
        // adding the renderer to the chart
        dipoleCurrentRenderer.getAxes().add(yAxis2);

        final XYChart chart = new XYChart(xAxis1, yAxis1);
        chart.legendVisibleProperty().set(true);
        chart.setAnimated(false);
        chart.getRenderers().set(0, beamIntensityRenderer);
//        chart.getRenderers().add(beamIntensityRenderer2);
//        chart.getRenderers().add(beamIntensityRenderer3);
//        chart.getRenderers().add(beamIntensityRenderer4);
//        chart.getRenderers().add(beamIntensityRenderer5);
//        chart.getRenderers().add(beamIntensityRenderer6);
//        chart.getRenderers().add(beamIntensityRenderer7);
//        chart.getRenderers().add(beamIntensityRenderer8);
//        chart.getRenderers().add(beamIntensityRenderer9);
//        chart.getRenderers().add(beamIntensityRenderer10);
        chart.getPlugins().add(new EditAxis());
        chart.getPlugins().add(new DataPointTooltip());
        chart.getPlugins().add(new Zoomer());//工具栏
        chart.getPlugins().add(new TableViewer());

        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity2);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity3);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity4);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity5);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity6);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity7);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity8);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity9);
        beamIntensityRenderer.getDatasets().add(rollingBufferBeamIntensity10);
        dipoleCurrentRenderer.getDatasets().add(rollingBufferDipoleCurrent);

        // set localised time offset
        if (xAxis1.isTimeAxis() && xAxis1.getAxisLabelFormatter() instanceof DefaultTimeFormatter) {
            final DefaultTimeFormatter axisFormatter = (DefaultTimeFormatter) xAxis1.getAxisLabelFormatter();

            axisFormatter.setTimeZoneOffset(ZoneOffset.UTC);
            axisFormatter.setTimeZoneOffset(ZoneOffset.ofHoursMinutes(5, 0));
        }

        yAxis1.setForceZeroInRange(true);
        yAxis2.setForceZeroInRange(true);
        yAxis1.setAutoRangeRounding(true);
        yAxis2.setAutoRangeRounding(true);

        // init menu bar
        root.setTop(getHeaderBar(scene));

        long startTime = ProcessingProfiler.getTimeStamp();
        ProcessingProfiler.getTimeDiff(startTime, "adding data to chart");

        startTime = ProcessingProfiler.getTimeStamp();
        root.setCenter(chart);
        TableView<Person> pane = getPane();
        root.setBottom(pane);

        ProcessingProfiler.getTimeDiff(startTime, "adding chart into StackPane");

        return root;
    }

    protected void initErrorDataSetRenderer(final ErrorDataSetRenderer eRenderer) {
        eRenderer.setErrorType(ErrorStyle.ERRORSURFACE);
        // for higher performance w/o error bars, enable this for comparing with
        // the standard JavaFX charting library (which does not support error
        // handling, etc.)
        eRenderer.setErrorType(ErrorStyle.NONE);
        eRenderer.setDashSize(RollingBufferSample.MIN_PIXEL_DISTANCE); // plot pixel-to-pixel distance
        eRenderer.setPointReduction(true);
        eRenderer.setDrawMarker(false);
        final DefaultDataReducer reductionAlgorithm = (DefaultDataReducer) eRenderer.getRendererDataReducer();
        reductionAlgorithm.setMinPointPixelDistance(RollingBufferSample.MIN_PIXEL_DISTANCE);
    }

    @Override
    public void start(final Stage primaryStage) {
        ProcessingProfiler.setVerboseOutputState(true);
        ProcessingProfiler.setLoggerOutputState(true);
        ProcessingProfiler.setDebugState(false);

        final BorderPane root = new BorderPane();
        final Scene scene = new Scene(root, 1800, 1000);
        root.setCenter(initComponents(scene));

        final long startTime = ProcessingProfiler.getTimeStamp();
        primaryStage.setTitle(this.getClass().getSimpleName());
        primaryStage.setScene(scene);
        primaryStage.setOnCloseRequest(evt -> Platform.exit());
        primaryStage.show();
        ProcessingProfiler.getTimeDiff(startTime, "for showing");
    }

    /**
     * @param args the command line arguments
     */
    public static void main(final String[] args) {
        Application.launch(args);
    }

    public static double rampFunctionBeamIntensity(final double t) {
        final int second = (int) Math.floor(t);
        final double subSecond = t - second;
        double offset = 0.3;
        final double y = (1 - 0.1 * subSecond) * 1e9;
        double gate = RollingBufferSample.square(2, subSecond - offset)
                      * RollingBufferSample.square(1, subSecond - offset);

        // every 5th cycle is a booster mode cycle
        if (second % 5 == 0) {
            offset = 0.1;
            gate = Math.pow(RollingBufferSample.square(3, subSecond - offset), 2);
        }

        if (gate <= 0 || subSecond < offset) {
            gate = 0;
        }

        return gate * y;
    }

    public static double rampFunctionDipoleCurrent(final double t) {
        final int second = (int) Math.floor(t);
        final double subSecond = t - second;
        double offset = 0.3;

        double y = 100 * RollingBufferSample.sine(1, subSecond - offset);

        // every 5th cycle is a booster mode cycle
        if (second % 5 == 0) {
            offset = 0.1;
            y = 100 * Math.pow(RollingBufferSample.sine(1.5, subSecond - offset), 2);
        }

        if (y <= 0 || subSecond < offset) {
            y = 0;
        }
        return y + 10;
    }

    private static double sine(final double frequency, final double t) {
        return Math.sin(2.0 * Math.PI * frequency * t);
    }

    private static double square(final double frequency, final double t) {
        final double sine = 100 * Math.sin(2.0 * Math.PI * frequency * t);
        final double squarePoint = Math.signum(sine);
        return squarePoint >= 0 ? squarePoint : 0.0;
    }

    ObservableList<Person> realTimeDataObservableList = FXCollections.observableArrayList();
    public TableView<Person> getPane(){
//创建一个表格来模仿实际业务刷新
            TableView<Person> tableView = new TableView<>();

            TableColumn<Person, String> name = new TableColumn<>("Firstname");
            name.setPrefWidth(200);
            name.setCellValueFactory(new PropertyValueFactory<>("firstName"));

            TableColumn<Person, String> lastName = new TableColumn<>("lastName");
            lastName.setPrefWidth(200);
            lastName.setCellValueFactory(person -> person.getValue().lastNameProperty());

            TableColumn<Person, String> email = new TableColumn<>("email");
            email.setPrefWidth(200);
            email.setCellValueFactory(new PropertyValueFactory<>("email"));

            //noinspection unchecked
            tableView.getColumns().addAll(name, lastName, email);


            for (int i = 0; i < 10; i++) {

                Person person = new Person("firstName" + i, "lastName" + i, "email" + i);
                realTimeDataObservableList.add(person);
            }

            tableView.setItems(realTimeDataObservableList);

//创建一个service用来高频刷新表格
            Service<Integer> service = new Service<Integer>() {

                @Override
                protected Task<Integer> createTask() {

                    return new Task<Integer>() {

                        @Override
                        protected Integer call() throws Exception {

                            for (int i = 0; i < 1000000000; i++) {

                                TimeUnit.MILLISECONDS.sleep(1);

                                updateValue(i);
                            }

                            return 1000000000;
                        }
                    };
                }
            };
//监听service的value属性更改
            service.valueProperty().addListener((o, oldValue, newValue) -> {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
                for (Person person : realTimeDataObservableList) {

                    person.setFirstName("firstname" + newValue);
                    person.setLastName("lastname" + newValue);
                    person.setEmail("email" + newValue);

//                System.out.println("正在更新" + newValue);
                }
            });
            service.stateProperty().addListener(new ChangeListener<Worker.State>() {
                @Override
                public void changed(ObservableValue<? extends Worker.State> observable, Worker.State oldValue, Worker.State newValue) {
                    System.out.println(newValue);
                }
            });
            service.start();
            return tableView;
        }
        //tableview里面的工具类
    public static class Person {

        private final StringProperty firstName;
        private final StringProperty lastName;
        private final StringProperty email;

        private Person(String fName, String lName, String email) {
            this.firstName = new SimpleStringProperty(fName);
            this.lastName = new SimpleStringProperty(lName);
            this.email = new SimpleStringProperty(email);
        }

        public String getFirstName() {
            return firstName.get();
        }

        public void setFirstName(String fName) {
            firstName.set(fName);
        }

        public StringProperty firstNameProperty() {
            return firstName;
        }

        public String getLastName() {
            return lastName.get();
        }

        public void setLastName(String lName) {
            lastName.set(lName);
        }

        public StringProperty lastNameProperty() {
            return lastName;
        }

        public String getEmail() {
            return email.get();
        }

        public void setEmail(String inMail) {
            email.set(inMail);
        }

        public StringProperty emailProperty() {
            return email;
        }  // if this method is commented out then the tableview will not refresh when the email is set.
    }
}``
4

1 回答 1

2

您应该考虑降低数据更新率。如果您的显示器更新速率仅为 16.7 毫秒(对应于 60 赫兹),那么以 5 毫秒更新是没有意义的。即使是 60 Hz 也可能对人眼来说太过分了,那么为什么要将资源浪费在太高的更新率上呢?从技术上讲,可能值得查看 AnimationTimer 类来驱动更新。与 Platform.runlater 相比,您可以在那里免费获得一些自动油门。

于 2021-07-12T08:45:37.147 回答