Finding and retrieving a component from its container - java

In my swing-based UI, I have a JMenuBar which contains a a series of JMenu and JMenuItem objects. One of the menu-item objects also happens to be a JCheckBoxMenuItem.
Now, while the user can click on this JCheckBoxMenuItem in order to toggle the state of an application level setting, the user (in my application) also has access to a command line API to change the application setting. The details of this command line API are not relevant.
My question is this: When the user goes through the command line API and toggles the state of the setting (a static property / setting that applies to all open instances of my application), I would like to update the "checked / unchecked" property on the JCheckBoxMenuItem. To do this, I can either:
Store a reference to the checkboxmenuitem.
Traverse the JMenu container hierarchy to find the checkboxmenuitem.
I don't want to use method 1 because in the future, if I have more of these checkboxmenuitems, then i'll have to hang on to a reference to each one.
Method 2 seems cumbersome because I need to do:
Component[] childComponents = menu.getComponents();
for(Component c:childComponents)
{
if(c.getName().equals("nameOfTheCheckBoxMenuItem"))
{
componentFound = c;
}
}
Is there a better / more efficient way to find a component in a component hierarchy? Is there maybe a better way to solve this problem in general (changing the state of the jcheckboxmenuitem when the value of a property in my application changes), using say, a PropertyChangeListener (Although my understanding is that these only work on "beans").

1) I'd suggest to use CardLayout for nicest and easiest workaround for multi_JPanel application
2) then you can imlements
add Action / ActionListener
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (myCheckBox.isSelected()) {
// something
} else {
// something
}
}
};
add ItemListener
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
if (Whatever) {
// something
}
}
};

Related

How do I add a tag to JMenuItem?

How can I set a tag for my menu item so that I can ue it later in the callback?
Something like this. Somebody have ever do it?
JMenuItem item = new JMenuItem(mnu.text);
item.setSomething(myTag) ???;
item.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(java.awt.event.ActionEvent evt)
{
start_something(myTag);
}
});
You can use .setName() method for tagging it
final JMenuItem item = new JMenuItem();
item.setName("item1");
item.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
String tag = item.getName();
}
});
You can create a subclass as mentioned by Adir D but you can also add properties to the component itself and read those properties somewhere else. For a small number of properties or where a subclass doesn't fit, it might solve your problem.
See https://docs.oracle.com/javase/7/docs/api/javax/swing/JComponent.html
putClientProperty
public final void putClientProperty(Object key, Object value)
Adds an arbitrary key/value "client property" to this component.
The get/putClientProperty methods provide access to a small per-instance hashtable. Callers can use get/putClientProperty to annotate components that were created by another module. For example, a layout manager might store per child constraints this way. For example:
componentA.putClientProperty("to the left of", componentB);
If value is null this method will remove the property. Changes to client properties are reported with PropertyChange events. The name of the property (for the sake of PropertyChange events) is key.toString().
The clientProperty dictionary is not intended to support large scale extensions to JComponent nor should be it considered an alternative to subclassing when designing a new component.
Parameters:
key - the new client property key
value - the new client property value; if null this method will remove the property
See Also:
getClientProperty(java.lang.Object), Container.addPropertyChangeListener(java.beans.PropertyChangeListener)

jMenuItem doesn't appear

