I've been developing with codenameone for over a year, and I never ran into this problem before, I feel like I'm losing my mind. I just redesigned one part of an app I'm working on, and now the ActionListeners aren't firing. I'm attaching them to a Button and a SpanButton in the code:
ActionListener goToDoc = new ActionListener() {
String mDocId = docId;
#Override
public void actionPerformed(ActionEvent evt) {
mStateMachine.currentExpertId = mDocId;
mStateMachine.showForm("DoctorDetails", null);
}
};
name.addActionListener(goToDoc);
Util.downloadImageToStorage(mStateMachine.URL_PREFIX+"images/doctors/"+(String)value.get("doc_pic"),
"doc_post_pic_"+(String)value.get("doc_id")+".png", new Callback<Image>() {
#Override
public void onSucess(Image img) {
pic.setIcon(img.scaledWidth(mStateMachine.getProportionalWidth(.23)));
StateMachine.applyGreenBorder(pic);
pic.addActionListener(goToDoc);
pic.getParent().revalidate();
}
#Override
public void onError(Object sender, Throwable err, int errorCode, String errorMessage) {
System.out.println("Unable to download expert profile picture");
}
});
When I debug the code, the components do show that the ActionListener is attached, but the actionPerformed method is never reached, no matter how many times I click on the buttons. I experience this problem both on the simulator and on an Android device. I have yet to test on an iPhone.
Did you set a parent to be a lead component or focusable?
The reason the click event wasn't firing was because the Components weren't enabled, possibly a bug in the BUI Builder. After checking off the 'Enabled' and 'Focusable' checkboxes in the GUI Builder, and seeing they were unchecked every time I went back to that form, I just used component.setFocusable(true) and component.setEnabled(true) in the code, and it worked fine after that.
I've got this Controller connected to a FXML-file with several buttons, labels, a table, etc.
I've got some popups that get initialized and shown when different buttons get clicked and that works fine.
I've got another popup that I'd like to 'pop up' when something goes wrong, so this is called when an event get's handled that has been sent from java-code in another class.
This message pop-up get's called, but the code within the Platform.runLater() isn't executed, actually freezing the GUI.
There's one distinction I've found that seems to cause this and that is that a Platform.isFxApplicationThread() that I call right before the Platform.runLater() returns false in this message pop-up where it returns true when one of the other pop-ups get called from a button-click.
As I've also tried one of those pop-ups that's normally called from a button-click and that also doesn't work when it's called from the code that get's executed because of the incoming event, I'm pretty sure this is the problem, but Platform.runLater states "This method, which may be called from any thread, will post the Runnable to an event queue and then return immediately to the caller." and that seems not true for me, so I'm kinda puzzled if this actually is the problem ...
Has anyone encountered this before and / or does anyone know what I'm doing wrong?
This works fine:
#FXML
private void btnCashClicked(ActionEvent event) {
screensController.getCashTransactionController().addCashTransactionListener(this);
labelToPay = new Label(eurosToPay + " euro");
sealbagTextField = new SealbagTextField();
PopupUtils.showCashPaymentPopup(btnSealbag, btnCashOk, labelPaid, labelSealbag, labelToPay, lblExchange,
labelExchange, labelReturnValue, eurosToPay, btnCash, this, sealbagTextField);
screensController.getMainController().startTransaction(amountInCents, PaymentType.Asap);
}
This code in the same controller class doesn't show a pop-up:
#Override
public void showErrorOnScreen(String message) {
// temporary usage of label and textfield
labelToPay = new Label(eurosToPay + " euro");
sealbagTextField = new SealbagTextField();
PopupUtils.showCashPaymentPopup(btnSealbag, btnCashOk, labelPaid, labelSealbag, labelToPay, lblExchange,
labelExchange, labelReturnValue, eurosToPay, btnCash, this, sealbagTextField);
//PopupUtils.showMessagePopup("Error", message, "Close", 374, 250, btnCancel);
}
I'm on Windows and using jre1.8.0_60
The code of the cashPopup:
public static int showCashPaymentPopup(Button btnSealbag, Button btnCashOk, Label labelPaid, Label labelSealbag, Label labelToPay, Label lblExchange, Label labelExchange, Label labelReturnAmount, int amount, Node node, PayScreen parent, SealbagTextField sealbagTextField) {
int paid = 0;
logger.debug("cashPopup is on GUI thread: " + Platform.isFxApplicationThread());
Platform.runLater(new Runnable() {
#Override
public void run() {
cashPopup.getContent().clear();
Rectangle rectangle = new Rectangle();
rectangle.setArcHeight(20);
rectangle.setArcWidth(20);
rectangle.setFill(Color.LIGHTBLUE);
rectangle.setWidth(466);
rectangle.setHeight(311);
rectangle.setStroke(Color.DARKBLUE);
rectangle.setStrokeType(StrokeType.INSIDE);
...
cashPopup.getContent().addAll(rectangle, textArea, headerLabel, lblDesc, lblAmount, labelAmount, lblPaid, labelPaid, lblToPay, labelToPay, btnCashOk, lblSealbag, labelSealbag, lblExchange, labelExchange, labelReturnAmount, btnSealbag, btnCancel);
cashPopup.show(node, 150, 164);
}
});
return paid;
}
And the showMessagePopup:
public static void showMessagePopup(String title, String text, String buttonText, int posX, int posY, Node parent) {
logger.debug("messagePopup is on GUI thread: " + Platform.isFxApplicationThread());
Platform.runLater(new Runnable() {
#Override
public void run() {
logger.debug("0");
messagePopup.getContent().clear();
Rectangle rectangle = new Rectangle();
rectangle.setArcHeight(20);
rectangle.setArcWidth(20);
rectangle.setFill(Color.LIGHTBLUE);
rectangle.setWidth(500);
rectangle.setHeight(300);
rectangle.setStroke(Color.DARKBLUE);
rectangle.setStrokeType(StrokeType.INSIDE);
Label headerLabel = new Label(title);
headerLabel.setStyle("-fx-font-size: 18; -fx-font-family: Arial;");
headerLabel.setLayoutX(15);
headerLabel.setLayoutY(10);
TextArea textArea = new TextArea();
textArea.setStyle("-fx-font-size: 14; -fx-font-family: Arial;");
textArea.setLayoutX(10);
textArea.setLayoutY(35);
textArea.setMaxWidth(480);
textArea.setMinHeight(190);
textArea.setMaxHeight(190);
textArea.setEditable(false);
textArea.setWrapText(true);
textArea.setText(text);
Button btnClose = new Button(buttonText);
btnClose.setLayoutX(180);
btnClose.setLayoutY(235);
btnClose.setPrefSize(120, 54);
btnClose.setStyle("-fx-font-size: 18; -fx-font-family: Arial; -fx-text-fill:white; -fx-background-color: linear-gradient(#8b9aa1, #456e84), linear-gradient(#c5dde7, #639fba), linear-gradient(#79abc1, #639fba); -fx-background-insets: 0,1,2; -fx-background-radius: 6,5,4;");
btnClose.setOnAction(new EventHandler<ActionEvent>() {
#Override
public void handle(ActionEvent event) {
messagePopup.hide();
}
});
messagePopup.getContent().addAll(rectangle, headerLabel, btnClose, textArea);
messagePopup.show(parent, posX, posY);
}
});
}
logger.debug("0") isn't even executed ...
Found it, by running in debug mode and suspending the Java FX thread to see what it is doing.
There's this 'other thread' that gets started from the main program and which needs to get started before the process can continue. This other thread looks like this:
pinPadAsSlaveThread = new Thread(pinPadAsSlave);
pinPadAsSlaveThread.start();
while (!pinPadAsSlave.isRunning()) {
// wait for pinPadAsSlave to be running
try {
Thread.sleep(10);
} catch(InterruptedException ie) {
// ignore
}
}
Normally this takes about 50 ms, but as the pin pad is unavailable on the network this becomes an infinite loop. That on itself should be handled of course, by letting this loop only try it for 50 times or so.
But the real problem is that this thread that is put to sleep for 10 ms all the time is the Java FX thread. I don't know why the Java FX thread is doing the setting up of the communication, as it shouldn't (and I didn't ask for that by putting it inside a platform.runLater or something alike), but the fact is: it is ...
The example on https://developers.google.com/cast/cast_2nd-screen_app_tutorial shows a onDestroy method which calls unregisterMediaRouteProvider. This causes the MediaRouter.Callback.onRouteUnselected method to get called which in turn ends the session. This leads to the app getting disconnected from the chromecast device and the MediaRouteButton stops being blue. Below is the onDestroy method from the example:
#Override
protected void onDestroy() {
MediaRouteHelper.unregisterMediaRouteProvider(mCastContext);
mCastContext.dispose();
super.onDestroy();
}
So my question is, what is the proper way to handle screen rotation when using the chromecast device from an app?
You can try using isFinishing() method of Activity to figure out if onDestroy is called due to application really "finishing" or is called for other reasons. Another option is to handle orientation change yourself.
You can see the guidelines for handling setup/destruction of the Chromecast (such as when orientation change is happening) on https://developers.google.com/cast/docs/android_sender
The relevant sections of code are the following ones:
#Override
protected void onResume() {
super.onResume();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}
#Override
protected void onPause() {
if (isFinishing()) {
mMediaRouter.removeCallback(mMediaRouterCallback);
}
super.onPause();
}
And also the following code:
#Override
protected void onStart() {
super.onStart();
mMediaRouter.addCallback(mMediaRouteSelector, mMediaRouterCallback,
MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY);
}
#Override
protected void onStop() {
mMediaRouter.removeCallback(mMediaRouterCallback);
super.onStop();
}
It is also a good idea to have a boolean value in your program, for instance "wasLaunched" or "isConnected" to keep track of whether the connection to the chromecast is active or not. I use this variable in my code to check if I can send messages to the receiver or not. Then simply remember to save this variable and restore it when there is an orientation change on the device. This works for me in my chromecast enabled app. The code for saving/restoring my variable, so it survices orientation change is shown below:
protected void onSaveInstanceState(Bundle bundle) {
if (bundle!=null)
{
bundle.putBoolean("wasLaunched", wasLaunched);
}
super.onSaveInstanceState(bundle);
}
protected void onRestoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState!=null)
{
wasLaunched = savedInstanceState.getBoolean("wasLaunched");
}
super.onRestoreInstanceState(savedInstanceState);
};
Of course you can also put other stuff in your bundle that needs to survive an orientation change. I am not using the onDestroy override you describe there, nor is it mentioned in the google documentation I link to. But I use the teardown() method described in that document I link to for cleaning up, but this only happens when I close the connection, because I dont want to close the connection to the chromecast upon orientation change.
I have a problem with a button in that it doesn't work on the first click. I have to click twice and it then gives double results:
Button button = new Button("Click Me");
button.addClickListener(
new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
new Thread(new Runnable() {
#Override
public void run() {
DateChooser dateChooser = new DateChooser(new com.kopiright.xkopi.lib.type.Date(2013, 12, 9));
System.out.println(dateChooser.selectDate(com.kopiright.xkopi.lib.type.Date.now()).toString());
}
}).start();
}
}
);
DateChooser extends com.vaadin.ui.Panel class.
Vaadin Button is always immediate so that's not the problem here.
The problem is that you are starting an external thread, which updates the UI, and to see changes made to the UI by an external thread, you should use pollig or pushing. In this case the second button click polls the changes to the browser. But in this case you can just remove the thread:
button.addClickListener(new Button.ClickListener() {
public void buttonClick(ClickEvent event) {
DateChooser dateChooser = new DateChooser(new com.kopiright.xkopi.lib.type.Date(2013, 12, 9));
System.out.println(dateChooser.selectDate(com.kopiright.xkopi.lib.type.Date.now()).toString());
}
});
And when an external thread is used to update Vaadin components, the code must be synchronized correctly.
Is there a reason you create a new thread for this?
Please be aware that modifying the GUI from a thread mus be synchronized.
Look in the book of vaadin for this:
11.16.3. Accessing UI from Another Thread
https://vaadin.com/de/book/vaadin7/-/page/advanced.push.html
André
I'm trying to play background audio in a loop in a JavaFX 2.0 application using JavaFX SDK 2.0.1. I decided to use a MediaPlayer created by the following piece of code:
MediaPlayerBuilder
.create().media(BACKGROUND_MEDIA)
.cycleCount(MediaPlayer.INDEFINITE);
This basically works, but when a new cycle starts there is a tiny (latency?) gap between the end and the start of the audio. So it's not a working option for me since it's not playing a clean loop.
I decided to build a new MediaPlayer object and start playback everytime Media ends. This works fine so far. Additionally, I use a button playing a short AudioClip when clicked.
I discoverd that frequent and fast clicking this button leads to interrupts in the background audio. I created an example to reproduce this behaviour by inifinitely playing an AudioClip with volume 0 when the button is clicked once. The example is not self contained, since the required audio files are missing. It requires to place 2 audio files in the project's source directory:
click.wav (a really short click sound ~300ms)
background.wav (~5 seconds of audio)
How do I achieve playing a clean audio loop in background without these interrupts when other one-shot audio sounds are played? Is it just a performance issue?
Example:
package mediatest;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.media.AudioClip;
import javafx.scene.media.Media;
import javafx.scene.media.MediaPlayer;
import javafx.scene.media.MediaPlayerBuilder;
import javafx.stage.Stage;
public class MediaTest extends Application {
private static final AudioClip CLICK_AUDIOCLIP = new AudioClip(MediaTest.class.getResource("/click.wav").toString());
private static final Media BACKGROUND_MEDIA = new Media(MediaTest.class.getResource("/background.wav").toString());
private MediaPlayerBuilder builder;
public static void main(String[] args) {
Application.launch(args);
}
#Override
public void start(Stage primaryStage) {
Group root = new Group();
Scene scene = new Scene(root, 300, 250);
this.builder = MediaPlayerBuilder
.create()
.media(BACKGROUND_MEDIA)
.onEndOfMedia(new Runnable() {
public void run() {
MediaPlayer player = MediaTest.this.builder.build();
player.play();
}
});
MediaPlayer player = this.builder.build();
player.play();
Button btn = new Button();
btn.setText("Repeat playing short audio clip");
btn.setOnAction(new EventHandler<ActionEvent>() {
public void handle(ActionEvent event) {
//Simulation of many button clicks
MediaTest.CLICK_AUDIOCLIP.setCycleCount(AudioClip.INDEFINITE);
MediaTest.CLICK_AUDIOCLIP.play(0);
}
});
root.getChildren().add(btn);
primaryStage.setScene(scene);
primaryStage.show();
}
}
have you looked into ExecutorService? You would then have a number of predefined threads like so:
ExecutorService service = Executors.newFixedThreadPool(4);
where 4 is the number of threads it makes.
It will improve performance because it uses already made threads rather than making a new one each time you want to run something.
You would create a Runnable and execute it with the service like so:
Runnable r = new Runnable() {
#Override
public void run() {
playSound();
}
};
service.execute(r);
Not only would this improve performance but it automatically assigns the job to a not-currently-busy thread in its thread pool.
Also look at this: Playing sound loops using javafx which I believe solves your small latency problem.
EDIT: damn sorry, I didn't know this post was that old. It was a top result in google.
Using another thread at
public void handle(ActionEvent event) {
Platform.runLater(new Runnable()
{
#Override
public void run()
{
//Simulation of many button clicks
MediaTest.CLICK_AUDIOCLIP.setCycleCount(AudioClip.INDEFINITE);
MediaTest.CLICK_AUDIOCLIP.play(0);
}
});
}
});
May solve the problem of the interruption