How do I dynamically change the image in an ImageView (JavaFX)? - java

I'm trying to implement a validation check for the contents of a TextField, displaying the validity as an icon next to it. However it doesn't seem to change the image. Here is the code I have so far, I've stripped out anything not related to the problem I'm experiencing.
Here's the view class:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyView extends VBox {
private final Foo model;
private final MyPresenter presenter;
HBox tokenValidationbox;
TextField tokentxt;
ImageView isValidimg;
public MyView(Foo model) {
this.model = model;
initFieldData();
layoutForm();
this.presenter = new MyPresenter(model, this);
}
private void initFieldData() {
tokenValidationbox = new HBox();
tokentxt = new TextField();
isValidimg = new ImageView();
}
private void layoutForm() {
tokenValidationbox.getChildren().addAll(tokentxt, isValidimg);
this.getChildren().add(tokenValidationbox);
}
}
And this is the presenter class that contains the logic:
package mypackage.view;
import mypackage.model.Foo;
// JavaFX imports
public class MyPresenter {
private final Foo model;
private final MyView view;
public MyPresenter(Foo model, MyView view) {
this.model = model;
this.view = view;
attachEvents();
}
private void attachEvents() {
view.tokentxt.setOnAction((ActionEvent event) -> {
view.isValidimg.setImage(new Image(validationImage(view.tokentxt.getText())))
});
}
public String validationImage(String token) {
String img = "dialog-error.png";
if(isValid(token)) img = "emblem-default.png";
return getClass().getClassLoader().getResource(img).toExternalForm();
}
private static boolean isValid(String token) {
// snip
}
}
As I understand this should check whether the entered token is valid whenever something is changed in the text field, and then load the corresponding image to display, however the image is not showing up.
emblem-default.png and dialog-error.png are located in the project resources folder and can be loaded statically (i.e. if I put an Image constructor inside the ImageView when initializing it, it works just fine)

Add a ChangeListener to the text property instead. onAction is only triggered, when Enter is pressed or similar.
Furthermore I recommend not recreating the images every time:
private static final Image VALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("emblem-default.png").toExternalForm());
private static final Image INVALID_IMG = new Image(MyPresenter.class.getClassLoader().getResource("dialog-error.png").toExternalForm());
public Image validationImage(String token) {
return isValid(token) ? VALID_IMG : INVALID_IMG;
}
view.tokentxt.textProperty().addListener((observable, oldValue, newValue) -> {
view.isValidimg.setImage(validationImage(newValue));
});
You could also use a Binding for this:
view.isValidating.imageProperty().bind(Bindings.createObjectBinding(() -> validationImage(view.tokentxt.getText()), view.tokentxt.textProperty()));
Which would update the image even before the text is modified.

Related

JavaFX Making an scrollable list of custom components

I am working on a project. In this project, I must have a shop that has a huge list of cards. I am also very new to JavaFX.
I made a custom class that inherits pane. It has an image view and some labels to show the name and description of card.
Know my problem is that how should I add them to scene to have an scrollable list of this items? What Components should my Scene have. (I omitted imports in the code below)
CardView.java ---- Custom component that loads an fxml
public class CardView extends Pane {
CardController cardController;
Node view;
public CardView() {
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("../FXMLFiles/Card.fxml"));
fxmlLoader.setControllerFactory(new Callback<Class<?>, Object>() {
#Override
public Object call(Class<?> param) {
return cardController = new CardController();
}
});
try {
view = (Node) fxmlLoader.load();
} catch (IOException ex) {
}
getChildren().add(view);
cardController.setNameAndDsc("Card", "This is A card", heroImg);
}
}
CardController.java
public class CardController {
#FXML
private Label name_lbl;
#FXML
private Label dsc_lbl;
#FXML
private ImageView card_img;
public void setNameAndDsc(String name, String dsc, Image img) {
name_lbl.setText(name);
dsc_lbl.setText(dsc);
card_img.setImage(img);
}
public void setName_lbl(Label name_lbl) {
this.name_lbl = name_lbl;
}
public void setDsc_lbl(Label dsc_lbl) {
this.dsc_lbl = dsc_lbl;
}
public void setCard_img(ImageView card_img) {
this.card_img = card_img;
}
}
Card.fxml
Overall View of Card.fxml:
Actually I want to have a huge list of this card that can be scrolled. How should I do that? What Components should I use. I must also note that I have access to JFoenix.
Use a listview. It's a virtual control so it only creates nodes that are in the visual bounds.
You can apply css to make background transparent.

