I've been trying to make a 9x9 grid of textFields, where each textField is allocated to an element in a 2d array. For example the top left textField is field[0][0], the one right of that is field[1][0], and the bottom right textField is field[8][8].
So far I have
TextField[][] fields = new TextField[9][9]; {
for (Y=0;Y<9;Y++) {
XPosition=0;
for (X=0;X<9;X++) {
fields[X][Y] = new TextField(1);
fields[X][Y].setColumns(1);
fields[X][Y].setBounds(XPosition, YPosition ,32, 32);
frame.getContentPane().add(fields[X][Y]);
XPosition=XPosition+32;
}
YPosition = YPosition+32;
}
}
For some reason when I run the program, only the first 5 textFields in the top row get created. I have a feeling all the other textFields are placed under the visible ones. The frame is definitely big enough, and I can't figure out any issues with the code.
Use a grid layout : https://docs.oracle.com/javase/tutorial/uiswing/layout/grid.html
Absolute positioning is not reliable.
Adding
frame.getContentPane().setLayout(null);
has fixed the error
Related
I had to create a matrix in javaFX. I created it without any problem with GridPane. The next thing is to create like "buttons" on the right side of the matrix, these buttons will move +1 element of the matrix to the right. Like this:
110 <-
101 <- //ie: I clicked this button
100 <-
The result:
110 <-
110 <-
100 <-
The way I handle this bit-shifting-moving was with circular linked list. I don't have any problem with that I think you can ommit that part. I use this method:
private void moveRowRight(int index){
//recives a index row and moves +1 the elements of that row in the matrix.
}
cells is the matrix
The problem is that, first the matrix can be modified by the user input i.e. 5x5 6x6 7x7, so the number of buttons will also change. I tried using a BorderPane(center: gridpane(matrix), right: VBox()) and this is the part of the code how I added some HBox inside the Vbox (right part of the border pane) and using the setOnMouseClicked.
private void loadButtonsRight(){
for(int i = 0; i < cells[0].length ; i++){
HBox newBox = new HBox();
newBox.getChildren().add(new Text("MOVE"));
newBox.prefHeight(50);
newBox.prefWidth(50);
newBox.setOnMouseClicked(e -> {
moveRowRight(i);
});
VBRightButtons.getChildren().add(newBox); //where I add the HBox to the VBox (right part of the Border Pane)
}
}
}
But then there's this problem.
Local variables referenced from lambda expression must be final or effectively final
It seems that I cannot implement lambda with a value that will change. Is there any way to help me to put "buttons" that depends of the matrix size and that uses the method I've created?
The message tells you all you need to know to fix the problem:
Local variables referenced from lambda expression must be final or effectively final
Assign your changing variable to a final constant and use the constant value in the lambda instead of the variable:
final int idx = i;
newBox.setOnMouseClicked(e ->
moveRowRight(idx);
);
If you wish to understand this more, see the baeldung tutorial
https://www.baeldung.com/java-lambda-effectively-final-local-variables
I made a grid of buttons in JavaFX.
When I resize the window with the grid inside, the buttons resize within the grid as well.
The problem is that the text on those buttons doesn't resize along with them: it stays the same size all the time, so when the buttons grow big, there's a lot of empty space on a button and then a tiny little text in the middle, which looks terrible.
I would rather like the text to automatically resize along with these buttons to fit the empty space, so that when the entire user interface gets bigger, the text on the buttons gets bigger as well.
How can I accomplish that?
I tried setting the -fx-font-size in the CSS stylesheet to percentage values, but it doesn't seem to work the same way as for websites: the text doesn't scale as a percentage of its container, but as a percentage of some predefined text size.
Edit
This is not a duplicate! Stop marking each question out there as duplicate! If it has been answered, I wouldn't have asked it in the first place!
From what I see, the first of those threads was about a situation where someone wanted to set the size/style of the text for newly-created buttons to account for the current size of their container etc. This is not what I need, because I want the buttons which has been already created as well to automatically resize their texts when these buttons resize inside their container in some way.
The other thread was about scaling the text along with the root container / window with a preset font size. This is also different from what I need, because I don't want the text to be scaled with the window, but with the sizes of the buttons themselves. And it has to be scaled in a certain way: to always fit the size of the button. You know: the text stays the same, but stretches so that it always fits the inside of the button (with a little padding, not a huge empty area around the text).
It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself (either the built-in one or a subclassed one), not manually by its encompassing container iterating over all these buttons and updating their text's sizes (which would be dumb way to do it).
This is, liked the linked questions, something of a hack: but consider scaling the text node inside the button instead of changing the font size. This seems to work ok:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
public class ScaledButtons extends Application {
#Override
public void start(Stage primaryStage) {
GridPane root = new GridPane();
root.setHgap(5);
root.setVgap(5);
for (int i = 1; i <= 9 ; i++) {
root.add(createScaledButton(Integer.toString(i)), (i-1) % 3, (i-1) / 3);
}
root.add(createScaledButton("#"), 0, 3);
root.add(createScaledButton("0"), 1, 3);
root.add(createScaledButton("*"), 2, 3);
primaryStage.setScene(new Scene(root, 250, 400));
primaryStage.show();
}
private Button createScaledButton(String text) {
Button button = new Button(text);
GridPane.setFillHeight(button, true);
GridPane.setFillWidth(button, true);
GridPane.setHgrow(button, Priority.ALWAYS);
GridPane.setVgrow(button, Priority.ALWAYS);
button.layoutBoundsProperty().addListener((obs, oldBounds, newBounds) ->
scaleButton(button));
button.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
return button ;
}
private void scaleButton(Button button) {
double w = button.getWidth();
double h = button.getHeight();
double bw = button.prefWidth(-1);
double bh = button.prefHeight(-1);
if (w == 0 || h == 0 || bw == 0 || bh == 0) return ;
double hScale = w / bw ;
double vScale = h / bw ;
double scale = Math.min(hScale, vScale);
button.lookup(".text").setScaleX(scale);
button.lookup(".text").setScaleY(scale);
}
public static void main(String[] args) {
launch(args);
}
}
An alternate approach to get a similar effect could be to subclass com.sun.javafx.scene.control.skin.ButtonSkin and override the layoutLabelInArea(double x, double y, double w, double h, Pos alignment) method from the skin's parent (LabeledSkinBase). You can then explicitly assign the updated skin to your button (either via CSS or via Java API calls).
Doing so would requires the subclassing of com.sun APIs which could change without notice in subsequent JavaFX releases. Also layoutLabelInArea is reasonably complex in its operation so changing the layout logic could be a little tricky. Certainly, James's suggestion of applying a text rescaling operation based upon a listener to the layout bounds property is simpler in this particular case.
I'm not necessarily advocating this approach, just providing a route to something that you could create that would satisfy your goal of: "It is the button's size which is to determine the size of the text on it, not the window or container or something else, and it needs to be done automatically by the button itself".
I am making a grid-style game/simulation based on bugs "sensing" and eating food. I am using a gridPane (called worldGrid) of labels to show the grid of bugs and food. This is obviously going to be constantly updated when a bug moves cells towards food etc.
I currently have a function updateGrid(int col, int row, String cellContent) which I want to replace the label at [row,col] with a label that has the new text in cellContent.
I have the follow which works
worldGrid.add(new Label(cellContent), row,col);
however im worried that that is just adding a label on top of the current label and obviously over 100 iterations of the simulation thats not ideal.
I have tried this before adding the label:
worldGrid.getChildren().remove(row,col);
However I then get an IllegalArgumentException when trying to do the add line.
Any ideas on how to do this? Or even better, any ideas on how best to show a constantly changing grid that will eventually use sprites instead of text?
The col/row provided by grid.add(node, col, row) (ATTENTION first comes col!) is only a layout constraint. This does not provide any means to access columns or rows like slots in a 2-dimensional array. So to replace a node, you have to know its object itself, e.g. remember them in a separate array.
Then you are able to call getChildren().remove(object)... e.g.:
GridPane grid = new GridPane();
Label first = new Label("first");
Label second = new Label("second");
grid.add(first, 1, 1);
grid.add(second, 2, 2);
second.setOnMouseClicked(e -> {
grid.getChildren().remove(second);
grid.add(new Label("last"), 2, 2);
});
box.getChildren().addAll(grid);
I agree with Jens-Peter but I would add that you can use GridPane's getColumnIndex and getRowIndex to obtain a particular node's location.
For example ( this is in a component which extends GridPane ):
// set the appropriate label
for (Node node : getChildren()) {
if (node instanceof Label
&& getColumnIndex(node) == column
&& getRowIndex(node) == row) {
((Label)node).setTooltip(new Tooltip(tooltip));
}
}
in other words, go through all the nodes in the GridPane and check for a match of the conditions you want, in this case row and column.
You called getChildren().remove() method directly will cause the gridpane to go out of sync with the constraints. When you add, it also setup the constraint for the node. Add clearConstraints() method.
i have done a small test on LibGdx, on Multi-line Label, it seems that i cant get the wrapped line's height. Following is the code. Theoretically, height for aLebel should be > bLabel. But the result appear the same.
code:
aLabel.setText("this is a super long long long text that need wrapping."); // line wrapped into 3 lines
aLabel.setWrap(true);
aLabel.setWidth(470);
doLog("aLabel.getHeight(): " + aLabel.getHeight());
bLabel.setText("this is short."); // unwrapped line
bLabel.setWrap(true);
bLabel.setWidth(470);
doLog("bLabel.getHeight(): " + bLabel.getHeight());
result:
aLabel.getHeight(): 45.0
bLabel.getHeight(): 45.0
Do anyone have any idea how to get the actual multi-line height in LibGdx? Thanks in advance.
I had this issue for years and accidentally solved it by setting the width and packing the label twice. Note that multiline labels were never intended to figure out their own width, so you have to set them externally, preferably from it's parent.
public Label createLabel() {
// Create label and set wrap
Label label = new Label("Some long string here...", skin);
label.setWrap(true);
// Pack label
label.pack(); // This might not be necessary, unless you're changing other attributes such as font scale.
// Manual sizing
label.setWidth(textWidth); // Set the width directly
label.pack(); // Label calculates it's height here, but resets width to 0 (bug?)
label.setWidth(textWidth); // Set width again
return label;
}
LibGDX version used: 1.6.4
Pack sizes the widget to its pref size, nothing more. Pref width of a label with wrapping is 0.
Label label = new Label(...);
label.setWrap(true);
label.setWidth(123);
label.setHeight(label.getPrefHeight());
I had the same issue and it seems there doesn't exist a method in Label class to solve this. Also, I agree with you, the getHeight() method should return the real height of the Actor, so I don't know if that's a bug or there is a reasoning behind that behaviour.
Anyways, how I solved the issue is by using BitmapFont's getWrappedBounds method. It's not short, but for your example it would be the following:
doLog("aLabel.getHeight(): " + aLabel.getStyle().font.getWrappedBounds(aLabel.getText(), aLabel.getWidth()).height);
This could be done by adding a restriction to the cell that contains the Label in the Table:
Label label = new Label("Example", new Label.LabelStyle(font, Color.WHITE));
label.setWrap(true);
Table table = new Table();
table.add(label).width(WITH);
For more information about how to use Table go to: https://github.com/libgdx/libgdx/wiki/Table
so I'm trying to make a battleship game, and I implemented a 10x10 grid containing gifs of some pixelated water. Now, what I'm trying to do is offset the grid one cell down and to the right so I can place the numbers and letters along the side and top like in a real battleship game. The problem is that when I try to offset it, it removes either the entire right side column of cells and reduces the window accordingly, and visa versa with the bottom. Here's my code:
EDIT 3: I have replaced the gif so that it can be used by everyone, simply copy the code and run it.
public class ButtonGrid {
JFrame frame=new JFrame(); //creates frame
JLabel[][] grid; //names the grid of buttons
public ButtonGrid(int width, int length) throws MalformedURLException{
URL urlPic = new URL("http://i47.tinypic.com/14wswi9.gif");
ImageIcon urlPicture = new ImageIcon(urlPic);
frame.setLayout(new GridLayout(width,length)); //set layout
grid=new JLabel[width][length]; //allocate the size of grid
for(int y=0; y<length; y++){
for(int x=0; x<width; x++){
grid[x][y]= new JLabel(urlPicture); //creates new button
frame.add(grid[x][y]); //adds button to grid
}
}
grid[1][1].setForeground(Color.red);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(270, 400);
frame.pack(); //sets appropriate size for frame
frame.setVisible(true); //makes frame visible
}
public static void main(String[] args) throws MalformedURLException{
new ButtonGrid(11,11);//makes new ButtonGrid with 2 parameters
}
}
EDIT: It seems that I may not have made this really clear. On a real battleship game, there is a row of numbers and a column of letters. The rest of the grid is simply water with the ships inside of it. With the code I have provided, the entire grid is water, and I want to offset to place a row of numbers and a column of letters just like in a real battleship board. If I try to place anything on top of the current grid, it doesn't change anything. I tried to change the foreground color and even changed the picture of one of the cells to something different, and the grid still remained the same; a 11 x 11 grid of animated pixel water.
Second EDIT: I have rewritten my code to draw a grid on top and on the left of the window, but now I am getting a null pointer exception. I don't exactly know what is wrong with this:
EDIT FOUR: Removed un-needed code.
One way to get what you need is to use the same technique I used in the Chess GUI with the columns (A-H) and rows (8-1) as seen below. Have a look over the code and see if you can implement that in your own code.
Failing that, post an MCVE (Minimal Complete and Verifiable Example) of your own (as opposed to uncompilable code snippets). One way to get image(s) for an example is to hot-link to the images seen in this answer (again as the linked code does).
Not sure I really understand the question, but I would think you should be using a GridLayout if you want to display components in a Grid.
//frame.setLayout(new FlowLayout());
frame.setLayout( new GridLayout(0, 11) );
Or maybe you would do something like this
JPanel topPanel = new JPanel( new GridLayout(1, 11) );
// use a loop to create 11 labels for the letters and add the labels to the panel
JPanel leftPanel = new JPanel( new GridLayout(10, 1) )
// use a loop to create 10 labels for the letters and and the labels to the panel
Use your current loop to create your "gamePanel" with the 10 x 10 grid of labels.
Then you add the 3 panels to the frame using:
frame.add(topPanel, BorderLayout.NORTH);
frame.add(leftPanel, BorderLayout.WEST);
frame.add(gamePanel, BorderLayout.CENTER);