I'm working on a large application with a lot of different windows/frames/panes/tabs, many of which may be open at any time. Requirements call for some hotkeys that give focus to a certain tab in a certain window. I would like to avoid registering a KeyListener with every single window/component that might have focus; as such, I tried the following
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher(){
#Override
public boolean dispatchKeyEvent(KeyEvent e) {
if (e.getKeyChar() == '-'){
doTheThing();
return false;
}
return false;
}
});
That first "return false" allows that character to go through to other possible consumers, such as text entry fields. The problem is that the code still fires, and will jerk the user back to the tab in question (potentially pulling them away from what they were doing, if it was another tab in the same window.)
Is there a way to have this code fired globally, EXCEPT if it would otherwise enter into a text panel? (Maybe a way to add a handler that would get it "last" rather than "first" - so it only fires if nothing else is consuming the event?)
I'm pretty new to UI design, so apologies if this is a dumb question, and thanks in advance for the help.
Try using KeyboardFocusManager#getFocusOwner and determine if it's a JTextComponent or not...
if (!(KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() instanceof JTextComponent)) {
//...
}
or some such
Related
I´ve been working on a GUI and I´ve run into some problems with a JMenu. The GUI is in a separate class that takes all the actions of the program from the rest of the classes. My problem with using the menu is that I want it to do 2 things: first, to show the correct panels based upon a user choice and second, wait for user input to complete the chosen task.
I´ve arranged it into 13 different if.. else clauses depending on the user choice, this part works correctly with the nescessary input options (panels) shown that the user need to input data.
Part two when the user sees the right panels and wants to input information (int and Strings) is where things don´t go as intended. Instead of waiting for user input and then take actions based on those data the program rushes forward and continues. Since no data is entered, needless to say, the output is not the intended one.
I´ll provide part of the class as it´s quite large.
class Employee implements ActionListener
{
public void actionPerformed(ActionEvent e)
{
if(e.getActionCommand().equals("1"))
{
panelB.setVisible(false);
panelC.setVisible(false);
panelD.setVisible(false);
display.setText(bank.infoBank()); }
else if(e.getActionCommand().equals("2"))
{
panelB.setVisible(true); //Show all panels
panelC.setVisible(true);
panelD.setVisible(true);
label2.setText("Accountnumber: ");
text2.setText("");
display.setText("");
}
...
else if(e.getActionCommand().equals("5"))
{
panelB.setVisible(true);
panelC.setVisible(false);
panelD.setVisible(true);
display.setText("");
if(e.getActionCommand().equals("Ok") && !pNrText.getText().isEmpty()) //This is a JButton that when pressed should send the data to the method
{
if(checkInput(pNrText) == true) //Method to validate input
{
pNr = Long.parseLong(pNrText.getText()); //pNr is an input field
bank.addSavingsAccount(pNr); //this is a method from another class
}
else
{
JOptionPane.showMessageDialog(null, "Only digits permitted!");
}
}
This last part (actioncommand equals 5) is one of the places where the program doesn´t wait for the user to input anything before it continues and thus receives no input at all to process.
This is part of an ATM-program built around different JMenus, in another place where an JRadioButton is used it works as intended but I can´t get it to work using the JMenu and I don´t want to use a JRadioButton with so many choices(13).
Any input greatly appreciated!
/Johan
"This last part (actioncommand equals 5) is one of the places where the program doesn´t wait for the user to input anything before it continues and thus receives no input at all to process."
else if(e.getActionCommand().equals("5")) {
panelB.setVisible(true);
panelC.setVisible(false);
panelD.setVisible(true);
display.setText("");
if(e.getActionCommand().equals("Ok") {
I don't know why you would expect this behavior. This is not a console program where a scanner waits for input. Event driven programming doesn't work like that. One event get's one response. There's no waiting. Everything in the actionPerformed happens exactly once. That means when you press 5, the corresponding if will perform. The OK-if will never be performed because no event will ever reach it because it's trapped in the 5-if. Quickes fix, would be to give the OK-block it's own else-if on the same level as the 5-if
Like I said in my comment. Avoid all these if statement. Add an anonymous listener to each menu item.
okItem.addActionListener(new ActionListener(){
public void actionPerforemd(ActionEvent e) {
}
});
But you can even excel beyond this and use Action. Personally I prefer to go this route for menu items, for a number of reasons.
Also as an aside, you should be using a CardLayout to switch between panels.
So, I will do my best to explain this question...
Basically, I have a GUI whose main window has several buttons on it (probably about 10). I am putting the buttons themselves in an array, but when it comes to handling click events for each button, something different is going to happen depending on which one is clicked.
Instead of doing something like this:
#Override
public void actionPerformed(ActionEvent e) {
if(e.getActionCommand().equals("Button1Text") { /* do stuff */ }
else if(e.getActionCommand().equals("Button2Text") { /* do stuff */ }
else if(e.getActionCommand().equals("Button3Text") { /* do stuff */ }
else if(e.getActionCommand().equals("Button4Text") { /* do stuff */ }
}
Is there a more efficient way to handle each button's response when it gets clicked? The idea is that whenever a button gets clicked, a new window will open to let the user perform various tasks associated with that button. I was thinking of somehow using getActionCommand() in combination with the Class.forName()/newInstance() methods, but I'm not sure if there is another (or easier) way to do something like this.
The ActionListener you've shown is sometimes known as a "switchboard listener", and you're correct to think that it can be improved upon. It is quite rigid code, making it hard to debug and enhance.
Best I think would be to not have the GUI implement ActionListener at all but rather to use unique Actions (i.e., AbstractActions) for each class of button and then plug the appropriate Action into the appropriate button. To plug with minimal coupling, consider using dependency injection a la Spring or Guice.
I'm working on a Java Game and I've come to a point where I'm having problems with the KeyListeners/KeyBinding. What I basically want to do is temporarily disable the keyboard/do not allow more inputs when an animation occurs. This animation is generated by updating data.
What I currently get is that I press the key for the animation, the animation starts, and I press another key that does some other function. It gets added to the stack/queue(?) of the keyboardlistener and triggers when the first animation finishes.
I'm using a JPanel that implements KeyListener.
To give an idea of the code:
public void keyPressed(KeyEvent arg0) {
//Prevents Repeated keys
pressed.add(arg0);
if (pressed.size() == 1) {
int key = ((KeyEvent) pressed.toArray()[0]).getKeyCode();
if (key == KeyEvent.VK_ENTER) {
doSomeAnimation();
} else if (key == KeyEvent.VK_SPACE) {
doADifferentAnimation();
}
Update();
}
}
Things I've tried:
1) Set the focusable(false) on the JPanel before the calls to the animations. Then set the focusable(true) and grab the focus when they've been completed.
2) Used a boolean to track when an animation occurred.
3) Use Key Bindings.
No matter what method I used, I always ended up with the problem that I would still take in input from the keyboard when the animation was occurring. Then, once that animation was finished, it'd go to the next element in the stack/queue(?) and process that. Also, these animations would need to occur more than once (so using an array of booleans to verify if it's been executed already wouldn't be helpful).
So, if you have any idea or help (or places to point me to) that would be greatly appreciated.
Some Extra Information: Java 1.6, IDE Eclipse, MVC structure. (This in question is the Controller/Model)
Assuming your animation is driven by an instance of javax.swing.Timer, the queue in question is the EventQueue, which runs events in "the same order as they are enqueued" by the Timer. Because it is impractical to stop other devices on the host platform from evoking your listeners, you have to track the effect in your application. As a concrete example, this game has several overloads of the model's move() method to handle input from disparate sources: keyboard, mouse or animation timer. The timer may be toggled on or off to see its effect. This example, which supplants the EventQueue with a custom implementation, may also offer some insight.
OK, so if I add an ActionListener to a GUI element, and it's the only element I use that ActionListener with, does it matter which of the following lines (a,b) I use to get the checkbox selected state?
final JCheckBox checkbox = (JCheckBox)this.buildResult.get("cbDebugTick");
checkbox.addActionListener(new ActionListener() {
#Override public void actionPerformed(ActionEvent event){
boolean bChecked =
// (a) checkbox.isSelected();
// (b) ((JCheckBox)event.getSource()).isSelected();
model.setPrintDebugOn(bChecked);
}
});
It makes sense to me that if I add the ActionListener object to multiple GUI elements, then I should use (b).
And in (b), is it OK to blindly cast event.getSource() to JCheckBox, since I'm the one who added the action listener, or should I program defensively and do an instanceof check?
note: this question is in the context of event listeners in general; kdgregory has some good points below specifically re: checkboxes which I had neglected to consider.
I'd do neither.
If clicking the checkbox is going to start some action, I'd attach an ItemListener, then just look at the selection state in the ItemEvent.
However, checkboxes don't normally invoke actions, they manage state. So a better approach is to examine all of your checkboxes in response to whatever does kick off the action.
Edit: some commentary about the larger issues that the OP raised.
First, it's important to realize that large parts of Swing represent implementation convenience rather than a coherent behavior model. JCheckBox and JButton have nothing in common other than the fact that clicking within their space is meaningful. However, they both inherit from AbstractButton, which provides implementation details such as the button's label. It also assumes that buttons are "pressed", and that pressing a button will initiate some meaningful behavior (the action). In the case of JCheckbox, however, the button press is not important, the change in state is. That state change is signaled to the ItemListener -- which is also defined on AbstractButton even though state changes are meaningless to other button types (the JavaDoc even says "checkbox").
One of the things that Swing did get right -- if hard to use -- is the idea of that an Action is separate from the control initiating that action. An Action object can be invoked from multiple controls: a menu item, a pushbutton on a dialog, a keystroke, whatever. More important from a design perspective is that it takes you away from the idea of a generic "listener" that tries to figure out what needs to happen. I've seen apps where a single listener receives input from the entire menu system, for example, and then runs through a big if/else chain to figure out which menu item was pressed. Using Actions means you have more classes, but in the long run gives you a more maintainable app.
Finally, from a usability perspective, there's a difference between controls that maintain state, such as JCheckbox and JTextArea, and those that initiate actions, such as JButton and JMenuItem. I have seen a (web) app where clicking on a radio button takes you to a different page. That's bad. Even if you're planning to use listeners internally, to update the state of some model, you should ask yourself why the collection of GUI elements do not in themselves provide you with a model.
For the case where the listener is exclusive (such as an anon listener), I use (a).
If the listener will be reused (eg, this is an instance of ActionListener) I'll write it as:
#Override
public void actionPerformed(ActionEvent event) {
Object src = event.getSource();
if (src == checkbox) {
boolean bChecked = checkbox.isSelected();
// ...
}
}
If you have several checkboxes and they are processed the same way, then instanceof makes sense.
in (b) to be rigourous, you should indeed do a instanceof check, but it's not that important. I would think both these lines are fine and acceptable, though (b) would be "better code"
Although, what is usually done in an action listener is simply call another method customized to your checkbox. So it would look like something like this:
#Override public void actionPerformed(ActionEvent event) {
//your treatment would be in this method, where it would be acceptable to use (a)
onCheckBoxActionPerformed(event)
}
I'd program with b defensively as it's the best-practice option. But if only you are ever going to use the code then there is no reason why you can't do a. However, imagine how happy you will be with yourself if you come back to it at some future point, change something and find you wrote good code which you can directly reuse...
So I want to trigger an event (pausing/unpausing some media) whenever the user presses spacebar anywhere in the my Swing app.
Since there are so many controls and panels that could have focus, its not really possible to add keyevents to them all(not to mention gross).
So I found
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher()
which is awesome, you can register global keypress pre-handlers. There's a major problem though - spaces will be typed all the time in input fields, table cells, etc, and I obviously dont want to trigger the pause event then!
So any ideas?
Perhaps there is way to detect globally whether the cursor is focused on something that allows text input, without having to check through a list of all the editable controls(vomit!)?
I think you answered that yourself - yes I think you can find out the current element that has focus, and if it is an instanceof a certain field class, you ignore the space for the purpose of pause event. If it seams heavy handed, don't worry, instanceof is VERY fast for the JVM (and in any cause you are talking human scale events which are an eon to a processor).
I'm rusty on my Swing, but I think you should try registering a global listener using Toolkit.addAWTEventListener with a KEY_EVENT_MASK. You can then filter the AWTEvent processing based on its type and source.
Ok...
Well im trying to filter based on source. Problem is my editable ComboBoxes...
They are instanceof
javax.swing.plaf.basic.BasicComboBoxEditor$BorderlessTextField
And since BorderlessTextField is a private inner class, I apparently cant do an instanceof check against it.
ideas?
EDIT:
ok so this works....
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventPostProcessor(new KeyEventPostProcessor() {
public boolean postProcessKeyEvent(KeyEvent e) {
if (e.getID() == KeyEvent.KEY_PRESSED) {
Object s = e.getComponent();
if (!(s instanceof JTextField) &&
!(s instanceof JTable && ((JTable) s).isEditing())
) {
music_player.pauseEvent();
}
//System.out.println(s.getClass().toString());
}
return true;
}
});
Its totally gross and I hate it.
I wish I could figure out a way to check if the keypress has been consumed by any component - then I could only perform the pause event when the keypress was not actioned by any component.