How to access a cell that is generated in a GridPane? - javafx

I need to add a shape into a specific cell (middle centered one) in my GridPane.
This is how cells (StackPane) are made in my GridPane:
private StackPane createCell() {
StackPane cell = new StackPane();
cell.getStyleClass().add("cell");
return cell;
}
I have tried to get the centered cell with this piece of method:
private Node getCenteredNodeGridPane(GridPane gridPane, int col, int row) {
for (Node node : gridPane.getChildren()) {
if (GridPane.getColumnIndex(node) == col/2 && GridPane.getRowIndex(node) == row/2) {
return node;
}
}
return null;
}
And then, get the node:
Node centeredNode = getCenteredNodeGridPane(grid, 20, 20);
centeredNode ...... ??????
But i have no access to the actual StackPane of this node.
I need something like,
centeredNode.getChildren().add(shape);

Assuming you only add StackPanes as child nodes of the grid pane, you just need to cast the result:
StackPane centeredNode = (StackPane) getCenteredNodeGridPane(grid, 20, 20);
Depending on your exact requirements, you could of course do this in your getCenteredNodeGridPane method, and add type checking:
private StackPane getCenteredNodeGridPane(GridPane gridPane, int col, int row) {
for (Node node : gridPane.getChildren()) {
if (node instanceof StackPane
&& GridPane.getColumnIndex(node) == col/2
&& GridPane.getRowIndex(node) == row/2) {
return (StackPane) node;
}
}
return null;
}

Related

Limit TextField to a specific range of number JavaFX?

Hi I need to limit the input of TextField javaFX not only for integer but also for numbers between 1 - 19 only.For example I should be allowed to type : "3" ,"19" ... but not: "33" , 44 ..
for example : What is the recommended way to make a numeric TextField in JavaFX? but this limits the text field just for integers.
You can use regex to allow your specific numbers' range(1-19) and add that validation on TextField's TextFormatter's filter.
Regex => ([1-9]|1[0-9])
[1-9] Either TextField allows you to enter 1 to 9 numbers
1[0-9] Or TextField allows you to enter 10 to 19 numbers
Regex Circut
TextField Validation Demo
public class TextFieldValidationDemo extends Application {
private static final String REGEX_VALID_INTEGER = "([1-9]|1[0-9])";
#Override
public void start(Stage primaryStage) throws Exception {
BorderPane root = new BorderPane();
root.setCenter(getRootPane());
primaryStage.setScene(new Scene(root, 200, 200));
primaryStage.show();
}
private BorderPane getRootPane() {
BorderPane root = new BorderPane();
root.setCenter(getTextField());
return root;
}
private TextField getTextField() {
TextField field = new TextField();
field.setTextFormatter(new TextFormatter<>(this::filter));
return field;
}
private TextFormatter.Change filter(TextFormatter.Change change) {
if (!change.getControlNewText().matches(REGEX_VALID_INTEGER)) {
change.setText("");
}
return change;
}
}
I have now revised my code. This code allows you to enter "13" as well as only 13. It also checks if the input is in the range.
Demo App
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField();
GridPane gridPane = new GridPane();
Scene scene = new Scene(gridPane);
gridPane.add(textField, 0, 0);
final int MIN = 1;
final int MAX = 19;
UnaryOperator<TextFormatter.Change> filter = change -> {
//if something got added
if (change.isAdded()) {
//if change is " add "" in texfield
if (change.getText().equals("\"")) {
if (!change.getControlText().contains("\"")) {
change.setText("\"\"");
return change;
} else {
//if textfield already contains ""
return null;
}
} else {
//If Input is not a number don't change anything
if (change.getText().matches("[^0-9]")) {
return null;
}
//If change don't contains " check if change is in range
if (!change.getControlText().contains("\"")) {
if (Integer.parseInt(change.getControlNewText()) < MIN || Integer.parseInt(change.getControlNewText()) > MAX) {
return null;
}
} else {
//if change contains "" remove "" and check if is in range
String s = change.getControlNewText();
s = s.replaceAll("[\"]", "");
int value = Integer.parseInt(s);
if (value < MIN || value > MAX) {
return null;
}
}
}
}
return change;
};
textField.setTextFormatter(new TextFormatter<>(filter));
primaryStage.setScene(scene);
primaryStage.show();
}

