Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get visible state of Node in JavaFX 8

I need to detect if a node is currently displaying. I.e. if my Node is in a TabPane, I need to know if it is in a selected tab or not.

In the example, I want to know when the HBox is displaying.The visibleProperty and managedProperty of Node, does not seem to help me:

public class VisibleTest extends Application {

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

    TabPane tabpane = new TabPane();
    tabpane.getTabs().add(new Tab("Tab1", new Label("Label1")));

    HBox hbox = new HBox(new Label("Label2"));
    hbox.setStyle("-fx-background-color: aquamarine;");

    hbox.visibleProperty().addListener((observable, oldValue, newValue) -> {
        System.out.println("Hbox visible changed. newValue: " + newValue);
    });

    hbox.managedProperty().addListener((observable, oldValue, newValue) -> {
        System.out.println("Hbox managed changed. newValue: " + newValue);
    });

    Tab tab2 = new Tab("tab2", hbox);
    tabpane.getTabs().add(tab2);

    primaryStage.setScene(new Scene(tabpane));
    primaryStage.setWidth(600);
    primaryStage.setHeight(500);
    primaryStage.show();
}

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

I know, it is possible to listen on the selectedProperty state of the tab, but this does not solve my real problem.

Node.impl_isTreeVisible() does what I want, but this is depricated API.

Any ideas?

------------------------------------ update--------------------
I realize the code example above does not explain well what I'm trying to accomplish.
Below is some Swing code that kind of demonstrates what I am trying to accomplish in JavaFX. Detect if the JComponent/Node is visible/shown, and based on that state, start or stop background processes. How would the constructor look like if it was a javaFX class.

public class SwingVisible extends JComponent {

    String instanceNR;
    Thread instanceThread;
    boolean doExpensiveStuff = false;

    public SwingVisible(String instanceNR) {
        this.instanceNR = instanceNR;
        this.setLayout(new FlowLayout());
        this.add(new JLabel(instanceNR));

        instanceThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    if (doExpensiveStuff) {
                        /*
                         * do expensive stuff.
                         */
                        System.out.println(instanceNR + " is visible " + isVisible());
                    }
                }
            }
        });

        /*
         * How to do this in FX?
         */
        addComponentListener(new ComponentAdapter() {
            @Override
            public void componentShown(ComponentEvent e) {
                if (!instanceThread.isAlive()) {
                    instanceThread.start();
                }
                doExpensiveStuff = true;
            }

            @Override
            public void componentHidden(ComponentEvent e) {
                doExpensiveStuff = false;
            }
        });
    }

    public static void main(String[] args) {    
        /*
         * This block represents code that is external to my library. End user
         * can put instances of SwingVisible in JTabbedPanes, JFrames, JWindows,
         * or other JComponents. How many instances there will bee is not in my
         * control.
         */
        JTabbedPane jtp = new JTabbedPane();
        jtp.add("tab1", new SwingVisible("1"));
        jtp.add("tab2", new SwingVisible("2"));
        jtp.add("tab3", new SwingVisible("3"));

        JFrame f = new JFrame("test");
        f.setContentPane(jtp);
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.setSize(300, 300);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }
}

Output when tab1 is selected:

1 is visible true
1 is visible true
1 is visible true

...

Output when tab2 is selected:

2 is visible true
2 is visible true
2 is visible true
...

like image 542
Olzen Avatar asked Mar 09 '23 09:03

Olzen


2 Answers

You can use Tab's selectedProperty to know if it is selected or not, and by extension if its content is visible or not. It is a boolean property.

I've converted your Swing code to JavaFX based on your initial JavaFX example:

public class VisibleTest extends Application {

    public class FXVisible extends Tab {

        FXVisible(String id) {
            super(id, new Label(id));

            Timeline thread = new Timeline(
                    new KeyFrame(Duration.ZERO, e -> { 
                        if (isSelected()) {
                            // do expensive stuff
                            System.out.println(id + " is visible");
                        }
                    }),
                    new KeyFrame(Duration.seconds(1))
            );
            thread.setCycleCount(Timeline.INDEFINITE);

            selectedProperty().addListener((selectedProperty, wasSelected, isSelected) -> {
                if (isSelected) {
                    if (thread.getStatus() != Status.RUNNING) {
                        System.out.println(id + " starting thread");
                        thread.play();
                    }
                }
                // else, it is not selected -> content not shown
            });
        }
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        TabPane tabpane = new TabPane();
        tabpane.getTabs().add(new FXVisible("1"));
        tabpane.getTabs().add(new FXVisible("2"));
        tabpane.getTabs().add(new FXVisible("3"));
        // add as many as you want

        primaryStage.setScene(new Scene(tabpane));
        primaryStage.setWidth(600);
        primaryStage.setHeight(500);
        primaryStage.show();
    }

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

I replaced your thread with a JavaFX Timeline. Your question is not about this topic so I won't go into details here, though it's self explanatory.

I don't understand why in the Swing example you have a listener changing a boolean that indicates if the component is visible or not when you can just call isVisible() directly in the thread (see comments below for a note about threading). This is why in my code above I took the approach of checking isSelected() directly with no self-declared boolean. If you need to revert to your design it's rather straightforward. Just noting this for clarity.

The ComponentListener can be replaced with a change listener on selectedProperty() and querying the new value. Just be sure that your example does what it's supposed to do: the first time the tab is selected the thread/timer starts. After that the thread/timer does nothing. You might have wanted to pause the computation for non-displaying content. Again, just noting it because it seemed like a potential mistake to me, otherwise you're fine.

like image 178
user1803551 Avatar answered Mar 11 '23 22:03

user1803551


Updated answer.

tab2.getContent().isVisible();
like image 39
Sedrick Avatar answered Mar 11 '23 23:03

Sedrick