Do event after click on ImageView which is inside of the tableView which contains observableArray

I have a class (TaskInOurProgram) which contains ImageView in constructer.
Here is a little code snipped - declaration and then constructer:
private ImageView closeTask;
private ImageView pauseTask;
private final Image pauseImage = new Image("file:images/pause.png");
private final Image closeImage = new Image("file:images/close.png");
public TaskInOurProgram(String name, String configName, String extensionOfTheFile) {
this.nameOfTheFile = name;
this.configName = configName;
this.extensionOfTheFile = extensionOfTheFile;
this.closeTask = new ImageView();
closeTask.setImage(closeImage);
this.pauseTask = new ImageView();
pauseTask.setImage(pauseImage);
}
Then I have a observableArrayList which contains many of these TaskInOurProgram objects.
private ObservableList<TaskInOurProgram> tasksInTheTableView = FXCollections.observableArrayList();
I have a tableView which displays this list of objects. So then it looks like this.
Object are not there from the beginning, they need to be added by clicking at button ADD TASK and because it is observableList, it will show this "tasks" immediately after adding.
WHAT I NEED: What I need is to create method inside of the controller which will do something after click on "pause" ImageView. I really do not know how to do it, I was thinking about listeners somehow. And before that I need to put click event on imageView.
Can you guys, please, help me with this? Thanks for any idea.
Try using a Button with a Graphic instead of a plain ImageView. That way you can use the Button.setOnAction() method.
Where you are currently defining your ImageView, change it to the following:
this.pauseTask = new Button("", new ImageView(pauseImage));
this.pauseTask.setOnAction(new EventHandler<ActionEvent>(){
#Override
public void handle(ActionEvent event) {
pause();
}
});
You can define a method somewhere else in your program, something like this:
private void pause() {
// your code here
}
Use setOnMouseClicked() method for imageView to handle click events.
pauseTask.setOnMouseClicked(new EventHandler<Event>() {
#Override
public void handle(Event event)
{
pauseTask();
}
});
Then you can handle it in other method
void pauseTask()
{
}

Setting ImageView in JavaFX based on a gif animation

