I am doing an homework in JavaFX2.1 and I have a problem with the setOnKeyPressed method.
My programs simulates a piano, so it does a sound every time I click on a button: 'Q' is 'do', 'W' is 're' and so on... I also have (for now) a mouse input, which will be disabled later since I can't play several notes at once with it...
My problem: if I hold down a key (on the keyboard of course, not with the mouse) its associated event will be triggered in a loop...
I did several tests and noticed that only the setOnKeyPressed is triggered, not the setOnKeyReleased.
I did some workarounds but they are not doing what I expect:
adding a boolean value to know if the key has been released disables the possibility of pushing on multiple keys at once.
turning off the volume after the sound has been played (and putting it back to its value when the key is released) seems to work, also for multiple keys, BUT the duration of the sound is considerably shorter compared to when I hold down the mouse on the same key.
Any suggestions?
You can't disable multiple events as it's system behavior. The best solution for you would be to improve boolean flag approach to store flag for each key. E.g. next way:
final Set<String> pressedKeys = new HashSet<String>();
keyboard.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
String note = t.getText();
if (!pressedKeys.contains(note)) {
// you may need to introduce synchronization here
pressedKeys.add(note);
playNote(note);
}
}
});
keyboard.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent t) {
pressedKeys.remove(t.getText());
}
});
Related
I know Piccolo2d is an old project, but I have a couple of questions.
1) Is it possible to disable dragging a selected object? For this particular use case, I only want to select an object, not move or delete it. I know I can disable deletion using:
this.selector.setDeleteKeyActive(false);
But I don't see an option to disable dragging. Is the only option to override the drag functionality in the event handler?
2) Is there no way to have the selection handler active at the same time as the pan/zoom handlers? It seems a bit archaic to disable pan/zoom when you want to support object picking. Or do I have to create my own handlers?
My current code is:
...
this.pluginContext.getCanvas().setPanEventHandler(null);
this.selector = new PSelectionEventHandler(this.mapLayer.getNode(), this.mapLayer.getNode()) {
};
this.selector.setDeleteKeyActive(false);
this.pluginContext.getCanvas().addInputEventListener(this.selector);
PNotificationCenter.defaultCenter().addListener(this, "nodeSelected",
PSelectionEventHandler.SELECTION_CHANGED_NOTIFICATION, this.selector);
...
public void nodeSelected(final PNotification notification) {
logger.debug("Selection - " + this.selector.getSelection().toString());
}
In true developer tradition, I found an answer.
My pickable nodes are all grouped under individual grouping nodes. This lets me add an event listener on the grouping node, like this:
groupNode.addInputEventListener(new PBasicInputEventHandler() {
public void mouseReleased(final PInputEvent event) {
PNode node = event.getPickedNode();
if (node != null) {
onNodeSelected(node); // your logic here
}
}
});
The beauty of this is that the event contains the picked node that is grouped under that group node. Perfect! Also, you don't have to disable the pan/zoom handlers.
The only downside is that this doesn't give you the selection decorator around the node. Can't win 'em all!
I have some suspicions about the way I'm handling the heldKeys variable (adding and removing keycodes from it).
Everything works fine when only a single key is pressed at once (and this is why I believe the problem, but be in the this class, and not the ones that take care of drawing or updating). However, everything completely breaks when I press more than one key at the same time.
By "breaks", it no longer works properly: pressing up moves all subscribers up sometimes, other times moves the one that should move up up, and the others down, etc. Other times a certain action stops doing anything until I restart the program.
I've tried debugging and seeing the evolution of the heldKeys array, but I really have no clue where the problem is.
Code:
<code>
public class MainInputProcessor implements InputProcessor {
// REFERENCE:
// <Listener, <ACTION, KEYCODE>>
private Array<ObjectMap<RacketMovementListener, ObjectMap<Racket.ACTIONS, Integer>>> racketMovementListeners;
// Stores the keys that are held down
private IntArray heldKeys;
// Initialisation
public MainInputProcessor() {
heldKeys = new IntArray();
racketMovementListeners = new Array<ObjectMap<RacketMovementListener, ObjectMap<Racket.ACTIONS, Integer>>>();
}
// Adds a RacketMovementListener to the respective subscriber list
public void subscribeToRacketMovement(RacketMovementListener newSubscriber, ObjectMap<Racket.ACTIONS, Integer> movementKeys) {
ObjectMap<RacketMovementListener, ObjectMap<Racket.ACTIONS, Integer>> map = new ObjectMap<RacketMovementListener, ObjectMap<Racket.ACTIONS, Integer>>();
map.put(newSubscriber, movementKeys);
racketMovementListeners.add(map);
}
// Update and execute event action
public void updateEvents() {
// Fire events for racket movement subscribers if the keys match
if (!heldKeys.isEmpty()) {
for (ObjectMap<RacketMovementListener, ObjectMap<Racket.ACTIONS, Integer>> map : racketMovementListeners) {
for (RacketMovementListener listener : map.keys()) {
for (Integer keycode : heldKeys.items) {
if (map.get(listener).get(Racket.ACTIONS.MOVE_DOWN).equals(keycode)) {
listener.moveDownPressed(listener);
} if (map.get(listener).get(Racket.ACTIONS.MOVE_UP).equals(keycode)) {
listener.moveUpPressed(listener);
}
}
}
}
}
}
#Override
public boolean keyDown(int keycode) {
// Add keycode to heldKeys if it's not already there
if (!heldKeys.contains(keycode)) {
heldKeys.add(keycode);
return true;
}
return false;
}
#Override
public boolean keyUp(int keycode) {
// Remove keycode from heldKeys since the key is no longer held down
for (Integer code : heldKeys.items) {
if (code == keycode) {
heldKeys.removeValue(code);
}
}
return true;
}
</code>
I'm not really looking for solutions or fixed code, just some starting point or somewhere to look, because I don't even know where to start. I've thought about rewriting this but I think I would do the same mistakes.
For context, this is code for a Pong clone, in LibGDX.
UPDATE
I rewrote the whole input manager to do the exact same but using switch statements and overall in a simpler way, but the exact same problem remains.
I'm now sure the problem is in the way I'm handling the heldKeys array.
However, I still can't figure out the problem with it...
I'd like to know how can I determine if the KeyCombination (constructed via Mnemonic) for a control is triggered. Or for simplicity's sake, a handler method for Mnemonics?
Basically, I'm working with a custom control which extends from Labeled and from its Behavior class, I want to perform some extra actions when the assigned Mnemonic is triggered.
EDIT
So after a little bit digging, I came up with an idea to simply listen to a KeyEvent from the Scene (but take note that this is JUST AN IDEA. Either way, I'll figure out the rest later on). Here's how:
public CustomLabeledBehavior(CustomLabeled control) {
super(control, ...);
/* IMPORTANT PART */
// Assume that mnemonicParsing is set to true
TextBinding tb = new TextBinding(control.getText());
KeyCombination kc = tb.getMnemonicKeyCombination();
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (kc.match(e)) {
System.out.println("MATCHED!");
}
});
}
UPDATE
To make this answer more useful than it used to be, I'll show you the usage of the method I'm currently using to determine if the KeyCombination is triggered. So I'll try to explain every single detail if I can.
We are only focusing on adding a Mnemonic, using the MnemonicParsing property of a custom control. And we will call this custom control as CustomLabeled since based on the question, it extends from the Labeled class, hence the name is based to it. Then we will work things inside its Skin class called, CustomLabeledSkin.
#1 Initializing the control:
<!-- Assuming that this FXML is already set and is attached to a Scene -->
<CustomLabeled text="_Hello World!" mnemonicParsing="true"/>
#2 Setting up our MnemonicHandler:
/* These two variables are initialized from the Constructor. */
private KeyCombination kb; // The combination of keys basically, ALT + Shortcut key
private String shortcut; // The shortcut key itself basically, a Letter key
private boolean altDown = false; // Indicator if the user press ALT key
/**
* This handler is a convenience variable to easily attach
* and detach to the Scene's event handlers. This is the
* whole idea to determine whether the KeyCombination is
* triggered.
*/
private EventHandler<KeyEvent> mnemonicHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// The KeyCombination, basically, the ALT + Shortcut key.
if (kb.match(event) {
// TODO: Execute command here.
event.consume(); // Prevent from further propagation.
return; // Make sure that the rest won't be executed.
}
// While these two functions are for separate event.
// Example, pressing ALT first before the Shortcut key.
if (event.isAltDown()) altDown = !altDown;
if (altDown && event.getCode() == KeyCode.getKeyCode(shortcut)) {
// TODO: Execute command here.
event.consume();
}
}
}
#3 Initialize our Skin class:
/**
* Constructor
*/
public CustomLabeledSkin(CustomLabeled control) {
// Since this is just an example/testing, we will only assume
// that MnemonicParsing is enabled and a valid Mnemonic is
// registered. So if you want to validate a mnemonic, you
// might want to do it here.
TextBinding tb = new TextBinding(control.getText());
kc = tb.getMnemonicKeyCombination();
shortcut = tb.getMnemonic();
// Then we can just filter for a KEY_PRESS from the Scene, then
// everything else will be handled by our MnemonicHandler.
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, mnemonicHandler);
}
NOTE: The TextBinding class is not part of the public API, it is used by the Labeled node to handle Mnemonics.
Further more, you can create a method to detach the MnemonicHandler if there's no currently Mnemonics assigned (E.g previously there's a mnemonic, then code was changed...).
I have a TextField where I have added an AjaxFormComponentUpdatingBehavior to get the current value when user write some string.
filterByObject = new TextField<String>("filterByObject", true, new PropertyModel<String>(searchParams, "objectFilter"));
AjaxFormComponentUpdatingBehavior changeFilterBinded = new AjaxFormComponentUpdatingBehavior ("onkeyup") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
target.addComponent(componentToUpdate);
}
};
filterByObject.add(changeFilterBinded);
When I put some chars inside textfield, onUpdate method is correctly called and my component, based on the current state of searchParams, changes correctly.
Unfortunally when I use Backspace to cancel what I have inserted, the onUpdate is not called.
I tried changing event (onkeypress, onkeydown, onchange etc...) but it doesn't work. Only onChange works but I have to change focus to another component.
How can I save the day?
Is the input in the field invalid (according to setRequired or IValidators added to the field) as a result of pressing the backspace key? If it is, the onError method will be called instead of onUpdate, because user input will be invalid and therefore will not reach the ModelObject of the component with the AjaxFormComponentUpdatingBehavior.
AjaxFormComponentUpdatingBehavior changeFilterBinded =
new AjaxFormComponentUpdatingBehavior ("onkeyup") {
#Override
protected void onUpdate(AjaxRequestTarget target) {
// Here the Component's model object has already been updated
target.addComponent(componentToUpdate);
}
#Override
protected void onError(AjaxRequestTarget target, RuntimeException e){
// Here the Component's model object will remain unchanged,
// so that it doesn't hold invalid input
}
};
Remember that any IFormValidator involving the ajax-ified component will not execute automatically, so you might be interested in checking the input for yourself manually before updating model objects if it's the case. You can tell AjaxFormComponentBehavior not to update model objects automatically by overriding getUpdateModel(). Then, in the onUpdate method, get the component's new input by means of getConvertedInput().
As a side note, onkeyup should be getting fired when pressing the backspace key. At least it does in this fiddle, and onchange is generally triggered on an <input type="text"> when focusing out of it.
Also, HTML5 introduces the oninput event handler, which may better suit your needs. It will get fired even when copying/pasting in the text field. See the following link for more information: Using the oninput event handler with onkeyup/onkeydown as its fallback.
I need to be able to detect if a certain key (e.g. CTRL) is pressed during a specific operation of mine. I don't have access to a key listener, nor to a mouse event. What I'm hoping is that there will be some class that has a method like "boolean isKeyPressed(keycode)".
Is anyone aware of a method like this in java?
For a bit of background, I am trying to override the default drag & drop behaviour for a component. By default, according to the javadocs for DropTargetDragEvent, if no key modifier is pressed, then the it looks in the component's supported actions list for a move, then a copy & then a link and stops after finding the first one.
In my application, we support both copy & link. As per the javadoc, without the CTRL key pressed, the default action is copy. We want the user to be able to specify the default action (allowing them to set their most commonly used) and then force a specific one using the modifier keys.
If I can detect the key pressed state then I can force this to happen but I can't see any other way of changing the default action.
Thanks in advance, Brian
The MouseEvent.getModifiers() method will return a bitmap of modifier keys that are pressed at the time the MouseEvent was generated. Or, you could use MouseEvent.isControlDown() to check specifically the CTRL key.
This is a possibly dirty way to go about it. But this allows you to 'record' key events and then query them.
//register this somewhere in the startup of your application
KeyboardFocusManager mgr = KeyboardFocusManager.getCurrentKeyboardFocusManager();
mgr.addKeyEventDispatcher(KeyEventRecorder.getInstance());
//then can query events later
KeyEvent keyEvt = KeyEventRecorder.getLastEvent();
if( keyEvt != null && keyEvt.getKeyCode() == KeyEvent.VK_CONTROL && keyEvt.getID() == KeyEvent.KEY_PRESSED )
//do something..
private class KeyEventRecorder implements KeyEventDispatcher
{
private static KeyEvent lastEvent;
private static KeyEventRecorder myInstance;
private KeyEventRecorder()
{
super();
}
public static synchronized KeyEventRecorder getInstance()
{
if( myInstance == null )
myInstance = new KeyEventRecorder();
return myInstance;
}
/**
* retrieve the last KeyEvent dispatched to this KeyEventDispatcher
*/
public static KeyEvent getLastEvent()
{
return lastEvent;
}//method
#Override
public boolean dispatchKeyEvent(KeyEvent e)
{
lastEvent = e;
//return false to let KeyboardFocusManager redistribute the event elsewhere
return false;
}//method
}//class
Even if there was such a method, what do you want to do with it? Call it in an endless loop in the hope it returns true at some point? I think an event-based / listener-based mechanism suits much better in this case.
I think you are going about this the wrong way. What you want to do is change the action when the drag is initiated, not when it is dropped. There are ways to change what the action is on initiation, including interrogating the user preferences in the "no modifiers" case. It's possible that changing the way the DropTargetDragEvent is called.