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
Related
I've created a custom component with custom properties which I later import in the Scene Builder.
I'd like to have the option to disable one or more of those properties based on the value of another, how can I do that?
CustomControl:
public class CustomControl extends TextField {
private BooleanProperty active = new SimpleBooleanProperty();
private ObjectProperty<TypesEnum> selectType = new SimpleObjectProperty<TypesEnum>();
public CustomControl() {
super();
getStyleClass().add("custom-control");
active.addListener(new ChangeListener<Boolean>() {
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if (newValue) {
// Enable 'selectType'
}
else {
// Disable 'selectType'
}
}
});
}
public boolean isActive() {
return active.get();
}
public void setActive(boolean active) {
this.active.set(active);
}
public BooleanProperty activeProperty() {
return active;
}
public TypesEnum getSelectType() {
return selectType.get();
}
public void setSelectType(TypesEnum selectType) {
this.selectType.set(selectType);
}
public ObjectProperty<TypesEnum> selectTypeProperty() {
return selectType;
}
}
TypesEnum:
public enum TypesEnum {
TYPE_A,
TYPE_B,
TYPE_C
}
In the image below you can see an example of some disabled properties in Scene Builder (default HBox, with default Layout properties).
So when my active and selectType properties are shown in Scene Builder, when I click on active, I'd like selectType to become disable like in the image above.
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've been trying to create a music player and part of that requires listening to a time slider. So I've added to the time slide and this is the error I get:
I've been trying to get my head around how you fix this error and the whole business of overriding.
Can anyone point me in the right direction on how to fix this error?
My Code:
public class graphicalController implements Initializable
{
//GUI Decleration
#FXML
public Button centreButton;
#FXML
public Button backButton;
#FXML
public Button forwardButton;
#FXML
public ToggleButton muteToggle;
#FXML
public MenuItem loadFolder;
#FXML
public Text nameText;
#FXML
public Text albumText;
#FXML
public Text timeText;
#FXML
public Text artistText;
#FXML
public Slider timeSlider;
#FXML
public Slider volumeSlider;
//Controller Decleration
String absolutePath;
SongQueue q = new SongQueue();
MediaPlayer player;
Status status;
boolean isPlaying = false;
boolean isMuted = false;
boolean isPaused = false;
private Duration duration;
/**
* The constructor. The constructor is called before the initialize()
* method.
*
* Anything in regards to CSS styling with FXML MUST be done within the initialize method.
*/
public graphicalController() {
}
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
#FXML
public void initialize(URL location, ResourceBundle resources)
{
centreButton.setStyle("-fx-background-image: url('/Resources/Play_Button.png')");
centreButton.setText("");
backButton.setStyle("-fx-background-image: url('/Resources/Back_Button.png')");
backButton.setText("");
forwardButton.setStyle("-fx-background-image: url('/Resources/Forward_Button.png')");
forwardButton.setText("");
muteToggle.setStyle("-fx-background-image: url('/Resources/ToggleSound_Button.png')");
muteToggle.setText("");
nameText.setText("");
albumText.setText("");
artistText.setText("");
volumeSlider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
double sliderValue = newValue.intValue();
handleVolumeSlider(sliderValue);
}
});
timeSlider.valueProperty().addListener(new ChangeListener<Number>() {
#Override
public void changed(ObservableValue<? extends Number> observable,
Number oldValue, Number newValue) {
//outputTextArea.appendText("Slider Value Changed (newValue: " + newValue.intValue() + ")\n");
}
});
timeSlider.valueProperty().addListener(new InvalidationListener() {
public void invalidated(Observable ov) {
if (timeSlider.isValueChanging()) {
// multiply duration by percentage calculated by slider position
if(duration!=null) {
player.seek(duration.multiply(timeSlider.getValue() / 100.0));
}
updateValues();
}
}
});
}
public void setSongText() {
String file = q.peek().fileName;
String songName = q.peek().songName;
String albumName = q.peek().albumName;
String artistName = q.peek().artistName;
if (songName == "") {
songName = "Song name not specified in metadata.";
}
else if (albumName == "")
{
albumName = " Album name not specified in metadata.";
}
else if (artistName == "")
{
artistName = "Artist name not specified in metadata.";
}
nameText.setText(songName);
albumText.setText(albumName);
artistText.setText(artistName);
}
}
You will find my problem in the initialize method.
Are you using the correct Observable type? It should be of type javafx.beans.Observable.
I have a ValueAwareEditor that contains a couple of sub editors:
Essentially, an OfferDto is composed of a TariffDto and a Commission. The Commission can be one of 4 sub-types, but there is only ever one. Usually this list of possible commissions inside the TariffDto will only contain one element, but it can sometimes contain two.
public class OfferDto
{
private TariffDto tariff;
// selected from the list in the tariff
private Commission commission;
}
public class TariffDto extends EntityDto
{
// omitted for brevity...
protected List<Commission> commissions = new ArrayList<Commission>();
}
When commissions contains more than one item, I want to display a dropdown with the two optiions, and add allow the user to choose between them, each time resetting the commission in the OfferDto and the CommissionEditor.
The problem is that, when call commission.setValue() for the second time, the editor does not change. What should I be doing here?
public class OfferEditor extends Composite implements ValueAwareEditor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Ignore
#UiField
HTMLPanel panel;
#UiField
CommissionEditor commission;
#Override
public void setValue(final OfferDto value)
{
panel.clear();
List<Commission> commissions = value.getTariff().getCommissions();
if(commissions.size() == 1)
{
value.setCommission(commissions.get(0));
}
else
{
// multiple commissions
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdown.setValue(value.getCommission());
dropdown.setAcceptableValues(commissions);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
// this works, but the CommissionEditor that was first rendered remains
value.setCommission(selected);
}
});
panel.add(dropdown);
}
}
}
Currently, I am rendering the list of commissions in a ValueListBox, then when the value changes I am pushing that value to the OfferDto. The Commission seems to get set right, but the subEditor does not change.
Any help greatly appreciated.
EDIT:
CommissionEditor shows the relevant sub-editor depending on the type.
public class CommissionEditor extends Composite implements Editor<Commission>
{
private static CommissionEditorUiBinder uiBinder = GWT.create(CommissionEditorUiBinder.class);
interface CommissionEditorUiBinder extends UiBinder<Widget, CommissionEditor>
{
}
#UiField
Panel subEditorPanel;
public CommissionEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Ignore
final UnitRateCommissionEditor unitRateCommissionEditor = new UnitRateCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor> unitRateCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, UnitRateCommission, UnitRateCommissionEditor>(
unitRateCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof UnitRateCommission)
{
setValue(value, value instanceof UnitRateCommission);
System.out.println("UnitRateCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(unitRateCommissionEditor);
}
}
};
#Ignore
final StandingChargeCommissionEditor standingChargeCommissionEditor = new StandingChargeCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor> standingChargeCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, StandingChargeCommission, StandingChargeCommissionEditor>(
standingChargeCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof StandingChargeCommission)
{
setValue(value, value instanceof StandingChargeCommission);
System.out.println("StandingChargeCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(standingChargeCommissionEditor);
}
}
};
#Ignore
final PerMwhCommissionEditor perMwhCommissionEditor = new PerMwhCommissionEditor();
#Path("")
final AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor> perMwhCommissionEditorWrapper = new AbstractSubTypeEditor<Commission, PerMwhCommission, PerMwhCommissionEditor>(
perMwhCommissionEditor)
{
#Override
public void setValue(final Commission value)
{
if(value instanceof PerMwhCommission)
{
setValue(value, value instanceof PerMwhCommission);
System.out.println("PerMwhCommission setValue");
subEditorPanel.clear();
subEditorPanel.add(perMwhCommissionEditor);
}
}
};
}
Possible Solution:
I changed OfferEditor as so:
public class OfferEditor extends Composite implements Editor<OfferDto>
{
#UiField
TariffRenderer tariff;
#Path("tariff.commissions")
#UiField
CommissionsEditor commission;
}
New editor CommissionsEditor is a CompositeEditor. It needs to take List tariff.commissions and set the chosen Commission into offer.commission:
public class CommissionsEditor extends Composite implements CompositeEditor<List<Commission>, Commission, CommissionEditor>
{
private static CommissionsEditorUiBinder uiBinder = GWT.create(CommissionsEditorUiBinder.class);
interface CommissionsEditorUiBinder extends UiBinder<Widget, CommissionsEditor>
{
}
private EditorChain<Commission, CommissionEditor> chain;
#UiField
FlowPanel dropdownPanel, subEditorPanel;
#Ignore
CommissionEditor subEditor;
public CommissionsEditor()
{
initWidget(uiBinder.createAndBindUi(this));
}
#Override
public void setValue(List<Commission> valueList)
{
// clear both panels
dropdownPanel.clear();
subEditorPanel.clear();
if(valueList.size() == 1)
{
// set the commission to the first in the list
Commission selected = valueList.get(0);
subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
else if(valueList.size() > 1)
{
ValueListBox<Commission> dropdown = new ValueListBox<Commission>(new Renderer<Commission>()
{
#Override
public String render(Commission object)
{
return object == null ? "" : object.getName();
}
#Override
public void render(Commission object, Appendable appendable) throws IOException
{
appendable.append(render(object));
}
});
dropdownPanel.add(dropdown);
dropdown.setValue(valueList.get(0));
dropdown.setAcceptableValues(valueList);
dropdown.addValueChangeHandler(new ValueChangeHandler<Commission>()
{
#Override
public void onValueChange(ValueChangeEvent<Commission> event)
{
Commission selected = event.getValue();
subEditorPanel.clear();
CommissionEditor subEditor = new CommissionEditor();
subEditorPanel.add(subEditor);
chain.attach(selected, subEditor);
}
});
}
}
#Override
public void flush()
{
}
#Override
public void onPropertyChange(String... paths)
{
// TODO Auto-generated method stub
}
#Override
public void setDelegate(EditorDelegate<List<Commission>> delegate)
{
// TODO Auto-generated method stub
}
#Override
public CommissionEditor createEditorForTraversal()
{
return new CommissionEditor();
}
#Override
public String getPathElement(CommissionEditor subEditor)
{
return null;
}
#Override
public void setEditorChain(EditorChain<Commission, CommissionEditor> chain)
{
this.chain = chain;
}
}
When the CommissionsEditor renders the dropdown and onValueChange() is called, the new editor gets created, but the value for the commission never seems to get set.
For some reason the selected subEditor's value is not pushed into offer.setCommission(). I thought chain.attach() would perform this for me?
have a look the following working code:
class MyType{
SimpleStringProperty myname;
SimpleObjectProperty<Color> mycolor;
}
TableColumn col;
arr = FXCollections.observableArrayList(new ArrayList<MyType>());
tblColName.setCellValueFactory(new PropertyValueFactory("myname"));
// Use the cell-factory provided by TextFieldTableCell.
tblColName.setCellFactory(TextFieldTableCell.forTableColumn());
tblColName.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent>() {
#Override
public void handle(TableColumn.CellEditEvent cellEditEvent) {
((MyType) cellEditEvent.getRowValue()).myname.set((String) cellEditEvent.getNewValue());
}
});
However, as soon as I am using a custom TableCell, the code in setOnEditCommit is not called anymore:
public class ColorPickerTableCell<S> extends TableCell<S, Color>{
private ColorPicker cp;
public ColorPickerTableCell(){
cp = new ColorPicker(Color.BLACK);
cp.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
commitEdit(cp.getValue());
updateItem(cp.getValue(), isEmpty());
}
});
setGraphic(cp);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setEditable(true);
}
#Override
protected void updateItem(Color item, boolean empty) {
super.updateItem(item, empty);
cp.setVisible(!empty);
this.setItem(item);
}
public static <T> Callback<TableColumn<Color, T>, TableCell<Color, T>> forTableColumn(){
return new Callback<TableColumn<Color, T>, TableCell<Color, T>>() {
#Override
public TableCell<Color, T> call(TableColumn<Color, T> colorTTableColumn) {
return new ColorPickerTableCell();
}
};
}
}
A slight change of the code above...
TableColumn col;
arr = FXCollections.observableArrayList(new ArrayList<MyType>());
tblColName.setCellValueFactory(new PropertyValueFactory("myname"));
// Use the cell-factory provided by TextFieldTableCell.
tblColName.setCellFactory(ColorPickerTableCell.forTableColumn());
tblColName.setOnEditCommit(new EventHandler<TableColumn.CellEditEvent>() {
#Override
public void handle(TableColumn.CellEditEvent cellEditEvent) {
throw new NotImplementedException(); // is never thrown.
}
});
... makes the code not work anymore. The exception is never thrown. I think that I am doing something wrong in the design of ColorPickerTableCell, but I cannot imagine what. How can I make JavaFX call my OnEditCommit ?
You need first to go to edit state with statEdit(); , if you now commit an event will be fired
I've recently had the same problem. Unfortunately, I haven't found any way of trigerring that event from the ColorPicker control. However, I came up with the following workaround.
First of all, I created a Color wrapper class:
public class ColorWrapper {
private Color color;
....
}
I replaced the Color instance with the wrapper instance in my model class M. Next, I implemented setCellFactory method in the following way:
myColumn.setCellFactory(new Callback<TableColumn<M, ColorWrapper>, TableCell<M, ColorWrapper>>() {
#Override
public TableCell<M, ColorWrapper> call(TableColumn<M,ColorWrapper> arg0) {
return new TableCell<M, ColorWrapper>(){
private ColorPicker colorPicker;
private ColorPicker createPicker(){
colorPicker = new ColorPicker();
colorPicker.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent evt) {
ColorPicker cp = (ColorPicker)evt.getSource();
ColorWrapper cw = (ColorWrapper)cp.getUserData();
cw.setColor(cp.getValue());
}
});
return colorPicker;
}
#Override
protected void updateItem(ColorWrapper value, boolean empty) {
super.updateItem(value, empty);
if(empty){
return;
}
if(colorPicker == null){
colorPicker = createPicker();
colorPicker.setUserData(value);
}
colorPicker.setValue(value.getColor());
setGraphic(colorPicker);
}
};
}
});
As you can see, I just made use of setUserData/getUserData methods of the ColorPicker class and that's it. It works.
I had the same problem for CheckBoxTableCell and DatePickerTableCell and ColorPickerTableCells :-(
I deal it like that: on the events of the controls I get back the POJO objects in use by the "((Inputs)getTableView().getItems().get(getTableRow().getIndex()" and I update similary like is it done in the OnEditCommit method...
So for me it's look like this (update the color):
((Inputs) getTableView().getItems().get(
getTableRow().getIndex())
).setColor(cp.getValue());
Here is example with ColorPickerCell
:
public class ColorPickerTableCell<Inputs> extends TableCell<Inputs, Color>{
private ColorPicker cp;
public ColorPickerTableCell(){
cp = new ColorPicker();
cp.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
commitEdit(cp.getValue());
updateItem(cp.getValue(), isEmpty());
((Inputs) getTableView().getItems().get(
getTableRow().getIndex())
).setColor(cp.getValue());
}
});
setGraphic(cp);
setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
setEditable(true);
}
#Override
protected void updateItem(Color item, boolean empty) {
super.updateItem(item, empty);
cp.setVisible(!empty);
this.setItem(item);
cp.setValue(item);
}
}
With this simple JavaFX's POJO:
public ObjectProperty<Color> color = new SimpleObjectProperty<Color>();
this.color = new SimpleObjectProperty(color);
public ObjectProperty<Color> colorProperty() {
return color;
}
public void setColor(Color color2) {
color.set(color2);
}
I do not know if it's a good way to achive that but it worked for me... Note that the JavaFX's POJO is only accessible within an "ActionEvent" request (combobox, datepicker, colorpicker, etc..)
Regards,
To elaborate on justcode's answer, here is my class where I had the problem and solved it:
public class DeleteButtonCell extends TableCell<Menu, Menu> {
private Button deleteButton;
public DeleteButtonCell() {
deleteButton = new Button();
deleteButton.setId("trash-button");
deleteButton.setOnAction((e) -> {
startEdit();
commitEdit((Menu) this.getTableView().getItems().get(this.getIndex()));
});
}
#Override
protected void updateItem(Menu t, boolean empty) {
super.updateItem(t, empty);
if (empty) {
setGraphic(null);
} else {
setGraphic(deleteButton);
}
}
}