Dynamic attribute values in FXML? - java

In this JavaFX tutorial, address book application creation suggested. Registered person could be deleted, however before deleting, person must be selected in table view.
There will be an ArrayIndexOutOfBoundsException because it could not
remove a person item at index -1. The index -1 was returned by
getSelectedIndex() - which means that there was no selection.
To ignore such an error is not very nice, of course. We should let the
user know that he/she must select a person before deleting. (Even
better would be if we disabled the button so that the user doesn’t
even have the chance to do something wrong.)
Author is totally correct about "Even better would be if we disabled the button ...", however he selected the first way. I suppose, manipulation of the button state is the essential skill for JavaFX application development, so I tried to implement the better solution.
Of course, we can do it by way like this:
peopleTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
showPersonDetails(newValue);
boolean somebodySelected = peopleTable.getSelectionModel().getSelectedIndex() >= 0;
button.setDisable(!somebodySelected);
}
);
How ever I am interested for the other way: using dynamic attribute value for button:
<Button
mnemonicParsing="false"
onAction="#handleDeletePerson"
text="Delete"
disable="disableDeleteButtonFlag"
/>
If dynamic attribute value is possible, there is no need explicitly invoke button.setDisable() anymore. However, below code does not work.
#FXML
private boolean disableDeleteButtonFlag = true;
// ...
#FXML
private void initialize() {
// ...
peopleTable.getSelectionModel().selectedItemProperty().addListener(
(observable, oldValue, newValue) -> {
showPersonDetails(newValue);
disableDeleteButtonFlag = peopleTable.getSelectionModel().getSelectedIndex() < 0;
}
);
}

