Как правильно убрать ownerWindow слушателя в popover? / JavaFX - PullRequest
0 голосов
/ 29 октября 2019

Я создал приложение JavaFX с JDK8, которое содержит окно и несколько объектов. Я пытаюсь теперь поставить бесполезный используемый объект доступным для GarbageCollector. (Тестирование с JVisualVM).

Но я застрял с одной проблемой:
Очистите Popover, который содержит обработчик и слушатель на элементе окна.

Оригинальный код поповера:

public class CustomPopOver extends PopOver {

    /**
     * Constructor with the Content of the PopOver.
     * @param content the Node.
     */
    public CustomPopOver (Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver () {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener((observable, oldValue, newValue) -> {
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                newValue.setOnCloseRequest(event -> {
                    if (this.isShowing()) {
                        this.hide(Duration.millis(0));
                    }
                    if (preExistingHandler != null) {
                        preExistingHandler.handle(event);
                    }
                });
            }
        });
    }
}

Я много чего пробовал, но это не работает должным образом:

public class CustomPopOver extends PopOver implements DisposableBean {

    private MyListener listener = new MyListener();

    public CustomPopOver (Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver () {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener(listener);
    }

    @Override
    public void destroy() {
        if (this.getOwnerWindow() != null){
            this.getOwnerWindow()
                    .removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, listener.windowCloseEventHandler);
            this.getOwnerWindow()
                    .removeEventHandler(WindowEvent.WINDOW_HIDING, listener.windowHidingEventHandler);
        }
        this.ownerWindowProperty().removeListener(listener);
        listener = null;
    }

    /**
     * ChangeListener that removes itself when needed.
     */
    private class MyListener implements ChangeListener<Window> {

        EventHandler<WindowEvent> windowCloseEventHandler;
        EventHandler<WindowEvent> windowHidingEventHandler;
        @Override
        public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
            if (oldValue != null) {
                oldValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, windowCloseEventHandler);
                oldValue.removeEventHandler(WindowEvent.WINDOW_HIDING, windowHidingEventHandler);
            }
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                windowCloseEventHandler = new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        if (isShowing()) {
                            hide(Duration.millis(0));
                            ownerWindowProperty().removeListener(MyListener.this);
                        }
                        if (preExistingHandler != null) {
                            preExistingHandler.handle(event);
                        }
                        newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
                    }
                };
                newValue.setOnCloseRequest(windowCloseEventHandler);
                windowHidingEventHandler = new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        ownerWindowProperty().removeListener(MyListener.this);
                        newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
                    }
                };
                newValue.setOnHiding(windowHidingEventHandler);
            }
        }
    }
}

и мы вызываемМетод destroy для очистки всплывающего окна из кэша jvm.

Код для тестирования класса CustomPopOver:

public class PopOverViewer extends Application {

    private BorderPane pane;

    public PopOverViewer() {
        pane = new BorderPane();
        pane.setCenter(button());

    }

    private Node button() {
        HBox hBox = new HBox();
        List<CustomPopOver > lists = new ArrayList<>();
        Button show = new Button("click");
        show.setOnAction(event -> {
            CustomPopOver popOver = new CustomPopOver ();
            lists.add(popOver);
            popOver.show(show);
        });
        Button clean = new Button("clean");
        clean.setOnAction(event -> {
            lists.forEach(CustomPopOver::destroy);
            lists.clear();
        });
        hBox.getChildren().addAll(show, clean);
        return hBox;
    }

    @Override
    public void start(Stage primaryStage) {
        PopOverViewer app = new PopOverViewer();
        primaryStage.setScene(new Scene(app.getPane()));
        primaryStage.show();
    }

    private Parent getPane() {
        return pane;
    }

}

Мне бы хотелось, чтобы класс CustomPopover был чистым от GC.

1 Ответ

0 голосов
/ 04 ноября 2019

Спасибо @fabian, поместив WeakEventHandler на Handler внутри слушателя с сильными ссылками, помог очистить его.

Код, который работал:

public class CustomPopOver extends PopOver implements DisposableBean {

    private MyListener listener = new MyListener();

    /**
     * Constructor with the Content of the PopOver.
     * @param content the Node.
     */
    public CustomPopOver(Node content) {
        super(content);
        addHandler();
    }

    /**
     * Empty Constructor.
     */
    public CustomPopOver() {
        super();
        addHandler();
    }

    private void addHandler() {
        this.ownerWindowProperty().addListener(listener);
    }

    @Override
    public void destroy() {
        this.ownerWindowProperty().removeListener(listener);
        listener = null;
    }

    /**
     * ChangeListener that removes itself when needed.
     */
    private class MyListener implements ChangeListener<Window> {

        @Override
        public void changed(ObservableValue<? extends Window> observable, Window oldValue, Window newValue) {
            if (newValue != null) {
                EventHandler<WindowEvent> preExistingHandler = newValue.getOnCloseRequest();
                EventHandler<WindowEvent> windowCloseEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        if (isShowing()) {
                            hide(Duration.millis(0));
                            ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
                        }
                        if (preExistingHandler != null) {
                            preExistingHandler.handle(event);
                        }
                        newValue.removeEventHandler(WindowEvent.WINDOW_CLOSE_REQUEST, this);
                    }
                });
                newValue.setOnCloseRequest(windowCloseEventHandler);
                EventHandler<WindowEvent> windowHidingEventHandler = new WeakEventHandler<>(new EventHandler<WindowEvent>() {
                    @Override
                    public void handle(WindowEvent event) {
                        ownerWindowProperty().removeListener(CustomPopOver.MyListener.this);
                        newValue.removeEventHandler(WindowEvent.WINDOW_HIDING, this);
                    }
                });
                newValue.setOnHiding(windowHidingEventHandler);
            }
        }
    }
}
...