search a word by key enter

i have a problem with my searching method.
With this method, I can enter a word in the textfield and display the word in the textarea. However, this only happens once if i let it run. I need to expand it so, that every time I click on "enter," the program should continue with searching in the textarea. How can i do this?
And please give me code examples. i have only 2 days left for my presentation.
Thanks a lot for the helps
textfield.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
if (event.getCode() == KeyCode.ENTER) {
String text = textarea.getText();
Labeled errorText = null;
if (textfield.getText() != null && !textfield.getText().isEmpty()) {
index = textarea.getText().indexOf(textfield.getText());
textarea.getText();
if (index == -1) {
errorText.setText("Search key Not in the text");
} else {
// errorText.setText("Found");
textarea.selectRange(index, index + textfield.getLength());
}
}
}
}
});
There's an overloaded version of the indexOf method allowing you to search starting at a specific index. Keep track of the index of your last find and start searching from this position:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
textField.setOnAction(evt -> {
String searchText = textField.getText();
if (searchText.isEmpty()) {
return; // searching for empty text doesn't make sense
}
int index = textarea.getSelection().getEnd();
// in case of the first search, start at the beginning
// TODO: adjust condition/starting index according to needs
if (textarea.getSelection().getLength() == 0) {
index = 0;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
textarea.selectRange(newStartIndex, newStartIndex + searchText.length());
}
});
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}
Edit
If you are not satisfied with searching the element after the selection ( or after the cursor, if there is no range selected), you could save the data of the end of the last match:
#Override
public void start(Stage primaryStage) throws Exception {
TextField textField = new TextField("foo");
TextArea textarea = new TextArea();
for (int i = 0; i < 10; i++) {
textarea.appendText("foo\nbarfoobarfoofoo\n");
}
class SearchHandler implements EventHandler<ActionEvent> {
int index = 0;
#Override
public void handle(ActionEvent event) {
String searchText = textField.getText();
String fullText = textarea.getText();
if (index + searchText.length() > fullText.length()) {
// no more matches possible
// TODO: notify user
return;
}
// find next occurrence
int newStartIndex = textarea.getText().indexOf(searchText, index);
// mark occurrence
if (newStartIndex >= 0) {
index = newStartIndex + searchText.length();
textarea.selectRange(newStartIndex, index);
} else {
index = fullText.length();
// TODO: notify user
}
}
}
SearchHandler handler = new SearchHandler();
textField.setOnAction(handler);
// reset index to search from start when changing the text of the TextField
textField.textProperty().addListener((o, oldValue, newValue) -> handler.index = 0);
Scene scene = new Scene(new VBox(textField, textarea));
primaryStage.setScene(scene);
primaryStage.show();
}

How to make an pane stay over line connecting draggablenode in javafx

