I'm designing a custom JavaFX node using FXML. This node fires a custom event.
And I would like to know how to add an event handler to the custom event in the FXML of the parent of this node.
I created my handler, passed it to the child as an object property and hooked it into the event system via the setEventHandler method. But it throws me an error when the event is fired.
Custom event code :
public class ValueUpdatedEvent extends Event {
public static final EventType<ActionEvent> VALUE =
new EventType<>(Event.ANY, "VALUE_UPDATED");
private float value;
public ValueUpdatedEvent() {
super(VALUE);
}
public float getValue() {
return value;
}
public void setValue(float value) {
this.value = value;
}
}
Child component controller :
public class CharacteristicBar extends Component {
#FXML
private JFXTextField field;
#FXML
private JFXProgressBar bar;
#FXML
private JFXButton plus;
#FXML
JFXButton minus;
private ObjectProperty<EventHandler<ValueUpdatedEvent>> onValueUpdated = new ObjectPropertyBase<EventHandler<ValueUpdatedEvent>>() {
#Override
public Object getBean() {
return CharacteristicBar.this;
}
#Override protected void invalidated() {
setEventHandler(new EventType<>(Event.ANY, "onValueUpdated"), get());
}
#Override
public String getName() {
return "onValueUpdated";
}
};
private SimpleFloatProperty value = new SimpleFloatProperty();
private boolean readonly = false;
public CharacteristicBar() {
super("CharacteristicBar.fxml");
value.addListener(
newValue -> {
ValueUpdatedEvent event = new ValueUpdatedEvent();
event.setValue(value.get());
fireEvent(event);
}
);
bar.progressProperty().bind(this.value);
if (this.readonly) {
this.field.setEditable(false);
this.minus.setVisible(false);
this.plus.setVisible(false);
}
}
#FXML
private void handleInput(KeyEvent event) {
try {
value.set(Float.parseFloat(field.getText()) / 20f);
} catch (NumberFormatException exception) {
field.setText("");
}
}
public float getValue() {
return value.get() * 20f;
}
#FXML
public void handleClickPlus(ActionEvent event) {
this.value.set((this.value.get() * 20f + 1f) / 20f);
this.field.setText(String.valueOf(this.value.get() * 20));
}
#FXML
public void handleClickMinus(ActionEvent event) {
this.value.set((this.value.get() * 20f - 1f) / 20f);
this.field.setText(String.valueOf(this.value.get() * 20));
}
public boolean isReadonly() {
return readonly;
}
public void setReadonly(boolean readonly) {
this.readonly = readonly;
this.field.setEditable(!readonly);
this.minus.setVisible(!readonly);
this.plus.setVisible(!readonly);
}
public EventHandler<ValueUpdatedEvent> getOnValueUpdated() {
return onValueUpdated.get();
}
public ObjectProperty<EventHandler<ValueUpdatedEvent>> onValueUpdatedProperty() {
return onValueUpdated;
}
public void setOnValueUpdated(EventHandler<ValueUpdatedEvent> onValueUpdated) {
this.onValueUpdated.set(onValueUpdated);
}
}
Parent's FXML :
<CharacteristicBar fx:id="courageBar" onBarValueChanged="#handleChangeCou"
GridPane.columnIndex="1" GridPane.rowIndex="7"/>
Handler in parent's controller:
#FXML
public void handleChangeCou(ValueUpdatedEvent event){
System.out.println(event.getValue());
}
Still, my event handler isn't called.
Do you guys have any clue on how to hook my handler with the event system ?
Thanks in advance
I could not get the custom event to work but instead I used properties to achieve my goal. Maybe it's intendend this way in JavaFX
So I have been working on a project and I have a CharmListView that populates with the name of the task to be used. I can login through my login screen, get to the CharmListView and click on the task I want to have open. It opens on the Desktop when I am testing it, but in Android it fails, saying the location is not found, and that:
java.lang.NullPointerException: Attempt to invoke virtual method 'void com.gluonhq.charm.glisten.mvc.View.setName(java.lang.String)' on a null object reference
Here are my charm classes that I have:
This one is the task model essentially.
public class CharmHomeNavTask {
private String taskName;
private String taskDesc;
private static final Image IMAGE_ADMIN = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/admin.png"));
private static final Image IMAGE_AUDIT_TOOL = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/auditTool.png"));
private static final Image IMAGE_CONSOLIDATE_PACKAGE = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/consolidatePackage.png"));
private static final Image IMAGE_DISPOSAL = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/disposal.png"));
private static final Image IMAGE_EQUIP_MANGAGE = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/equipmentManagement.png"));
private static final Image IMAGE_INTRA_TRANSFER = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/intratransfer.png"));
private static final Image IMAGE_PICKUP = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/pickup.png"));
private static final Image IMAGE_TRU_WASTE_PREP = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/truWastePrep.png"));
private static final Image IMAGE_VISUAL_INSPECTION = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/visualInspection.png"));
private static final Image IMAGE_WALL2WALL = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/wall2wall.png"));
private static final Image IMAGE_WASTE_ID = new Image(WcatsAndroidDemo.class.getResourceAsStream("/gov/lanl/taskImages/wasteIdentification.png"));
private static Image[] listOfImages = {IMAGE_WASTE_ID, IMAGE_VISUAL_INSPECTION, IMAGE_TRU_WASTE_PREP, IMAGE_CONSOLIDATE_PACKAGE, IMAGE_INTRA_TRANSFER, IMAGE_PICKUP, IMAGE_DISPOSAL, IMAGE_ADMIN, IMAGE_WALL2WALL,
IMAGE_EQUIP_MANGAGE, IMAGE_AUDIT_TOOL };
public CharmHomeNavTask(String taskName, String taskDesc){
this.taskName = taskName;
this.taskDesc = taskDesc;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskDesc() {
return taskDesc;
}
public void setTaskDesc(String taskDesc) {
this.taskDesc = taskDesc;
}
public static void setListOfImages(Image[] listOfImages) {
listOfImages = listOfImages;
}
public static Image[] getListOfImages(){
return listOfImages;
}
public static Image getSingleImageFromList(int index){
return listOfImages[index];
}
}
This one is the creation of the tasks into an ObservableList
public class CharmHomeNavTasks {
public static ObservableList<CharmHomeNavTask> tasksList = FXCollections.observableArrayList(
new CharmHomeNavTask("Waste Identification", "Identify you waste from this screen."),
new CharmHomeNavTask("TRU Visual Inspection", "Visual Inspection of TRU waste."),
new CharmHomeNavTask("TRU Drum Preparation", "Prepare your TRU drums here."),
new CharmHomeNavTask("Consolidation/Packaging", "Consolidate and package your waste."),
new CharmHomeNavTask("Intra-Facility Transfer", "Transferring of waste within the same facility."),
new CharmHomeNavTask("Inter-Facility Pickup", "Picking up waste within the same facility."),
new CharmHomeNavTask("Disposal Tasks", "Disposal of waste tasks."),
new CharmHomeNavTask("Administrative Tasks", "Administrators have special tasks they can perform here."),
new CharmHomeNavTask("Wall-Wall Inventory", "Wall to Wall inventory tasks."),
new CharmHomeNavTask("Equipment Management", "Waste related equipment tasks."),
new CharmHomeNavTask("Audit Support Tool", "Tool for audit related tasks."));
public static ObservableList<CharmHomeNavTask> getTasksList() {
return tasksList;
}
}
This is the one that is giving me trouble because I have my onMouseClicked event here and I set the item(or task) to selected. This seems to work fine even on Android as I can the the system to tell me which item was clicked.
public class CharmHomeNavTaskCell extends CharmListCell<CharmHomeNavTask> {
private final ListTile tile;
private final ImageView imageView;
private CharmHomeNavTask item;
public CharmHomeNavTask carryOverItem;
public CharmHomeNavTaskCell(CharmListView listView){
tile = new ListTile();
imageView = new ImageView();
tile.setPrimaryGraphic(imageView);
carryOverItem = item;
tile.setOnMouseClicked(e -> {
System.out.println("******************* Item clicked " + item.getTaskName());
listView.setSelectedItem(item);
});
setText(null);
}
#Override
public void updateItem(CharmHomeNavTask item, boolean empty){
super.updateItem(item, empty);
this.item = item;
imageView.setFitWidth(32);
imageView.setFitHeight(32);
if (item != null && !empty) {
tile.textProperty().setAll(item.getTaskName() + " ", item.getTaskDesc());
tile.setWrapText(true);
final Image[] image = CharmHomeNavTask.getListOfImages();
super.setStyle("-fx-font-weight: bold");
switch (item.getTaskName()) {
case "Waste Identification":
imageView.setImage(image[0]);
break;
case "TRU Visual Inspection":
imageView.setImage(image[1]);
break;
case "TRU Drum Preparation":
imageView.setImage(image[2]);
break;
case "Consolidation/Packaging":
imageView.setImage(image[3]);
break;
case "Intra-Facility Transfer":
imageView.setImage(image[4]);
break;
case "Inter-Facility Pickup":
imageView.setImage(image[5]);
break;
case "Disposal Tasks":
imageView.setImage(image[6]);
break;
case "Administrative Tasks":
imageView.setImage(image[7]);
break;
case "Wall-Wall Inventory":
imageView.setImage(image[8]);
break;
case "Equipment Management":
imageView.setImage(image[9]);
break;
case "Audit Support Tool":
imageView.setImage(image[10]);
break;
}
setGraphic(tile);
} else {
setGraphic(null);
}
}
}
Here is the AppViewManager Class. I am using Glisten and Afterburner just so that you know.
public class AppViewManager {
private static String getLoggedInUser(User user){
if (user != null) {
return user.getId();
} else {
return "Not Logged In";
}
}
public static final AppViewRegistry REGISTRY = new AppViewRegistry();
public static final AppView PRIMARY_VIEW = view("Home", PrimaryPresenter.class, MaterialDesignIcon.HOME, SHOW_IN_DRAWER, HOME_VIEW);
public static final AppView SECONDARY_VIEW = view("Task List", SecondaryPresenter.class, MaterialDesignIcon.LIST, SHOW_IN_DRAWER);
public static final AppView SETTINGS_VIEW = view("Settings", SettingsView.class, MaterialDesignIcon.SETTINGS_APPLICATIONS, SHOW_IN_DRAWER);
public static final AppView INTRAFACILITYTRANSFER_VIEW = view("Intra-Facility Transfer", IntraFacilityView.class, MaterialDesignIcon.EDIT_LOCATION);
private static AppView view(String title, Class<? extends GluonPresenter<?>> presenterClass, MaterialDesignIcon menuIcon, AppView.Flag... flags ) {
return REGISTRY.createView(name(presenterClass), title, presenterClass, menuIcon, flags);
}
private static String name(Class<? extends GluonPresenter<?>> presenterClass) {
return presenterClass.getSimpleName().toUpperCase(Locale.ROOT).replace("PRESENTER", "");
}
public static void registerViewsAndDrawer(MobileApplication app) {
for (AppView view : REGISTRY.getViews()) {
view.registerView(app);
}
Image image = new Image(WcatsAndroidDemo.class.getResourceAsStream("/icon.png"));
NavigationDrawer.Header header = new NavigationDrawer.Header("\nWCATS" + "- " + getLoggedInUser(WcatsAndroidDemo.getInstance().getLoggedUser()),
"Waste Management System", new ImageView(image));
// TODO: Add a footer to the drawer that contains settings, help & feedback, and About
NavigationDrawer.Footer footer = new NavigationDrawer.Footer("No tasks currently need to be synchronized.", null);
//Create the sub items for the drawer
NavigationDrawer.Item about = new NavigationDrawer.Item("About", MaterialDesignIcon.INFO.graphic());
NavigationDrawer.Item logOut = new NavigationDrawer.Item("Logout", MaterialDesignIcon.EXIT_TO_APP.graphic());
// TODO: make the rest of the submenu items that go in the header.
DefaultDrawerManager drawerManager = new DefaultDrawerManager(app, header, REGISTRY.getViews()) {
{
NavigationDrawer drawer = getDrawer();
drawer.visibleProperty().addListener((observable, oldValue, newValue) -> {
if (newValue){
header.setTitle("\nWCATS - " + getLoggedInUser(WcatsAndroidDemo.getInstance().getLoggedUser()));
}
});
// Add items
drawer.setFooter(footer);
footer.setStyle("-fx-text-size: 6");
drawer.getItems().addAll(new Separator(), about, logOut, new Separator());
// TODO: provide action based on item selected
drawer.selectedItemProperty().addListener(((observable, oldValue, newValue) -> {
if(newValue.equals(about)) {
System.out.println("test");
} else if (newValue.equals(logOut)){
WcatsAndroidDemo.getInstance().userLogout();
} else if (newValue.equals(SECONDARY_VIEW.getMenuItem())){
if (getLoggedInUser(WcatsAndroidDemo.getInstance().getLoggedUser()).equals("Not Logged In")) {
AppViewManager.PRIMARY_VIEW.switchView();
} else {
AppViewManager.SECONDARY_VIEW.switchView();
}
}
} ));
}
};
drawerManager.installDrawer();
}
}
Lastly, here is the Presenter class for the view that I am navigation from and want to go to the task that is selected.
public class SecondaryPresenter extends GluonPresenter<WcatsAndroidDemo> {
#FXML
private View homeView;
#FXML
public CharmListView<CharmHomeNavTask, Integer> charmListView;
public void initialize() {
homeView.setShowTransitionFactory(BounceInRightTransition::new);
AppBar appBar = getApp().getAppBar();
homeView.showingProperty().addListener((observable, oldValue, newValue) -> {
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> getApp().showLayer(DRAWER_LAYER)));
appBar.setTitleText("Task Selection");
});
charmListView.setFloatingHeaderVisible(false);
charmListView.setItems(CharmHomeNavTasks.getTasksList());
charmListView.setCellFactory(param -> new CharmHomeNavTaskCell(charmListView));
charmListView.selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if (newValue.getTaskName().equals("Intra-Facility Transfer")){
AppViewManager.INTRAFACILITYTRANSFER_VIEW.switchView(ViewStackPolicy.SKIP);
}
});
}
}
Just in case you do need it to figure anything out here is the view that I am trying to navigate to.
public class IntraFacilityView extends GluonPresenter<WcatsAndroidDemo> {
#FXML
public ChoiceBox facilityCMBox;
#FXML
public ChoiceBox storageUnitOrgCMBox;
#FXML
public ChoiceBox storageUnitDestCMBox;
#FXML
public ChoiceBox gridXCMBox;
#FXML
public ChoiceBox gridYCMBox;
#FXML
public ChoiceBox gridZCMBox;
#FXML
public CheckBox organizeUnitCHKBox;
#FXML
public Button viewMoreReqsBTN;
#FXML
public Button viewPendingMovesBTN;
#FXML
public Button resumeTaskBTN;
#FXML
private View intrafacility;
public void initialize(){
intrafacility.setShowTransitionFactory(BounceInRightTransition::new);
intrafacility.showingProperty().addListener((observable, oldValue, newValue) -> {
if (newValue) {
AppBar appBar = getApp().getAppBar();
appBar.setNavIcon(MaterialDesignIcon.MENU.button(e -> {
getApp().showLayer(DRAWER_LAYER);
}));
appBar.setTitleText("Intra-Facility Transfer");
appBar.getActionItems().add(MaterialDesignIcon.CLOSE.button(e -> {
AppViewManager.SECONDARY_VIEW.switchView();
}));
}
});
}
public void handleOrganizeUnitsCHKBox(ActionEvent actionEvent) {
}
public void handleViewMoreReqs(ActionEvent actionEvent) {
}
public void handleResumeTask(ActionEvent actionEvent) {
}
public void handlevVewPendingMoves(ActionEvent actionEvent) {
}
}
I know that some Android devices are not the best with JavaFxPorts, but I haven't seen anything about Panasonic toughpads having known issues like Samsung. I did have to do some janky stuff to get the abdroid devices to register the touches as clicks, but that seems to be working fine now.
This has me as a standstill as I can not figure out why Android can not find the location but it works fine on the desktop.
Here is the file structure:
Project structure
I've got a ListView and I listen to the selectedItemProperty for when the user changes the selection.
In this listener I add an event to my UndoManager. When I try to undo the selection, the selectedItemProperty fires the ChangeListener and it will add an other event to the UndoManger and creating an infinit loop because it will add a ListViewSelectionChange to the UndoManger when it undoes something.
public class DeviceConfigurationController {
#FXML private ListView<DeviceConfiguration> device_list;
#FXML
private void initialize() {
device_list.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
UndoManager.add(new ListViewSelectionChange<>(oldValue, device_list));
});
}
//redo/undo stuff
#FXML
private void undo() {
UndoManager.undo(); //calls the last Change
}
}
public class ListViewSelectionChange<T> implements Change {
privateT lastValue;
private T redoValue;
ListView<T> listView;
public ListViewSelectionChange(T lastValue, ListView<T> listView) {
this.lastValue = lastValue;
this.listView = listView;
}
//gets called from the undomanager
#Override
public void undo() {
redoValue = listView.getSelectionModel().getSelectedItem();
listView.getSelectionModel().select(lastValue); //fires the selection listener again, thus adding a ListViewSelection to the UndoManager
}
}
Does someone has any idea how to stop the listview from calling the listener?
Sebastian
You could add a simple flag to indicate if the listener should be fired:
public class DeviceConfigurationController {
#FXML private ListView<DeviceConfiguration> device_list;
private boolean pauseListener;
#FXML
private void initialize() {
device_list.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> {
if(!pauseListener)
UndoManager.add(new ListViewSelectionChange<>(oldValue, device_list));
}
});
}
#FXML
private void undo() {
pauseListener = true;
UndoManager.undo();
pauseListener = false;
}
}
I have implemented fft for my school project(tuner), although, im not able to pass calculated frequency to GUI. I tried binding, keyframes, i just cant seem to get a grasp of it, im really new to java.
public class FrequencyBean {
double freq;
private SimpleDoubleProperty value = new SimpleDoubleProperty(this, "value");
public void setValue(double value){
this.value.set(value);
System.out.println(value+" set");
}
public DoubleProperty getDoublePropertyValue(){
System.out.println("gotvals");
return value;
}
public FrequencyBean(){
freq = 10.0;
}
that is part of my controller, also i got reccomended to use something called tight binding or so, which would be abstracting of this class. Is that good for my code?
This is my main controller:
public class Controller implements Initializable{
FrequencyBean fbean;
#FXML
private Label otherFq;
#FXML
private Text frequency;
#FXML
private Text sharpFq;
#FXML
private Rectangle sharp6;
#FXML
private Text flatFq;
#FXML
private Rectangle center_rectangle;
#FXML
private Rectangle sharp1;
#FXML
private Rectangle sharp2;
#FXML
private Rectangle sharp3;
#FXML
private Rectangle sharp4;
#FXML
private Rectangle sharp5;
#FXML
private Text centerFq;
#FXML
private Rectangle flat6;
#FXML
private Rectangle flat5;
#FXML
private Rectangle flat4;
#FXML
private Rectangle flat3;
#FXML
private Rectangle flat2;
#FXML
private Rectangle flat1;
#Override
public void initialize(URL location, ResourceBundle resources) {
fbean = new FrequencyBean();
otherFq = new Label();
frequency = new Text();
boolean stop = false;
InputThread input = new InputThread();
Task<Void> in = new Task<Void>() {
#Override
protected Void call() throws Exception {
input.run();
return null;
}
};
Thread th0 = new Thread(in);
th0.start();
frequency.textProperty().bind(fbean.getDoublePropertyValue());
}
Rewrite your FrequencyBean correctly as a 'JavaFX-Bean':
public class FrequencyBean {
private SimpleDoubleProperty frequency = new SimpleDoubleProperty();
/**
* #param frequency the frequency to set
*/
public void setFrequency(double value){
this.frequency.set(value);
}
/**
* #return the frequency as double
*/
public double getFrequency(){
return this.frequency.get();
}
/**
* #return the frequency property
*/
public DoubleProperty frequencyProperty(){
return value;
}
public FrequencyBean(){
frequency = 10.0;
}
}
As Jame_D pointed it out: don't initialize a control annotated with #FXML.
Just bind the control in question like so:
...
#FXML
TextField tf_Frequency;
...
fbean = new FrequencyBean(20.3);
tfFrequency.textProperty().bind(fbean.frequencyProperty().asString("%.2f"));
Note that this is correct if you need a uni-directional binding.
There is also a bindBidirectional method.
I want to get the event of two buttons in my custom component.
the component is a imageview with two buttons to move between images, but I need to get the position of the image that is currently displayed, Im storing the key of the image, but I need to know when a button have been pressed outside the custom component, so I can change a Label outside the custom component.
public class TransitionSlider extends AnchorPane {
#FXML
private AnchorPane transitionSliderPane;
#FXML
private ImageView transitionSliderImageView;
#FXML
private Button prevButton;
#FXML
private Button nextButton;
private Map<Integer,Image> imageMap;
private Image currentImage;
private DropShadow imageViewDropShadow;
private int currentKey = 1;
private Image[] images;
public TransitionSlider() {
FXMLLoader loader = new FXMLLoader();
loader.setRoot(this);
loader.setController(this);
loader.setLocation(this.getClass().getResource("TransitionSlider.fxml"));
loader.setClassLoader(this.getClass().getClassLoader());
try {
loader.load();
} catch (IOException exception) {
throw new RuntimeException(exception);
}
prevButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
if(currentKey <= 1){
currentKey = currentKey + 1;
currentImage = imageMap.get(currentKey);
createTransition(transitionSliderImageView, currentImage);
}
}
});
nextButton.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent t) {
if(currentKey <= imageMap.size()){
currentKey = currentKey - 1;
currentImage = imageMap.get(currentKey);
createTransition(transitionSliderImageView, currentImage);
}
}
});
}
// more code here...
}
I want a way to capture the event and get variables inside the component and change a label outside the custom component...
for example:
public class Gallery extends Application {
#FXML
TransitionSlider ts;
Label label;
#Override
public void start(Stage stage) throws Exception {
label = new Label();
TransitionSlider ts = new TransitionSlider();
ts.captureButtonEvent(){ // need a way to capture this
label.setText(ts.getCurrentKey());
}
// more code here....
}
If I understood your question correctly, you want a binding.. Follow these steps:
1) Put bindable field and its getter/setter into TransitionSlider:
private IntegerProperty currentKey = new SimpleIntegerProperty(1);
public int getCurrentKey() {
return currentKey.get();
}
public void setCurrentKey(int val) {
return currentKey.set(val);
}
public IntegerProperty currentKeyProperty() {
return currentKey;
}
2) Bind this property to label's text in Gallery:
label = new Label();
TransitionSlider ts = new TransitionSlider();
label.textProperty.bind(ts.currentKeyProperty().asString());
Alternatively, if you want to do stuff more than just changing label's text, you can add a change listener to currentKeyProperty:
ts.currentKeyProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
label.setText(newValue);
// do other stuff according to "oldValue" and "newValue".
}
});