Using same EventHandler for MouseEvent & KeyEvent in JavaFX? - java

I'm new to Java programming, so this question may sound stupid to many here. I'm trying to get myself comfortable with JavaFX Event handling mechanism.
I'm developing a GUI where I want a button to perform the same function when it's clicked and also when the Enter key is pressed.
Can I do the following?
public class ButtonHandler implements EventHandler<ActionEvent>
{
somefunction();
}
And then use it for both KeyEvent & MouseEvent
button.setOnMouseClicked(new ButtonHandler);
button.setOnKeyPressed(new ButtonHandler);

As long as you don't need any information from the specific event (such as coordinates of the mouse, or the key that was pressed), you can do
EventHandler<Event> handler = event -> {
// handler code here...
};
and then
button.addEventHandler(MouseEvent.MOUSE_CLICKED, handler);
button.addEventHandler(KeyEvent.KEY_PRESSED, handler);
Of course, you can also just delegate the actual work to a regular method:
button.setOnMouseClicked(e -> {
doHandle();
});
button.setOnKeyPressed(e -> {
doHandle();
});
// ...
private void doHandle() {
// handle event here...
}

Related

Equivalent of FocusEvent.getOppositeComponent in JavaFx

In my JavaFx application, I want to call a method when the main frame gains focus. However, I want to react only in the case where the focus was outside my application and came back (not when a dialog closes for example).
When the application was in Swing, I could use the method
FocusEvent.getOppositeComponent
(which corresponds to the element that lost focus), and if it was null I knew the focus was previously outside my application.
I have not found any equivalent in JavaFX.
I have tried looking at window events, by adding an event filter on my window:
primaryStage.addEventFilter(Event.ANY, e -> System.out.println("event " + e));
but it doesn't track focus events.
There is no equivalent in JavaFX. Focus changes are handled as a boolean property for each window separately, so you can only tell if a window received or lost focus. If you register a listener to all windows in your application, you could tell if one of them lost focus when another gained it.
There is no "FocusEvent" in JavaFX, you can find all event types listed in Event.
You can request the feature here.
I finally found a semi-satisfactory way of handling the problem, using the order of the events in JavaFX, so I'm posting it as an answer in case it can help others.
When a window w1 closes, giving focus to a window w2, the event order is as follow:
w1 receives event WINDOW_HIDING
w2 focusProperty changes to true
w1 receives event WINDOW_HIDDEN
So I wrote the following code to allow me to know whether the focus comes from an internal window:
public class MainStage {
private Stage primaryStage;
private AtomicBoolean triggerEventOnFocusGain = new AtomicBoolean(true);
...
primaryStage.focusedProperty.addListener((prop, oldVal, newVal) -> {
if(newVal.booleanValue() && triggerEventOnFocusGain.get()) {
doStuff();
}
});
}
public class SomeDialog {
private MainStage mainStage;
private Window dialogWindow;
...
dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDING, event ->
mainStage.setTriggerEventOnFocusGain(false));
dialogWindow.addEventHandler(WindowEvent.WINDOW_HIDDEN, event ->
mainStage.setTriggerEventOnFocusGain(true));
}
The only issue is that I have to do that for all internal windows/dialogs.
In my case I eventually decided that I could get away doing that for only a handful of dialogs, for which them triggering the event would be problematic, and ignore the others.
The other way of course would be to introduce a common abstract parent of all my view classes that does the above code.
JavaFX hierarchy is based on: Stage -> Scene -> Nodes -> ... -> Nodes:
If you want to listen focus of Stage (window), you can add listener to Stage focused Property of Stage:
Stage stage = ...
stage.focusedProperty()
.addListener((observable, oldValue, newValue) -> {
if (!stage.isFocused()) {
//action
}
}
);
This doesn't solve the problem in the question. You can't tell here what component had the focus. oldValue and newValue are booleans, so your if is trivial
You can check that you all Stages lost
focuses (implement custom ChangeListener):
class AllStageUnfocusedListener implements ChangeListener<Boolean>{
//IdentitySet and Runnable use only as example
private final Set<Stage> stageSet;
private final Runnable runnable;
public AllStageUnfocusedListener(Runnable runnable) {
this.stageSet = Collections.newSetFromMap(new IdentityHashMap<>());
this.runnable = runnable;
}
public ChangeListener<Boolean> add(Stage stage){
stageSet.add(stage);
return this;
}
#Override
public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
if(isAllStageLostFocus()){
runnable.run();
}
}
private boolean isAllStageLostFocus() {
for (Stage stage : stageSet) {
if (stage.isFocused()) {
return false;
}
}
return true;
}
}
and add Listener to Focused Property:
AllStageUnfocusedListener changeListener = new AllStageUnfocusedListener(() -> { /* action */ });
Stage stage = ...
stage.focusedProperty()
.addListener(changeListener.add(stage))