I am designing a UI of a graph structure with draggable nodes. In the graph I have a component called relation(it is a pane) which shows the link between two nodes.
I want relation to stay and move along with line at mid of line.
Current UI design is as shown below
And the expected one is like:
You need to refresh the position of the node when the line's end coordinates are modified. To avoid triggering the calculation multiple times per layout pass, I recommend doing this from the layoutChildren method of the parent, but you could also do this from a listener to the startX, endY, ... properties. This will lead to some unnecessary computations though.
As for calcualting the position of the node: The center of the node needs to align with the midpoint of the line, so you need to solve the following equation for markTopLeft:
markTopLeft + (markWidth, markHeight) / 2 = (lineStart + lineEnd) / 2
markTopLeft = (lineStart + lineEnd - (markWidth, markHeight)) / 2
Example
Pane allowing for custom layout calculations
public class PostProcessPane extends Pane {
private final Set<Node> modifiedChildren = new HashSet<>();
private final Set<Node> modifiedChildrenUnmodifiable = Collections.unmodifiableSet(modifiedChildren);
private final List<Consumer<Set<Node>>> postProcessors = new ArrayList<>();
public List<Consumer<Set<Node>>> getPostProcessors() {
return postProcessors;
}
private final ChangeListener listener = (o, oldValue, newValue) -> modifiedChildren.add((Node) ((ReadOnlyProperty) o).getBean());
private void initListener() {
getChildren().addListener((ListChangeListener.Change<? extends Node> c) -> {
while (c.next()) {
if (c.wasRemoved()) {
for (Node n : c.getRemoved()) {
n.boundsInParentProperty().removeListener(listener);
}
}
if (c.wasAdded()) {
for (Node n : c.getAddedSubList()) {
n.boundsInParentProperty().addListener(listener);
}
}
}
});
}
public PostProcessPane() {
initListener();
}
public PostProcessPane(Node... children) {
super(children);
initListener();
for (Node n : children) {
n.boundsInParentProperty().addListener(listener);
}
}
#Override
protected void layoutChildren() {
super.layoutChildren();
if (!modifiedChildren.isEmpty()) {
for (Consumer<Set<Node>> processor : postProcessors) {
processor.accept(modifiedChildrenUnmodifiable);
}
modifiedChildren.clear();
}
}
}
Usage
#Override
public void start(Stage primaryStage) throws Exception {
Rectangle r1 = new Rectangle(200, 50, Color.BLUE);
Rectangle r2 = new Rectangle(200, 50, Color.RED);
Rectangle mark = new Rectangle(200, 50, Color.YELLOW);
Line line = new Line();
r1.setX(20);
r2.setX(380);
r2.setY(450);
PostProcessPane root = new PostProcessPane(line, r1, r2, mark);
root.getPostProcessors().add(changedNodes -> {
if (changedNodes.contains(r1) || changedNodes.contains(r2) || changedNodes.contains(mark)) {
Bounds bounds1 = r1.getBoundsInParent();
Bounds bounds2 = r2.getBoundsInParent();
// refresh line ends
line.setStartX(bounds1.getMinX() + bounds1.getWidth() / 2);
line.setStartY(bounds1.getMaxY());
line.setEndX(bounds2.getMinX() + bounds2.getWidth() / 2);
line.setEndY(bounds2.getMinY());
// recalculate mark position
mark.setX((line.getStartX() + line.getEndX() - mark.getWidth()) / 2);
mark.setY((line.getStartY() + line.getEndY() - mark.getHeight()) / 2);
}
});
// add some movement for the nodes
Timeline timeline = new Timeline(
new KeyFrame(Duration.ZERO,
new KeyValue(r1.xProperty(), r1.getX()),
new KeyValue(r1.yProperty(), r1.getY()),
new KeyValue(r2.xProperty(), r2.getX())),
new KeyFrame(Duration.seconds(1),
new KeyValue(r2.xProperty(), r1.getX())),
new KeyFrame(Duration.seconds(2),
new KeyValue(r1.xProperty(), r2.getX()),
new KeyValue(r1.yProperty(), r2.getY() / 2))
);
timeline.setAutoReverse(true);
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
primaryStage.show();
}

Drag BorderPane body