I've just starting using Java Swing and I have a issue.
I tried to do a simple menuBar and a menuItem 'Exit', but before linking the button to the action the menuItem appeared, now that I've linked the button to a System.exit(0) action it disappeared. Help?
The code is the following:
in MainPanel (the autogenerated code from swing is excluded):
public void init() {
initComponents();
initActions();
setLocationRelativeTo(null);
pack();
setVisible(true);
}
private void initActions() {
this.menuItemExit.setAction(Application.getInstance().getPanelControl().getActionExit());
}
In PanelControl:
public class PanelControl {
private Action actionExit;
public Action getActionExit() {
return actionExit;
}
public class ActionExit extends AbstractAction{
public ActionExit(){
putValue(Action.NAME, "Exit");
putValue(Action.SHORT_DESCRIPTION, "Exit from the application");
putValue(Action.ACCELERATOR_KEY, KeyStroke.getKeyStroke("ctrl e"));
putValue(Action.MNEMONIC_KEY, KeyEvent.VK_E);
}
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
}
In Application:
private void init() {
viewMainPanel = new MainPanel();
controlPanel = new ControlPanel();
viewMainPanel.init();
}
i think the problem is somewhere in here but i can't figure out where. any help?
(there's other code but i just put the more relevant part, also i translated the code from italian so i'm sorry if there are any problems or a few names dont match up)
private Action actionExit;
public Action getActionExit() {
return actionExit;
}
Your actionExit variable is null.
Nowhere in your code do you create an instance of your ActionExit class.
Somewhere you need:
actionExit = new ActionExit();
Your design seems a bit complicated, I have no idea why you have a panel just to create an instance of the ActionExit class.
I would suggest you just create the ActionExit instance in your main class and get rid of the PanelControl class.
Instead of using an IDE to generate confusing code you should consider learning how to write the code yourself so you can better structure your classes. Read the section from the Swing tutorial on How to Use Menus for a working example to get you started.
A menu item has to be added to a Native Java Swing component. You have to add it to a JFrame. You can't add a MenuItem to a Panel. The Parent 'root' container in any Java Swing application is 'native' and a JFrame. Everything else in that container is 'drawn' into the rectangle using the look and feel of your choosing.
Then you CREATE a MenuItem using your TAbstractAction item. That object CAN be used to create a JButton, JMenuItem or ToolBar button. Keeping a reference to your TAbstractAction in your code, you can enable/disable the object and it implements an 'observable' pattern where it will enable/disable ALL UI controls you used to build with it. I actually wrote a Java Swing framework for doing Java Applications. It used to be on the Sun Open Source web site. If you wish I can put it up on GitLab for you to play with. Java Swing is nice but JavaFX should be the long term goal for UI on a JVM.
In your JFrame object you need to do this:
_menuBar = new JMenuBar();
// add controls to the frame
setJMenuBar(_menuBar);
Then you need to add your 'exitMenuItem' to your _MenuBar control.
Cheers

Using multiple JButtons with the same label in Java

I have two buttons in my project that both have a "+" label. When the actionPerformed() method is called, it calls a specific method based on the label. How can I distiguish between two JButtons with the same label? Is there a better way to do this then how I've done it?
This is the definition of the buttons:
JButton keypadPlus1 = new JButton(" + ");
JButton keypadMinus1 = new JButton(" - ");
JButton keypadPlus2 = new JButton("+");
JButton keypadMinus2 = new JButton("-");
Adding the ActionListeners for the buttons:
keypadPlus1.addActionListener(backEnd);
keypadPlus2.addActionListener(backEnd);
keypadMinus1.addActionListener(backEnd);
keypadMinus2.addActionListener(backEnd);
The actionPerformed #Override in the backEnd:
public void actionPerformed (ActionEvent event) {
String command = event.getActionCommand();
if (command.equals("+")) {
calcLifePoints(command);
}
if (command.equals("-")) {
calcLifePoints(command);
}
if (command.equals(" + ")) {
calcLifePoints(command);
}
if (command.equals(" - ")) {
calcLifePoints(command);
}
}
You could...
Use ActionEvent#getSource
You could...
Set the actionCommand property of each button to something unique and use ActionEvent#getActionCommand
You could...
Use separate listeners, either anonymously or as inner or outer classes depending on your needs
You could...
Make use of the Action API, which would allow you to define a common/abstract Action which defined the common properties (like the + text) and then extend this to make unique actions for each button
See How to Use Actions for more details
You could...
Use JButton#putClientProperty to set some unique flag on each button and cast the ActionEvent to a JComponent and use getClientProperty to retrieve the flag ... but given the previous suggestions, I'm not sure why you'd bother
You shouldn't have a single listener handle the behavior for different responsibilities. If the two + buttons do not do the same thing, give the buttons separate listeners.
This will allow your code to be a lot more cohesive. By reducing your listeners to 1 responsibility each, you'll be able to re-use those responsibilities. It also make testing easier, allowing you to test each behavior in complete isolation.
Although if you must, ActionEvent#getSource() returns which ever component triggered the event. Doing a reference comparison will allow you to determine which object triggered the event.
The best way to handle this would to separate the responsibilities your current listener has into separate classes:
class FirstListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
}
}
Lets assume FirstListener represents your first + button behavior. If that behavior requires any external objects (such as a field in a different class), simply pass it through the constructor:
class FirstListener implements ActionListener {
private Object dependency;
public FirstListener(Object dependency) {
this.dependency = dependency;
}
//actionPerformed declaration
}
You can do the same for the other buttons (for example, the second + button).
If you feel this is a bit excessive, feel free to use lambda expressions to declare the listeners:
//Java 8+
button.addActionListener(event -> {
});
This doesn't give you the same modularity as the previous example, as the behavior is no longer separated from the actual class: you will be forced to change the implementation to change the behavior, rather than using dependency inversion to simply pass a different object which also implements ActionListener.
Instead of this,
public void actionPerformed (ActionEvent event) {
String command = event.getActionCommand();
if (command.equals("+")) {
calcLifePoints(command);
}
if (command.equals("-")) {
calcLifePoints(command);
}
if (command.equals(" + ")) {
calcLifePoints(command);
}
if (command.equals(" - ")) {
calcLifePoints(command);
}
}
Use like this,
public void actionPerformed (ActionEvent event) {
Object command = event.getSource();
if (command.equals(keypadPlus1)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadMinus1)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadPlus2)) {
calcLifePoints(event.getActionCommand());
}
if (command.equals(keypadMinus2)) {
calcLifePoints(event.getActionCommand());
}
}

Change focus to next component in JTable using TAB