Mouse click to boolean

Hello from novice java developer, I created a MouseListener and MouseAdapter in a thread to control mouse action for mouse pressed, released and drag action. Each action will do specific things but i could not assign each MouseEvent e from each action to a variable.
So, how can deal with this problem? I also wonder if the method parameter MouseEvent e is specific to each method?
Here is my code:
Thread thread = new Thread() {
public void run() {
addMouseListener(new MouseAdapter() {
//#override deleted because i want to use e as a different action.
public void mouseaction(MouseEvent e) {
/* In here i want to control MouseEvent e action
(drag, pressed and released) and do specific things in with e event
and if e changes state should be changed in code during while(true) */
}
}
}
You can get all this information from the mouseEvent by calling the method getModifiersEx(), for example:
int eventType = e.getModifiersEx();
if (eventType & MOUSE_DRAGGED > 0) {
// Code to be executed when mouse is dragged
}
if (eventType & MOUSE_PRESSED > 0) {
// Code to be executed when mouse button is pressed
}
...
Note that the eventType is a bit field where multiple bits can be activated simultaneously.
//#override deleted because i want to use e as a different action.
public void mouseaction(MouseEvent e)
You can't just make up method names. You need to implement the methods of the listener. You need to handle the mousePressed, mouseReleased methods separately. For the mouseDragged you need to implement the MouseMotionListener.
Read the section from the Swing tutorial on Implementing Listener. You can find sections on:
How to Implement a MouseListener
How to Implement a MouseMotionListener
which both contain working examples.
I'll address this concern:
I also wonder if the method parameter MouseEvent e is specific to each method?
Every time this method is invoked by Swing, a new Event is generated. Your #Override annotation makes no difference.
So when user clicks somewhere, a MouseEvent N°2556 is generated for it, and the method is invoked with that event as a parameter.
When user drags the mouse away, a MouseEvent N°2557 is generated, and the method is again invoked with this new event as a parameter.
More broadly: All those MouseEvents will always be different instances. They are immutable, as well.
This means if you want to persist some information for your game loop to see, you need to store the relevant conditions in a field somewhere. And you won't be able to access it from an anonymous class because you won't have a handle to it. Here is a quick and dirty example (shameless reuse of #FrankPuffer's code):
public class MyMouseAdapter extends MouseAdpater {
public boolean isMousePressed = false; // This info is persisted here
public void mouseaction(MouseEvent e) { // This is only triggered upon user input
int eventType = e.getModifiersEx();
if (eventType & MouseEvent.MOUSE_PRESSED) {
isMousePressed = true;
}
if (eventType & MouseEvent.MOUSE_RELEASED) {
isMousePressed = false;
}
}
}
public static void main(String[] argc){
// Before the game loop:
MyMouseAdapter myAdapter = new MyMouseAdapter();
jpanel.addMouseListener(myAdapter);
// In the game loop
while(true) {
if(myAdapter.isMousePressed) { // This info is available anytime now!
// Do something
}
}
}

How to use FutureTask<V>, waiting for ui event?

I have an interface method which is supposed to return a Future object.
Future<Result> doSomething()
The implementation of this method shows some ui (javafx).
One of the ui elements has a listener, that needs to be called in order to receive the actual result, I need.
How do I achieve this?
Is there a better solution?
Here an example action I need to wait for:
// this is some framework method I cannot change
#Override
public Data execute(Data data) {
Future<Data> dataFuture = handler.doSomething(data);
// this should basically wait until the user clicked a button
return dataFuture.get();
}
// handler implementation
public Future<Data> doSomething(Data data) {
// the question is how to implement this part, to be able to
// return a future object
Button button = new Button("Wait until click");
// create thread that waits for the button click ?!????
// modify incoming data object when the button was clicked
// somehow create the Future object that's bound to the button click
return future;
}
This is what I want to achieve:
my method doSomething shows a new scene(ui) with a button on it
and returns immedeately the future object
future.get() waits until the user pressed the button
limitations: it has to be done with no extra library and on >=Java7
Use a javafx.concurrent.Task. It derives from FutureTask. There are extensive examples in the linked javadoc on Task usage.
Oracle also provide a tutorial which discusses Task usage:
Concurrency in JavaFX
I think this is what you want, but I may have understood the question, if so, please edit the question a bit to clarify requirements (perhaps with an mcve). The bit that makes me a little unsure is the part in your title "waiting for ui event?", I'm not quite sure what that means in this context.
This is a solution I was searching for. It's not very nice, since the Thread.sleep doesn't convince me.
but now you propably get an idea of what I want to achieve
// make sure this is not called on the ui thread
public Future<Data> doSomething(Data data) {
WaitingFuture future = new WaitingFuture(data);
Platform.runLater(() -> {
Button button = new Button("Wait until click");
button.setOnAction(future);
// show button on ui...
});
favouriteExecutorService.submit(future);
return future;
}
static class WaitingFuture extends Task<Data> implements EventHandler<ActionEvent> {
private Data data;
WaitingFuture(Data originalData) {
this.data = originalData;
}
private Data waitingData;
#Override
public void handle(ActionEvent event) {
waitingData = data.modify();
}
#Override
protected Data call() throws Exception {
while (waitingData == null) {
Thread.sleep(100);
}
return waitingData;
}
}

Add listener to SpanElement

I wanna add mouse over listener to SpanElement, which i created by:
SpanElement span = Document.get().createSpanElement();
span.setInnerText("my text");
I found in google how to do it with Label-wrapper, but I wanna to do it without any wrappers. Is it possible?
Thank you.
It is possible without JSNI too.
So with your element:
SpanElement span = Document.get().createSpanElement();
span.setInnerText("my text");
to add event listener directly to element:
Event.sinkEvents(span, Event.ONCLICK);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if(Event.ONCLICK == event.getTypeInt()) {
//do your on click action
}
}
});
...and it looks really ugly ;)
as you notice - the event listener is "common" for all dom events conceived by this element. so to make sure you handle the proper event you should check event type when you sink more than one Event type (this time it's overhead - as we sinked only CLICK event's bit). And as to sinking -> this initializes the element to take part in gwt global dom event dispatching system - the event are handled globaly to decrease number of closures so minimize memory leaks in older IE browsers.
on one more thing. you can set only one event listener per element - if you set a new one it overwrites the previous one. So i assuming somwehere later you want to add MOUSEOVER listener to your span and not to clear off allready added CLICK listener you might do something like this:
//add mouseover event bit to existing sunk event bits
Event.sinkEvents(span, Event.getEventsSunk(span) | Event.ONMOUSEOVER);
final EventListener oldListener = Event.getEventListener(span);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
if(Event.ONMOUSEOVER == event.getTypeInt()) {
//your mouseover action
}
if(oldListener != null) {
oldListener.onBrowserEvent(event);
}
}
});
or adding more events at once:
//add mouseover event bit to existing sunk event bits
Event.sinkEvents(span, Event.ONCLICK | Event.ONMOUSEOVER | Event.ONMOUSEOUT);
Event.setEventListener(span, new EventListener() {
#Override
public void onBrowserEvent(Event event) {
switch(event.getTypeInt()) {
case Event.ONCLICK:
break;
case Event.ONMOUSEOVER:
break;
case Event.ONMOUSEOUT:
break;
}
}
});
so after saying that all you probably aprecciate using a label widget wrapping your span ;)
Label.wrap(span).addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
//do your on click action
}
});
And last thing do not be afraid of widget even if you want to do DOM programming- look at them as something like jquery node wrapper object. they're not heavy but give much power.
you can also wrap widgets directly over existing DOM elements without attaching them to "panel infrastructure" .