I use this JavaFX code to drag BorderPane into FlowPane:
private Node dragPanel(Node bp)
{
bp.setOnDragDetected(new EventHandler<MouseEvent>()
{
#Override
public void handle(MouseEvent event)
{
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
});
bp.setOnDragOver(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean accept = true;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
int myIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
if (incomingIndex == myIndex)
{
accept = false;
}
}
catch (java.lang.NumberFormatException e)
{
// handle null or not number string in clipboard
accept = false;
}
}
else
{
accept = false;
}
if (accept)
{
event.acceptTransferModes(TransferMode.MOVE);
}
}
});
bp.setOnDragDropped(new EventHandler<DragEvent>()
{
#Override
public void handle(DragEvent event)
{
boolean success = false;
final Dragboard dragboard = event.getDragboard();
if (dragboard.hasString())
{
try
{
int incomingIndex = Integer.parseInt(dragboard.getString());
final Pane parent = (Pane) bp.getParent();
final ObservableList<Node> children = parent.getChildren();
int myIndex = children.indexOf(bp);
final int laterIndex = Math.max(incomingIndex, myIndex);
Node removedLater = children.remove(laterIndex);
final int earlierIndex = Math.min(incomingIndex, myIndex);
Node removedEarlier = children.remove(earlierIndex);
children.add(earlierIndex, removedLater);
children.add(laterIndex, removedEarlier);
success = true;
}
catch (java.lang.NumberFormatException e)
{
//TO DO... handle null or not number string in clipboard
}
}
event.setDropCompleted(success);
}
});
// bp.setMinSize(50, 50);
return bp;
}
I enable this drag event using this code:
BorderPane panel = new BorderPane();
dragPanel(panel),
I also have resize code which is also activated. I need some way to apply the drag code only of I click and drag the panel. I want to disable the drag listener when I drag the panel borders. Is there a way to limit this?
I'm guessing by "borders" you just mean the edges of the border panes. You can just check the coordinates of the mouse event and only initiate dragging if you're away from the borders. To do this, you need to know the width and height of the border pane. The methods to get those are defined in Region, so you need to narrow the type of the parameter from Node to Region. This will still work if you call dragPanel(panel) but you won't be able to pass in a Node that is not a Region instance.
final int borderSize = 5 ;
// ...
private Node dragPane(Region bp) {
bp.setOnDragDetected(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
double x = event.getX();
double y = event.getY();
double width = bp.getWidth();
double height = bp.getHeight();
if (x > borderSize && x < width - borderSize
&& y > borderSize && y < height - borderSize) {
Dragboard db = bp.startDragAndDrop(TransferMode.MOVE);
ClipboardContent clipboard = new ClipboardContent();
final int nodeIndex = bp.getParent().getChildrenUnmodifiable()
.indexOf(bp);
clipboard.putString(Integer.toString(nodeIndex));
db.setContent(clipboard);
Image img = bp.snapshot(null, null);
db.setDragView(img, 7, 7);
event.consume();
}
}
});
// ...
}

How to create dynamically Resizable shapes in javafx?

I have three problems:
I want to create resizable shapes with box bounding...
I also want to know how to get child seleted in a Pane.
I'm creating multiple shapes on a pane. I want to change some property of that shape say Fill.. How do i do it??
Thanx
Next example will answer your questions:
for (1) it uses binding, connecting pane size with rectangle size
for (2) it adds setOnMouseClick for each rectangle which stores clicked one in the lastOne field.
for (3) see code of setOnMouseClick() handler
public class RectangleGrid extends Application {
private Rectangle lastOne;
public void start(Stage stage) throws Exception {
Pane root = new Pane();
int grid_x = 7; //number of rows
int grid_y = 7; //number of columns
// this binding will find out which parameter is smaller: height or width
NumberBinding rectsAreaSize = Bindings.min(root.heightProperty(), root.widthProperty());
for (int x = 0; x < grid_x; x++) {
for (int y = 0; y < grid_y; y++) {
Rectangle rectangle = new Rectangle();
rectangle.setStroke(Color.WHITE);
rectangle.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent t) {
if (lastOne != null) {
lastOne.setFill(Color.BLACK);
}
// remembering clicks
lastOne = (Rectangle) t.getSource();
// updating fill
lastOne.setFill(Color.RED);
}
});
// here we position rects (this depends on pane size as well)
rectangle.xProperty().bind(rectsAreaSize.multiply(x).divide(grid_x));
rectangle.yProperty().bind(rectsAreaSize.multiply(y).divide(grid_y));
// here we bind rectangle size to pane size
rectangle.heightProperty().bind(rectsAreaSize.divide(grid_x));
rectangle.widthProperty().bind(rectangle.heightProperty());
root.getChildren().add(rectangle);
}
}
stage.setScene(new Scene(root, 500, 500));
stage.show();
}
public static void main(String[] args) { launch(); }
}

Resources