JTable's default behavior is changing focus to next cell and I want to force it to move focus to next component (e.g. JTextField) on TAB key pressed.
I overrided isCellEditable method of DefaultTableModel to always return false:
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
But it still doesn't change focus to next component!
How should I make JTable change focus to next component instead of next cell?
The shift-/tab keys are used by default for transfering focus between components. JTable explicitly requests to handle the shift-/tab internally (by providing sets of focusTraversalKeys which doesn't include those).
Following the general rule (if there's specilized api available for a task, use that instead of rolling your own), the solution is to set traversal keys to again contain them:
Set<AWTKeyStroke> forward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
forward.add(KeyStroke.getKeyStroke("TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward);
Set<AWTKeyStroke> backward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
backward.add(KeyStroke.getKeyStroke("shift TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward);
If you really want this, you need to change the default behavior of the tables action map.
ActionMap am = table.getActionMap();
am.put("selectPreviousColumnCell", new PreviousFocusHandler());
am.put("selectNextColumnCell", new NextFocusHandler());
Then you need a couple of actions to handle the traversal
public class PreviousFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusPreviousComponent();
}
}
public class NextFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusNextComponent();
}
}
Another approach would be to disable the underlying Action...
ActionMap am = table.getActionMap();
am.get("selectPreviousColumnCell").setEnabled(false);
am.get("selectNextColumnCell").setEnabled(false);
(haven't tested this)
The benefit of this approach is can enable/disable the behaviour as you need it without needing to maintain a reference to the old Actions
default (implemented KeyBinding for JTable) is about next cell and from last cell to first,
you can to remove KeyBindings by setting to the null value
To reset to the standard keyboard bindings (typically TAB and SHIFT+TAB), simply specify null for the keystrokes parameter to Component.setFocusTraversalKeys:
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);

How do I assign Enter as the trigger key of all JButtons in my Java application?

I'm writing a Java Swing application using the Metal look-and-feel. Every time there is a JButton in my application the user uses the Tab key to move the focus to the button and then hits the Enter key. Nothing happens! If he hits the Space key the button events are fired. How do I assign the Enter key to trigger the same events as the Space key? Thank you for your help.
I found the following:
http://tips4java.wordpress.com/2008/10/25/enter-key-and-button/
Where Rob Camick writes that when using JDK5 and later you simply add...
UIManager.put("Button.defaultButtonFollowsFocus", Boolean.TRUE);
...to the application to solve the problem. This did the trick for me! And I can't imagine anything simpler. However, when using older versions of Java you will have to do something like Richard and Peter describe in their answers to this question.
Here is complete example. Richard was close, but you also need to map pressed ENTER to action, not just released. To make it work for ALL buttons, I have put this mapping to default input map for buttons. Add imports, and it should be runnable.
public class Main implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Main());
}
#Override
public void run() {
setupEnterActionForAllButtons();
JFrame frame = new JFrame("Button test");
frame.getContentPane().add(createButton(), BorderLayout.NORTH);
frame.getContentPane().add(createButton(), BorderLayout.SOUTH);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
private void setupEnterActionForAllButtons() {
InputMap im = (InputMap) UIManager.getDefaults().get("Button.focusInputMap");
Object pressedAction = im.get(KeyStroke.getKeyStroke("pressed SPACE"));
Object releasedAction = im.get(KeyStroke.getKeyStroke("released SPACE"));
im.put(KeyStroke.getKeyStroke("pressed ENTER"), pressedAction);
im.put(KeyStroke.getKeyStroke("released ENTER"), releasedAction);
}
private JButton createButton() {
JButton b = new JButton("press enter");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Pressed");
}
});
return b;
}
}
Actually, this is a look and feel issue. It is (and should be) up to the look and feel as to which key triggers the focused button.
The "default button" work-around works since the L&F you're using uses enter for the default button.
Peter's workaround explicitly changes the L&F default "focus action" key - which is somewhat more convincing if you ask me.
I would add that I don't think many users would want to tab to the button then hit enter (most won't even notice the focus indicator) - they want the default action to be the "right" one and work wherever they press enter. This can only be done with input maps as Richard suggests.
I would certainly suggest getting a very clear picture of what your users actually want and expect (preferably with reference to other apps they use) before changing anything globally.
You do it by assigning an input / action map for the Enter key. Something like the following:
// save the command mapping for space
Object spaceMap = button.getInputMap.get(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true));
// add a mapping from enter to the same command.
button.getInputMap().put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true),spaceMap);
You can also set the "default button" to the button most recently focussed.
I did this on an application, and all methods of doing this are a nightmare to maintain and debug. The fact is, this is clearly not what the designers of Swing intended to happen.
Extension to above answers to do same with radio , checkboxes.
Called this before creating components
void setupEnterAction(String componentName){
String keyName = componentName + ".focusInputMap";
InputMap im = (InputMap) UIManager.getDefaults().get(keyName);
Object pressedAction = im.get(KeyStroke.getKeyStroke("pressed SPACE"));
Object releasedAction = im.get(KeyStroke.getKeyStroke("released SPACE"));
im.put(KeyStroke.getKeyStroke("pressed ENTER"), pressedAction);
im.put(KeyStroke.getKeyStroke("released ENTER"), releasedAction);
}
public void setEnterEvent(){
setupEnterAction("Button");
setupEnterAction("RadioButton");
setupEnterAction("CheckBox");
}

Categories