Stop a event from bubbling in GWT

I have the following snippet of code, changeTextArea is a TextArea object.
changeTextArea.addKeyboardListener(new KeyboardListenerAdapter()
public void onKeyPress( Widget sender, char keyCode, int modifier){
//do something
//I WISH TO STOP THE EVENT THAT MAPS TO THIS KEYPRESS FROM BUBBLING ANY FURTHER
}
}
How would I stop the Event that is causing this method to be called from bubbling up from changeTextArea into the Panels/Widgets/Composites/Whatever that contain changeTextArea. Put succinctly, how do I stop it from bubbling any further. Any help would be appreciated (especially code samples).
As far as I know you can't do it via a keyboard listener, but it is possible by adding an event preview using the DOM class:
DOM.addEventPreview(EventPreview preview)
Then when you get the event:
onEventPreview(Event event)
You should return false, to say you want to cancel the event. The Event object also supports this method:
public final void cancelBubble(boolean cancel)
Cancels bubbling for the given event. This will stop the event from being propagated to parent elements.
You can find more details here:
http://google-web-toolkit.googlecode.com/svn/javadoc/1.5/index.html?overview-summary.html
You can definitely use the Event's cancelBubble() and preventDefault() methods from within any code that has access to the Event. There's no need to have an event preview...
You can call the sender's cancelKey() event. Here's an example that will only allow numbers to be inputted, all other keys get rejected.
private class RowColChangeHandler implements KeyPressHandler
{
public void onKeyPress(KeyPressEvent event) {
char keyCode = event.getCharCode();
if(keyCode <48 || keyCode >57)
{
((TextArea)event.getSource()).cancelKey();
}
}
}
you could reach it when possible by doing
event.doit = false

Categories