Reputation: 1041
This Question builds off of a question I previously asked: JavaFX Sync Duplicate Views to the Same Controller (FXML & MVC)
but relates to a separate issue. I am trying to create a Play/Pause button that will swap the graphic whenever the button is clicked to the appropriate > play and || pause images
ButtonModel.java
public class ButtonModel {
private final ObjectProperty<ImageView> playImage = new SimpleObjectProperty<ImageView>();
private boolean paused;
public final ObjectProperty<ImageView> playImageProperty() {
return this.playImage;
}
//... Other Getters and Setters...
}
ButtonPanelController.java
public class ButtonPanelController implements Initializable {
@FXML
Button myButton
ButtonModel model
public ButtonPanelController(ButtonModel model) {
this.model = model;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
model.setPaused(true);
myButton.graphicProperty().bind(model.playImageProperty());
}
public void buttonClickedAction(ActionEvent e) {
Image image = null;
if(model.isPaused()) {
image = new Image(this.getClass().getResourceAsStream("play.png"));
} else {
image = new Image(this.getClass().getResourceAsStream("pause.png"));
}
model.setPlayImage(new ImageView(image));
model.togglePaused();
}
}
I am creating two scenes using the same .fxml and am passing a single instance of my model to both Controllers to control state.
The issue I am having is that when I change the ImageView property, The image is only showing up on the button on my 2nd stage. The button on my 1st stage is resizing to fit the image like it's supposed to and when I place break points and look inside the 1st Button's 'graphic' attribute, I can see its 'observable' attribute is set to my ObjectProperty reference, However the image itself is not displayed on the button. The Button on the 2nd stage is working as intended, but the button on the 1st stage is not.
I have been able to successfully bind a RadioButton's SelectedProperty using the same architecture so I'm not sure why a Button's graphicProperty is having issues.
Upvotes: 0
Views: 879
Reputation: 209330
The conceptual problem here is that you are using the model to store UI components (e.g. the ImageView
). The model should store the state of the application, not the view of the application.
The technical reason it's not actually working is that a node can only appear in one scene, and at most once in any scene graph. Since you are sharing the model between two controller-view pairs, you are trying to show the same ImageView
instance in each view, which is not allowed. (For example, since you have only one ImageView
instance, what are you expecting to be returned by method calls like playImage.get().getScene()
, or playImage.get().getLayoutX()
?)
Your model should just store state (i.e. data):
public class ButtonModel {
private final BooleanProperty paused = new SimpleBooleanProperty();
public final BooleanProperty pausedProperty() {
return paused ;
}
public final boolean isPaused() {
return pausedProperty().get();
}
public final void setPaused(boolean paused) {
pausedProperty().set(paused);
}
public void togglePaused() {
setPaused(! isPaused());
}
//... Other Getters and Setters...
}
and your controllers should concern themselves with updating their own views based on the data in the model (as well as updating the model in response to user input):
public class ButtonPanelController implements Initializable {
// We can use a single instance of each image (which is not a node)
// and share it between
// multiple image views, so we can make the images static
// to conserve memory. Note that fairly spectacularly bad things
// will happen if there is an exception thrown loading these images...
private static Image playImage =
new Image(ButtonPanelController.class.getResource("play.png").toExternalForm());
private static Image pauseImage =
new Image(ButtonPanelController.class.getResource("pause.png").toExternalForm());
// But each controller *must* have its own imageview to display in
// the button in its view:
private ImageView buttonGraphic ;
@FXML
Button myButton
ButtonModel model
public ButtonPanelController(ButtonModel model) {
this.model = model;
}
@Override
public void initialize(URL location, ResourceBundle resources) {
buttonGraphic = new ImageView();
myButton.setGraphic(buttonGraphic);
model.setPaused(true);
buttonGraphic.imageProperty().bind(Bindings
.when(model.pausedProperty())
.then(playImage)
.otherwise(pauseImage));
}
public void buttonClickedAction(ActionEvent e) {
model.togglePaused();
}
}
Note you could define the ImageView
in FXML instead and inject it into the controller:
<Button fx:id="myButton" onAction="#handleButtonAction">
<graphic>
<ImageView fx:id="buttonGraphic"/>
</graphic>
</Button>
and then of course get rid of the first two lines of the initialize
method and annotate the buttonGraphic
field with @FXML
. (The main idea is basically the same.)
Upvotes: 3