I am fairly new to programming and have recently encountered a problem that I have not found a solution for online. I have created an ImageView in FXML and gave it an FXid of "gif1". I am using code from this stackoverflow post but I tried modifying it so that it would fit my needs. I put the entirety of the code in a separate java file that I called "Gif.java". Typically, "Gif.java" uses an hbox and other non applicable methods to set the ImageView. I removed those because I will be using an existing FXML document so there is no need to create a new one. In my code, I call on "Gif.java" and expect a returned ImageView. However, when I set my ImageView (gif1) to the returned ImageView, it doesn't update on the screen.
Here is the link to the gif post: How I can stop an animated GIF in JavaFX?
Here is my code that I use to update gif1:
#FXML
private void setUp(){
Gif newGif = new Gif("rightrock.gif"); // Creates object newGif and passes through path to gif
gif1 = newGif.returnImage2(); // Sets gif1 to returned ImageView
}
Here is the return code in Gif.java:
public class Gif{
private Animation ani;
private ImageView test;
public Gif(String image) {
// TODO: provide gif file, ie exchange banana.gif with your file
ani = new AnimatedGif(getClass().getResource(image).toExternalForm(), 1000);
ani.setCycleCount(1);
ani.play();
Button btPause = new Button( "Pause");
btPause.setOnAction( e -> ani.pause());
Button btResume = new Button( "Resume");
btResume.setOnAction( e -> ani.play());
}
public void returnImage(ImageView x){
test = x; // Sets ImageView test to parameter x
}
public ImageView returnImage2() {
return test; // Return instance field test
}
public static void main(String[] args) {
launch(args);
}
public class AnimatedGif extends Animation {
public AnimatedGif( String filename, double durationMs) {
GifDecoder d = new GifDecoder();
d.read( filename);
Image[] sequence = new Image[ d.getFrameCount()];
for( int i=0; i < d.getFrameCount(); i++) {
WritableImage wimg = null;
BufferedImage bimg = d.getFrame(i);
sequence[i] = SwingFXUtils.toFXImage( bimg, wimg);
}
super.init( sequence, durationMs);
}
}
public class Animation extends Transition {
private ImageView imageView;
private int count;
private int lastIndex;
private Image[] sequence;
private Animation() {
}
public Animation( Image[] sequence, double durationMs) {
init( sequence, durationMs);
}
private void init( Image[] sequence, double durationMs) {
this.imageView = new ImageView(sequence[0]);
this.sequence = sequence;
this.count = sequence.length;
setCycleCount(1);
setCycleDuration(Duration.millis(durationMs));
setInterpolator(Interpolator.LINEAR);
}
protected void interpolate(double k) {
final int index = Math.min((int) Math.floor(k * count), count - 1);
if (index != lastIndex) {
imageView.setImage(sequence[index]);
lastIndex = index;
}
}
public ImageView getView() {
returnImage(imageView); // This runs returnImage with the ImageView that I want to set
return imageView;
}
}
Any help would be greatly appreciated!

Managing the runtime behavior of a JavaFX MenuBar

I have a BorderPane, onto which I placed a MenuBar. At the center of the BorderPane I display differnt AnchorPanes depending on the MenuItem selected. So far so good.
Now, how do I make sure that the Menus change behavior in response to the item selected in the child AnchorPane? So for example if the user selects "Edit", there will be a different action depending on whether the item currently higlighted is a user account, a file etc.
So far I made something along these lines:
The BorderPane controller:
public class MenuTest implements Initializable{
#FXML
private BorderPane borderPaneMain;
#FXML
private AnchorPane anchorPaneMain;
#FXML
private Menu menuEdit;
#FXML
private MenuItem itemEdit;
static String menuMode;
static String entityName;
public MenuTest(){
menuMode ="";
entityName = "";
}
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
AnchorPane anchor;
try {
anchor = (AnchorPane) new FXMLLoader().load(getClass().getResource("views/MainView.fxml"));
borderPaneMain.setCenter(anchor);
} catch (IOException e) {
e.printStackTrace();
}
}
protected static void setMenuMode(String menuMd, String entityNm){
entityName = entityNm;
menuMode = menuMd;
}
#FXML
private void onEditClick(){
if(entityName.equals(AnchorTest.FILE)){
//Launches correct edit view
new FXMLLoader().load(getClass().getResource("views/EditFile.fxml"));
//Passes the name of the entity so that the controller can retrieve its data
FileEditController.setFile(entityName);
}else if(entityName.equals(AnchorTest.PERSON)){
new FXMLLoader().load(getClass().getResource("views/EditPerson.fxml"));
PersonEditController.setFile(entityName);
}
}
}
The child AnchorPane controller:
public class AnchorTest implements Initializable{
public static final String PERSON = "PERSON";
public static final String FILE = "FILE";
ObservableList<String> peopleList;
ObservableList<String> fileList;
#FXML
private ListView<String> listPeople;
#FXML
private ListView<String> listFiles;
#Override
public void initialize(URL location, ResourceBundle resources) {
peopleList = FXCollections.observableArrayList("Frank","Matin","Anne");
fileList = FXCollections.observableArrayList("hello.txt","holiday.jpg","cv.doc");
listPeople.setItems(peopleList);
listFiles.setItems(fileList);
}
#FXML
private void personSelected(){
MenuTest.setMenuMode(this.PERSON, listPeople.getSelectionModel().getSelectedItem());
}
#FXML
private void fileSelected(){
MenuTest.setMenuMode(this.FILE, listFiles.getSelectionModel().getSelectedItem());
}
}
However I'm not sure that it's the best solution, especially considering the if/elseif statement will need to be altered whenever I add a new element type and its corresponding edit options. So is there any way that I can do this better?
I think if your application has only a few (2-4) different types of "things" that are represented by a AnchorPane, then your approach is totally fine. An alternative to your approach is the state pattern. In that case, your currently selected "item type" would be your state.

