I have two sets of Objects, first stores Labels and the second one stores Buttons. Every label is connected to its own button i.e label11 is placed on btn11 and so on. I want to change visibility of labels if I click its button. I managed to do this in this way:
btn11.setOnAction(x->label11.setVisible(true));
btn12.setOnAction(x->label12.setVisible(true));
btn21.setOnAction(x->label21.setVisible(true));
btn22.setOnAction(x->label22.setVisible(true));
btn111.setOnAction(x->label111.setVisible(true));
btn112.setOnAction(x->label112.setVisible(true));
btn121.setOnAction(x->label121.setVisible(true));
btn122.setOnAction(x->label122.setVisible(true));
Doing this way I have to write every single line one by one which makes my code unnecessary long and hard to read.I tried placing them into two lists but couldn't find a way to iterate through two lists in one loop.
So far I managed to create something like this:
for(int i=0;i<listOfButtons.size();i++){
listOfButtons.get(i).setOnAction(x->listOfLabels.get(i).setVisible(true));
}
But I'm getting error which says that variables in lambda expressions should be final.
I'd be glad for any help.
Thank you in advance.
Assign the values you need to a final variable before using it in the lambda. This will also increase readability.
for (int i = 0; i < listOfButtons.size(); i++) {
final Button button = listOfButtons.get(i);
final Label label = listOfLabels.get(i);
button.setOnAction(x -> label.setVisible(true));
}
You can do it in a "smooth"-er way by utilizing some additional Java 8 functionality instead of the traditional for and final declarations which should be resolving an issue with i and can therefore be fixed with final int fi = i; and use the final one instead in your lambda. Or this:
IntStream.range(0, buttons.size())
.forEach(i -> buttons.get(i).setOnAction(e -> labels.get(i).setVisible(true)));
Additionally, given your example of poorly named label and button references, I would also suggest you put the pair in a convenience class and a single list of the components.
class ButtonLabelPair {
Button button;
Label label;
}
List<ButtonPairs> buttonPairs = ...
And set the actions like:
buttonPairs.forEach(p -> p.button.setOnAction(e -> p.label.setVisible(true)));
Or, you could do it in the constructor of the pair:
class ButtonLabelPair {
...
public ButtonLabelPair(Button button, Label label) {
this.button = button;
this.label = label;
this.button.setOnAction(e -> this.label.setVisible(true));
}
}
Related
I am trying to make a Java tic tac toe game and am having some issues with the code.
What I want to do is just increment an int when the user clicks a button.
The problem is is that the compiler says "Error:local variables referenced from a lambda expression must be final or effectively final". What I understand this to mean is that I can only create an value and not redefine it in the same method but I am new to java and have no idea for a work around.
I am thinking of a work around by having multiple classes but this is overly complicated and I just want to add to the int like in python. If anyone has any idea on how to do this or a work around then it would be greatly appreciated.
Below is my program
public class TicTacToe extends Application {
#Override
public void start(Stage primaryStage) {
//Some code here
//this is Cerateing all the buttons
//the buttons are formated in a 3X3 array for a total of 9 buttons
Button B1 = new Button();
//More buttons
//this sets the size of the button
//this is the int that tracks what button the user has pressed
//this is my value
int User = 0;
//this gets if the user has clicked any of the buttons shold in therory do somthing
B1.setOnAction((event) -> {
B1.setText("X");
//this is where I want to add to an value
User++;
});
//More code
System.out.println(User);
}
}
You didn't specify where you got error, but this part should give an error:
int User = 0;
B1.setOnAction((event) -> {
B1.setText("X");
User++;
});
The User variable either needs to be a field, a wrapper like AtomicInteger or new int[1], or a final object. Here are a couple of ways to get around this. First of all, you can just make User a field.
private int User = 0;
Then don't declare it in the method. Another thing you can do is make a new int[1].
int User = new int[]{0};
B1.setOnAction((event) -> {
B1.setText("X");
User[0] ++;
});
Then when accessing it, you can use User[0]. Another way is using AtomicInteger. I don't really like it that much, but here is how you can use it anyways:
AtomicInteger User = new AtomicInteger(0);
B1.setOnAction((event) -> {
B1.setText("X");
User.incrementAndGet();
});
I have the following code to set up a Button in JavaFX 8:
Button button = new Button("target name");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
Button pressed = (Button)event.getSource();
// next target name is the title of the button
handleClick(pressed.getText());
}
});
I would like to pass some information to the handleClick() routine. In this case I have a String that is a "target name" and handleClick() is a method that knows what to do with that. Right now I'm passing that information by making it the name of the Button. Is there a better way? For instance, what if I wanted to pass two pieces of information? Or, what if the info I want to pass is not a String?
The handle() routine only accepts an ActionEvent parameter. I can't find anything in ActionEvent that would help me here.
One idea I had: Maybe I could write my own SmartButton subclass that extends Button, and include in that subclass some additional info that is passed in at the time the SmartButton is constructed (so in the subclass I would only implement an extra constructor and one or more new getters/setters). The handleClick() routine would then have access to that information via getters called on (SmartButton)event.getSource().
This seems like a common UI programming issue. What is the best practice?
Thanks.
In the code snippet you posted, the event handler is only associated with the button created in the first line. Furthermore, since it's an anonymous inner class you know you only have the one instance of that class, and consequently that event handler cannot be associated with any other control. So your code is completely equivalent to
Button button = new Button("target name");
button.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
handleClick(button.getText());
}
});
or, using lambda expressions:
Button button = new Button("target name");
button.setOnAction(event -> handleClick(button.getText()));
You can basically always do this, even if the data needed to handle the button press is not the text in the button. Consider a calculator app (restrict it to integers for simplicity). You would likely have some variable storing the current value displayed:
private int value ;
and then you want a bunch of numeric buttons which "append" their own value to the current value (so if the current value is 1 and you press "2", the current value should be 12, etc):
You would just need
for (int i = 0 ; i <= 9 ; i++) {
Button button = createButton(i);
// place button in UI...
}
with
private Button createButton(int buttonValue) {
Button button = new Button(Integer.toString(buttonValue));
button.setOnAction(e -> value = value * 10 + buttonValue);
return button ;
}
Some variation on this idea will always give you what you need.
I am trying to create array of TextFields in JavaFX but getting error.
I have 10 TextFields: path1, path2... path10.
//initialization
#FXML
private TextField path1,path2,path3,path4,path5,path6,path7,path8,path9,path10;
#FXML
private TextField[] paths = {path1,path2,path3,path4,path5,path6,path7,path8,path9,path10};
However, when I write
String text = paths[0].getText();
paths[1].setText(name);
This first line gets me NullPointerException.
The solution I found is to use Initizlizatior of my Đ¡ontroller, but this is so ugly:
public void initialize(URL fxmlFileColation, ResourceBundle resources) {
paths[0] = path1;
paths[1] = path2;
paths[2] = path3;
paths[3] = path4;
paths[4] = path5;
paths[5] = path6;
paths[6] = path7;
paths[7] = path8;
paths[8] = path9;
paths[9] = path10;
}
How can I get rid of manual assinment in many lines and make
#FXML
private TextField[] paths = {path1,path2,path3,path4,path5,path6,path7,path8,path9,path10};
work?
You cannot make this exact code work. The array creation in
private TextField[] paths = {path1,path2,path3,path4,path5,path6,path7,path8,path9,path10};
happens in the initializer. The loading process for fxml files works like this however:
Create controller class instance (or use a existing one).
Load fxml content injecting objects created by the loader.
Call initialize on the controller, if it exists.
The above code is executed in step 1 while the objects you want to store in the array are created later in step 2.
You can work around this by using the fxml to create a List of the TextFields and inject it to the controller (see my answer to Grouping together JavaFX FXML Objects)
Of course you can also create a array of TextFields containing specific elements using a array initializer in the initialize method:
public void initialize() {
paths = new TextField[] {path1,path2,path3,path4,path5,path6,path7,path8,path9,path10};
}
I don't think you can. You need to declare the variables separately so FXML can initialise the variable. Then, you would need to make your TextField array work by setting each element to something. The best way of doing it would be to use the solution you have in the initialize(...) function and use it for what it meant to do – initialising things.
A simple solution i use
TextField[] txt = new TextField[beanFields.length];
for (int i=0; i<=beanFields.length-1; i++) {
TextField textField = new TextField();
txt[i] = textField;
textField.setPrefWidth(200);
textField.setPrefHeight(32);
}
I've been looking all over, and i cant find anyone who can solve this problem. I'm making a game, and in that game, i have editable controls. the controls window is a seperate JFrame, and when i click the confirm button, it is supposed to write the items in the JTextFields (holding the controls) to a file. but that wasnt working, so instead i have it print the arraylist that holds the values. here is the code:
public void writeControls() {
ArrayList<String> al = new ArrayList<String>();
al.add(up.getText());
al.add(down.getText());
al.add(left.getText());
al.add(right.getText());
al.add(jump.getText());
al.add(duck.getText());
al.add(attack.getText());
for (int i = 0; i < al.size(); i++) {
System.out.println(al.get(i));
}
System.exit(0);
}
the problem is this: if i change the final JTextField attack or any other one for that matter, and click submit, the system prints out the default controls. for example, if the JTextFields have the values w,a,s,d,r,t,q and i change the value q to i, it prints out q. what am i doing wrong? thanks in advance!
EDIT 1:
code for the textfields, and the FILES.... is simply a string stored in a different class. the class setText() is below the textfields.
up = new JTextField(setText(FILES.controlsFileFinalDir, 1));
down = new JTextField(setText(FILES.controlsFileFinalDir, 2));
left = new JTextField(setText(FILES.controlsFileFinalDir, 3));
right = new JTextField(setText(FILES.controlsFileFinalDir, 4));
jump = new JTextField(setText(FILES.controlsFileFinalDir, 5));
duck = new JTextField(setText(FILES.controlsFileFinalDir, 6));
attack = new JTextField(setText(FILES.controlsFileFinalDir, 7));
public String setText(String fileDir, int lineNum) {
String txt = "";
txt = io.readSpecificLine(fileDir, lineNum);
txt = switchCase(txt);
return txt;
}
switchcase() is only taking what you have written in the text file that these are getting the values from, and translating them. so if the value is 0, it is turned into Space, etc. io.readSpecificLine(); is only to get the line of text from the file. does this help?
EDIT 2:
i just was dinking around and found out that if i set the JTextField text by using setText(""); then use getText(); it works. so the problem is that when i change it manually, and use getText(); it wont work. Why?
To update the text to a currently existing JTextField, I would establish the JTextField as a class variable, and create a setter/getter method to adjust it (which I'm assuming you're doing).
According to your methods, you would use something like:
up.setText(setText(FILES.controlsFileFinalDir, 7));
Edit: **The first setText is the JTextField.setText, the second setText is your public method you posted. I'm assuming your second getText() isn't working because you're probably not setting the text correctly.
Without seeing more code, I can't really give a better guess.
The main possibilities:
(1) The text fields have their editable property set to false.
(2) You are creating multiple copies of the JTextFields, then editing a new one on the screen, but referring to the old one when you get the value.
(3) You have a ValueChanged or LostFocus event handler that is resetting the text fields to their defaults
(4) It is actually JFormattedTextField not a JTextField
If I was you, I would try to debug the programm. You will probably do some Mistake in your code, you won't be able to see, by just checking the code.
For example in which order do you call the functions and so on, maybe you have a fault here, or maybe you have several threads, so you try to read the Textfields without even set them and so on ... It's hard to say without reviewing the whole Code.
So if you use eclipse you can follow this link for an explanation on how to debug: http://www.vogella.com/articles/EclipseDebugging/article.html
Netbeans or any other IDE should support debugging as well.
This may seem like a strange thing to suggest, but I think this is an issue with pointers. If you create a new string before passing it in, JTextField will be able to change it internally and return what you expect when asked for the modified value.
down = new JTextField("" + setText(FILES.controlsFileFinalDir, 2));
// or
down = new JTextField(new String(setText(FILES.controlsFileFinalDir, 2)));
You might want to try the following:
create a class Test.java
import java.util.ArrayList;
import javax.swing.JTextField;
public class Test implements Runnable {
private ArrayList<JTextField> textFields = null;
private ArrayList<String> stringList = null;
public Test(ArrayList<JTextField> textFields, ArrayList<String> stringList) {
this.textFields = textFields;
this.stringList = stringList;
}
#Override
public void run() {
for ( JTextField textField : this.textFields )
this.stringList.add( textField.getText() );
}
}
and then, at the place where you use the "getText() method .. "
do the following...
ArrayList<JTextField> textFields = new ArrayList<JTextField>();
// add all the JTextField to textFields
ArrayList<String> stringList = new ArrayList<String>();
Test test = new Test( textFields, stringList );
SwingUtilities.invokeLater( test );
// check if the stringList is populated.
If this work, then what I believe is that, for some reason, the JTextField hasn't finished
"setting" the text, and before it finishes your getText() was called. I've had similar problems before, and this solved my problem that time, but still, this might not be the perfect solution.
First, you should change your "setText()" method name to something like "getTextFromFile()" it would be more readable
Then, if you are setting and reading the new text in different threads, my bet is that the setText() is taking long to return, because it is accessing the file system, while the method that read the values run instantly
I would try to do run a little test:
public void test(){ // must be run after the JTextFields be initialized
up.setText("TEST")
System.out.println(up.getText());
up.setText(setText(FILES.controlsFileFinalDir, 1));
System.out.println(up.getText());
}
If the test() prints the correct values, then we can assume that if you set and read the new value in the same thread it works fine
The other test I would do is:
public void testThread(){
new Thread(){
public void run(){
while(true){
if(up!=null){
System.out.println(up.getText());
}
try{
Thread.sleep(1000);
}catch(Exception e){
e.printStackTrace();
}
}
}
}.start();
}
It will print the value of up each 1 second, so that you can see if after some time you get the new value. If it does, then the answer is: Your setText() is taking long to run and you are reading the value before the new value is set
SOLUTION
none of the above answers were working for me, so i finally decided to just start over with that class. the few things i changed were the way i made the JTextFields. I made them as an array instead of individual objects. Second is the way i put what they say. When i initialized them, i was unable to get them to create WITH the text in the parameters. so i had to do that seperately. i changed some of the method names so as to reduce future confusion, and it worked! so im not sure what was up with that, maybe it was the way i did it, maybe just a fluke. it happens sometimes, so im sorry for the delay and waste of your time! thanks for all the answers anyway!
Try this:
textbox.setText(setFile(args)); // your function for set file
I'm a newbie in Java/JavaFX (I began yesterday evening). I'm building a dynamic GUI (crud) reading off a MySQL database.
I managed to display the data in a table and add a button next to each row.
Since the number of buttons is variable, I want to define only a common eventhandler.
The problem is that whenever I use event.getSource() (it's an ActionEvent) and display it, I get something like "Button[id=0, styleClass=button].
Question 1: Is there any way I could put the id in a variable? I can't get it out of the object.
As far as I know, I have to use the id, since I can't do something like this "if(event.getSource() == somebutton) {...}" since every generated button had the same variable name.
Now, this is the loop (inside a method called make_buttons) that builds the buttons. n_buttons is the number of buttons I want to build.
for(int counter = 0; counter < n_buttons; counter++){
String newtext = new String("btn"+counter);
Button btn = new Button();
btn.setText(newtext);
btn.setId(Integer.toString(counter));
btn.setOnAction(myHandler);
grid.add(btn,0,counter);
}
Note that I'm placing the buttons on a gridpane one on top of the other.
Before that part I have my handler:
final EventHandler<ActionEvent> myHandler = new EventHandler<ActionEvent>(){
public void handle(final ActionEvent event) {
Object new_output = event.getSource();
System.out.println(new_output);
event.consume();
}
};
Question 2: so, how can I differentiate which button fired the event in my particular case?
I know quite a few programming languages (Matlab, R, Python, C, Assembly, etc... but I'm a hobbyist), but it's the first time I'm working with GUI elements (except web languages and ActionScript 3).
In actionscript I could just do something like event.getCurrentTarget and the use it exactly as if it were the object itself to read the id, properties, etc.
I looked everywhere and couldn't find anything (maybe my terminology was a bit approximative...).
If I understand your question correcty, you can simply access the clicked button in you handle method with the following code:
Object source = event.getSource();
if (source instanceof Button) { //should always be true in your example
Button clickedBtn = (Button) source; // that's the button that was clicked
System.out.println(clickedBtn.getId()); // prints the id of the button
}