I've got 2 android native audio instances in two of my views. I'm trying to get the audio to stop when the user presses the back button and leaves the view as it's not happening automatically. I've looked at the documentation and seen the MobileEvent class. I've tried implementing it's constructor with no luck. This is my first app altogether and I've only just learnt Java and JavaFX on my own for this purpose so some help would be great. My current attempt is below.
public void MobileEvent(javafx.event.EventTarget source,
javafx.event.EventType<MobileApplication.MobileEvent> BACK_BUTTON_PRESSED) {
service.backPressed();
}
This is a Gluon application.
The idea of a custom event like MobileEvent.BACK_BUTTON_PRESSED is that you can subscribe to it using an event handler.
For instance, if you create a layer and you want to close it when the user presses the back button:
public BasicView(String name) {
super(name);
// create a custom layer
MobileApplication.getInstance().addLayerFactory("My Layer", () -> new Layer() {
private final Node root;
private final double size = 300;
{
root = new StackPane(new Button("A custom layer"));
root.setStyle("-fx-background-color: lightgreen;");
getChildren().add(root);
getApp().getGlassPane().getLayers().add(this);
// Add event handler to listen to Android Back Button Pressed event, hiding the layer
addEventHandler(MobileApplication.MobileEvent.BACK_BUTTON_PRESSED, e -> {
hide();
e.consume();
});
}
#Override
public void hide() {
setShowing(false);
super.hide();
}
#Override
public void layoutChildren() {
root.setVisible(isShowing());
if (!isShowing()) {
return;
}
root.resize(size, size);
resizeRelocate(0, 0, size, size);
}
});
Button button = new Button("Show Layer");
button.setOnAction(e -> MobileApplication.getInstance().showLayer("My Layer"));
VBox controls = new VBox(15.0, button);
controls.setAlignment(Pos.CENTER);
setCenter(controls);
}
If you create a Single View project, use the snippet above, and deploy it on an Android device, you can verify that when you click the button the layer shows up, and if you hit the Android back button, it will close the layer.
Notice that if you hit it again, it will close the app: The home view already has a listener on this event, that's why the app gets closed. Or if you are in a secondary view, with this event you will return to the previous view.
While you can subscribe to this event at any point in your code, like I've done in the example above, there are already other events that you can track more easily. For instance, the LifecycleEvent events, likeSHOWINGorHIDING`, are already used by all the Views.
So you can add to your custom view a listener to any of those events:
public BasicView(String name) {
super(name);
Label label = new Label("This is a custom view");
VBox controls = new VBox(15.0, label);
controls.setAlignment(Pos.CENTER);
setCenter(controls);
setOnShowing(e -> System.out.println("Showing Event"));
setOnHiding(e -> System.out.println("Hiding Event"));
}
Note that in your case, you can easily find out when the user leaves the view, and then react accordingly calling the service to stop the audio:
setOnHiding(e -> {
Services.get(MyAudioService.class).ifPresent(service -> service.stop());
});
Related
I have a project using Spring Boot and Vaadin. In this project I have a MainView that contains some Dialog components and upon closing these dialogues I would like the MainView to show a little plus button on the page and clicking this button would reopen the dialog (in the future). For this I have created a custom component event, added a listener for this event to the main view component and I "fire" the event after closing the dialog. The problem is that the MainView component registers the listener to its own event bus, but when the event is fired (from the dialog component) there are no listeners. Here is my code:
public class ComponentCloseEvent extends ComponentEvent<CustomDialog> {
public ComponentCloseEvent(CustomDialog source, boolean fromClient) {
super(source, fromClient);
}
}
// constructor for MainView
public MainView() {
addListener(ComponentCloseEvent.class, e -> System.out.println("I listened to the event!"));
add(new CustomDialog());
}
// method inside CustomDialog
private ButtonEx createCloseButton() {
return new Button("Close", e -> {
fireEvent(new ComponentCloseEvent(this, true));
close();
});
}
When I debug the code the fireEvent is not called because the function hasListeners returns false:
protected void fireEvent(ComponentEvent<?> componentEvent) {
if (hasListener(componentEvent.getClass())) {
getEventBus().fireEvent(componentEvent);
}
}
You can use the UI event bus.
MainView can attach a listener to UI in the onAttach() method, and the component can fire events using ComponentUtil.fireEvent() utility.
Take a look at this example https://cookbook.vaadin.com/ui-eventbus
my problem is the following:
I have a JavaFX application with a Button called "bindB";
Button bindB = new Button("None");
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
with the text "None". Is this button pressed, his text first changes to "..."
and by then calling the method "BindKey.bindKey();", the text will change to the
name of the key, the user is pressing on his keyboard.
This is the code of the method "BindKey.bindKey();":
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
As you can see, in the args of the method we give the button "bindB", so that the method knows
what button to change the name of, aswell as the current scene.
This code does work, but the problem is, that even after the button was
pressed, and its text has already be changed, the text still changes to the name of different
keys if you press them afterwards WITHOUT having to press the button a second time.
I thought that you had to end the "setOnAction" event by calling
event.consume();
but that didnt work...
So how do I make the buttons text only change, if the button has actually been pressed a second or third time?
Otherwise, the task which the button performs is toggled by EVERY key because technically
every key is the toggle key as the task reads the name of the button to know what key is for toggling.
Full code example:
Main class:
public class Main {
// Initialize GUI
public static void main(String[] args) {
GUI gui = new GUI();
gui.run();
}
}
GUI class:
public class GUI extends Application {
public void run() {
launch();
}
#Override
public void start(Stage window) throws Exception {
// When closing Window
window.setOnCloseRequest(event -> {
exitApplication(window);
});
// GridPane
GridPane grid = new GridPane();
grid.setPadding(new Insets(10, 10, 10,10));
grid.setVgap(15);
grid.setHgap(30);
// Scene
Scene scene = new Scene(grid, 200, 200);
window.setScene(scene);
// Bind Button
Button bindB = new Button("None");
GridPane.setConstraints(bindB, 1, 1);
bindB.setOnAction(event -> {
bindB.setText("...");
BindKey.bindKey(scene, bindB);
});
// Add to Grid
grid.getChildren().addAll(bindB);
// Show Window
window.show();
}
// Provide a clean exit
private void exitApplication(Stage window){
window.close();
Platform.exit();
System.exit(0);
}
}
BindKey class:
public class BindKey {
// Changes Buttons text
public static void bindKey(Scene scene, Button bindB){
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
});
}
}
I am not to 100% sure if this is the problem, but I think the only thing you have to do is the following:
scene.setOnKeyPressed(event -> {
bindB.setText(String.valueOf(event.getCode()));
scene.setOnKeyPressed(null);
});
}
You just have to remove the Key-Listener from the scene after you set the text. Because otherwise it will listen for keys the entire time.
I'm currently made an Form with JavaFX.
Always i press a Button, i call the "addAnswer()"-Method.
In that I create a RadioButton, a Label and a delete-Button, which i bundle in a HBox. All that HBoxes i pack in a vBox.
The Problem now is the delete-Button. I want to delte just THAT HBox in which the clicked Button is.
Here is my code:
public void addAnswer() {
this.rB = new RadioButton();
checkAnswer.getToggles().add(rB);
hBox = new HBox();
tF = new TextField();
delAnswer = new Button("Löschen");
delAnswer.setId(Integer.toString(counter));
hBox.getChildren().addAll(rB, tF, delAnswer);
hBox.setId(Integer.toString(counter));
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBox.getId())));
System.out.println(delAnswer.getId());
vBox.getChildren().addAll(hBox);
counter++;
}
public void delAnswer(int e){
vBox.getChildren().remove(delAnswer.getId());
}
i tried this one above but i realized, that all the delAnswers-Buttons have the same ID: the number of how often i pressed the add-Button.
Is there any solution where i can just select that one i pressed with that dynamic way? Cause i don't kow how often somebody will press or delete something.
Thanks
hbox is a field and this is why always the HBox last added is used. (hBox is evaluated, when lambda body is executed, not at the time of the lambda creation). This would be different, if you used a (effectively) final local variable:
final HBox hBoxLocal = hBox;
delAnswer.setOnAction(e -> delAnswer(Integer.parseInt(hBoxLocal.getId())));
However I'd like to present a different solution which would allow you to use the same EventHandler<ActionEvent> for all delete Buttons:
You can get the Node that triggered the event using getSource. From this Node you can get the parent, which is the HBox. You can remove this from the VBox using the remove(Object) method
delAnswer.setOnAction(e -> {
// get button
Node source = (Node) e.getSource();
// remove parent of button from VBox
vBox.getChildren().remove(source.getParent());
});
I think your problem is that you give the same event to all your button,Begin by creating a list that stores your buttons and then increments the value of the ID after affecting it to an item :
List<Button> buttons = new ArrayList<>();
/*
Create Button and call IDEvt method to create new event
for each button
*/
private void IDEvt(Button btn){
btn.setId(String.valueOf(IDRank));
btn.setOnMousePressed(new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
System.out.println(btn.getId());
}
});
IDRank++;
}
Having a
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
getWindow().removeWindow(popup);
log.warn("Removed Popup");
}
I would expect to show a popup window and after some milisecundom (after the expensive method calls) it should hide itself. The log says :
2014-02-19 15:26:51 WARN xyzClass:82 - Added POPUP
2014-02-19 15:26:51 WARN xyzClass:135 - Removed Popup
But the truth is that there is no popup showing here.
If i only show it, and not remove it later (the popup will show)
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
//lot of method calling here then
log.warn("Removed Popup");
}
My main reason for this i want to achieve a glasspanel/loading screen functionality # Vaadin, and not had found better solution yet. Any solution/description why the popup not shown up i would appreciate
Just do not have time to render it. You add it and immediately remove.
Try this approach, for example:
private MyPopup popup;
public void buttonClick(ClickEvent event) {
Thread workThread = new Thread() {
#Override
public void run() {
// some initialization here
getWindow().removeWindow(popup);
}
};
workThread.start();
popup = new MyPopup();
getWindow().addWindow(popup);
}
Depending on Vaadin version you can make use of ICEPush plugin (Vaadin 6) or built-in feature called Server Push (Vaadin 7).
public void buttonClick(ClickEvent event) {
MyPopup popup = new MyPopup();
getWindow().addWindow(popup);
log.warn("Added POPUP");
// start background thread with ICEPush or ServerPush
}
// Background thread in a separate class
// update UI accordingly when thread finished the job
getWindow().removeWindow(popup);
log.warn("Removed Popup");
Thanks to it you can move your time-consuming operations to another class thus decouple your business logic from the presentation layer. You can find examples of usage in the links above.
I am just wondering...
When I click refresh button my gwt app comes to its default UI state despite its UI was modified during client-server interactions (callbacks) etc... But sometimes it is really essential thing to "cache" UI if user clicks refresh by mistake or reopened web page which user still logged-in;
So my question is...
Is there a way to restore gwt app UI (its before-refreshed state) in some standard way? Can History tokens help for this kind of issue?
edit
Concerning the history tokens I saw this example :
History.addValueChangeHandler(new ValueChangeHandler<String>() {
public void onValueChange(ValueChangeEvent<String> event) {
String historyToken = event.getValue();
// Parse the history token
try {
if (historyToken.substring(0, 4).equals("page")) {
String tabIndexToken = historyToken.substring(4, 5);
int tabIndex = Integer.parseInt(tabIndexToken);
// Select the specified tab panel
tabPanel.selectTab(tabIndex);
} else {
tabPanel.selectTab(0);
}
} catch (IndexOutOfBoundsException e) {
tabPanel.selectTab(0);
}
}
});
... and I could notice it restores tabIndex from history; so will it help if tab panel won't be init-ed by module load (by default) but something this way:
//on button click...
getSimplePanel().setWidget(new MyTabbedPane());
edit
To be more clear here is my test code which I am trying to figure out how to restore MainUI I mean its previous UI state as if refresh button wasn't clicked.
the EntryPoint...
public class Main implements EntryPoint {
private SimplePanel simplePanel;
public void onModuleLoad() {
RootPanel rootPanel = RootPanel.get();
FlowPanel flowPanel = new FlowPanel();
rootPanel.add(flowPanel, 10, 10);
flowPanel.setSize("410px", "280px");
Button setWidgetButton = new Button("Click");
setWidgetButton.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
getSimplePanel().setWidget(new MainUI());
}
});
flowPanel.add(setWidgetButton);
simplePanel = new SimplePanel();
flowPanel.add(simplePanel);
}
protected SimplePanel getSimplePanel() {
return simplePanel;
}
}
...and composite;
public class MainUI extends Composite {
private VerticalPanel verticalPanel;
int index;
public MainUI() {
FlowPanel flowPanel = new FlowPanel();
initWidget(flowPanel);
Button button = new Button("+");
button.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
getVerticalPanel().add(new Label(""+(++index)+": "+Math.random()));
}
});
flowPanel.add(button);
DecoratorPanel decoratorPanel = new DecoratorPanel();
flowPanel.add(decoratorPanel);
verticalPanel = new VerticalPanel();
decoratorPanel.setWidget(verticalPanel);
}
protected VerticalPanel getVerticalPanel() {
return verticalPanel;
}
}
...and, as a result, to have "cached" ui state without regenerating it again with extracting strings from history tokens and re-instantiate objects or what so ever...
for example if I have this UI (see image) I am interested to have totally the same one after refresh button is pressed...
but I am not pretty sure which way should I look for? I haven't seen any gwt snippet in this direction; So I really need your advice what way should I dig in?
Any useful comment is appreciated
Thanks
P.S. GWT 2.3
I think you miss to store the state into the URLs-Hashtag.
You can use GWTP (as suggested in the comments)
In fact you need to read the Hashtag in your onModuleLoad and restore your state.
This may work with getHash():
String state = Window.Location.getHash();
myRestoreStateFromTokenMethod(state);
update
Here are some snippets to create a push store.
List<String> states = [...]
public void onClick(){ states.add("newState");changeHash(states); }
public void changeHash(){
String hash = states.get(0) + ";"
for(other states) hash += states.get(i);
// use a UrlBuilder to set the Hash
}
Documentation for UrlBuilder
Or you can try this: https://github.com/jbarop/gwt-pushstate