JavaFx scene lookup returning null - javafx

Button btn = new Button("ohot");
btn.setId("testId");
itemSection.getChildren().add(btn);
Node nds = itemSection.lookup("‪#‎testId‬");
What is wrong with above code?? I am getting nds=null it should be btn

Lookups in conjunction with applyCSS
Lookups are based on CSS. So CSS needs to be applied to the scene for you to be able to lookup items in the scene. See the applyCSS documentation for more information. To get accurate results from your lookup, you might also want to invoke layout, as the layout operation can effect scene graph attributes.
So you could do this:
Button btn = new Button("ohot");
btn.setId("testId");
itemSection.getChildren().add(btn);
itemSection.applyCss();
itemSection.layout();
Node nds = itemSection.lookup("‪#‎testId‬");
Alternate lookup after showing a stage
Note that some operations in JavaFX, such as initially showing a Stage or waiting for a pulse to occur, will implicitly execute a CSS application, but most operations will not.
So you could also do this:
Button btn = new Button("ohot");
btn.setId("testId");
itemSection.getChildren().add(btn);
stage.setScene(new Scene(itemSection);
stage.show();
Node nds = itemSection.lookup("‪#‎testId‬");
On CSS based lookups VS explicit references
Storing and using explicit references in your code is often preferred to using lookups. Unlike a lookup, using an explicit reference is type safe and does not depend upon a CSS application. Generating explicit references can also be facilitated by using JavaFX and FXML with the #FXML annotation for type-safe reference injection. However, both lookup and explicit reference approaches have valid use cases, so it is a really just a matter of using the right approach at the right time.

Related

Clone a Scene in JavaFX

Is there a way to clone a scene in JavaFX ?
I currently want to keep a reference of the previous scene but can't find a simple way of doing it (except passing in the previous scene as a parameter to every new class responsible for scene creation).
Heres what I was thinking of doing (this is a snipped of my view controller)
public void switchScene(Scene newScene){
previousScene = currentScene; // something which achieves this without aliasing
currentScene = newScene;
window.setScene(newScene);
}.
However as one can observe this leads to aliasing problems.
Any way to solve this problem ?

Javafx: copy node by dragging it and paste the copy by dropping it to a location in Pane

Is it possible to deep copy a Label (or any other node), so that it creates a new Label object with the same property values and then put it in Dragboard?
I want to implement this functionality: drag a label an drop it at some location in Pane. A new Label object with same property values is created in Pane at the location of the drop.
As far as I know, Label does not implement the Java Cloneable interface, and so does not have any built in way to deep copy itself, nor does Node.
You could create your own class which extends Label and implements Cloneable and in that class override the clone method, and do that for every other Node you wish to deep copy as well, this is the most robust solution, but it may be more than you need.
The other option is to just create a new Label with the same properties, which could be as simple as something like
Label newLabel = new Label(oldLabel.getText(), oldLabel.getGraphic());
Note that you may have problems with that method, as it is not a true deep copy, newLabel and oldLabel now reference the same Graphic Node, which again, you may have problems adding the same Graphic Node to the scene twice. A better copy might do something like
ImageView oldGraphic = (ImageView) oldLabel.getGraphic();
Label newLabel = new Label(oldLabel.getText(), new ImageView(oldGraphic.getImage());
This still isn't a true deep copy, but there are no rules against adding the same Image to the scene as many times as you wish, so you're safe there. This sort of approach is fine for Labels, it's only two lines (it could be collapsed down to one even, but I went for more readability), but for more complex types of nodes could get really cumbersome. If it's just labels, here's an okay solution, but otherwise it would make sense to encapsulate all the needed copying into a new clone method.

How to obtain access to children nodes in javafx?

I have a tree like this:
As you can see there is GridPane with 10 columns. Each of them include BorderPane wrapped into AnchorPane. Each BorderPane is composed of 2 labels and 1 radioButton. Below you can see how it look like:
I want to ask you how to get acces from code side to these elements. I know that I can use getChildren() method on GridPane but then I get only AnchorPanes from GridPane columns. I want to go deeply, and for example set text into one of the labels.
I want to add that set id in Scene Builder is not what I want cause there will be many columns and I'm going to fill it in some loop.
Could you help me with this ?
One more thing: I build the view in Scene Builder.
You you can set a css id for the nodes within your loop at creation time. You can later lookup the nodes by the id you set. A lookup will also work on a scene to find any node or matching set of nodes within the scene.
HBox parent = new HBox();
for (int i = 0; i < N_COLS, i++) {
Node childNode = createNode();
childNode.setId("child" + i);
parent.getChildren().add(childNode);
}
. . .
Node redheadedStepchild = parent.lookup("#child5");
Of course, you can always access the child the standard object accessor way too:
Node redheadedStepchild = parent.getChildren().get(5);
If the structure is nested as you have in your question, then you can do things such as:
Node redheadedGrandchild = grandparent.getChildren().get(3).getChildren().get(5);
Such searches get convoluted over large structures and probably are best avoided as your code would get more brittle. So a css based lookup or tree traversal would probably be preferred, or a direct injection of a reference via an fx:id and #FXML, which wouldn't work well in your case because you create the items in code in a loop, hence the lookup method is probably best for you.

Custom TreeCell with cell factory, and background threads

How do I make it so the TreeView is populated with ProgressIndicators while stuff is happening, but without blocking the thread, using a cell factory?
How does TreeItem.setGraphic() differ from TreeCell.setGraphic()?
When I instantiate the TreeItem, I need to set the graphic to a ProgressIndicator, but I'm not sure whether this ought to happen while creating the TreeItem or from the TreeCell.updateItem dumped out by the factory.
I think when using cell factories, all graphical stuff needs to happen there, thus TreeItem.setGraphic is merely a convenience, and I should figure out my problem from within updateItem.
I'm doing the file explorer example. Each item in the TreeView has the value set to a sun.nio.fs.WindowsPath, and is implemented by inheriting from TreeItem. I override isLeaf() and getChildren(). The problem is isLeaf() can take a long time on network drives when I'm not plugged into the network.
So this is what I'm doing to create a new tree item with a path value (not using cell factory yet):
Start new thread (using Clojure futures) to check if the path value is a path or file using isRegularFile(). The result from this is available later when dereferencing the future.
Instantiate instance of anonymous TreeItem derivative (using Clojure proxy).
Call setGraphic() on the new TreeItem instance with a ProgressIndicator().
Start another thread which checks the result of the first thread. When the first thread is finished, then based on the value of the leaf function, the first thread sets the appropriate file or folder icon, and calls addEventHandler() with local anonymous functions that change the graphic based on expanded or collapsed.
Return the new instance of TreeItem (from step 2) before either of the new threads is finished.
This works and has the effect of putting a swirly graphic at each network drive while isLeaf is running. But I'm not sure how to do all this when both TreeItem and TreeCell seem to have a setGraphic() function; I'm not sure who "owns" what. I think the TreeView owns the both the items and the cells, and calling setGraphic() on a TreeItem somehow references the default cell's graphic, when not using a custom cell factory.
I need to figure out how to access the isLeaf value from the cell factory updateItem(), etc. etc.

How to listen to visible changes to the JavaFX SceneGraph for specific node

We created a small painting application in JavaFX. A new requirement arose, where we have to warn the user, that he made changes, which are not yet persisted and asking him, if the user might like to save first before closing.
Sample Snapshot:
Unfortunately there are a lot of different Nodes, and Nodes can be changed in many ways, like for example a Polygon point can move. The Node itself can be dragged. They can be rotated and many more. So before firing a zillion events for every possible change of a Node object to the canvas I`d like to ask, if anyone might have an idea on how to simplify this approach. I am curious, if there are any listeners, that I can listen to any changes of the canvas object within the scene graph of JavaFX.
Especially since I just want to know if anything has changed and not really need to know the specific change.
Moreover, I also do not want to get every single event, like a simple select, which causes a border to be shown around the selected node (like shown on the image), which does not necessary mean, that the user has to save his application before leaving.
Anyone have an idea? Or do I really need to fire Events for every single change within a Node?
I think you are approaching this problem in the wrong way. The nodes displayed on screen should just be a visual representation of an underlying model. All you really need to know is that the underlying model has changed.
If, for example, you were writing a text editor, the text displayed on the screen would be backed by some sort of model. Let's assume the model is a String. You wouldn't need to check if any of the text nodes displayed on screen had changed you would just need to compare the original string data with the current string data to determine if you need to prompt the user to save.
Benjamin's answer is probably the best one here: you should use an underlying model, and that model can easily check if relevant state has changed. At some point in the development of your application, you will come to the point where you realize this is the correct way to do things. It seems like you have reached that point.
However, if you want to delay the inevitable redesign of your application a little further (and make it a bit more painful when you do get to that point ;) ), here's another approach you might consider.
Obviously, you have some kind of Pane that is holding the objects that are being painted. The user must be creating those objects and you're adding them to the pane at some point. Just create a method that handles that addition, and registers an invalidation listener with the properties of interest when you do. The structure will look something like this:
private final ReadOnlyBooleanWrapper unsavedChanges =
new ReadOnlyBooleanWrapper(this, "unsavedChanged", false);
private final ChangeListener<Object> unsavedChangeListener =
(obs, oldValue, newValue) -> unsavedChanges.set(true);
private Pane drawingPane ;
// ...
Button saveButton = new Button("Save");
saveButton.disableProperty().bind(unsavedChanges.not());
// ...
#SafeVarArgs
private final <T extends Node> void addNodeToDrawingPane(
T node, Function<T, ObservableValue<?>>... properties) {
Stream.of(properties).forEach(
property -> property.apply(node).addListener(unsavedChangeListener));
drawingPane.getChildren().add(node);
}
Now you can do things like
Rectangle rect = new Rectangle();
addNodeToDrawingPane(rect,
Rectangle::xProperty, Rectangle::yProperty,
Rectangle::widthProperty, Rectangle::heightProperty);
and
Text text = new Text();
addNodeToDrawingPane(text,
Text::xProperty, Text::yProperty, Text::textProperty);
I.e. you just specify the properties to observe when you add the new node. You can create a remove method which removes the listener too. The amount of extra code on top of what you already have is pretty minimal, as (probably, I haven't seen your code) is the refactoring.
Again, you should really have a separate view model, etc. I wanted to post this to show that #kleopatra's first comment on the question ("Listen for invalidation of relevant state") doesn't necessarily involve a lot of work if you approach it in the right way. At first, I thought this approach was incompatible with #Tomas Mikula's mention of undo/redo functionality, but you may even be able to use this approach as a basis for that too.

Resources