First off, I'm not sure if referencing a boolean field that way would even let the FXML file be loaded (you don't specific exactly how it "doesn't work"). But ignoring that, fields are not observable in Java which means updating the field will not automagically cause the disable property of the Button to be updated. This is why JavaFX has the [ReadOnly]Property interfaces and introduces the "JavaFX Bean" (an extension to Java Bean that adds a "property getter"); it allows observers to see changes in an object's properties. Note you can bind a writable property to an ObservableValue so it always has the same value.
Now, I would have expected an expression binding would be what you're looking for, but the following:
<ListView fx:id="list"/>
<Button disable="${list.selectionModel.selectedIndex == -1}"/>
Doesn't appear to work—I get an exception (using JavaFX 12.0.1):
Caused by: java.lang.ClassCastException: class java.lang.Long cannot be cast to class java.lang.Integer (java.lang.Long and java.lang.Integer are in module java.base of loader 'bootstrap')
at java.base/java.lang.Integer.compareTo(Integer.java:64)
at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo$5(Expression.java:1105)
at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
... 17 more
Using the selectedItem property instead:
<Button disable="${list.selectionModel.selectedItem == null}"/>
Gives a different exception:
Caused by: java.lang.NullPointerException
at javafx.fxml/com.sun.javafx.fxml.expression.Expression.lambda$equalTo$5(Expression.java:1105)
at javafx.fxml/com.sun.javafx.fxml.expression.BinaryExpression.evaluate(BinaryExpression.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.getValue(ExpressionValue.java:192)
at javafx.base/com.sun.javafx.binding.ExpressionHelper.addListener(ExpressionHelper.java:53)
at javafx.base/javafx.beans.value.ObservableValueBase.addListener(ObservableValueBase.java:55)
at javafx.fxml/com.sun.javafx.fxml.expression.ExpressionValue.addListener(ExpressionValue.java:201)
at javafx.base/javafx.beans.binding.BooleanBinding.bind(BooleanBinding.java:106)
at javafx.base/javafx.beans.property.BooleanPropertyBase$ValueWrapper.<init>(BooleanPropertyBase.java:254)
at javafx.base/javafx.beans.property.BooleanPropertyBase.bind(BooleanPropertyBase.java:168)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processPropertyAttribute(FXMLLoader.java:326)
at javafx.fxml/javafx.fxml.FXMLLoader$Element.processInstancePropertyAttributes(FXMLLoader.java:242)
at javafx.fxml/javafx.fxml.FXMLLoader$ValueElement.processEndElement(FXMLLoader.java:775)
at javafx.fxml/javafx.fxml.FXMLLoader.processEndElement(FXMLLoader.java:2838)
at javafx.fxml/javafx.fxml.FXMLLoader.loadImpl(FXMLLoader.java:2557)
... 17 more
As both of these exceptions are emanating from JavaFX code (i.e. not our code) this behavior is probably a bug. That, or I'm using expression bindings incorrectly—in which case I hope someone corrects me. Since attempting to bind the property in FXML is not working, the workaround is to bind the property in code.
<VBox xmlns="http://javafx.com/" xmlns:fx="http://javafx.com/fxml" fx:controller="Controller">
<ListView fx:id="list"/>
<Button fx:id="button"/>
</VBox>
public class Controller {
#FXML private ListView<YourType> list;
#FXML private Button button;
#FXML
private void initialize() {
button.disableProperty()
.bind(list.getSelectionModel().selectedIndexProperty().isEqualTo(-1));
}
}
The isEqualTo method will return a BooleanBinding whose value is true whenever the selectedIndex property holds -1. The disable property is then bound to this BooleanBinding so it always has the same value. You can read Using JavaFX Properties and Binding for more information.

Related

Neither BindingResult nor plain target object for bean name 'testCheck' available as request attribute When Attempting To Set up Boolean CheckBox

OK, I am new to Spring MVC, but I am trying to do something quite simple. I am adding a checkbox adjacent to a search input that will make it so that if the user clicks the check box all the content whether it has been previously deleted or not will come back in the search.
Now I have not even gone about setting up that functionality, but already have that part figured out. However, I started to set up the basics for the checkbox itself which will be a simple Boolean, and it throws the following error when I go the page in the stack trace.
Neither BindingResult nor plain target object for bean name 'testCheck' available as request attribute
Can anyone look through what I have here and tell me what I need to add to at the very least get the page to load? I'm not asking about how to make it function, I already have that in mind.
Here is what I have in my JSP
<label for="active">Show All Employees:</label>
<form:checkbox path="testCheck" />
Here is what I have for it in my bean, and it's set up in my employee.java bean which has no other problems at this point.
boolean testCheck;
public boolean isTestCheck() {
return testCheck; }
public void setTestCheck(boolean testCheck) {
this.testCheck = testCheck
}
Stopping right here for a moment, everything else in my bean is a column. This is not. Should I make this a column, every example I've seen this far does not show a Boolean checkbox being set as a column?
Finally, here's what I have in my controller thus far, although the error was throwing before I started adding to my controller, and to reiterate, I haven't added any of the functionality I am planning to this.
This is only a method in a longer controller
#RequestMapping("/viewer")
public ModelAndView loadViewerPage(
#RequestParam(value = "testCheck", required = false) boolean testCheck,
In order to get it worked, you have to initialize TestCheck object and set it as ModelMap attribute. So, your loadViewPage controller should look like this
public ModelAndView loadPageView(ModelMap model) {
TestCheck o = new TestCheck();
model.addAttributeA("testCheck", o);
// your code goes here
return new ModelAndView("...");
}

"Handler method not accessible" error in FXML (works only if I make method with "Event" or "ActionEvent" parameter)

I want to call handler method when any key on keyboard is pressed, and then get pressed key character. So I wrote this line for button in fxml file:
<Button fx:id="button" layoutX="126.0" layoutY="90.0" onKeyPressed="#handleButton" text="Test!" />
When any key is pressed, this should call handleButton method in controller class and pass KeyEvent parameter to it. So I wrote this method inside it:
#FXML
private void handleButton(KeyEvent event) {
System.out.println(event);
}
In the fxml file NetBeans shows error "Handler method is not accessible. Make public, or annotate with #FXML.", which I've already done.
As soon as I change from private void handleButton(KeyEvent event) to private void handleButton(Event event) NetBeans stops showing error and app works.
On this page I found answer, which uses onKeyPressed exactly the same as I do, so I'm really confused why it isn't working in my case.
Thanks for your help,
Vid
You probably imported wrong KeyEvent. It must be javafx.​scene.​input.KeyEvent.
Right-click with the mouse on your file.FXML and choose from options make controller and automatically the problem will be resolved and this error will be gone.

how to refer to the property of bean used in BeanEditForm (tapestry5) in Java?

I've got in my .tml file something like this:
<t:beaneditform t:id="adForm" object="editableAd"
reorder="actiontype,shops,movies,streams,widgets" ....
My question is how to access (refer) actionType, which is an Enum (and in fact SELECT) in .java file? I just need to handle event when user changes the value of this select (dropdown), obviously before submitting the form itself.
If something like this would work for me...
#OnEvent(component = "adForm.actionType", value=EventConstants.VALUE_CHANGED)
public void actionTypeValueChanged(String value) {
log.info("value is: " + value);
}
To be updated with the changed value in a Select html component on the client side, have a tapestry select component in your template file with a t:zone attribute (i.e. in your case it could point to any dummy zone, this is only needed to be set correctly if you need to update a zone when a value is changed)
Also set the t:value attribute to your enum variable in your page\component java file, usually this variable will be annotated with tapestry's #Property.
Example:
<t:select t:id="myEnumVariable" t:zone="dummyZone" t:value="myEnumVariable"/>
myEnumVariable is used to refer to your class's variable AND to act as an ID (i.e. the actual string myEnumVariable is used as an id), this is not necessary, but it's more readable and maintainable that way)
public class MyClass{
#Property
private MyEnum myEnumVariable;
#OnEvent(component = "myEnumVariable", value=EventConstants.VALUE_CHANGED)
public void actionTypeValueChanged(**MyEnum** newValue) {
this.myEnumVariable = newValue; // <<<<<<
log.info("value is: " + myEnumVariable );
}
}
If you don't mind using the ChenilleKit framework for tapestry you could try using the
framework's OnEvent mixin.
You 'll find the example on the link I share but basically you add two attributes the select tag:
<t:select t:id="myselect" ... t:mixins="ck/OnEvent" t:event="change" />
then you add the event handler on your java class:
#OnEvent(component="myselect", value='change')
public void onChangeDoSomething(String value) {
hope that helps, by the way I think Muhammad's answer is equally correct (and doesn't requires the use of an extra framework).

Which ChoiceBox-Event to choose?

I placed a ChoiceBox inside an fxml with JavaFX Scene Builder.
The FXML has a controller assigned to it.
My question is: Which event do I need to register if I want to know about changed values?
onInputMethodTextChanged="#languageSelectionModified"
this does not work with the following code
public void languageSelectionModified(Event event) {
ChoiceBox<String> box = (ChoiceBox<String>) event.getSource();
System.out.println(box.getValue());
}
and this only works for the initial click (i.e. opening the list, not when selecting an item):
onMouseClicked="#languageSelectionModified"
Although the Mouse-Events would never be a good choise because of situations where the touch or keyboard is the input-method, it still proves that the System.out can be reached.
I have absolutly no idea where those things are documentated (in the default Java-API they are not)
Add a listener to your #FXML injected choicebox in your controller:
choicebox.getSelectionModel().selectedItemProperty().addListener(choiceboxSelectionChangeListener);
You can also bind to the selected item:
label.textProperty().bind(choicebox.getSelectionModel().selectedItemProperty());
Here is an example of hooking up a listener in a controller for a ComboBox defined in FXML. Logic for a ChoiceBox is pretty much identical.
You can also use FXML onAction attribute:
<ChoiceBox onAction="#languageSelectionModified" />

NetBeans Platform - how to refresh the property sheet view of a node?

I am using the PropertySheetView component to visualize and edit the properties of a node. This view should always reflect the most recent properties of the object; if there is a change to the object in another process, I want to somehow refresh the view and see the updated properties.
The best way I was able to do this is something like the following (making use of EventBus library to publish and subscribe to changes in objects):
public DomainObjectWrapperNode(DomainObject obj) {
super (Children.LEAF, Lookups.singleton(obj));
EventBus.subscribe(DomainObject.class, this);
}
public void onEvent(DomainObject event) {
// Do a check to determine if the updated object is the one wrapped by this node;
// if so fire a property sets change
firePropertySetsChange(null, this.getPropertySets());
}
This works, but my place in the scrollpane is lost when the sheet refreshes; it resets the view to the top of the list and I have to scroll back down to where I was before the refresh action.
So my question is, is there a better way to refresh the property sheet view of a node, specifically so my place in the property list is not lost upon refresh?
The solution of firePropertySetsChange comes from this thread.
Just to make a clarification about my old answer as an unregistered user: Calling createSheet(null) will raise a NullPointerException. Use setSheet(createSheet()) instead.
The solution is to fire a property change for each of the changed property of the updated object. So, in context of the snippet in the question this could be something like:
public void onEvent(DomainObject event) {
// Do a check to determine if the updated object is the one wrapped by this node;
// if so fire a property sets change
Set<Property> changes = new HashSet<Property>();
// Populate the set from property set of the node using the event
// (or add all properties to force updating all properties)
for (Property change : changes) {
firePropertyChange(change.getName(), null, change.getValue());
}
}
Note that property set should not be changed as that would mess with the property editors. Consequentially, the actual Property objects have to support changing of the domain object behind the property.
You could also set the node's property sheet to null, so the createSheet method is called again.

Categories

Resources