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.
Related
I found this
In which the accepted answer is almost perfect, but I would like to instead add the elements to an ArrayList. Is this possible?
I'll try to be concise: I'm creating a JavaFX application and I have an ArrayList containing all my TextFields. I used this list to add an event handler for the Action event to each field, and this handler calls a method that moves focus to the next field in the list (this way the user can press return and will navigate to the next field automatically).
In the same loop I also add an event listener to each field, so that if a field loses focus another method is called to update the form. In this way the user can navigate however they choose (return, tab, mouse click, etc.), and the form will update automatically without the need for a button click when they are finished entering data.
Now, in the update method, I determine which field triggered the event and validate the text in the field and parse a double from it. I then need to do something with that data. Here is where I want to use my ArrayList of "worker functions." I can simply create a parallel ArrayList that matches the index order of the field traversal ArrayList, and call the correct update method.
I want to do it this way because each TextField is tied to a specific member of a specific object for example - the TextField qtyOrdered would be tied to the workOrder.qtyOrdered member, and the TextField materialWidth would be tied to the masterRoll.materialWidth member. These objects would of course have their own accessors and mutators, so I wanted to use the interface to create all these methods beforehand and call the correct one based on the index of the TextField.
I want something like this (pseudocode):
ArrayList<Worker> workerMethods = new ArrayList<>();
workerMethods.add(new Worker() {
public void work(double input) {
workOrder.setQtyOrdered(input);
}
});
//Add remaining methods...
method updateForm(TextField caller)
{
get callerIndex;
// validate user input
if (valid)
workerMethods.get(callerIndex).work(input);
}
In fact, using the method I linked to, this works with arrays like so:
workerMethods[callerIndex].update(callerValue);
However, when I use the same syntax for adding to an ArrayList instead it obviously does not work.
I would like to use an ArrayList instead of an array if possible. This is because some fields in the form are removed from the traversableFields ArrayList when they are deactivated so that field traversal will skip these fields when they are hidden from view, and resume in the correct order when they are visible. For example, if the user wants to estimate the length of material on the master roll, they can click a CheckBox to show those fields.
The Action event handler for that CheckBox would then call my updateControls() method which would enable the required fields, set them to visible, and add them to the list of traversable fields. If they uncheck it, the reverse happens.
If I am using an array to store my update methods, the indexes may not match the indexes of my traversableFields List at any given time.
Edit: Added my actual code below (this is a simplified version I am using in a small test environment).
Code for creating the list of traversable TextFields:
// ArrayList of traversable TextFields for form navigation.
private ArrayList<TextField> traversableFields = new ArrayList<>();
// Add all TextFields to the ArrayList.
traversableFields.addAll(Arrays.asList(field1, field2, field3));
// Loop through the ArrayList to add handlers/listeners.
for (int i = 0; i < traversableFields.size(); i++) {
// Get the Control at index i in the ArrayList.
TextField currentControl = traversableFields.get(i);
// Add an event handler so that when the user presses return we can
// move to the next field in the ArrayList.
currentControl.addEventHandler(ActionEvent.ACTION, e ->
moveToNextField(e));
// Add a listener so that if a field loses focus we update the form.
currentControl.focusedProperty().
addListener((obs, wasFocused, isNowFocused) ->
{
if (!isNowFocused) {
updateForm(currentControl);
}
});
}
Code for creating the array of worker methods:
interface Update { public void update(double input); }
class Label1 { public void update(double input) { label1.setText(String.valueOf(input)); } }
class Label2 { public void update(double input) { label2.setText(String.valueOf(input)); } }
class Label3 { public void update(double input) { label3.setText(String.valueOf(input)); } }
Label1 l1 = new Label1();
Label2 l2 = new Label2();
Label3 l3 = new Label3();
Update[] updateFunctions = new Update[] {
new Update() { public void update(double input) { l1.update(input); } },
new Update() { public void update(double input) { l2.update(input); } },
new Update() { public void update(double input) { l3.update(input); } }
};
The snippet above is basically exactly what they did in the link I posted. As I mentioned earlier, this works. I can call updateFunctions[callerIndex].update(callerValue); and it does what I want. Here is the method called by the event handler:
// Move to the next field in the form. Called by the Action Event for each
// field in the traversableFields ArrayList.
private void moveToNextField(ActionEvent event)
{
// Get the TextField that triggered the event.
TextField caller = (TextField)event.getSource();
// Get the index of this Control from the ArrayList.
int callerIndex = traversableFields.indexOf(caller);
// If we have reached the end of the list then we move to the
// first field in the list.
if (callerIndex == traversableFields.size() - 1)
traversableFields.get(0).requestFocus();
// Otherwise move to the next field.
else
traversableFields.get(++callerIndex).requestFocus();
}
and here is the method called by the focus listener:
// Update the form. This will contain the majority of functional code
// and will be called automatically when any TextField loses focus.
private void updateForm(TextField caller)
{
// Get the index of the TextField
int callerIndex = traversableFields.indexOf(caller);
// Get the CSS id of the TextField for testing purposes.
String callerID = caller.getId();
// Get the TextField contents.
String callerText = caller.getText();
// Flag variable.
boolean fieldEmpty = callerText.equals("");
// Double for storing parsed input.
double callerValue = 0;
// If the field is not empty, ensure that it contains valid data before
// proceeding.
if (!fieldEmpty)
{
try
{
callerValue = Validation.getDouble(callerText);
updateFunctions[callerIndex].update(callerValue);
clearError(caller);
}
catch (Exception e)
{
// If the input was invalid, alert the user and move focus
// back to the offending TextField.
System.out.println("Invalid input.");
markEntryInvalid(caller);
caller.requestFocus();
}
}
// Trace statements.
System.out.println("updateForm() called by " + callerID);
System.out.println("Parsed value was " + callerValue);
}
However, I want to use an ArrayList instead of an array. But if I do this:
ArrayList<Update> updateMethods = new ArrayList<>();
updateMethods.add(new Update() { public void update(double input) { l1.update(input); } });
I get <identifier> expected error.
If I understand your question correctly, what you want to know is how to initialize an ArrayList with a given collection of values?
That would be done like this:
List<Workers> myWorkers = new ArrayList<>(
Arrays.asList(
new Worker() { ... },
new Worker() { ... }
)
);
Arrays.asList(element1, element2, ...) returns an immutable list of these elements. And the constructor of ArrayList can take a Collection to initialize the list.
Of course, you can also create the list and add single elements. The resulting list is the same:
List<Workers> myWorkers = new ArrayList<>();
myWorkers.add(new Worker() { ... });
myWorkers.add(new Worker() { ... });
In both cases, the resulting list can be modified using add and remove methods.
Two notes on the usecase, though:
First, a Map might be better suited for your usecase, because you don't need to take care about indexes:
// initialization:
Map<TextField, Worker> workers = new HashMap<TextField, Worker>();
workers.put(textfield1, worker1);
workers.put(textfield2, worker2);
// ...
void updateForm(TextField caller) {
workers.get(caller).work();
}
Second, if your Worker interface only has one method, since Java 8 you can use it as a functional interface with a closure. So the initialization would look like this:
Map<TextField, Worker> workers = new HashMap<TextField, Worker>();
workers.put(textfield1, input -> { /* do something with input here */ });
workers.put(textfield2, input -> { /* do something with input here */ });
And maybe your Worker interface is even not needed and you can just use Consumer<InputType> instead.
You can create List<Runnable> and execute the code in the current thread or in the parallel thread:
List<Runnable> workerMethods = List.of(
() -> System.out.println("worker 1"),
() -> System.out.println("worker 2"));
// execute code in the current thread
workerMethods.get(0).run(); // worker 1
// execute code in a new thread
new Thread(workerMethods.get(1)).start(); // worker 2
Similarly, you can create Map<String, Runnable>:
Map<String, Runnable> workerMethods = Map.of(
"w1", () -> System.out.println("worker 1"),
"w2", () -> System.out.println("worker 2"));
// execute code in the current thread
workerMethods.get("w1").run(); // worker 1
// execute code in a new thread
new Thread(workerMethods.get("w2")).start(); // worker 2
Might I suggest an entirely different approach?
Why not use the user data field of the TextField to point to your own object that can handle all of your needs.
e.g.
class FieldContext {
TextField previous;
TextField next;
Update updater;
// add whatever else you need, e.g. validation interface etc.
}
so when you get an event from the TextField you can just call:
FieldContext fieldCtx = (FieldContext)((TextField)event.getSource()).getUserData();
and then you are free to handle whatever specific event processing is needed having the context information for the TexTField.
I find a lot of people overlook the fact that you have a field for user data on the controls. I find it can simplify things quite a bit
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();
});
Solved: I figured that it isn't a bug of JavaFX, it is made like that on purpose, so you can request the result multiple times from the dialog if you don't wanna save it into a variable. To flush the cache i used setResult(null) on the dialog. That did the job for me :)
I've created a Dialog to edit/create a Person using JavaFX. But i guess i found a bug. I've added two ButtonType-objects to my dialog. One for saving and one for aborting the actions. When i use those buttons it works just fine. But if i press the 'X' to close the dialog-window, the dialog automaticly returns the last result again. That means if i aborted my last action and in my current one i press 'X' to close the window, the dialog returns no result since the abort-button didn't have one last time. But if i pressed the save-button on my last action and i press 'X' in my current one it returns the same person again, since save-button had this person in its result last time. How can i make the dialog changing to no result on closing?
Here is the action i've created:
this.createPersonAction = new Callback<ButtonType, PersonSession>() {
#Override
public PersonSession call(final ButtonType param) {
if (param.equals(PersonDialogController.this.saveButton)) {
final String firstName = PersonDialogController.this.firstNameField.getText();
final String lastName = PersonDialogController.this.lastNameField.getText();
final Person p = BeanFactory.createPerson(firstName, lastName);
if (p != null) {
return new PersonSession(p);
}
}
return null;
}
};
And here you have my two ButtonType-Objects:
private final ButtonType saveButton = new ButtonType(GuiStringRresource.LABEL_SAVE_BUTTON, ButtonData.OK_DONE);
private final ButtonType abortButton = new ButtonType(GuiStringRresource.LABEL_ABORT_BUTTON,
ButtonData.CANCEL_CLOSE);
Use the Dialog.setOnCloseRequest method to handle this case.
First of all, hi everyone. I'm new here and i just have started learning gwt. There is one thing that i dont understand regarding stockwatch example. First of all, there is add stock method, which adds new stock to a list. Inside that method we also add remove button and attach listener to it. My question is, how is it possible that indexOf attr is set, when u dont enter that part of code when u add new stock, u only enter that part when u click remove button. But this code works, and i cant find explanation why..I tried to debug app, but still having trouble to undestand. Sorry for my bad english.
private void addStock()
{
final String symbol = newSymbolTextBox.getText().toUpperCase().trim();
//validaciju vrsimo upotrebom regularnih izraza
if(symbol.matches("[0-9A-Z]"))
{
Window.alert("'" + symbol + "' is not a valid symbol.");
newSymbolTextBox.selectAll();
return;
}
newSymbolTextBox.setText("");
if(stocks.contains(symbol))
{
return;
}
int row = stocksFlexTable.getRowCount();
stocks.add(symbol);
stocksFlexTable.setText(row, 0, symbol);
Button removeStockButton = new Button("x");
removeStockButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
int indexOf = stocks.indexOf(symbol);
stocks.remove(indexOf);
stocksFlexTable.removeRow(indexOf + 1);
}
});
stocksFlexTable.setWidget(row, 3, removeStockButton);
refreshWatchList();
}
My question is, how is it possible that indexOf attr is set, when u
dont enter that part of code when u add new stock, u only enter that
part when u click remove button.
Read about anonymous inner classes as event listeners. new ClickHandler() provides a handler to each Button, which catches the click event, with the functionality that will remove the line, whenever the specific delete button is pressed. Every button has it's own clickHandler.
indexOf is not a great name for a variable. I would rather stick to removedIndex, used in the www.gwtproject.org sample code:
// Add a button to remove this stock from the table.
Button removeStockButton = new Button("x");
removeStockButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
int removedIndex = stocks.indexOf(symbol);
stocks.remove(removedIndex);
stocksFlexTable.removeRow(removedIndex + 1);
}
});
stocksFlexTable.setWidget(row, 3, removeStockButton);
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
}