How can I use Program a game after swing.Timer stopped?

I'm writing a simple pikachu game in Java, and I use swing.Timer with JProgress Bar, my code is like this:
public static void TimeBarListener(final View scr){
ActionListener updateProBar = new ActionListener() {
#Override
public void actionPerformed(ActionEvent actionEvent) {
int val = scr.timeBar.getValue();
if (val <= 0) {
scr.gameOver();
scr.timer = null;
return;
}
scr.timeBar.setValue(--val);
}
};
int t = n*400;
scr.timer = new Timer(t, updateProBar);
}
The "src" here is a class which extends JFrame to display the game I wrote, and 'n' is the number of pikachu pieces on a level. It works perfectly but after I add "timer", there are lots of problems occured:
I set the variable 't' change by level, but seems like it doesn't work ( I test the value, it was the right value but seems like 'timer' could'n get it). The time ran out too fast, faster if I click more on the pieces, and even if I set it longer it didn't change anything.
When I clicked "New Game" the second times, timer ran out even faster. But if I close the programs and then run again, the time return normal.
If the time ran out and then I click the "New Game" button again, It appears for 1 second then return to the "Game Over screen". Sometimes it works, but "IndexArrayOutOfBounds" Ecception appears.
I want to use the "Pause" button, which means that timer must pause and then continue to run, too. Is there anyway that I can do it?
I guess my problems are based on the code
if (val <= 0) {
scr.gameOver();
scr.timer = null;
return;
}
which makes the Game Over screen appears if the timer run out. But I'm new to this and I cant understand how I works, so I can't think of any solutions myself, or maybe it's not the problem.
Hope that I'll get some helps from you guys. Thanks a lot :)
Your problem is that you don't use correct architecture pattern. You should separate your business logic from your presentation. Also you should store variables (like time_left) in model, not in the controller (i.e. ActionListener). Please read about: Model View Controller pattern. It's a basic pattern and it'll solve most of yours problems.
Basic Example
Editor.java - main class
public class Editor {
public static void main(String[] args) {
Model model = new Model();
View view = new View(model);
new Controller(model, view);
}
}
View.java
public class View extends JFrame {
private Model model;
private JButton btn;
public View(Model model) {
this.model = model;
this.btn = new JButton("Test");
}
public void addViewControlListener(ActionListener al){
btn.addActionListener(al);
}
}
Controller.java
public class Controller {
public Controller(Model model, View view) {
view.addViewControlListener(new ViewControlListener(model, view));
}
}
ViewControlListener.java - implements ActionListener
public class ViewControlListener implements ActionListener {
private Model model;
private View view;
public ViewControlListener(Model model, View view){
this.model = model;
this.view = view;
}
public void actionPerformed(ActionEvent e) {
//update model
//refresh view
}
}
As you can see I have the one place (Controller.java) where I create listeners and add
these to components in view. You created multiple instances of the same listeners and lost control.
Also my ActionListener (ViewControlListener.java) holds instances of model and view, so it can update variables in model and refresh view.
Your application
And now something more about your application. You need thread (not realy action listener) that would decrement time variable and update view to show actual state (but only when game is active).
So you could create in model leftTime and isActive variables with setters and getters:
private int leftTime;
private boolean isActive;
public int getLeftTime() {
return leftTime;
}
public void setLeftTime(int leftTime) {
this.leftTime = leftTime;
}
public void decLeftTime() {
this.leftTime--;
}
public boolean isActive() {
return isActive;
}
public void setActive(boolean isActive) {
this.isActive = isActive;
}
And create thread that decrement time every second and repaint the view:
public class Timer extends Thread {
private Model model;
private View view;
public Timer(Model model, View view) {
this.model = model;
this.view = view;
}
public void run() {
while(true) { //could be better
if(model.isActive()) {
model.decLeftTime();
view.repaint();
}
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Then create and start that thread in Controller:
public class Controller {
public Controller(Model model, View view) {
view.addViewControlListener(new ViewControlListener(model, view));
...
Timer timer = new Timer(model, view);
timer.start();
}
}
In view you would add some component that shows left time from model and that's it.
PS do not forget set leftTime on game start.

Categories

Resources